@@ -859,5 +859,111 @@ BOOST_FIXTURE_TEST_CASE(ZapSelectTx, TestChain100Setup)
859
859
TestUnloadWallet (std::move (wallet));
860
860
}
861
861
862
+ /* * RAII class that provides access to a FailDatabase. Which fails if needed. */
863
+ class FailBatch : public DatabaseBatch
864
+ {
865
+ private:
866
+ bool m_pass{true };
867
+ bool ReadKey (CDataStream&& key, CDataStream& value) override { return m_pass; }
868
+ bool WriteKey (CDataStream&& key, CDataStream&& value, bool overwrite=true ) override { return m_pass; }
869
+ bool EraseKey (CDataStream&& key) override { return m_pass; }
870
+ bool HasKey (CDataStream&& key) override { return m_pass; }
871
+
872
+ public:
873
+ explicit FailBatch (bool pass) : m_pass(pass) {}
874
+ void Flush () override {}
875
+ void Close () override {}
876
+
877
+ bool StartCursor () override { return true ; }
878
+ bool ReadAtCursor (CDataStream& ssKey, CDataStream& ssValue, bool & complete) override { return false ; }
879
+ void CloseCursor () override {}
880
+ bool TxnBegin () override { return false ; }
881
+ bool TxnCommit () override { return false ; }
882
+ bool TxnAbort () override { return false ; }
883
+ };
884
+
885
+ /* * A dummy WalletDatabase that does nothing, only fails if needed.**/
886
+ class FailDatabase : public WalletDatabase
887
+ {
888
+ public:
889
+ bool m_pass{true }; // false when this db should fail
890
+
891
+ void Open () override {};
892
+ void AddRef () override {}
893
+ void RemoveRef () override {}
894
+ bool Rewrite (const char * pszSkip=nullptr ) override { return true ; }
895
+ bool Backup (const std::string& strDest) const override { return true ; }
896
+ void Close () override {}
897
+ void Flush () override {}
898
+ bool PeriodicFlush () override { return true ; }
899
+ void IncrementUpdateCounter () override { ++nUpdateCounter; }
900
+ void ReloadDbEnv () override {}
901
+ std::string Filename () override { return " faildb" ; }
902
+ std::string Format () override { return " faildb" ; }
903
+ std::unique_ptr<DatabaseBatch> MakeBatch (bool flush_on_close = true ) override { return std::make_unique<FailBatch>(m_pass); }
904
+ };
905
+
906
+ /* *
907
+ * Checks a wallet invalid state where the inputs (prev-txs) of a new arriving transaction are not marked dirty,
908
+ * while the transaction that spends them exist inside the in-memory wallet tx map (not stored on db due a db write failure).
909
+ */
910
+ BOOST_FIXTURE_TEST_CASE (wallet_sync_tx_invalid_state_test, TestingSetup)
911
+ {
912
+ CWallet wallet (m_node.chain .get (), " " , m_args, std::make_unique<FailDatabase>());
913
+ {
914
+ LOCK (wallet.cs_wallet );
915
+ wallet.SetWalletFlag (WALLET_FLAG_DESCRIPTORS);
916
+ wallet.SetupDescriptorScriptPubKeyMans ();
917
+ }
918
+
919
+ // Add tx to wallet
920
+ const auto & op_dest = wallet.GetNewDestination (OutputType::BECH32M, " " );
921
+ BOOST_ASSERT (op_dest.HasRes ());
922
+ const CTxDestination& dest = op_dest.GetObj ();
923
+
924
+ CMutableTransaction mtx;
925
+ mtx.vout .push_back ({COIN, GetScriptForDestination (dest)});
926
+ mtx.vin .push_back (CTxIn (g_insecure_rand_ctx.rand256 (), 0 ));
927
+ const auto & tx_id_to_spend = wallet.AddToWallet (MakeTransactionRef (mtx), TxStateInMempool{})->GetHash ();
928
+
929
+ {
930
+ // Cache and verify available balance for the wtx
931
+ LOCK (wallet.cs_wallet );
932
+ const CWalletTx* wtx_to_spend = wallet.GetWalletTx (tx_id_to_spend);
933
+ BOOST_CHECK_EQUAL (CachedTxGetAvailableCredit (wallet, *wtx_to_spend), 1 * COIN);
934
+ }
935
+
936
+ // Now the good case:
937
+ // 1) Add a transaction that spends the previously created transaction
938
+ // 2) Verify that the available balance of this new tx and the old one is updated (prev tx is marked dirty)
939
+
940
+ mtx.vin .clear ();
941
+ mtx.vin .push_back (CTxIn (tx_id_to_spend, 0 ));
942
+ wallet.transactionAddedToMempool (MakeTransactionRef (mtx), 0 );
943
+ const uint256& good_tx_id = mtx.GetHash ();
944
+
945
+ {
946
+ // Verify balance update for the new tx and the old one
947
+ LOCK (wallet.cs_wallet );
948
+ const CWalletTx* new_wtx = wallet.GetWalletTx (good_tx_id);
949
+ BOOST_CHECK_EQUAL (CachedTxGetAvailableCredit (wallet, *new_wtx), 1 * COIN);
950
+
951
+ // Now the old wtx
952
+ const CWalletTx* wtx_to_spend = wallet.GetWalletTx (tx_id_to_spend);
953
+ BOOST_CHECK_EQUAL (CachedTxGetAvailableCredit (wallet, *wtx_to_spend), 0 * COIN);
954
+ }
955
+
956
+ // Now the bad case:
957
+ // 1) Make db always fail
958
+ // 2) Try to add a transaction that spends the previously created transaction and
959
+ // verify that we are not moving forward if the wallet cannot store it
960
+ static_cast <FailDatabase&>(wallet.GetDatabase ()).m_pass = false ;
961
+ mtx.vin .clear ();
962
+ mtx.vin .push_back (CTxIn (good_tx_id, 0 ));
963
+ BOOST_CHECK_EXCEPTION (wallet.transactionAddedToMempool (MakeTransactionRef (mtx), 0 ),
964
+ std::runtime_error,
965
+ HasReason (" DB error adding transaction to wallet, write failed" ));
966
+ }
967
+
862
968
BOOST_AUTO_TEST_SUITE_END ()
863
969
} // namespace wallet
0 commit comments