Coverage Report

Created: 2026-10-19 15:22

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/tmp/bitcoin/src/node/block_template_manager.cpp
Line
Count
Source
1
// Copyright (c) The Bitcoin Core developers
2
// Distributed under the MIT software license, see the accompanying
3
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4
5
#include <node/block_template_manager.h>
6
7
#include <chain.h>
8
#include <consensus/amount.h>
9
#include <consensus/params.h>
10
#include <consensus/validation.h>
11
#include <interfaces/types.h>
12
#include <kernel/chainparams.h>
13
#include <node/block_validation_state_catcher.h>
14
#include <node/kernel_notifications.h>
15
#include <node/miner.h>
16
#include <node/mining_args.h>
17
#include <primitives/block.h>
18
#include <sync.h>
19
#include <uint256.h>
20
#include <util/check.h>
21
#include <util/signalinterrupt.h>
22
#include <validation.h>
23
#include <validationinterface.h>
24
25
#include <algorithm>
26
#include <compare>
27
#include <condition_variable>
28
#include <numeric>
29
#include <utility>
30
#include <vector>
31
32
namespace node {
33
34
using interfaces::BlockRef;
35
36
BlockTemplateManager::BlockTemplateManager(CTxMemPool& mempool, ChainstateManager& chainman,
37
                                           KernelNotifications& notifications,
38
                                           BlockCreateOptions block_create_args)
39
1.33k
    : m_mempool(mempool), m_chainman(chainman), m_notifications(notifications), m_block_create_args(std::move(block_create_args))
40
1.33k
{
41
1.33k
}
42
43
std::unique_ptr<CBlockTemplate> BlockTemplateManager::CreateNewTemplate(const BlockCreateOptions& options)
44
43.3k
{
45
43.3k
    return BlockAssembler{
46
43.3k
        m_chainman.ActiveChainstate(),
47
43.3k
        &m_mempool,
48
43.3k
        MergeMiningOptions(options, m_block_create_args),
49
43.3k
    }.CreateNewBlock();
50
43.3k
}
51
52
bool BlockTemplateManager::SubmitBlock(const std::shared_ptr<const CBlock>& block, std::string& reason, std::string& debug)
53
197
{
54
197
    reason.clear();
55
197
    debug.clear();
56
57
    // This follows the submitblock RPC's validation-state capture pattern, but
58
    // is intentionally kept separate from the RPC implementation. The RPC entry
59
    // point decodes hex, formats BIP22/JSONRPC results, and calls
60
    // UpdateUncommittedBlockStructures() for legacy witness handling. IPC
61
    // callers submit already-formed blocks and need bool + reason/debug
62
    // results.
63
197
    auto sc = std::make_shared<BlockValidationStateCatcher>(block->GetHash());
64
197
    CHECK_NONFATAL(m_chainman.m_options.signals)->RegisterSharedValidationInterface(sc);
65
197
    bool new_block;
66
197
    bool accepted = m_chainman.ProcessNewBlock(block, /*force_processing=*/true, /*min_pow_checked=*/true, /*new_block=*/&new_block);
67
    // No queue drain is needed. The BlockChecked notification used above is
68
    // emitted synchronously by ProcessNewBlock, unlike most validation signals.
69
197
    CHECK_NONFATAL(m_chainman.m_options.signals)->UnregisterSharedValidationInterface(sc);
70
71
197
    if (!new_block && accepted) {
72
58
        reason = "duplicate";
73
139
    } else if (!accepted && (!sc->m_state || sc->m_state->IsValid())) {
74
        // ProcessNewBlock can fail without a validation result, for example
75
        // from an activation or system error. It can also fail after a valid
76
        // BlockChecked result. In these cases the validation result is
77
        // inconclusive.
78
0
        reason = "inconclusive";
79
139
    } else if (!sc->m_state) {
80
        // The block was accepted but not connected, for example if it does not
81
        // have more work than the current tip.
82
2
        reason = "inconclusive";
83
137
    } else if (!sc->m_state->IsValid()) {
84
6
        reason = sc->m_state->GetRejectReason();
85
6
        debug = sc->m_state->GetDebugMessage();
86
6
    }
87
197
    const bool result{accepted && new_block && reason.empty()};
88
197
    CHECK_NONFATAL(result == reason.empty());
89
197
    return result;
90
197
}
91
92
std::optional<BlockRef> BlockTemplateManager::GetTip()
93
429
{
94
429
    LOCK(::cs_main);
95
429
    CBlockIndex* tip{m_chainman.ActiveChain().Tip()};
96
429
    if (!tip) return {};
97
429
    return BlockRef{tip->GetBlockHash(), tip->nHeight};
98
429
}
99
100
void BlockTemplateManager::InterruptWait(bool& interrupt_wait)
101
3
{
102
3
    LOCK(m_notifications.m_tip_block_mutex);
103
3
    interrupt_wait = true;
104
3
    m_notifications.m_tip_block_cv.notify_all();
105
3
}
106
107
std::unique_ptr<CBlockTemplate> BlockTemplateManager::WaitAndCreateNewBlock(
108
    const std::unique_ptr<CBlockTemplate>& block_template,
109
    const BlockWaitOptions& wait_options,
110
    const BlockCreateOptions& create_options,
111
    bool& interrupt_wait)
112
75
{
113
    // Delay calculating the current template fees, just in case a new block
114
    // comes in before the next tick.
115
75
    CAmount current_fees = -1;
116
117
    // Alternate waiting for a new tip and checking if fees have risen.
118
    // The latter check is expensive so we only run it once per second.
119
75
    auto now{NodeClock::now()};
120
75
    const auto deadline = now + wait_options.timeout;
121
75
    const MillisecondsDouble tick{1000};
122
75
    const bool allow_min_difficulty{m_chainman.GetParams().GetConsensus().fPowAllowMinDifficultyBlocks};
123
124
83
    do {
125
83
        bool tip_changed{false};
126
83
        {
127
83
            WAIT_LOCK(m_notifications.m_tip_block_mutex, lock);
128
            // Note that wait_until() checks the predicate before waiting
129
108
            m_notifications.m_tip_block_cv.wait_until(lock, std::min(now + tick, deadline), [&]() EXCLUSIVE_LOCKS_REQUIRED(m_notifications.m_tip_block_mutex) {
130
108
                AssertLockHeld(m_notifications.m_tip_block_mutex);
131
108
                const auto tip_block{m_notifications.TipBlock()};
132
                // We assume tip_block is set, because this is an instance
133
                // method on BlockTemplate and no template could have been
134
                // generated before a tip exists.
135
108
                tip_changed = Assume(tip_block) && tip_block != block_template->block.hashPrevBlock;
136
108
                return tip_changed || m_chainman.m_interrupt || interrupt_wait;
137
108
            });
138
83
            if (interrupt_wait) {
139
1
                interrupt_wait = false;
140
1
                return nullptr;
141
1
            }
142
83
        }
143
144
82
        if (m_chainman.m_interrupt) return nullptr;
145
        // At this point the tip changed, a full tick went by or we reached
146
        // the deadline.
147
148
        // Must release m_tip_block_mutex before locking cs_main, to avoid deadlocks.
149
82
        LOCK(::cs_main);
150
151
        // On test networks return a minimum difficulty block after 20 minutes
152
82
        if (!tip_changed && allow_min_difficulty) {
153
18
            const NodeClock::time_point tip_time{std::chrono::seconds{m_chainman.ActiveChain().Tip()->GetBlockTime()}};
154
18
            if (now > tip_time + 20min) {
155
1
                tip_changed = true;
156
1
            }
157
18
        }
158
159
        /**
160
         * We determine if fees increased compared to the previous template by generating
161
         * a fresh template. There may be more efficient ways to determine how much
162
         * (approximate) fees for the next block increased, perhaps more so after
163
         * Cluster Mempool.
164
         *
165
         * We'll also create a new template if the tip changed during this iteration.
166
         */
167
82
        if (wait_options.fee_threshold < MAX_MONEY || tip_changed) {
168
82
            auto new_tmpl{CreateNewTemplate(create_options)};
169
170
            // If the tip changed, return the new template regardless of its fees.
171
82
            if (tip_changed) return new_tmpl;
172
173
            // Calculate the original template total fees if we haven't already
174
21
            if (current_fees == -1) {
175
13
                current_fees = std::accumulate(block_template->vTxFees.begin(), block_template->vTxFees.end(), CAmount{0});
176
13
            }
177
178
            // Check if fees increased enough to return the new template
179
21
            const CAmount new_fees = std::accumulate(new_tmpl->vTxFees.begin(), new_tmpl->vTxFees.end(), CAmount{0});
180
21
            Assume(wait_options.fee_threshold != MAX_MONEY);
181
21
            if (new_fees >= current_fees + wait_options.fee_threshold) return new_tmpl;
182
21
        }
183
184
14
        now = NodeClock::now();
185
14
    } while (now < deadline);
186
187
6
    return nullptr;
188
75
}
189
190
bool BlockTemplateManager::CooldownIfHeadersAhead(const BlockRef& last_tip, bool& interrupt_mining)
191
19
{
192
19
    uint256 last_tip_hash{last_tip.hash};
193
194
20
    while (const std::optional<int> remaining = m_chainman.BlocksAheadOfTip()) {
195
2
        const int cooldown_seconds = std::clamp(*remaining, 3, 20);
196
2
        const auto cooldown_deadline{MockableSteadyClock::now() + std::chrono::seconds{cooldown_seconds}};
197
198
2
        {
199
2
            WAIT_LOCK(m_notifications.m_tip_block_mutex, lock);
200
4
            m_notifications.m_tip_block_cv.wait_until(lock, cooldown_deadline, [&]() EXCLUSIVE_LOCKS_REQUIRED(m_notifications.m_tip_block_mutex) {
201
4
                const auto tip_block = m_notifications.TipBlock();
202
4
                return m_chainman.m_interrupt || interrupt_mining || (tip_block && *tip_block != last_tip_hash);
203
4
            });
204
2
            if (m_chainman.m_interrupt || interrupt_mining) {
205
0
                interrupt_mining = false;
206
0
                return false;
207
0
            }
208
209
            // If the tip changed during the wait, extend the deadline
210
2
            const auto tip_block = m_notifications.TipBlock();
211
2
            if (tip_block && *tip_block != last_tip_hash) {
212
1
                last_tip_hash = *tip_block;
213
1
                continue;
214
1
            }
215
2
        }
216
217
        // No tip change and the cooldown window has expired.
218
1
        if (MockableSteadyClock::now() >= cooldown_deadline) break;
219
1
    }
220
221
19
    return true;
222
19
}
223
224
std::optional<BlockRef> BlockTemplateManager::WaitTipChanged(const uint256& current_tip, MillisecondsDouble timeout)
225
27
{
226
27
    bool interrupt_wait{false};
227
27
    return WaitTipChanged(current_tip, timeout, interrupt_wait);
228
27
}
229
230
std::optional<BlockRef> BlockTemplateManager::WaitTipChanged(const uint256& current_tip, MillisecondsDouble& timeout, bool& interrupt)
231
200
{
232
200
    Assume(timeout >= 0ms); // No internal callers should use a negative timeout
233
200
    if (timeout < 0ms) timeout = 0ms;
234
200
    if (timeout > std::chrono::years{100}) timeout = std::chrono::years{100}; // Upper bound to avoid UB in std::chrono
235
200
    auto deadline{std::chrono::steady_clock::now() + timeout};
236
200
    {
237
200
        WAIT_LOCK(m_notifications.m_tip_block_mutex, lock);
238
        // For callers convenience, wait longer than the provided timeout
239
        // during startup for the tip to be non-null. That way this function
240
        // always returns valid tip information when possible and only
241
        // returns null when shutting down, not when timing out.
242
200
        m_notifications.m_tip_block_cv.wait(lock, [&]() EXCLUSIVE_LOCKS_REQUIRED(m_notifications.m_tip_block_mutex) {
243
200
            AssertLockHeld(m_notifications.m_tip_block_mutex);
244
200
            return m_notifications.TipBlock() || m_chainman.m_interrupt || interrupt;
245
200
        });
246
200
        if (m_chainman.m_interrupt || interrupt) {
247
2
            interrupt = false;
248
2
            return {};
249
2
        }
250
        // At this point TipBlock is set, so continue to wait until it is
251
        // different from `current_tip` provided by caller.
252
225
        m_notifications.m_tip_block_cv.wait_until(lock, deadline, [&]() EXCLUSIVE_LOCKS_REQUIRED(m_notifications.m_tip_block_mutex) {
253
225
            return Assume(m_notifications.TipBlock()) != current_tip || m_chainman.m_interrupt || interrupt;
254
225
        });
255
198
        if (m_chainman.m_interrupt || interrupt) {
256
2
            interrupt = false;
257
2
            return {};
258
2
        }
259
198
    }
260
261
    // Must release m_tip_block_mutex before GetTip() locks cs_main, to
262
    // avoid deadlocks.
263
196
    return GetTip();
264
198
}
265
266
} // namespace node