mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-10-23 00:02:28 +00:00
introduce name model for db to put scanned names into
This commit is contained in:
1 parent
defd51cd81
commit
6999e63a66
10 files changed
+141
-11
No files matched your search
@@ -169,6 +169,7 @@ library
|
||||
Simplex.Chat.Store.Postgres.Migrations.M20260904_file_badges
|
||||
Simplex.Chat.Store.Postgres.Migrations.M20260915_user_badges
|
||||
Simplex.Chat.Store.Postgres.Migrations.M20260921_wallet_seeds
|
||||
Simplex.Chat.Store.Postgres.Migrations.M20260923_wallet_names
|
||||
else
|
||||
exposed-modules:
|
||||
Simplex.Chat.Archive
|
||||
@@ -345,6 +346,7 @@ library
|
||||
Simplex.Chat.Store.SQLite.Migrations.M20260904_file_badges
|
||||
Simplex.Chat.Store.SQLite.Migrations.M20260915_user_badges
|
||||
Simplex.Chat.Store.SQLite.Migrations.M20260921_wallet_seeds
|
||||
Simplex.Chat.Store.SQLite.Migrations.M20260923_wallet_names
|
||||
other-modules:
|
||||
Paths_simplex_chat
|
||||
hs-source-dirs:
|
||||
|
||||
@@ -68,7 +68,7 @@ import Simplex.Chat.Names (SimplexDomainProof (..), SimplexDomainClaim (..), cla
|
||||
import Simplex.Chat.Store.Wallets (WalletSeed (..), accountHeldByOther, bindAccount, createWalletSeed, deleteWalletSeed, getUserAccounts, getWalletSeed, recordScan, resolveAccount)
|
||||
import Simplex.Chat.Wallet (AccountIndex, AccountKey, WalletAddress (..), WalletError (..), accountSecret, deriveAccountKey, entropyFromMnemonic, newSeedEntropy, renderAccountPath, scanGapLimit, scanMaxAccounts, seedMaster, seedMnemonic)
|
||||
import Simplex.Messaging.Eth.Address (addressFromPrivateKey)
|
||||
import Simplex.Messaging.Names.Record (OwnedNames (..))
|
||||
import Simplex.Messaging.Names.Record (NameResponse, OwnedNames (..))
|
||||
import Simplex.Chat.Call
|
||||
import Simplex.Chat.Controller
|
||||
import Simplex.Chat.Delivery (DeliveryJobScope (..), DeliveryJobSpec (..), DeliveryWorkerScope (..))
|
||||
@@ -5971,17 +5971,17 @@ accountKey :: WalletSeed -> AccountIndex -> CM AccountKey
|
||||
accountKey seed n = liftWallet $ seedMaster (wsEntropy seed) >>= (`deriveAccountKey` n)
|
||||
|
||||
-- | Walk the accounts until 'scanGapLimit' in a row are untouched, or 'scanMaxAccounts' is reached, asking each on a relay the scan has not used where the configured set allows.
|
||||
scanAccounts :: NetworkRequestMode -> User -> WalletSeed -> CM [AccountIndex]
|
||||
scanAccounts :: NetworkRequestMode -> User -> WalletSeed -> CM [(AccountIndex, [NameResponse])]
|
||||
scanAccounts nm user seed = do
|
||||
master <- liftWallet $ seedMaster (wsEntropy seed)
|
||||
let go :: AccountIndex -> [SMPServer] -> [AccountIndex] -> Int -> CM [AccountIndex]
|
||||
let go :: AccountIndex -> [SMPServer] -> [(AccountIndex, [NameResponse])] -> Int -> CM [(AccountIndex, [NameResponse])]
|
||||
go n used found gap
|
||||
| gap >= scanGapLimit || n >= scanMaxAccounts = pure (reverse found)
|
||||
| otherwise = do
|
||||
addr <- addressFromPrivateKey <$> liftWallet (deriveAccountKey master n)
|
||||
(srv, owned) <- withAgent $ \a -> ownedSimplexNames a nm (aUserId user) used addr 0
|
||||
if ownInUse owned
|
||||
then go (n + 1) (srv : used) (n : found) 0
|
||||
then go (n + 1) (srv : used) ((n, ownNames owned) : found) 0
|
||||
else go (n + 1) (srv : used) found (gap + 1)
|
||||
go 0 [] [] 0
|
||||
|
||||
|
||||
@@ -52,6 +52,7 @@ import Simplex.Chat.Store.Postgres.Migrations.M20260828_file_expiry
|
||||
import Simplex.Chat.Store.Postgres.Migrations.M20260904_file_badges
|
||||
import Simplex.Chat.Store.Postgres.Migrations.M20260915_user_badges
|
||||
import Simplex.Chat.Store.Postgres.Migrations.M20260921_wallet_seeds
|
||||
import Simplex.Chat.Store.Postgres.Migrations.M20260923_wallet_names
|
||||
import Simplex.Messaging.Agent.Store.Shared (Migration (..))
|
||||
|
||||
schemaMigrations :: [(String, Text, Maybe Text)]
|
||||
@@ -103,7 +104,8 @@ schemaMigrations =
|
||||
("20260828_file_expiry", m20260828_file_expiry, Just down_m20260828_file_expiry),
|
||||
("20260904_file_badges", m20260904_file_badges, Just down_m20260904_file_badges),
|
||||
("20260915_user_badges", m20260915_user_badges, Just down_m20260915_user_badges),
|
||||
("20260921_wallet_seeds", m20260921_wallet_seeds, Nothing)
|
||||
("20260921_wallet_seeds", m20260921_wallet_seeds, Nothing),
|
||||
("20260923_wallet_names", m20260923_wallet_names, Nothing)
|
||||
]
|
||||
|
||||
-- | The list of migrations in ascending order by date
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
{-# LANGUAGE QuasiQuotes #-}
|
||||
|
||||
module Simplex.Chat.Store.Postgres.Migrations.M20260923_wallet_names where
|
||||
|
||||
import Data.Text (Text)
|
||||
import Text.RawString.QQ (r)
|
||||
|
||||
m20260923_wallet_names :: Text
|
||||
m20260923_wallet_names =
|
||||
[r|
|
||||
-- the columns are commented in the SQLite migration
|
||||
CREATE TABLE wallet_names (
|
||||
wallet_name_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||
wallet_seed_id BIGINT NOT NULL REFERENCES wallet_seeds ON DELETE CASCADE,
|
||||
account_index BIGINT NOT NULL CHECK (account_index BETWEEN 0 AND 2147483647),
|
||||
name TEXT NOT NULL,
|
||||
name_response TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX idx_wallet_names_wallet_seed_id_name ON wallet_names(wallet_seed_id, name);
|
||||
|]
|
||||
|
||||
-- No reverse step: wallet_seeds, which the rows hang off, has none either.
|
||||
@@ -1745,6 +1745,28 @@ ALTER TABLE test_chat_schema.wallet_accounts ALTER COLUMN wallet_account_id ADD
|
||||
|
||||
|
||||
|
||||
CREATE TABLE test_chat_schema.wallet_names (
|
||||
wallet_name_id bigint NOT NULL,
|
||||
wallet_seed_id bigint NOT NULL,
|
||||
account_index bigint NOT NULL,
|
||||
name text NOT NULL,
|
||||
name_response text NOT NULL,
|
||||
CONSTRAINT wallet_names_account_index_check CHECK (((account_index >= 0) AND (account_index <= 2147483647)))
|
||||
);
|
||||
|
||||
|
||||
|
||||
ALTER TABLE test_chat_schema.wallet_names ALTER COLUMN wallet_name_id ADD GENERATED ALWAYS AS IDENTITY (
|
||||
SEQUENCE NAME test_chat_schema.wallet_names_wallet_name_id_seq
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1
|
||||
);
|
||||
|
||||
|
||||
|
||||
CREATE TABLE test_chat_schema.wallet_seeds (
|
||||
wallet_seed_id bigint NOT NULL,
|
||||
entropy bytea NOT NULL,
|
||||
@@ -2185,6 +2207,11 @@ ALTER TABLE ONLY test_chat_schema.wallet_accounts
|
||||
|
||||
|
||||
|
||||
ALTER TABLE ONLY test_chat_schema.wallet_names
|
||||
ADD CONSTRAINT wallet_names_pkey PRIMARY KEY (wallet_name_id);
|
||||
|
||||
|
||||
|
||||
ALTER TABLE ONLY test_chat_schema.wallet_seeds
|
||||
ADD CONSTRAINT wallet_seeds_pkey PRIMARY KEY (wallet_seed_id);
|
||||
|
||||
@@ -2999,6 +3026,10 @@ CREATE UNIQUE INDEX idx_wallet_accounts_wallet_seed_id_account_index ON test_cha
|
||||
|
||||
|
||||
|
||||
CREATE UNIQUE INDEX idx_wallet_names_wallet_seed_id_name ON test_chat_schema.wallet_names USING btree (wallet_seed_id, name);
|
||||
|
||||
|
||||
|
||||
CREATE UNIQUE INDEX idx_wallet_seeds_single_seed ON test_chat_schema.wallet_seeds USING btree (single_seed);
|
||||
|
||||
|
||||
@@ -3738,6 +3769,11 @@ ALTER TABLE ONLY test_chat_schema.wallet_accounts
|
||||
|
||||
|
||||
|
||||
ALTER TABLE ONLY test_chat_schema.wallet_names
|
||||
ADD CONSTRAINT wallet_names_wallet_seed_id_fkey FOREIGN KEY (wallet_seed_id) REFERENCES test_chat_schema.wallet_seeds(wallet_seed_id) ON DELETE CASCADE;
|
||||
|
||||
|
||||
|
||||
ALTER TABLE ONLY test_chat_schema.xftp_file_descriptions
|
||||
ADD CONSTRAINT xftp_file_descriptions_user_id_fkey FOREIGN KEY (user_id) REFERENCES test_chat_schema.users(user_id) ON DELETE CASCADE;
|
||||
|
||||
|
||||
@@ -175,6 +175,7 @@ import Simplex.Chat.Store.SQLite.Migrations.M20260828_file_expiry
|
||||
import Simplex.Chat.Store.SQLite.Migrations.M20260904_file_badges
|
||||
import Simplex.Chat.Store.SQLite.Migrations.M20260915_user_badges
|
||||
import Simplex.Chat.Store.SQLite.Migrations.M20260921_wallet_seeds
|
||||
import Simplex.Chat.Store.SQLite.Migrations.M20260923_wallet_names
|
||||
import Simplex.Messaging.Agent.Store.Shared (Migration (..))
|
||||
|
||||
schemaMigrations :: [(String, Query, Maybe Query)]
|
||||
@@ -349,7 +350,8 @@ schemaMigrations =
|
||||
("20260828_file_expiry", m20260828_file_expiry, Just down_m20260828_file_expiry),
|
||||
("20260904_file_badges", m20260904_file_badges, Just down_m20260904_file_badges),
|
||||
("20260915_user_badges", m20260915_user_badges, Just down_m20260915_user_badges),
|
||||
("20260921_wallet_seeds", m20260921_wallet_seeds, Nothing)
|
||||
("20260921_wallet_seeds", m20260921_wallet_seeds, Nothing),
|
||||
("20260923_wallet_names", m20260923_wallet_names, Nothing)
|
||||
]
|
||||
|
||||
-- | The list of migrations in ascending order by date
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
{-# LANGUAGE QuasiQuotes #-}
|
||||
|
||||
module Simplex.Chat.Store.SQLite.Migrations.M20260923_wallet_names where
|
||||
|
||||
import Database.SQLite.Simple (Query)
|
||||
import Database.SQLite.Simple.QQ (sql)
|
||||
|
||||
m20260923_wallet_names :: Query
|
||||
m20260923_wallet_names =
|
||||
[sql|
|
||||
CREATE TABLE wallet_names (
|
||||
wallet_name_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
wallet_seed_id INTEGER NOT NULL REFERENCES wallet_seeds ON DELETE CASCADE,
|
||||
account_index INTEGER NOT NULL CHECK (account_index BETWEEN 0 AND 2147483647),
|
||||
name TEXT NOT NULL,
|
||||
name_response TEXT NOT NULL -- the NameResponse a scan last read for this name
|
||||
) STRICT;
|
||||
|
||||
CREATE UNIQUE INDEX idx_wallet_names_wallet_seed_id_name ON wallet_names(wallet_seed_id, name);
|
||||
|]
|
||||
|
||||
-- No reverse step: wallet_seeds, which the rows hang off, has none either.
|
||||
@@ -987,6 +987,13 @@ CREATE TABLE wallet_accounts(
|
||||
account_index INTEGER CHECK(account_index BETWEEN 0 AND 2147483647),
|
||||
user_id INTEGER REFERENCES users ON DELETE SET NULL
|
||||
) STRICT;
|
||||
CREATE TABLE wallet_names(
|
||||
wallet_name_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
wallet_seed_id INTEGER NOT NULL REFERENCES wallet_seeds ON DELETE CASCADE,
|
||||
account_index INTEGER NOT NULL CHECK(account_index BETWEEN 0 AND 2147483647),
|
||||
name TEXT NOT NULL,
|
||||
name_response TEXT NOT NULL -- the NameResponse a scan last read for this name
|
||||
) STRICT;
|
||||
CREATE INDEX contact_profiles_index ON contact_profiles(
|
||||
display_name,
|
||||
full_name
|
||||
@@ -1556,6 +1563,10 @@ CREATE UNIQUE INDEX idx_wallet_accounts_wallet_seed_id_account_index ON wallet_a
|
||||
account_index
|
||||
);
|
||||
CREATE INDEX idx_wallet_accounts_user_id ON wallet_accounts(user_id);
|
||||
CREATE UNIQUE INDEX idx_wallet_names_wallet_seed_id_name ON wallet_names(
|
||||
wallet_seed_id,
|
||||
name
|
||||
);
|
||||
CREATE TRIGGER on_group_members_insert_update_summary
|
||||
AFTER INSERT ON group_members
|
||||
FOR EACH ROW
|
||||
|
||||
@@ -20,7 +20,7 @@ module Simplex.Chat.Store.Wallets
|
||||
)
|
||||
where
|
||||
|
||||
import Control.Monad (join, unless)
|
||||
import Control.Monad (forM_, join, unless)
|
||||
import Control.Monad.Except
|
||||
import Control.Monad.IO.Class (liftIO)
|
||||
import qualified Data.ByteArray as BA
|
||||
@@ -28,8 +28,10 @@ import Data.ByteString (ByteString)
|
||||
import Data.Int (Int64)
|
||||
import Simplex.Chat.Wallet (AccountIndex, WalletError (..), checkAccountIndex)
|
||||
import Simplex.Messaging.Agent.Protocol (UserId)
|
||||
import Simplex.Messaging.Names.Record (NameRecord (..), NameRegistration (..), NameResponse (..))
|
||||
import Simplex.Messaging.Agent.Store.AgentStore (maybeFirstRow)
|
||||
import qualified Simplex.Messaging.Agent.Store.DB as DB
|
||||
import Simplex.Messaging.Util (encodeJSON)
|
||||
|
||||
#if defined(dbPostgres)
|
||||
import Database.PostgreSQL.Simple (Only (..))
|
||||
@@ -145,8 +147,8 @@ setAccountUser db sId userId n =
|
||||
accountHeldBy :: DB.Connection -> SeedId -> UserId -> AccountIndex -> IO Bool
|
||||
accountHeldBy db sId userId n = (== Just (Just userId)) <$> accountUser db sId n
|
||||
|
||||
-- | The accounts a scan found, bound to the profile that ran it, and the counter raised past every account recorded, not just those found.
|
||||
recordScan :: DB.Connection -> SeedId -> UserId -> [AccountIndex] -> IO ()
|
||||
-- | The accounts a scan found with the names each holds, bound to the profile that ran it, and the counter raised past every account recorded, not just those found.
|
||||
recordScan :: DB.Connection -> SeedId -> UserId -> [(AccountIndex, [NameResponse])] -> IO ()
|
||||
recordScan db sId userId inUse = do
|
||||
mapM_ insertScanned inUse
|
||||
DB.execute
|
||||
@@ -158,7 +160,7 @@ recordScan db sId userId inUse = do
|
||||
|]
|
||||
(sId, sId, sId)
|
||||
where
|
||||
insertScanned n =
|
||||
insertScanned (n, names) = do
|
||||
DB.execute
|
||||
db
|
||||
[sql|
|
||||
@@ -166,6 +168,20 @@ recordScan db sId userId inUse = do
|
||||
ON CONFLICT (wallet_seed_id, account_index) DO NOTHING
|
||||
|]
|
||||
(sId, accountIndexCol n, userId)
|
||||
mapM_ (insertName n) names
|
||||
-- a name the scan no longer sees is left alone: it reads one page per account, so unseen is not unowned
|
||||
insertName n r = forM_ (registeredName r) $ \nm ->
|
||||
DB.execute
|
||||
db
|
||||
[sql|
|
||||
INSERT INTO wallet_names (wallet_seed_id, account_index, name, name_response) VALUES (?, ?, ?, ?)
|
||||
ON CONFLICT (wallet_seed_id, name)
|
||||
DO UPDATE SET account_index = EXCLUDED.account_index, name_response = EXCLUDED.name_response
|
||||
|]
|
||||
(sId, accountIndexCol n, nm, encodeJSON r)
|
||||
registeredName NameResponse {registration} = case registration of
|
||||
NRRegistered {nameRecord} -> Just $ nrName nameRecord
|
||||
_ -> Nothing
|
||||
|
||||
-- | Keep the counter a high-water mark. Never lowers it, never gives one to an imported phrase that has none.
|
||||
raiseNextAccount :: DB.Connection -> SeedId -> AccountIndex -> IO ()
|
||||
|
||||
+16
-1
@@ -1,3 +1,4 @@
|
||||
{-# LANGUAGE NamedFieldPuns #-}
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
{-# LANGUAGE PostfixOperators #-}
|
||||
|
||||
@@ -12,6 +13,7 @@ import Data.ByteString.Char8 (ByteString)
|
||||
import qualified Data.ByteString.Char8 as B
|
||||
import Data.Char (toUpper)
|
||||
import Data.Either (isRight)
|
||||
import Data.Int (Int64)
|
||||
import Data.List (nub)
|
||||
import Data.Text (Text)
|
||||
import qualified Data.Text as T
|
||||
@@ -19,8 +21,11 @@ import NameResolver (ownedName)
|
||||
import Simplex.Chat.Wallet (AccountIndex, WalletError (..), accountSecret, deriveAccountKey, entropyFromMnemonic, renderAccountPath, seedMaster, seedMnemonic)
|
||||
import qualified Simplex.Messaging.Crypto.BIP39 as B39
|
||||
import qualified Simplex.Messaging.Crypto.Secp256k1 as S
|
||||
import Simplex.Messaging.Agent.Store.AgentStore (withTransaction)
|
||||
import qualified Simplex.Messaging.Agent.Store.DB as DB
|
||||
import Simplex.Messaging.Eth.Address (addressFromPrivateKey)
|
||||
import Simplex.Messaging.Util (safeDecodeUtf8)
|
||||
import Simplex.Messaging.Names.Record (NameRecord (..), NameRegistration (..), NameResponse (..))
|
||||
import Simplex.Messaging.Util (decodeJSON, safeDecodeUtf8)
|
||||
import Test.Hspec hiding (it)
|
||||
import qualified Test.Hspec as Hspec
|
||||
|
||||
@@ -115,6 +120,10 @@ testWalletScan ps = withSmpServerAndNames $ \reg -> withNewTestChat ps "alice" a
|
||||
ownedName reg "alice.simplex" (accountAddress 1)
|
||||
alice ##> "/_wallet scan"
|
||||
alice <## "accounts: 1"
|
||||
-- the names the scan saw are recorded against the account that holds them
|
||||
names <- withCCTransaction alice $ \db -> DB.query_ db "SELECT account_index, name, name_response FROM wallet_names"
|
||||
map (\(n, nm, _) -> (n, nm)) names `shouldBe` [(1 :: Int64, "alice.simplex" :: Text)]
|
||||
map (\(_, _, r) -> registeredName <$> decodeJSON r) names `shouldBe` [Just (Just "alice.simplex")]
|
||||
-- the scan gives the imported phrase the counter it had none of
|
||||
alice ##> "/_wallet bind"
|
||||
alice <## "accounts: 1, 2"
|
||||
@@ -299,6 +308,12 @@ testWalletScanHiddenProfile ps = withSmpServerAndNames $ \_reg -> withNewTestCha
|
||||
alice ##> "/_wallet scan"
|
||||
alice <## "wallet: a hidden profile cannot own an account"
|
||||
|
||||
-- | The name a stored NameResponse carries, which is what makes the row findable again.
|
||||
registeredName :: NameResponse -> Maybe Text
|
||||
registeredName NameResponse {registration} = case registration of
|
||||
NRRegistered {nameRecord} -> Just $ nrName nameRecord
|
||||
_ -> Nothing
|
||||
|
||||
testWalletPersists :: HasCallStack => TestParams -> IO ()
|
||||
testWalletPersists ps = do
|
||||
phrase <- withNewTestChat ps "alice" aliceProfile $ \alice -> do
|
||||
|
||||
Reference in new issue
Block a user