Skip to content

Commit 04c115d

Browse files
committed
Merge bitcoin/bitcoin#33078: kernel: improve BlockChecked ownership semantics
1d9f1cb kernel: improve BlockChecked ownership semantics (stickies-v) 9ba1fff kernel: refactor: ConnectTip to pass block pointer by value (stickies-v) Pull request description: Subscribers to the BlockChecked validation interface event may need access to the block outside of the callback scope. Currently, this is only possible by copying the block, which makes exposing this validation interface event publicly either cumbersome or with significant copy overhead. By using shared_ptr, we make the shared ownership explicit and allow users to safely use the block outside of the callback scope. By using a const-ref shared_ptr, no atomic reference count cost is incurred if a subscriber does not require block ownership. For example: in #30595, this would allow us to drop the `kernel_BlockPointer` handle entirely, and generalize everything into `kernel_Block`. This PoC is implemented in https://github.com/stickies-v/bitcoin/commits/kernel/remove-blockpointer/. --- ### Performance I have added a benchmark in a [separate branch](https://github.com/stickies-v/bitcoin/commits/2025-07/validation-interface-ownership-benched/), to ensure this change does not lead to a problematic performance regression. Since most of the overhead comes from the subscribers, I have added scenarios for `One`, `Two`, and `Ten` subscribers. From these results, it appears there is no meaningful performance difference on my machine. When `BlockChecked()` takes a `const CBlock&` reference _(master)_: | ns/op | op/s | err% | total | benchmark |--------------------:|--------------------:|--------:|----------:|:---------- | 170.09 | 5,879,308.26 | 0.3% | 0.01 | `BlockCheckedOne` | 1,603.95 | 623,460.10 | 0.5% | 0.01 | `BlockCheckedTen` | 336.00 | 2,976,173.37 | 1.1% | 0.01 | `BlockCheckedTwo` When `BlockChecked()` takes a `const std::shared_ptr<const CBlock>&` _(this PR)_: | ns/op | op/s | err% | total | benchmark |--------------------:|--------------------:|--------:|----------:|:---------- | 172.20 | 5,807,155.33 | 0.1% | 0.01 | `BlockCheckedOne` | 1,596.79 | 626,254.52 | 0.0% | 0.01 | `BlockCheckedTen` | 333.38 | 2,999,603.17 | 0.3% | 0.01 | `BlockCheckedTwo` ACKs for top commit: achow101: ACK 1d9f1cb w0xlt: reACK bitcoin/bitcoin@1d9f1cb ryanofsky: Code review ACK 1d9f1cb. These all seem like simple changes that make sense TheCharlatan: ACK 1d9f1cb yuvicc: Code Review ACK 1d9f1cb Tree-SHA512: 7ed0cccb7883dbb1885917ef749ab7cae5d60ee803b7e3145b2954d885e81ba8c9d5ab1edb9694ce6b308235c621094c029024eaf99f1aab1b47311c40958095
2 parents bc797d2 + 1d9f1cb commit 04c115d

9 files changed

+42
-33
lines changed

src/bitcoin-chainstate.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -200,9 +200,9 @@ int main(int argc, char* argv[])
200200
explicit submitblock_StateCatcher(const uint256& hashIn) : hash(hashIn), found(false), state() {}
201201

202202
protected:
203-
void BlockChecked(const CBlock& block, const BlockValidationState& stateIn) override
203+
void BlockChecked(const std::shared_ptr<const CBlock>& block, const BlockValidationState& stateIn) override
204204
{
205-
if (block.GetHash() != hash)
205+
if (block->GetHash() != hash)
206206
return;
207207
found = true;
208208
state = stateIn;

src/net_processing.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -513,7 +513,7 @@ class PeerManagerImpl final : public PeerManager
513513
EXCLUSIVE_LOCKS_REQUIRED(!m_tx_download_mutex);
514514
void UpdatedBlockTip(const CBlockIndex *pindexNew, const CBlockIndex *pindexFork, bool fInitialDownload) override
515515
EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex);
516-
void BlockChecked(const CBlock& block, const BlockValidationState& state) override
516+
void BlockChecked(const std::shared_ptr<const CBlock>& block, const BlockValidationState& state) override
517517
EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex);
518518
void NewPoWValidBlock(const CBlockIndex *pindex, const std::shared_ptr<const CBlock>& pblock) override
519519
EXCLUSIVE_LOCKS_REQUIRED(!m_most_recent_block_mutex);
@@ -2071,11 +2071,11 @@ void PeerManagerImpl::UpdatedBlockTip(const CBlockIndex *pindexNew, const CBlock
20712071
* Handle invalid block rejection and consequent peer discouragement, maintain which
20722072
* peers announce compact blocks.
20732073
*/
2074-
void PeerManagerImpl::BlockChecked(const CBlock& block, const BlockValidationState& state)
2074+
void PeerManagerImpl::BlockChecked(const std::shared_ptr<const CBlock>& block, const BlockValidationState& state)
20752075
{
20762076
LOCK(cs_main);
20772077

2078-
const uint256 hash(block.GetHash());
2078+
const uint256 hash(block->GetHash());
20792079
std::map<uint256, std::pair<NodeId, bool>>::iterator it = mapBlockSource.find(hash);
20802080

20812081
// If the block failed validation, we know where it came from and we're still connected

src/rpc/mining.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1030,9 +1030,9 @@ class submitblock_StateCatcher final : public CValidationInterface
10301030
explicit submitblock_StateCatcher(const uint256 &hashIn) : hash(hashIn), state() {}
10311031

10321032
protected:
1033-
void BlockChecked(const CBlock& block, const BlockValidationState& stateIn) override {
1034-
if (block.GetHash() != hash)
1035-
return;
1033+
void BlockChecked(const std::shared_ptr<const CBlock>& block, const BlockValidationState& stateIn) override
1034+
{
1035+
if (block->GetHash() != hash) return;
10361036
found = true;
10371037
state = stateIn;
10381038
}

src/test/util/mining.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include <versionbits.h>
1919

2020
#include <algorithm>
21+
#include <memory>
2122

2223
using node::BlockAssembler;
2324
using node::NodeContext;
@@ -83,9 +84,9 @@ struct BlockValidationStateCatcher : public CValidationInterface {
8384
m_state{} {}
8485

8586
protected:
86-
void BlockChecked(const CBlock& block, const BlockValidationState& state) override
87+
void BlockChecked(const std::shared_ptr<const CBlock>& block, const BlockValidationState& state) override
8788
{
88-
if (block.GetHash() != m_hash) return;
89+
if (block->GetHash() != m_hash) return;
8990
m_state = state;
9091
}
9192
};

src/test/validationinterface_tests.cpp

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,12 @@
1212
#include <validationinterface.h>
1313

1414
#include <atomic>
15+
#include <memory>
1516

1617
BOOST_FIXTURE_TEST_SUITE(validationinterface_tests, ChainTestingSetup)
1718

1819
struct TestSubscriberNoop final : public CValidationInterface {
19-
void BlockChecked(const CBlock&, const BlockValidationState&) override {}
20+
void BlockChecked(const std::shared_ptr<const CBlock>&, const BlockValidationState&) override {}
2021
};
2122

2223
BOOST_AUTO_TEST_CASE(unregister_validation_interface_race)
@@ -25,10 +26,9 @@ BOOST_AUTO_TEST_CASE(unregister_validation_interface_race)
2526

2627
// Start thread to generate notifications
2728
std::thread gen{[&] {
28-
const CBlock block_dummy;
2929
BlockValidationState state_dummy;
3030
while (generate) {
31-
m_node.validation_signals->BlockChecked(block_dummy, state_dummy);
31+
m_node.validation_signals->BlockChecked(std::make_shared<const CBlock>(), state_dummy);
3232
}
3333
}};
3434

@@ -60,15 +60,14 @@ class TestInterface : public CValidationInterface
6060
{
6161
if (m_on_destroy) m_on_destroy();
6262
}
63-
void BlockChecked(const CBlock& block, const BlockValidationState& state) override
63+
void BlockChecked(const std::shared_ptr<const CBlock>& block, const BlockValidationState& state) override
6464
{
6565
if (m_on_call) m_on_call();
6666
}
6767
void Call()
6868
{
69-
CBlock block;
7069
BlockValidationState state;
71-
m_signals.BlockChecked(block, state);
70+
m_signals.BlockChecked(std::make_shared<const CBlock>(), state);
7271
}
7372
std::function<void()> m_on_call;
7473
std::function<void()> m_on_destroy;

src/validation.cpp

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3108,26 +3108,28 @@ class ConnectTrace {
31083108
*
31093109
* The block is added to connectTrace if connection succeeds.
31103110
*/
3111-
bool Chainstate::ConnectTip(BlockValidationState& state, CBlockIndex* pindexNew, const std::shared_ptr<const CBlock>& pblock, ConnectTrace& connectTrace, DisconnectedBlockTransactions& disconnectpool)
3111+
bool Chainstate::ConnectTip(
3112+
BlockValidationState& state,
3113+
CBlockIndex* pindexNew,
3114+
std::shared_ptr<const CBlock> block_to_connect,
3115+
ConnectTrace& connectTrace,
3116+
DisconnectedBlockTransactions& disconnectpool)
31123117
{
31133118
AssertLockHeld(cs_main);
31143119
if (m_mempool) AssertLockHeld(m_mempool->cs);
31153120

31163121
assert(pindexNew->pprev == m_chain.Tip());
31173122
// Read block from disk.
31183123
const auto time_1{SteadyClock::now()};
3119-
std::shared_ptr<const CBlock> pthisBlock;
3120-
if (!pblock) {
3124+
if (!block_to_connect) {
31213125
std::shared_ptr<CBlock> pblockNew = std::make_shared<CBlock>();
31223126
if (!m_blockman.ReadBlock(*pblockNew, *pindexNew)) {
31233127
return FatalError(m_chainman.GetNotifications(), state, _("Failed to read block."));
31243128
}
3125-
pthisBlock = pblockNew;
3129+
block_to_connect = std::move(pblockNew);
31263130
} else {
31273131
LogDebug(BCLog::BENCH, " - Using cached block\n");
3128-
pthisBlock = pblock;
31293132
}
3130-
const CBlock& blockConnecting = *pthisBlock;
31313133
// Apply the block atomically to the chain state.
31323134
const auto time_2{SteadyClock::now()};
31333135
SteadyClock::time_point time_3;
@@ -3137,9 +3139,9 @@ bool Chainstate::ConnectTip(BlockValidationState& state, CBlockIndex* pindexNew,
31373139
Ticks<MillisecondsDouble>(time_2 - time_1));
31383140
{
31393141
CCoinsViewCache view(&CoinsTip());
3140-
bool rv = ConnectBlock(blockConnecting, state, pindexNew, view);
3142+
bool rv = ConnectBlock(*block_to_connect, state, pindexNew, view);
31413143
if (m_chainman.m_options.signals) {
3142-
m_chainman.m_options.signals->BlockChecked(blockConnecting, state);
3144+
m_chainman.m_options.signals->BlockChecked(block_to_connect, state);
31433145
}
31443146
if (!rv) {
31453147
if (state.IsInvalid())
@@ -3175,8 +3177,8 @@ bool Chainstate::ConnectTip(BlockValidationState& state, CBlockIndex* pindexNew,
31753177
Ticks<MillisecondsDouble>(m_chainman.time_chainstate) / m_chainman.num_blocks_total);
31763178
// Remove conflicting transactions from the mempool.;
31773179
if (m_mempool) {
3178-
m_mempool->removeForBlock(blockConnecting.vtx, pindexNew->nHeight);
3179-
disconnectpool.removeForBlock(blockConnecting.vtx);
3180+
m_mempool->removeForBlock(block_to_connect->vtx, pindexNew->nHeight);
3181+
disconnectpool.removeForBlock(block_to_connect->vtx);
31803182
}
31813183
// Update m_chain & related variables.
31823184
m_chain.SetTip(*pindexNew);
@@ -3202,7 +3204,7 @@ bool Chainstate::ConnectTip(BlockValidationState& state, CBlockIndex* pindexNew,
32023204
m_chainman.MaybeCompleteSnapshotValidation();
32033205
}
32043206

3205-
connectTrace.BlockConnected(pindexNew, std::move(pthisBlock));
3207+
connectTrace.BlockConnected(pindexNew, std::move(block_to_connect));
32063208
return true;
32073209
}
32083210

@@ -4515,7 +4517,7 @@ bool ChainstateManager::ProcessNewBlock(const std::shared_ptr<const CBlock>& blo
45154517
}
45164518
if (!ret) {
45174519
if (m_options.signals) {
4518-
m_options.signals->BlockChecked(*block, state);
4520+
m_options.signals->BlockChecked(block, state);
45194521
}
45204522
LogError("%s: AcceptBlock FAILED (%s)\n", __func__, state.ToString());
45214523
return false;

src/validation.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -799,7 +799,12 @@ class Chainstate
799799

800800
protected:
801801
bool ActivateBestChainStep(BlockValidationState& state, CBlockIndex* pindexMostWork, const std::shared_ptr<const CBlock>& pblock, bool& fInvalidFound, ConnectTrace& connectTrace) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_mempool->cs);
802-
bool ConnectTip(BlockValidationState& state, CBlockIndex* pindexNew, const std::shared_ptr<const CBlock>& pblock, ConnectTrace& connectTrace, DisconnectedBlockTransactions& disconnectpool) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_mempool->cs);
802+
bool ConnectTip(
803+
BlockValidationState& state,
804+
CBlockIndex* pindexNew,
805+
std::shared_ptr<const CBlock> block_to_connect,
806+
ConnectTrace& connectTrace,
807+
DisconnectedBlockTransactions& disconnectpool) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_mempool->cs);
803808

804809
void InvalidBlockFound(CBlockIndex* pindex, const BlockValidationState& state) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
805810
CBlockIndex* FindMostWorkChain() EXCLUSIVE_LOCKS_REQUIRED(cs_main);

src/validationinterface.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include <util/task_runner.h>
1818

1919
#include <future>
20+
#include <memory>
2021
#include <unordered_map>
2122
#include <utility>
2223

@@ -245,9 +246,10 @@ void ValidationSignals::ChainStateFlushed(ChainstateRole role, const CBlockLocat
245246
locator.IsNull() ? "null" : locator.vHave.front().ToString());
246247
}
247248

248-
void ValidationSignals::BlockChecked(const CBlock& block, const BlockValidationState& state) {
249+
void ValidationSignals::BlockChecked(const std::shared_ptr<const CBlock>& block, const BlockValidationState& state)
250+
{
249251
LOG_EVENT("%s: block hash=%s state=%s", __func__,
250-
block.GetHash().ToString(), state.ToString());
252+
block->GetHash().ToString(), state.ToString());
251253
m_internals->Iterate([&](CValidationInterface& callbacks) { callbacks.BlockChecked(block, state); });
252254
}
253255

src/validationinterface.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ class CValidationInterface {
150150
* is guaranteed to be the current best block at the time the
151151
* callback was generated (not necessarily now).
152152
*/
153-
virtual void BlockChecked(const CBlock&, const BlockValidationState&) {}
153+
virtual void BlockChecked(const std::shared_ptr<const CBlock>&, const BlockValidationState&) {}
154154
/**
155155
* Notifies listeners that a block which builds directly on our current tip
156156
* has been received and connected to the headers tree, though not validated yet.
@@ -224,7 +224,7 @@ class ValidationSignals {
224224
void BlockConnected(ChainstateRole, const std::shared_ptr<const CBlock> &, const CBlockIndex *pindex);
225225
void BlockDisconnected(const std::shared_ptr<const CBlock> &, const CBlockIndex* pindex);
226226
void ChainStateFlushed(ChainstateRole, const CBlockLocator &);
227-
void BlockChecked(const CBlock&, const BlockValidationState&);
227+
void BlockChecked(const std::shared_ptr<const CBlock>&, const BlockValidationState&);
228228
void NewPoWValidBlock(const CBlockIndex *, const std::shared_ptr<const CBlock>&);
229229
};
230230

0 commit comments

Comments
 (0)