fix: follow up backport bitcoin#16426

This commit is contained in:
Konstantin Akimov 2023-02-10 22:04:34 +07:00 committed by PastaPastaPasta
parent d5269332a4
commit 3f491fa7cb
5 changed files with 57 additions and 41 deletions

View File

@ -139,7 +139,7 @@ public:
explicit ChainImpl(NodeContext& node) : m_node(node) {} explicit ChainImpl(NodeContext& node) : m_node(node) {}
std::optional<int> getHeight() override std::optional<int> getHeight() override
{ {
LOCK(cs_main); LOCK(::cs_main);
int height = ::ChainActive().Height(); int height = ::ChainActive().Height();
if (height >= 0) { if (height >= 0) {
return height; return height;
@ -148,7 +148,7 @@ public:
} }
std::optional<int> getBlockHeight(const uint256& hash) override std::optional<int> getBlockHeight(const uint256& hash) override
{ {
LOCK(cs_main); LOCK(::cs_main);
CBlockIndex* block = LookupBlockIndex(hash); CBlockIndex* block = LookupBlockIndex(hash);
if (block && ::ChainActive().Contains(block)) { if (block && ::ChainActive().Contains(block)) {
return block->nHeight; return block->nHeight;
@ -157,7 +157,7 @@ public:
} }
uint256 getBlockHash(int height) override uint256 getBlockHash(int height) override
{ {
LOCK(cs_main); LOCK(::cs_main);
CBlockIndex* block = ::ChainActive()[height]; CBlockIndex* block = ::ChainActive()[height];
assert(block != nullptr); assert(block != nullptr);
return block->GetBlockHash(); return block->GetBlockHash();

View File

@ -54,7 +54,7 @@ class Handler;
//! internal workings of the bitcoin node, and not being very convenient to use. //! internal workings of the bitcoin node, and not being very convenient to use.
//! Chain methods should be cleaned up and simplified over time. Examples: //! Chain methods should be cleaned up and simplified over time. Examples:
//! //!
//! * The initMessage() and showProgress() methods which the wallet uses to send //! * The initMessages() and showProgress() methods which the wallet uses to send
//! notifications to the GUI should go away when GUI and wallet can directly //! notifications to the GUI should go away when GUI and wallet can directly
//! communicate with each other without going through the node //! communicate with each other without going through the node
//! (https://github.com/bitcoin/bitcoin/pull/15288#discussion_r253321096). //! (https://github.com/bitcoin/bitcoin/pull/15288#discussion_r253321096).

View File

@ -317,7 +317,7 @@ public:
WalletValueMap value_map, WalletValueMap value_map,
WalletOrderForm order_form) override WalletOrderForm order_form) override
{ {
LOCK2(m_wallet->cs_wallet, cs_main); LOCK(m_wallet->cs_wallet);
ReserveDestination m_dest(m_wallet.get()); ReserveDestination m_dest(m_wallet.get());
m_wallet->CommitTransaction(std::move(tx), std::move(value_map), std::move(order_form)); m_wallet->CommitTransaction(std::move(tx), std::move(value_map), std::move(order_form));
} }

View File

@ -238,10 +238,7 @@ static UniValue gobject_prepare(const JSONRPCRequest& request)
} }
// -- send the tx to the network // -- send the tx to the network
{
LOCK(cs_main);
wallet->CommitTransaction(tx, {}, {}); wallet->CommitTransaction(tx, {}, {});
}
LogPrint(BCLog::GOBJECT, "gobject_prepare -- GetDataAsPlainString = %s, hash = %s, txid = %s\n", LogPrint(BCLog::GOBJECT, "gobject_prepare -- GetDataAsPlainString = %s, hash = %s, txid = %s\n",
govobj.GetDataAsPlainString(), govobj.GetHash().ToString(), tx->GetHash().ToString()); govobj.GetDataAsPlainString(), govobj.GetHash().ToString(), tx->GetHash().ToString());

View File

@ -534,10 +534,7 @@ public:
bilingual_str error; bilingual_str error;
CCoinControl dummy; CCoinControl dummy;
BOOST_CHECK(wallet->CreateTransaction({recipient}, tx, fee, changePos, error, dummy)); BOOST_CHECK(wallet->CreateTransaction({recipient}, tx, fee, changePos, error, dummy));
{
LOCK2(wallet->cs_wallet, cs_main);
wallet->CommitTransaction(tx, {}, {}); wallet->CommitTransaction(tx, {}, {});
}
CMutableTransaction blocktx; CMutableTransaction blocktx;
{ {
LOCK(wallet->cs_wallet); LOCK(wallet->cs_wallet);
@ -746,10 +743,7 @@ public:
bilingual_str strError; bilingual_str strError;
CCoinControl coinControl; CCoinControl coinControl;
BOOST_CHECK(wallet->CreateTransaction(GetRecipients(vecEntries), tx, nFeeRet, nChangePosRet, strError, coinControl)); BOOST_CHECK(wallet->CreateTransaction(GetRecipients(vecEntries), tx, nFeeRet, nChangePosRet, strError, coinControl));
{
LOCK2(wallet->cs_wallet, cs_main);
wallet->CommitTransaction(tx, {}, {}); wallet->CommitTransaction(tx, {}, {});
}
CMutableTransaction blocktx; CMutableTransaction blocktx;
{ {
LOCK(wallet->cs_wallet); LOCK(wallet->cs_wallet);
@ -1087,10 +1081,7 @@ BOOST_FIXTURE_TEST_CASE(select_coins_grouped_by_addresses, ListCoinsTestingSetup
tx1, fee, changePos, error, dummy)); tx1, fee, changePos, error, dummy));
BOOST_CHECK(wallet->CreateTransaction({CRecipient{GetScriptForRawPubKey({}), 1 * COIN, true /* subtract fee */}}, BOOST_CHECK(wallet->CreateTransaction({CRecipient{GetScriptForRawPubKey({}), 1 * COIN, true /* subtract fee */}},
tx2, fee, changePos, error, dummy)); tx2, fee, changePos, error, dummy));
{
LOCK2(wallet->cs_wallet, cs_main);
wallet->CommitTransaction(tx1, {}, {}); wallet->CommitTransaction(tx1, {}, {});
}
BOOST_CHECK_EQUAL(wallet->GetAvailableBalance(), 0); BOOST_CHECK_EQUAL(wallet->GetAvailableBalance(), 0);
CreateAndProcessBlock({CMutableTransaction(*tx2)}, GetScriptForRawPubKey({})); CreateAndProcessBlock({CMutableTransaction(*tx2)}, GetScriptForRawPubKey({}));
{ {
@ -1143,11 +1134,20 @@ BOOST_FIXTURE_TEST_CASE(wallet_disableprivkeys, TestChain100Setup)
//! conditions if it's called the same time an incoming transaction shows up in //! conditions if it's called the same time an incoming transaction shows up in
//! the mempool or a new block. //! the mempool or a new block.
//! //!
//! It isn't possible for a unit test to totally verify there aren't race //! It isn't possible to verify there aren't race condition in every case, so
//! conditions without hooking into the implementation more, so this test just //! this test just checks two specific cases and ensures that timing of
//! verifies that new transactions are detected during loading without any //! notifications in these cases doesn't prevent the wallet from detecting
//! notifications at all, to infer that timing of notifications shouldn't //! transactions.
//! matter. The test could be extended to cover other scenarios in the future. //!
//! In the first case, block and mempool transactions are created before the
//! wallet is loaded, but notifications about these transactions are delayed
//! until after it is loaded. The notifications are superfluous in this case, so
//! the test verifies the transactions are detected before they arrive.
//!
//! In the second case, block and mempool transactions are created after the
//! wallet rescan and notifications are immediately synced, to verify the wallet
//! must already have a handler in place for them, and there's no gap after
//! rescanning where new transactions in new blocks could be lost.
BOOST_FIXTURE_TEST_CASE(CreateWalletFromFile, TestChain100Setup) BOOST_FIXTURE_TEST_CASE(CreateWalletFromFile, TestChain100Setup)
{ {
gArgs.ForceSetArg("-unsafesqlitesync", "1"); gArgs.ForceSetArg("-unsafesqlitesync", "1");
@ -1159,6 +1159,7 @@ BOOST_FIXTURE_TEST_CASE(CreateWalletFromFile, TestChain100Setup)
AddKey(*wallet, key); AddKey(*wallet, key);
TestUnloadWallet(std::move(wallet)); TestUnloadWallet(std::move(wallet));
// Add log hook to detect AddToWallet events from rescans, blockConnected, // Add log hook to detect AddToWallet events from rescans, blockConnected,
// and transactionAddedToMempool notifications // and transactionAddedToMempool notifications
int addtx_count = 0; int addtx_count = 0;
@ -1167,21 +1168,14 @@ BOOST_FIXTURE_TEST_CASE(CreateWalletFromFile, TestChain100Setup)
return false; return false;
}); });
bool rescan_completed = false; bool rescan_completed = false;
DebugLogHelper rescan_check("[default wallet] Rescan completed", [&](const std::string* s) { DebugLogHelper rescan_check("[default wallet] Rescan completed", [&](const std::string* s) {
if (s) { if (s) rescan_completed = true;
// For now, just assert that cs_main is being held during the
// rescan, ensuring that a new block couldn't be connected
// that the wallet would miss. After
// https://github.com/bitcoin/bitcoin/pull/16426 when cs_main is no
// longer held, the test can be extended to append a new block here
// and check it's handled correctly.
// AssertLockHeld(::cs_main);
rescan_completed = true;
}
return false; return false;
}); });
// Block the queue to prevent the wallet receiving blockConnected and // Block the queue to prevent the wallet receiving blockConnected and
// transactionAddedToMempool notifications, and create block and mempool // transactionAddedToMempool notifications, and create block and mempool
// transactions paying to the wallet // transactions paying to the wallet
@ -1196,27 +1190,52 @@ BOOST_FIXTURE_TEST_CASE(CreateWalletFromFile, TestChain100Setup)
auto mempool_tx = TestSimpleSpend(*m_coinbase_txns[1], 0, coinbaseKey, GetScriptForRawPubKey(key.GetPubKey())); auto mempool_tx = TestSimpleSpend(*m_coinbase_txns[1], 0, coinbaseKey, GetScriptForRawPubKey(key.GetPubKey()));
BOOST_CHECK(chain->broadcastTransaction(MakeTransactionRef(mempool_tx), error, DEFAULT_TRANSACTION_MAXFEE, false)); BOOST_CHECK(chain->broadcastTransaction(MakeTransactionRef(mempool_tx), error, DEFAULT_TRANSACTION_MAXFEE, false));
// Reload wallet and make sure new transactions are detected despite events // Reload wallet and make sure new transactions are detected despite events
// being blocked // being blocked
wallet = TestLoadWallet(*chain); wallet = TestLoadWallet(*chain);
BOOST_CHECK(rescan_completed); BOOST_CHECK(rescan_completed);
BOOST_CHECK_EQUAL(addtx_count, 2); BOOST_CHECK_EQUAL(addtx_count, 2);
unsigned int block_tx_time, mempool_tx_time;
{ {
LOCK(wallet->cs_wallet); LOCK(wallet->cs_wallet);
block_tx_time = wallet->mapWallet.at(block_tx.GetHash()).nTimeReceived; BOOST_CHECK_EQUAL(wallet->mapWallet.count(block_tx.GetHash()), 1);
mempool_tx_time = wallet->mapWallet.at(mempool_tx.GetHash()).nTimeReceived; BOOST_CHECK_EQUAL(wallet->mapWallet.count(mempool_tx.GetHash()), 1);
} }
// Unblock notification queue and make sure stale blockConnected and // Unblock notification queue and make sure stale blockConnected and
// transactionAddedToMempool events are processed // transactionAddedToMempool events are processed
promise.set_value(); promise.set_value();
SyncWithValidationInterfaceQueue(); SyncWithValidationInterfaceQueue();
BOOST_CHECK_EQUAL(addtx_count, 4); BOOST_CHECK_EQUAL(addtx_count, 4);
TestUnloadWallet(std::move(wallet));
// Load wallet again, this time creating new block and mempool transactions
// paying to the wallet as the wallet finishes loading and syncing the
// queue so the events have to be handled immediately. Releasing the wallet
// lock during the sync is a little artificial but is needed to avoid a
// deadlock during the sync and simulates a new block notification happening
// as soon as possible.
addtx_count = 0;
auto handler = HandleLoadWallet([&](std::unique_ptr<interfaces::Wallet> wallet) EXCLUSIVE_LOCKS_REQUIRED(wallet->wallet()->cs_wallet) {
BOOST_CHECK(rescan_completed);
m_coinbase_txns.push_back(CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())).vtx[0]);
block_tx = TestSimpleSpend(*m_coinbase_txns[2], 0, coinbaseKey, GetScriptForRawPubKey(key.GetPubKey()));
m_coinbase_txns.push_back(CreateAndProcessBlock({block_tx}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())).vtx[0]);
mempool_tx = TestSimpleSpend(*m_coinbase_txns[3], 0, coinbaseKey, GetScriptForRawPubKey(key.GetPubKey()));
BOOST_CHECK(chain->broadcastTransaction(MakeTransactionRef(mempool_tx), error, DEFAULT_TRANSACTION_MAXFEE, false));
LEAVE_CRITICAL_SECTION(wallet->wallet()->cs_wallet);
SyncWithValidationInterfaceQueue();
ENTER_CRITICAL_SECTION(wallet->wallet()->cs_wallet);
});
wallet = TestLoadWallet(*chain);
BOOST_CHECK_EQUAL(addtx_count, 4);
{ {
LOCK(wallet->cs_wallet); LOCK(wallet->cs_wallet);
BOOST_CHECK_EQUAL(block_tx_time, wallet->mapWallet.at(block_tx.GetHash()).nTimeReceived); BOOST_CHECK_EQUAL(wallet->mapWallet.count(block_tx.GetHash()), 1);
BOOST_CHECK_EQUAL(mempool_tx_time, wallet->mapWallet.at(mempool_tx.GetHash()).nTimeReceived); BOOST_CHECK_EQUAL(wallet->mapWallet.count(mempool_tx.GetHash()), 1);
} }
TestUnloadWallet(std::move(wallet)); TestUnloadWallet(std::move(wallet));