Skip to content

Commit d4020f5

Browse files
committed
Add waitNext() to BlockTemplate interface
1 parent 785649f commit d4020f5

File tree

5 files changed

+157
-11
lines changed

5 files changed

+157
-11
lines changed

src/interfaces/mining.h

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
#include <consensus/amount.h> // for CAmount
99
#include <interfaces/types.h> // for BlockRef
10-
#include <node/types.h> // for BlockCreateOptions
10+
#include <node/types.h> // for BlockCreateOptions, BlockWaitOptions
1111
#include <primitives/block.h> // for CBlock, CBlockHeader
1212
#include <primitives/transaction.h> // for CTransactionRef
1313
#include <stdint.h> // for int64_t
@@ -56,6 +56,16 @@ class BlockTemplate
5656
* @returns if the block was processed, independent of block validity
5757
*/
5858
virtual bool submitSolution(uint32_t version, uint32_t timestamp, uint32_t nonce, CTransactionRef coinbase) = 0;
59+
60+
/**
61+
* Waits for fees in the next block to rise, a new tip or the timeout.
62+
*
63+
* @param[in] options Control the timeout (default forever) and by how much total fees
64+
* for the next block should rise (default infinite).
65+
*
66+
* @returns a new BlockTemplate or nothing if the timeout occurs.
67+
*/
68+
virtual std::unique_ptr<BlockTemplate> waitNext(const node::BlockWaitOptions options = {}) = 0;
5969
};
6070

6171
//! Interface giving clients (RPC, Stratum v2 Template Provider in the future)

src/ipc/capnp/mining.capnp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ interface BlockTemplate $Proxy.wrap("interfaces::BlockTemplate") {
3131
getWitnessCommitmentIndex @7 (context: Proxy.Context) -> (result: Int32);
3232
getCoinbaseMerklePath @8 (context: Proxy.Context) -> (result: List(Data));
3333
submitSolution @9 (context: Proxy.Context, version: UInt32, timestamp: UInt32, nonce: UInt32, coinbase :Data) -> (result: Bool);
34+
waitNext @10 (context: Proxy.Context, options: BlockWaitOptions) -> (result: BlockTemplate);
3435
}
3536

3637
struct BlockCreateOptions $Proxy.wrap("node::BlockCreateOptions") {
@@ -39,6 +40,11 @@ struct BlockCreateOptions $Proxy.wrap("node::BlockCreateOptions") {
3940
coinbaseOutputMaxAdditionalSigops @2 :UInt64 $Proxy.name("coinbase_output_max_additional_sigops");
4041
}
4142

43+
struct BlockWaitOptions $Proxy.wrap("node::BlockWaitOptions") {
44+
timeout @0 : Float64 $Proxy.name("timeout");
45+
feeThreshold @1 : Int64 $Proxy.name("fee_threshold");
46+
}
47+
4248
# Note: serialization of the BlockValidationState C++ type is somewhat fragile
4349
# and using the struct can be awkward. It would be good if testBlockValidity
4450
# method were changed to return validity information in a simpler format.

src/node/interfaces.cpp

Lines changed: 92 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ using interfaces::Mining;
8282
using interfaces::Node;
8383
using interfaces::WalletLoader;
8484
using node::BlockAssembler;
85+
using node::BlockWaitOptions;
8586
using util::Join;
8687

8788
namespace node {
@@ -877,7 +878,11 @@ class ChainImpl : public Chain
877878
class BlockTemplateImpl : public BlockTemplate
878879
{
879880
public:
880-
explicit BlockTemplateImpl(std::unique_ptr<CBlockTemplate> block_template, NodeContext& node) : m_block_template(std::move(block_template)), m_node(node)
881+
explicit BlockTemplateImpl(BlockAssembler::Options assemble_options,
882+
std::unique_ptr<CBlockTemplate> block_template,
883+
NodeContext& node) : m_assemble_options(std::move(assemble_options)),
884+
m_block_template(std::move(block_template)),
885+
m_node(node)
881886
{
882887
assert(m_block_template);
883888
}
@@ -942,9 +947,94 @@ class BlockTemplateImpl : public BlockTemplate
942947
return chainman().ProcessNewBlock(block_ptr, /*force_processing=*/true, /*min_pow_checked=*/true, /*new_block=*/nullptr);
943948
}
944949

950+
std::unique_ptr<BlockTemplate> waitNext(BlockWaitOptions options) override
951+
{
952+
// Delay calculating the current template fees, just in case a new block
953+
// comes in before the next tick.
954+
CAmount current_fees = -1;
955+
956+
// Alternate waiting for a new tip and checking if fees have risen.
957+
// The latter check is expensive so we only run it once per second.
958+
auto now{NodeClock::now()};
959+
const auto deadline = now + options.timeout;
960+
const MillisecondsDouble tick{1000};
961+
962+
do {
963+
bool tip_changed{false};
964+
{
965+
WAIT_LOCK(notifications().m_tip_block_mutex, lock);
966+
// Note that wait_until() checks the predicate before waiting
967+
notifications().m_tip_block_cv.wait_until(lock, std::min(now + tick, deadline), [&]() EXCLUSIVE_LOCKS_REQUIRED(notifications().m_tip_block_mutex) {
968+
AssertLockHeld(notifications().m_tip_block_mutex);
969+
const auto tip_block{notifications().TipBlock()};
970+
// We assume tip_block is set, because this is an instance
971+
// method on BlockTemplate and no template could have been
972+
// generated before a tip exists.
973+
tip_changed = Assume(tip_block) && tip_block != m_block_template->block.hashPrevBlock;
974+
return tip_changed || chainman().m_interrupt;
975+
});
976+
}
977+
978+
if (chainman().m_interrupt) return nullptr;
979+
// At this point the tip changed, a full tick went by or we reached
980+
// the deadline.
981+
982+
// Must release m_tip_block_mutex before locking cs_main, to avoid deadlocks.
983+
LOCK(::cs_main);
984+
985+
/**
986+
* We determine if fees increased compared to the previous template by generating
987+
* a fresh template. There may be more efficient ways to determine how much
988+
* (approximate) fees for the next block increased, perhaps more so after
989+
* Cluster Mempool.
990+
*
991+
* We'll also create a new template if the tip changed during this iteration.
992+
*/
993+
if (options.fee_threshold < MAX_MONEY || tip_changed) {
994+
auto tmpl{std::make_unique<BlockTemplateImpl>(m_assemble_options,
995+
BlockAssembler{
996+
chainman().ActiveChainstate(),
997+
context()->mempool.get(),
998+
m_assemble_options}
999+
.CreateNewBlock(),
1000+
m_node)};
1001+
1002+
// If the tip changed, return the new template regardless of its fees.
1003+
if (tip_changed) return tmpl;
1004+
1005+
// Calculate the original template total fees if we haven't already
1006+
if (current_fees == -1) {
1007+
current_fees = 0;
1008+
for (CAmount fee : m_block_template->vTxFees) {
1009+
// Skip coinbase
1010+
if (fee < 0) continue;
1011+
current_fees += fee;
1012+
}
1013+
}
1014+
1015+
CAmount new_fees = 0;
1016+
for (CAmount fee : tmpl->m_block_template->vTxFees) {
1017+
// Skip coinbase
1018+
if (fee < 0) continue;
1019+
new_fees += fee;
1020+
Assume(options.fee_threshold != MAX_MONEY);
1021+
if (new_fees >= current_fees + options.fee_threshold) return tmpl;
1022+
}
1023+
}
1024+
1025+
now = NodeClock::now();
1026+
} while (now < deadline);
1027+
1028+
return nullptr;
1029+
}
1030+
1031+
const BlockAssembler::Options m_assemble_options;
1032+
9451033
const std::unique_ptr<CBlockTemplate> m_block_template;
9461034

1035+
NodeContext* context() { return &m_node; }
9471036
ChainstateManager& chainman() { return *Assert(m_node.chainman); }
1037+
KernelNotifications& notifications() { return *Assert(m_node.notifications); }
9481038
NodeContext& m_node;
9491039
};
9501040

@@ -991,7 +1081,7 @@ class MinerImpl : public Mining
9911081
{
9921082
BlockAssembler::Options assemble_options{options};
9931083
ApplyArgsManOptions(*Assert(m_node.args), assemble_options);
994-
return std::make_unique<BlockTemplateImpl>(BlockAssembler{chainman().ActiveChainstate(), context()->mempool.get(), assemble_options}.CreateNewBlock(), m_node);
1084+
return std::make_unique<BlockTemplateImpl>(assemble_options, BlockAssembler{chainman().ActiveChainstate(), context()->mempool.get(), assemble_options}.CreateNewBlock(), m_node);
9951085
}
9961086

9971087
NodeContext* context() override { return &m_node; }

src/node/types.h

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,11 @@
1313
#ifndef BITCOIN_NODE_TYPES_H
1414
#define BITCOIN_NODE_TYPES_H
1515

16+
#include <consensus/amount.h>
1617
#include <cstddef>
1718
#include <policy/policy.h>
1819
#include <script/script.h>
20+
#include <util/time.h>
1921

2022
namespace node {
2123
enum class TransactionError {
@@ -61,6 +63,28 @@ struct BlockCreateOptions {
6163
*/
6264
CScript coinbase_output_script{CScript() << OP_TRUE};
6365
};
66+
67+
struct BlockWaitOptions {
68+
/**
69+
* How long to wait before returning nullptr instead of a new template.
70+
* Default is to wait forever.
71+
*/
72+
MillisecondsDouble timeout{MillisecondsDouble::max()};
73+
74+
/**
75+
* The wait method will not return a new template unless it has fees at
76+
* least fee_threshold sats higher than the current template, or unless
77+
* the chain tip changes and the previous template is no longer valid.
78+
*
79+
* A caller may not be interested in templates with higher fees, and
80+
* determining whether fee_threshold is reached is also expensive. So as
81+
* an optimization, when fee_threshold is set to MAX_MONEY (default), the
82+
* implementation is able to be much more efficient, skipping expensive
83+
* checks and only returning new templates when the chain tip changes.
84+
*/
85+
CAmount fee_threshold{MAX_MONEY};
86+
};
87+
6488
} // namespace node
6589

6690
#endif // BITCOIN_NODE_TYPES_H

src/test/miner_tests.cpp

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -180,8 +180,11 @@ void MinerTestingSetup::TestPackageSelection(const CScript& scriptPubKey, const
180180
tx.vout[0].nValue = 5000000000LL - 1000 - 50000 - feeToUse;
181181
Txid hashLowFeeTx = tx.GetHash();
182182
AddToMempool(tx_mempool, entry.Fee(feeToUse).FromTx(tx));
183-
block_template = mining->createNewBlock(options);
184-
BOOST_REQUIRE(block_template);
183+
184+
// waitNext() should return nullptr because there is no better template
185+
auto should_be_nullptr = block_template->waitNext({.timeout = MillisecondsDouble{0}, .fee_threshold = 1});
186+
BOOST_REQUIRE(should_be_nullptr == nullptr);
187+
185188
block = block_template->getBlock();
186189
// Verify that the free tx and the low fee tx didn't get selected
187190
for (size_t i=0; i<block.vtx.size(); ++i) {
@@ -196,7 +199,9 @@ void MinerTestingSetup::TestPackageSelection(const CScript& scriptPubKey, const
196199
tx.vout[0].nValue -= 2; // Now we should be just over the min relay fee
197200
hashLowFeeTx = tx.GetHash();
198201
AddToMempool(tx_mempool, entry.Fee(feeToUse + 2).FromTx(tx));
199-
block_template = mining->createNewBlock(options);
202+
203+
// waitNext() should return if fees for the new template are at least 1 sat up
204+
block_template = block_template->waitNext({.fee_threshold = 1});
200205
BOOST_REQUIRE(block_template);
201206
block = block_template->getBlock();
202207
BOOST_REQUIRE_EQUAL(block.vtx.size(), 6U);
@@ -671,9 +676,15 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity)
671676
for (const auto& bi : BLOCKINFO) {
672677
const int current_height{mining->getTip()->height};
673678

674-
// Simple block creation, nothing special yet:
675-
block_template = mining->createNewBlock(options);
676-
BOOST_REQUIRE(block_template);
679+
/**
680+
* Simple block creation, nothing special yet.
681+
* If current_height is odd, block_template will have already been
682+
* set at the end of the previous loop.
683+
*/
684+
if (current_height % 2 == 0) {
685+
block_template = mining->createNewBlock(options);
686+
BOOST_REQUIRE(block_template);
687+
}
677688

678689
CBlock block{block_template->getBlock()};
679690
CMutableTransaction txCoinbase(*block.vtx[0]);
@@ -709,8 +720,13 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity)
709720
auto maybe_new_tip{Assert(m_node.chainman)->ActiveChain().Tip()};
710721
BOOST_REQUIRE_EQUAL(maybe_new_tip->GetBlockHash(), block.GetHash());
711722
}
712-
// This just adds coverage
713-
mining->waitTipChanged(block.hashPrevBlock);
723+
if (current_height % 2 == 0) {
724+
block_template = block_template->waitNext();
725+
BOOST_REQUIRE(block_template);
726+
} else {
727+
// This just adds coverage
728+
mining->waitTipChanged(block.hashPrevBlock);
729+
}
714730
}
715731

716732
LOCK(cs_main);

0 commit comments

Comments
 (0)