Skip to content
This repository was archived by the owner on Aug 18, 2020. It is now read-only.

Commit d787f38

Browse files
iohk-bors[bot]KtorZkderme
committed
Merge #3943 #3947
3943: [CBR-495] Fix inconsistent metadata store after deletion r=jmitchell a=KtorZ ## Description We store some information related to transaction in the metadata store (sqlite). However, when looking up transaction with metadata referring to wallet we don't know about, we fail with a not so friendly error "WalletNotFound" despite no wallet being given as part of the query. This fix is actually two folds: - It discards incoherent transactions fetched from the DB, if any, and shout a warning in the log. This is in order to make the system more resilient to conconcurrent calls while a wallet or account is being deleted (since metadata and accounts / wallets are stored in separated databases, we can't easily run both delete in a single transaction). - It also deletes corresponding metadata when an account or a wallet is removed. This may cause extra damage? What if there are pending transactions when we delete the account or wallet. ## Linked issue [CBR-495](https://iohk.myjetbrains.com/youtrack/issue/CBR-495) 3947: [CBR-496] Fix --rebuild-db not rebuilding sqlite r=jmitchell a=KtorZ ## Description <!--- A brief description of this PR and the problem is trying to solve --> Turns out we aren't actually removing the sqlite database when passing --rebuild-db becaue the SQLite db is actually stored in a file and not a directory. ## Linked issue <!--- Put here the relevant issue from YouTrack --> [[CBR-496]](https://iohk.myjetbrains.com/youtrack/issue/CO-445) Co-authored-by: KtorZ <[email protected]> Co-authored-by: kderme <[email protected]>
3 parents bf565e8 + a74f4a0 + 56c179d commit d787f38

File tree

6 files changed

+100
-30
lines changed

6 files changed

+100
-30
lines changed

wallet-new/src/Cardano/Wallet/Kernel.hs

+3-3
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ import Data.Acid (AcidState, createArchive, createCheckpoint,
3131
openLocalStateFrom)
3232
import Data.Acid.Memory (openMemoryState)
3333
import qualified Data.Map.Strict as Map
34-
import System.Directory (doesDirectoryExist, removeDirectoryRecursive)
34+
import System.Directory (doesPathExist, removePathForcibly)
3535

3636
import Pos.Chain.Txp (TxAux (..))
3737
import Pos.Crypto (ProtocolMagic)
@@ -128,8 +128,8 @@ handlesOpen mode =
128128
UseFilePath (DatabaseOptions acidDb sqliteDb rebuildDB) -> do
129129
let deleteMaybe fp = do
130130
when rebuildDB $ do
131-
itsHere <- doesDirectoryExist fp
132-
when itsHere $ removeDirectoryRecursive fp
131+
itsHere <- doesPathExist fp
132+
when itsHere $ removePathForcibly fp
133133
deleteMaybe acidDb
134134
db <- openLocalStateFrom acidDb defDB
135135
deleteMaybe sqliteDb

wallet-new/src/Cardano/Wallet/Kernel/DB/Sqlite.hs

+35-7
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ module Cardano.Wallet.Kernel.DB.Sqlite (
1818
, putTxMeta
1919
, getTxMeta
2020
, getTxMetas
21+
, deleteTxMetas
2122

2223
-- * Unsafe functions
2324
, unsafeMigrateMetaDB
@@ -447,6 +448,34 @@ clearMetaDB conn = do
447448
putTxMeta :: Sqlite.Connection -> Kernel.TxMeta -> IO ()
448449
putTxMeta conn txMeta = void $ putTxMetaT conn txMeta
449450

451+
-- | Clear some metadata from the database
452+
deleteTxMetas
453+
:: Sqlite.Connection
454+
-- | Database Handle
455+
-> Core.Address
456+
-- | Target wallet
457+
-> Maybe Word32
458+
-- | A target account index. If none, delete metas for all accounts
459+
-> IO ()
460+
deleteTxMetas conn walletId mAccountIx = do
461+
runBeamSqlite conn $ SQL.runDelete $ SQL.delete (_mDbMeta metaDB) $ \meta ->
462+
conditionWalletId meta &&. conditionAccountIx meta
463+
where
464+
conditionWalletId
465+
:: TxMetaT (SQL.QExpr SqliteExpressionSyntax s)
466+
-> SQL.QGenExpr SQL.QValueContext SqliteExpressionSyntax s Bool
467+
conditionWalletId meta =
468+
_txMetaTableWalletId meta ==. SQL.val_ walletId
469+
conditionAccountIx
470+
:: TxMetaT (SQL.QExpr SqliteExpressionSyntax s)
471+
-> SQL.QGenExpr SQL.QValueContext SqliteExpressionSyntax s Bool
472+
conditionAccountIx meta = case mAccountIx of
473+
Nothing ->
474+
SQL.val_ True
475+
Just ix ->
476+
_txMetaTableAccountIx meta ==. SQL.val_ ix
477+
478+
450479
-- | Inserts a new 'Kernel.TxMeta' in the database, given its opaque
451480
-- 'MetaDBHandle'.
452481
putTxMetaT :: Sqlite.Connection -> Kernel.TxMeta -> IO Kernel.PutReturn
@@ -469,17 +498,16 @@ putTxMetaT conn txMeta =
469498
-- This is the only acceptable exception here. If anything else is thrown, that`s an error.
470499
t <- getTxMetasById conn txId
471500
case (Kernel.txIdIsomorphic txMeta <$> t) of
472-
Nothing ->
473-
-- Output is there but not TxMeta. This should never happen.
474-
-- This could be improved with foreign keys, which indicate
475-
-- the existence of at least one Meta entry for each Output.
476-
throwIO $ Kernel.InvariantViolated (Kernel.UndisputableLookupFailed "txId")
477501
Just False ->
478502
-- This violation means the Tx has same TxId but different
479503
-- Inputs (as set) or Outputs (ordered).
480504
throwIO $ Kernel.InvariantViolated (Kernel.TxIdInvariantViolated txId)
481-
Just True -> do
482-
-- If there not a TxId violation, we can try to insert TxMeta.
505+
_ -> do
506+
-- If there is not a TxId violation, we can try to insert TxMeta.
507+
-- We handle Nothing and (Just True) the same here, since
508+
-- it's possible that there is no Meta with this Inputs/Outputs.
509+
-- In the future we may consider doing a better cleanup to avoid
510+
-- such cases.
483511
res2 <- Sqlite.runDBAction $ runBeamSqlite conn $
484512
SQL.runInsert $ SQL.insert (_mDbMeta metaDB) $ SQL.insertValues [tMeta]
485513
case res2 of

wallet-new/src/Cardano/Wallet/Kernel/DB/TxMeta.hs

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ openMetaDB fp = do
2424
closeMetaDB = withMVar lock ConcreteStorage.closeMetaDB
2525
, migrateMetaDB = withMVar lock ConcreteStorage.unsafeMigrateMetaDB
2626
, clearMetaDB = withMVar lock ConcreteStorage.clearMetaDB
27+
, deleteTxMetas = \w a -> withMVar lock $ \c -> ConcreteStorage.deleteTxMetas c w a
2728
, getTxMeta = \t w a -> withMVar lock $ \c -> ConcreteStorage.getTxMeta c t w a
2829
, putTxMeta = \ t -> withMVar lock $ \c -> ConcreteStorage.putTxMeta c t
2930
, putTxMetaT = \ t -> withMVar lock $ \c -> ConcreteStorage.putTxMetaT c t

wallet-new/src/Cardano/Wallet/Kernel/DB/TxMeta/Types.hs

+1
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,7 @@ data MetaDBHandle = MetaDBHandle {
292292
closeMetaDB :: IO ()
293293
, migrateMetaDB :: IO ()
294294
, clearMetaDB :: IO ()
295+
, deleteTxMetas :: Core.Address -> Maybe Word32 -> IO ()
295296
, getTxMeta :: Txp.TxId -> Core.Address -> Word32 -> IO (Maybe TxMeta)
296297
, putTxMeta :: TxMeta -> IO ()
297298
, putTxMetaT :: TxMeta -> IO PutReturn

wallet-new/src/Cardano/Wallet/WalletLayer/Kernel/Accounts.hs

+10-3
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@ import Cardano.Wallet.API.V1.Types (V1 (..), WalletAddress)
2222
import qualified Cardano.Wallet.API.V1.Types as V1
2323
import qualified Cardano.Wallet.Kernel.Accounts as Kernel
2424
import qualified Cardano.Wallet.Kernel.DB.HdWallet as HD
25+
import Cardano.Wallet.Kernel.DB.InDb (fromDb)
2526
import Cardano.Wallet.Kernel.DB.Read (addressesByAccountId)
27+
import qualified Cardano.Wallet.Kernel.DB.TxMeta.Types as Kernel
2628
import Cardano.Wallet.Kernel.DB.Util.IxSet (Indexed (..), IxSet)
2729
import qualified Cardano.Wallet.Kernel.DB.Util.IxSet as IxSet
2830
import qualified Cardano.Wallet.Kernel.Internal as Kernel
@@ -122,10 +124,15 @@ deleteAccount :: MonadIO m
122124
-> V1.AccountIndex
123125
-> m (Either DeleteAccountError ())
124126
deleteAccount wallet wId accIx = runExceptT $ do
127+
rootId <- withExceptT DeleteAccountWalletIdDecodingFailed $
128+
fromRootId wId
125129
accId <- withExceptT DeleteAccountWalletIdDecodingFailed $
126-
fromAccountId wId accIx
127-
withExceptT DeleteAccountError $ ExceptT $ liftIO $
128-
Kernel.deleteAccount accId wallet
130+
fromAccountId wId accIx
131+
withExceptT DeleteAccountError $ ExceptT $ liftIO $ do
132+
let walletId = HD.getHdRootId rootId ^. fromDb
133+
let accountIx = Just $ V1.getAccIndex accIx
134+
Kernel.deleteTxMetas (wallet ^. Kernel.walletMeta) walletId accountIx
135+
Kernel.deleteAccount accId wallet
129136

130137
updateAccount :: MonadIO m
131138
=> Kernel.PassiveWallet

wallet-new/src/Cardano/Wallet/WalletLayer/Kernel/Transactions.hs

+50-17
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
{-# LANGUAGE LambdaCase #-}
2+
13
module Cardano.Wallet.WalletLayer.Kernel.Transactions (
24
getTransactions
35
, toTransaction
@@ -6,11 +8,13 @@ module Cardano.Wallet.WalletLayer.Kernel.Transactions (
68
import Universum
79

810
import Control.Monad.Except
11+
import Formatting (build, sformat)
912
import GHC.TypeLits (symbolVal)
1013

1114
import Pos.Chain.Txp (TxId)
1215
import Pos.Core (Address, Coin, SlotCount, SlotId, Timestamp,
1316
decodeTextAddress, flattenSlotId, getBlockCount)
17+
import Pos.Util.Wlog (Severity (..))
1418

1519
import Cardano.Wallet.API.Indices
1620
import Cardano.Wallet.API.Request
@@ -43,23 +47,52 @@ getTransactions wallet mbWalletId mbAccountIndex mbAddress params fop sop = lift
4347
let PaginationParams{..} = rpPaginationParams params
4448
let PerPage pp = ppPerPage
4549
let Page cp = ppPage
46-
accountFops <- castAccountFiltering mbWalletId mbAccountIndex
47-
mbSorting <- castSorting sop
48-
db <- liftIO $ Kernel.getWalletSnapshot wallet
49-
sc <- liftIO $ Node.getSlotCount (wallet ^. Kernel.walletNode)
50-
currentSlot <- liftIO $ Node.getTipSlotId (wallet ^. Kernel.walletNode)
51-
(meta, mbTotalEntries) <- liftIO $ TxMeta.getTxMetas
52-
(wallet ^. Kernel.walletMeta)
53-
(TxMeta.Offset . fromIntegral $ (cp - 1) * pp)
54-
(TxMeta.Limit . fromIntegral $ pp)
55-
accountFops
56-
(unV1 <$> mbAddress)
57-
(castFiltering $ mapIx unV1 <$> F.findMatchingFilterOp fop)
58-
(castFiltering $ mapIx unV1 <$> F.findMatchingFilterOp fop)
59-
mbSorting
60-
txs <- withExceptT GetTxUnknownHdAccount $
61-
mapM (metaToTx db sc currentSlot) meta
62-
return $ respond params txs mbTotalEntries
50+
(txs, total) <- go cp pp ([], Nothing)
51+
return $ respond params txs total
52+
where
53+
-- NOTE: See cardano-wallet#141
54+
--
55+
-- We may end up with some inconsistent metadata in the store. When fetching
56+
-- them all, instead of failing with a non very helpful 'WalletNotfound' or
57+
-- 'AccountNotFound' error because one or more metadata in the list contains
58+
-- unknown ids, we simply discard them from what we fetched and we fetch
59+
-- another batch up until we have enough (== pp).
60+
go cp pp (acc, total)
61+
| length acc >= pp =
62+
return $ (take pp acc, total)
63+
| otherwise = do
64+
accountFops <- castAccountFiltering mbWalletId mbAccountIndex
65+
mbSorting <- castSorting sop
66+
(metas, mbTotalEntries) <- liftIO $ TxMeta.getTxMetas
67+
(wallet ^. Kernel.walletMeta)
68+
(TxMeta.Offset . fromIntegral $ (cp - 1) * pp)
69+
(TxMeta.Limit . fromIntegral $ pp)
70+
accountFops
71+
(unV1 <$> mbAddress)
72+
(castFiltering $ mapIx unV1 <$> F.findMatchingFilterOp fop)
73+
(castFiltering $ mapIx unV1 <$> F.findMatchingFilterOp fop)
74+
mbSorting
75+
db <- liftIO $ Kernel.getWalletSnapshot wallet
76+
sc <- liftIO $ Node.getSlotCount (wallet ^. Kernel.walletNode)
77+
currentSlot <- liftIO $ Node.getTipSlotId (wallet ^. Kernel.walletNode)
78+
if null metas then
79+
-- A bit artificial, but we force the termination and make sure
80+
-- in the meantime that the algorithm only exits by one and only
81+
-- one branch.
82+
go cp (min pp $ length acc) (acc, total <|> mbTotalEntries)
83+
else do
84+
txs <- catMaybes <$> forM metas (\meta -> do
85+
runExceptT (metaToTx db sc currentSlot meta) >>= \case
86+
Left e -> do
87+
let warn = lift . ((wallet ^. Kernel.walletLogMessage) Warning)
88+
warn $ "Inconsistent entry in the metadata store: " <> sformat build e
89+
return Nothing
90+
91+
Right tx ->
92+
return (Just tx)
93+
)
94+
go (cp + 1) pp (acc ++ txs, total <|> mbTotalEntries)
95+
6396

6497
toTransaction :: MonadIO m
6598
=> Kernel.PassiveWallet

0 commit comments

Comments
 (0)