diff --git a/src/sync.cpp b/src/sync.cpp index e59f86b94fca..0e5c623d2126 100644 --- a/src/sync.cpp +++ b/src/sync.cpp @@ -4,6 +4,7 @@ #include +#include #include #include #include @@ -19,6 +20,19 @@ #include #include +#ifdef DEBUG_LOCKCONTENTION + +template +void ContendedLock(std::string_view name, std::string_view file, int nLine, LockType& lock) +{ + LOG_TIME_MICROS_WITH_CATEGORY(strprintf("lock contention %s, %s:%d", name, file, nLine), BCLog::LOCK); + lock.lock(); +} +template void ContendedLock(std::string_view name, std::string_view file, int nLine, std::unique_lock& lock); +template void ContendedLock(std::string_view name, std::string_view file, int nLine, std::unique_lock& lock); + +#endif + #ifdef DEBUG_LOCKORDER // // Early deadlock detection. diff --git a/src/sync.h b/src/sync.h index 548b51d9d168..00fdaae942b8 100644 --- a/src/sync.h +++ b/src/sync.h @@ -6,10 +6,6 @@ #ifndef BITCOIN_SYNC_H #define BITCOIN_SYNC_H -#ifdef DEBUG_LOCKCONTENTION -#include -#endif - #include // IWYU pragma: export #include @@ -77,6 +73,16 @@ inline void DeleteLock(void* cs) {} inline bool LockStackEmpty() { return true; } #endif +/* + * Called when a mutex fails to lock immediately because it is held by another + * thread, or spuriously. Responsible for locking the lock before returning. + */ +#ifdef DEBUG_LOCKCONTENTION + +template +void ContendedLock(std::string_view name, std::string_view file, int nLine, LockType& lock); +#endif + /** * Template mixin that adds -Wthread-safety locking annotations and lock order * checking to a subset of the mutex API. @@ -151,10 +157,12 @@ class SCOPED_LOCKABLE UniqueLock : public MutexType::unique_lock { EnterCritical(pszName, pszFile, nLine, Base::mutex()); #ifdef DEBUG_LOCKCONTENTION - if (Base::try_lock()) return; - LOG_TIME_MICROS_WITH_CATEGORY(strprintf("lock contention %s, %s:%d", pszName, pszFile, nLine), BCLog::LOCK); -#endif + if (!Base::try_lock()) { + ContendedLock(pszName, pszFile, nLine, static_cast(*this)); + } +#else Base::lock(); +#endif } bool TryEnter(const char* pszName, const char* pszFile, int nLine) diff --git a/src/validation.cpp b/src/validation.cpp index 1efe109b6f60..00c1bab501f3 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -2982,7 +2982,7 @@ bool Chainstate::DisconnectTip(BlockValidationState& state, DisconnectedBlockTra // Let wallets know transactions went from 1-confirmed to // 0-confirmed or conflicted: if (m_chainman.m_options.signals) { - m_chainman.m_options.signals->BlockDisconnected(pblock, pindexDelete); + m_chainman.m_options.signals->BlockDisconnected(std::move(pblock), pindexDelete); } return true; } @@ -3391,9 +3391,9 @@ bool Chainstate::ActivateBestChain(BlockValidationState& state, std::shared_ptr< } pindexNewTip = m_chain.Tip(); - for (const auto& [index, block] : connected_blocks) { + for (auto& [index, block] : std::move(connected_blocks)) { if (m_chainman.m_options.signals) { - m_chainman.m_options.signals->BlockConnected(chainstate_role, Assert(block), Assert(index)); + m_chainman.m_options.signals->BlockConnected(chainstate_role, std::move(Assert(block)), Assert(index)); } } diff --git a/src/validationinterface.cpp b/src/validationinterface.cpp index c7be6abc3a1e..45f38b3740b3 100644 --- a/src/validationinterface.cpp +++ b/src/validationinterface.cpp @@ -156,33 +156,39 @@ void ValidationSignals::SyncWithValidationInterfaceQueue() // Use a macro instead of a function for conditional logging to prevent // evaluating arguments when logging is not enabled. -// -// NOTE: The lambda captures all local variables by value. -#define ENQUEUE_AND_LOG_EVENT(event, fmt, name, ...) \ - do { \ - auto local_name = (name); \ - LOG_EVENT("Enqueuing " fmt, local_name, __VA_ARGS__); \ - m_internals->m_task_runner->insert([=] { \ - LOG_EVENT(fmt, local_name, __VA_ARGS__); \ - event(); \ - }); \ +#define ENQUEUE_AND_LOG_EVENT(event, log_msg) \ + do { \ + static_assert(std::is_rvalue_reference_v, \ + "event must be passed as an rvalue"); \ + static_assert(std::is_rvalue_reference_v, \ + "log_msg must be passed as an rvalue"); \ + auto enqueue_log_msg = (log_msg); \ + LOG_EVENT("Enqueuing %s", enqueue_log_msg); \ + m_internals->m_task_runner->insert([local_log_msg = std::move(enqueue_log_msg), local_event = (event)] { \ + LOG_EVENT("%s", local_log_msg); \ + local_event(); \ + }); \ } while (0) +#define LOG_MSG(fmt, ...) \ + (ShouldLog(BCLog::VALIDATION, BCLog::Level::Debug) ? tfm::format((fmt), __VA_ARGS__) : std::string{}) + #define LOG_EVENT(fmt, ...) \ - LogDebug(BCLog::VALIDATION, fmt "\n", __VA_ARGS__) + LogDebug(BCLog::VALIDATION, fmt, __VA_ARGS__) void ValidationSignals::UpdatedBlockTip(const CBlockIndex *pindexNew, const CBlockIndex *pindexFork, bool fInitialDownload) { // Dependencies exist that require UpdatedBlockTip events to be delivered in the order in which // the chain actually updates. One way to ensure this is for the caller to invoke this signal // in the same critical section where the chain is updated - auto event = [pindexNew, pindexFork, fInitialDownload, this] { - m_internals->Iterate([&](CValidationInterface& callbacks) { callbacks.UpdatedBlockTip(pindexNew, pindexFork, fInitialDownload); }); - }; - ENQUEUE_AND_LOG_EVENT(event, "%s: new block hash=%s fork block hash=%s (in IBD=%s)", __func__, + auto log_msg = LOG_MSG("%s: new block hash=%s fork block hash=%s (in IBD=%s)", __func__, pindexNew->GetBlockHash().ToString(), pindexFork ? pindexFork->GetBlockHash().ToString() : "null", fInitialDownload); + auto event = [pindexNew, pindexFork, fInitialDownload, this] { + m_internals->Iterate([&](CValidationInterface& callbacks) { callbacks.UpdatedBlockTip(pindexNew, pindexFork, fInitialDownload); }); + }; + ENQUEUE_AND_LOG_EVENT(std::move(event), std::move(log_msg)); } void ValidationSignals::ActiveTipChange(const CBlockIndex& new_tip, bool is_ibd) @@ -193,61 +199,67 @@ void ValidationSignals::ActiveTipChange(const CBlockIndex& new_tip, bool is_ibd) void ValidationSignals::TransactionAddedToMempool(const NewMempoolTransactionInfo& tx, uint64_t mempool_sequence) { + auto log_msg = LOG_MSG("%s: txid=%s wtxid=%s", __func__, + tx.info.m_tx->GetHash().ToString(), + tx.info.m_tx->GetWitnessHash().ToString()); auto event = [tx, mempool_sequence, this] { m_internals->Iterate([&](CValidationInterface& callbacks) { callbacks.TransactionAddedToMempool(tx, mempool_sequence); }); }; - ENQUEUE_AND_LOG_EVENT(event, "%s: txid=%s wtxid=%s", __func__, - tx.info.m_tx->GetHash().ToString(), - tx.info.m_tx->GetWitnessHash().ToString()); + ENQUEUE_AND_LOG_EVENT(std::move(event), std::move(log_msg)); } void ValidationSignals::TransactionRemovedFromMempool(const CTransactionRef& tx, MemPoolRemovalReason reason, uint64_t mempool_sequence) { - auto event = [tx, reason, mempool_sequence, this] { - m_internals->Iterate([&](CValidationInterface& callbacks) { callbacks.TransactionRemovedFromMempool(tx, reason, mempool_sequence); }); - }; - ENQUEUE_AND_LOG_EVENT(event, "%s: txid=%s wtxid=%s reason=%s", __func__, + auto log_msg = LOG_MSG("%s: txid=%s wtxid=%s reason=%s", __func__, tx->GetHash().ToString(), tx->GetWitnessHash().ToString(), RemovalReasonToString(reason)); + auto event = [tx, reason, mempool_sequence, this] { + m_internals->Iterate([&](CValidationInterface& callbacks) { callbacks.TransactionRemovedFromMempool(tx, reason, mempool_sequence); }); + }; + ENQUEUE_AND_LOG_EVENT(std::move(event), std::move(log_msg)); } -void ValidationSignals::BlockConnected(const ChainstateRole& role, const std::shared_ptr& pblock, const CBlockIndex* pindex) +void ValidationSignals::BlockConnected(const ChainstateRole& role, std::shared_ptr pblock, const CBlockIndex* pindex) { - auto event = [role, pblock, pindex, this] { - m_internals->Iterate([&](CValidationInterface& callbacks) { callbacks.BlockConnected(role, pblock, pindex); }); - }; - ENQUEUE_AND_LOG_EVENT(event, "%s: block hash=%s block height=%d", __func__, + auto log_msg = LOG_MSG("%s: block hash=%s block height=%d", __func__, pblock->GetHash().ToString(), pindex->nHeight); + auto event = [role, pblock = std::move(pblock), pindex, this] { + m_internals->Iterate([&](CValidationInterface& callbacks) { callbacks.BlockConnected(role, pblock, pindex); }); + }; + ENQUEUE_AND_LOG_EVENT(std::move(event), std::move(log_msg)); } void ValidationSignals::MempoolTransactionsRemovedForBlock(const std::vector& txs_removed_for_block, unsigned int nBlockHeight) { + auto log_msg = LOG_MSG("%s: block height=%s txs removed=%s", __func__, + nBlockHeight, + txs_removed_for_block.size()); auto event = [txs_removed_for_block, nBlockHeight, this] { m_internals->Iterate([&](CValidationInterface& callbacks) { callbacks.MempoolTransactionsRemovedForBlock(txs_removed_for_block, nBlockHeight); }); }; - ENQUEUE_AND_LOG_EVENT(event, "%s: block height=%s txs removed=%s", __func__, - nBlockHeight, - txs_removed_for_block.size()); + ENQUEUE_AND_LOG_EVENT(std::move(event), std::move(log_msg)); } -void ValidationSignals::BlockDisconnected(const std::shared_ptr& pblock, const CBlockIndex* pindex) +void ValidationSignals::BlockDisconnected(std::shared_ptr pblock, const CBlockIndex* pindex) { - auto event = [pblock, pindex, this] { - m_internals->Iterate([&](CValidationInterface& callbacks) { callbacks.BlockDisconnected(pblock, pindex); }); - }; - ENQUEUE_AND_LOG_EVENT(event, "%s: block hash=%s block height=%d", __func__, + auto log_msg = LOG_MSG("%s: block hash=%s block height=%d", __func__, pblock->GetHash().ToString(), pindex->nHeight); + auto event = [pblock = std::move(pblock), pindex, this] { + m_internals->Iterate([&](CValidationInterface& callbacks) { callbacks.BlockDisconnected(pblock, pindex); }); + }; + ENQUEUE_AND_LOG_EVENT(std::move(event), std::move(log_msg)); } void ValidationSignals::ChainStateFlushed(const ChainstateRole& role, const CBlockLocator& locator) { + auto log_msg = LOG_MSG("%s: block hash=%s", __func__, + locator.IsNull() ? "null" : locator.vHave.front().ToString()); auto event = [role, locator, this] { m_internals->Iterate([&](CValidationInterface& callbacks) { callbacks.ChainStateFlushed(role, locator); }); }; - ENQUEUE_AND_LOG_EVENT(event, "%s: block hash=%s", __func__, - locator.IsNull() ? "null" : locator.vHave.front().ToString()); + ENQUEUE_AND_LOG_EVENT(std::move(event), std::move(log_msg)); } void ValidationSignals::BlockChecked(const std::shared_ptr& block, const BlockValidationState& state) diff --git a/src/validationinterface.h b/src/validationinterface.h index 4777e8dca8db..8cfe41380ecd 100644 --- a/src/validationinterface.h +++ b/src/validationinterface.h @@ -223,8 +223,8 @@ class ValidationSignals { void TransactionAddedToMempool(const NewMempoolTransactionInfo&, uint64_t mempool_sequence); void TransactionRemovedFromMempool(const CTransactionRef&, MemPoolRemovalReason, uint64_t mempool_sequence); void MempoolTransactionsRemovedForBlock(const std::vector&, unsigned int nBlockHeight); - void BlockConnected(const kernel::ChainstateRole&, const std::shared_ptr&, const CBlockIndex* pindex); - void BlockDisconnected(const std::shared_ptr &, const CBlockIndex* pindex); + void BlockConnected(const kernel::ChainstateRole&, std::shared_ptr, const CBlockIndex* pindex); + void BlockDisconnected(std::shared_ptr, const CBlockIndex* pindex); void ChainStateFlushed(const kernel::ChainstateRole&, const CBlockLocator&); void BlockChecked(const std::shared_ptr&, const BlockValidationState&); void NewPoWValidBlock(const CBlockIndex *, const std::shared_ptr&); diff --git a/src/wallet/test/fuzz/scriptpubkeyman.cpp b/src/wallet/test/fuzz/scriptpubkeyman.cpp index 77b913897817..a627c7705413 100644 --- a/src/wallet/test/fuzz/scriptpubkeyman.cpp +++ b/src/wallet/test/fuzz/scriptpubkeyman.cpp @@ -22,7 +22,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -47,10 +49,15 @@ void initialize_spkm() { static const auto testing_setup{MakeNoLogFileContext()}; g_setup = testing_setup.get(); - SelectParams(ChainType::MAIN); MOCKED_DESC_CONVERTER.Init(); } +void initialize_spkm_migration() +{ + static const auto testing_setup{MakeNoLogFileContext()}; + g_setup = testing_setup.get(); +} + static std::optional> CreateWalletDescriptor(FuzzedDataProvider& fuzzed_data_provider) { const std::string mocked_descriptor{fuzzed_data_provider.ConsumeRandomLengthString()}; @@ -194,5 +201,141 @@ FUZZ_TARGET(scriptpubkeyman, .init = initialize_spkm) (void)spk_manager->GetKeyPoolSize(); } +FUZZ_TARGET(spkm_migration, .init = initialize_spkm_migration) +{ + SeedRandomStateForTest(SeedRand::ZEROS); + FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()}; + SetMockTime(ConsumeTime(fuzzed_data_provider)); + const auto& node{g_setup->m_node}; + Chainstate& chainstate{node.chainman->ActiveChainstate()}; + + std::unique_ptr wallet_ptr{std::make_unique(node.chain.get(), "", CreateMockableWalletDatabase())}; + CWallet& wallet{*wallet_ptr}; + wallet.m_keypool_size = 1; + { + LOCK(wallet.cs_wallet); + wallet.UnsetWalletFlag(WALLET_FLAG_DESCRIPTORS); + wallet.SetLastBlockProcessed(chainstate.m_chain.Height(), chainstate.m_chain.Tip()->GetBlockHash()); + } + + auto& legacy_data{*wallet.GetOrCreateLegacyDataSPKM()}; + + std::vector keys; + LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 30) { + const auto key{ConsumePrivateKey(fuzzed_data_provider)}; + if (!key.IsValid()) return; + auto pub_key{key.GetPubKey()}; + if (!pub_key.IsFullyValid()) return; + if (legacy_data.LoadKey(key, pub_key) && std::find(keys.begin(), keys.end(), key) == keys.end()) keys.push_back(key); + } + + bool add_hd_chain{fuzzed_data_provider.ConsumeBool() && !keys.empty()}; + CHDChain hd_chain; + auto version{fuzzed_data_provider.ConsumeBool() ? CHDChain::VERSION_HD_CHAIN_SPLIT : CHDChain::VERSION_HD_BASE}; + CKey hd_key; + if (add_hd_chain) { + hd_key = PickValue(fuzzed_data_provider, keys); + hd_chain.nVersion = version; + hd_chain.seed_id = hd_key.GetPubKey().GetID(); + legacy_data.LoadHDChain(hd_chain); + } + + bool add_inactive_hd_chain{fuzzed_data_provider.ConsumeBool() && !keys.empty()}; + if (add_inactive_hd_chain) { + hd_key = PickValue(fuzzed_data_provider, keys); + hd_chain.nVersion = fuzzed_data_provider.ConsumeBool() ? CHDChain::VERSION_HD_CHAIN_SPLIT : CHDChain::VERSION_HD_BASE; + hd_chain.seed_id = hd_key.GetPubKey().GetID(); + legacy_data.AddInactiveHDChain(hd_chain); + } + + bool watch_only = false; + const auto pub_key = ConsumeDeserializable(fuzzed_data_provider); + if (!pub_key || !pub_key->IsFullyValid()) return; + auto script_dest{GetScriptForDestination(WitnessV0KeyHash{*pub_key})}; + if (fuzzed_data_provider.ConsumeBool()) { + script_dest = GetScriptForDestination(CTxDestination{PKHash(*pub_key)}); + } + if (legacy_data.LoadWatchOnly(script_dest)) watch_only = true; + + size_t added_script{0}; + bool good_data{true}; + LIMITED_WHILE(good_data && fuzzed_data_provider.ConsumeBool(), 30) { + CallOneOf( + fuzzed_data_provider, + [&] { + CKey key; + if (!keys.empty()) { + key = PickValue(fuzzed_data_provider, keys); + } else { + key = ConsumePrivateKey(fuzzed_data_provider, /*compressed=*/fuzzed_data_provider.ConsumeBool()); + } + if (!key.IsValid()) return; + auto pub_key{key.GetPubKey()}; + CScript script; + CallOneOf( + fuzzed_data_provider, + [&] { + script = GetScriptForDestination(CTxDestination{PKHash(pub_key)}); + }, + [&] { + script = GetScriptForDestination(WitnessV0KeyHash(pub_key)); + }, + [&] { + std::optional script_opt{ConsumeDeserializable(fuzzed_data_provider)}; + if (!script_opt) { + good_data = false; + return; + } + script = script_opt.value(); + } + ); + if (fuzzed_data_provider.ConsumeBool()) script = GetScriptForDestination(ScriptHash(script)); + if (!legacy_data.HaveCScript(CScriptID(script)) && legacy_data.AddCScript(script)) added_script++; + }, + [&] { + CKey key; + if (!keys.empty()) { + key = PickValue(fuzzed_data_provider, keys); + } else { + key = ConsumePrivateKey(fuzzed_data_provider, /*compressed=*/fuzzed_data_provider.ConsumeBool()); + } + if (!key.IsValid()) return; + const auto num_keys{fuzzed_data_provider.ConsumeIntegralInRange(1, MAX_PUBKEYS_PER_MULTISIG)}; + std::vector pubkeys; + pubkeys.emplace_back(key.GetPubKey()); + for (size_t i = 1; i < num_keys; i++) { + if (fuzzed_data_provider.ConsumeBool()) { + pubkeys.emplace_back(key.GetPubKey()); + } else { + CKey private_key{ConsumePrivateKey(fuzzed_data_provider, /*compressed=*/fuzzed_data_provider.ConsumeBool())}; + if (!private_key.IsValid()) return; + pubkeys.emplace_back(private_key.GetPubKey()); + } + } + if (pubkeys.size() < num_keys) return; + CScript multisig_script{GetScriptForMultisig(num_keys, pubkeys)}; + if (!legacy_data.HaveCScript(CScriptID(multisig_script)) && legacy_data.AddCScript(multisig_script)) { + added_script++; + } + } + ); + } + + auto result{legacy_data.MigrateToDescriptor()}; + assert(result); + size_t added_chains{static_cast(add_hd_chain) + static_cast(add_inactive_hd_chain)}; + if ((add_hd_chain && version >= CHDChain::VERSION_HD_CHAIN_SPLIT) || (!add_hd_chain && add_inactive_hd_chain)) { + added_chains *= 2; + } + size_t added_size{keys.size() + added_chains}; + if (added_script > 0) { + assert(result->desc_spkms.size() >= added_size); + } else { + assert(result->desc_spkms.size() == added_size); + } + if (watch_only) assert(!result->watch_descs.empty()); + if (!result->solvable_descs.empty()) assert(added_script > 0); +} + } // namespace } // namespace wallet