@@ -859,5 +859,111 @@ BOOST_FIXTURE_TEST_CASE(ZapSelectTx, TestChain100Setup)
859859 TestUnloadWallet (std::move (wallet));
860860}
861861
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+
862968BOOST_AUTO_TEST_SUITE_END ()
863969} // namespace wallet
0 commit comments