From 72862bec63112241f7b4af07cb7a6bfe8bbdf75a Mon Sep 17 00:00:00 2001 From: shum Date: Wed, 21 Oct 2026 23:31:04 +0000 Subject: [PATCH] store: move transactions from the status read --- .../src/StoreService/Names/Store.hs | 9 ++++---- tests/Bots/StoreService/NamesStoreTests.hs | 22 ++++++++++++++++--- 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/apps/simplex-store-service/src/StoreService/Names/Store.hs b/apps/simplex-store-service/src/StoreService/Names/Store.hs index 2d9f476abe..479fab42a7 100644 --- a/apps/simplex-store-service/src/StoreService/Names/Store.hs +++ b/apps/simplex-store-service/src/StoreService/Names/Store.hs @@ -767,13 +767,14 @@ insertTransaction db for NewTransaction {ntxRaw, ntxHash, ntxNonce} now = do (rid, editId, kind, Binary ntxRaw, Binary ntxHash, int64 ntxNonce, truncateToSecond now, TSPending) insertedRowId db -recordTransactionOutcome :: DB.Connection -> ByteString -> TxStatus -> Maybe Word64 -> IO Bool -recordTransactionOutcome db hash status block = +-- | Moves a transaction on from the status the caller read, so an outcome is never overwritten unseen; False when it is not in that status. +recordTransactionOutcome :: DB.Connection -> ByteString -> TxStatus -> TxStatus -> Maybe Word64 -> IO Bool +recordTransactionOutcome db hash from status block = (> 0) <$> executeChanging db - "UPDATE sx_badge_service_name_transactions SET status = ?, mined_block = ? WHERE tx_hash = ?" - (status, int64 <$> block, Binary hash) + "UPDATE sx_badge_service_name_transactions SET status = ?, mined_block = ? WHERE tx_hash = ? AND status = ?" + (status, int64 <$> block, Binary hash, from) transactionColumns :: Query transactionColumns = diff --git a/tests/Bots/StoreService/NamesStoreTests.hs b/tests/Bots/StoreService/NamesStoreTests.hs index 847897e090..ad66c6abff 100644 --- a/tests/Bots/StoreService/NamesStoreTests.hs +++ b/tests/Bots/StoreService/NamesStoreTests.hs @@ -76,6 +76,7 @@ namesStoreTests = describe "StoreService names: store" $ do it "revokes an unspent code, reserved or not, never a spent one" testRevoke describe "transactions" $ do it "keeps a signed transaction pending until its outcome, and lists them by owner" testTransactions + it "moves a transaction only from the status its caller read" testTransactionFromStatus describe "record edits" $ do it "holds one pending or mined edit per owner and nonce, freed by a revert" testEditNonceUnique it "settles a pending edit once, with its mined hash" testSettleEdit @@ -710,15 +711,30 @@ testTransactions = withServiceStore $ \st -> do map (\NameTransaction {ntFor, ntNonce, ntStatus} -> (ntFor, ntNonce, ntStatus)) unsettled `shouldBe` [(TxEdit editId, 1, TSPending), (TxCommit r1, 2, TSPending), (TxRegister r1, 3, TSPending)] map ntRaw unsettled `shouldBe` map (\n -> digestFixture (n + 100)) [1, 2, 3] - withTransaction st (\db -> recordTransactionOutcome db (digestFixture 2) TSMined (Just 77)) `shouldReturn` True - withTransaction st (\db -> recordTransactionOutcome db (digestFixture 1) TSReverted (Just 78)) `shouldReturn` True - withTransaction st (\db -> recordTransactionOutcome db (digestFixture 50) TSMined (Just 79)) `shouldReturn` False + let outcome n from status block = withTransaction st $ \db -> recordTransactionOutcome db (digestFixture n) from status block + outcome 2 TSPending TSMined (Just 77) `shouldReturn` True + outcome 1 TSPending TSReverted (Just 78) `shouldReturn` True + outcome 50 TSPending TSMined (Just 79) `shouldReturn` False + outcome 3 TSMined TSPending Nothing `shouldReturn` False map ntFor <$> withTransaction st pendingTransactions `shouldReturn` [TxRegister r1] ofRegistration <- withTransaction st $ \db -> registrationTransactions db r1 map (\NameTransaction {ntId, ntFor, ntMinedBlock, ntStatus, ntSentAt} -> (ntId, ntFor, ntMinedBlock, ntStatus, ntSentAt)) ofRegistration `shouldBe` [(commitId, TxCommit r1, Just 77, TSMined, at 1), (registerId, TxRegister r1, Nothing, TSPending, at 3)] map ntStatus <$> withTransaction st (\db -> editTransactions db editId) `shouldReturn` [TSReverted] +testTransactionFromStatus :: IO () +testTransactionFromStatus = withServiceStore $ \st -> do + code <- issue st 1 usable + placeCode st "r1" ownerA (order "alicebakery" 1) code `shouldReturn` Right () + let outcome from status block = withTransaction st $ \db -> recordTransactionOutcome db (digestFixture 2) from status block + current = withTransaction st $ \db -> map (\NameTransaction {ntStatus, ntMinedBlock} -> (ntStatus, ntMinedBlock)) <$> registrationTransactions db (RegistrationId "r1") + void $ withTransaction st $ \db -> insertTransaction db (TxCommit $ RegistrationId "r1") NewTransaction {ntxRaw = "raw", ntxHash = digestFixture 2, ntxNonce = 0} t0 + outcome TSPending TSMined (Just 77) `shouldReturn` True + outcome TSPending TSDropped Nothing `shouldReturn` False + current `shouldReturn` [(TSMined, Just 77)] + outcome TSMined TSPending Nothing `shouldReturn` True + current `shouldReturn` [(TSPending, Nothing)] + testEditNonceUnique :: IO () testEditNonceUnique = withServiceStore $ \st -> do let run action = withTransaction st action