Skip to content

Commit

Permalink
Add waitNext() to BlockTemplate interface
Browse files Browse the repository at this point in the history
  • Loading branch information
Sjors committed Nov 14, 2024
1 parent f9200f6 commit bc05bc0
Show file tree
Hide file tree
Showing 4 changed files with 100 additions and 7 deletions.
11 changes: 11 additions & 0 deletions src/interfaces/mining.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,17 @@ class BlockTemplate
* @returns if the block was processed, independent of block validity
*/
virtual bool submitSolution(uint32_t version, uint32_t timestamp, uint32_t nonce, CMutableTransaction coinbase) = 0;

/**
* Waits for fees in the next block to rise, a new tip or the timeout.
*
* @param[in] fee_threshold By how much total fees for the next block should rise.
* Default is to not monitor fee changes and only wait for a new chaintip.
* @param[in] timeout How long to wait. Default is forever.
*
* @returns a new BlockTemplate or nullptr if the timeout occurs
*/
virtual std::unique_ptr<BlockTemplate> waitNext(CAmount fee_threshold = MAX_MONEY, MillisecondsDouble timeout = MillisecondsDouble::max()) = 0;
};

//! Interface giving clients (RPC, Stratum v2 Template Provider in the future)
Expand Down
1 change: 1 addition & 0 deletions src/ipc/capnp/mining.capnp
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ interface BlockTemplate $Proxy.wrap("interfaces::BlockTemplate") {
getWitnessCommitmentIndex @6 (context: Proxy.Context) -> (result: Int32);
getCoinbaseMerklePath @7 (context: Proxy.Context) -> (result: List(Data));
submitSolution@8 (context: Proxy.Context, version: UInt32, timestamp: UInt32, nonce: UInt32, coinbase :Data) -> (result: Bool);
waitNext @9 (context: Proxy.Context, feeThreshold: Int64, timeout: Float64) -> (result: BlockTemplate);
}

struct BlockCreateOptions $Proxy.wrap("node::BlockCreateOptions") {
Expand Down
72 changes: 70 additions & 2 deletions src/node/interfaces.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -871,7 +871,7 @@ class ChainImpl : public Chain
class BlockTemplateImpl : public BlockTemplate
{
public:
explicit BlockTemplateImpl(std::unique_ptr<CBlockTemplate> block_template, NodeContext& node) : m_block_template(std::move(block_template)), m_node(node)
explicit BlockTemplateImpl(CScript script_pub_key, BlockAssembler::Options assemble_options, std::unique_ptr<CBlockTemplate> block_template, NodeContext& node) : m_script_pub_key(script_pub_key), m_assemble_options(std::move(assemble_options)), m_block_template(std::move(block_template)), m_node(node)
{
assert(m_block_template);
}
Expand Down Expand Up @@ -938,9 +938,77 @@ class BlockTemplateImpl : public BlockTemplate
return chainman().ProcessNewBlock(block_ptr, /*force_processing=*/true, /*min_pow_checked=*/true, /*new_block=*/nullptr);
}

std::unique_ptr<BlockTemplate> waitNext(CAmount fee_threshold, MillisecondsDouble timeout) override
{
if (timeout > std::chrono::years{100}) timeout = std::chrono::years{100}; // Upper bound to avoid UB in std::chrono

// Delay calculating the current template fees, just in case a new block
// comes in before the next tick.
CAmount current_fees = -1;

// Alternate waiting for a new tip and checking if fees have risen.
// The latter check is expensive so we only run it once per second.
auto now{std::chrono::steady_clock::now()};
const auto deadline = now + timeout;
const MillisecondsDouble tick{1000};
std::unique_ptr<BlockTemplateImpl> block_template;

while (now < deadline) {
{
WAIT_LOCK(notifications().m_tip_block_mutex, lock);
notifications().m_tip_block_cv.wait_until(lock, std::min(now + tick, deadline), [&]() EXCLUSIVE_LOCKS_REQUIRED(notifications().m_tip_block_mutex) {
return notifications().m_tip_block != m_block_template->block.hashPrevBlock || chainman().m_interrupt;
});
}

if (chainman().m_interrupt) return nullptr;

// Must release m_tip_block_mutex before locking cs_main, to avoid deadlocks.
LOCK(::cs_main);

// The only way to determine if fees increased compared to the previous template,
// is to generate a fresh template. Cluster Mempool may allow for a more efficient
// way to determine how much (approximate) fees for the next block increased.
block_template = std::make_unique<BlockTemplateImpl>(m_script_pub_key, m_assemble_options, BlockAssembler{chainman().ActiveChainstate(), context()->mempool.get(), m_assemble_options}.CreateNewBlock(m_script_pub_key), m_node);

// If the tip changed, return the new template regardless of its fees.
if (block_template->m_block_template->block.hashPrevBlock != m_block_template->block.hashPrevBlock) {
return block_template;
}

if (current_fees == -1) {
current_fees = 0;
if (fee_threshold < MAX_MONEY) {
for (CAmount fee : m_block_template->vTxFees) {
// Skip coinbase
if (fee < 0) continue;
current_fees += fee;
}
}
}

CAmount new_fees = 0;
for (CAmount fee : block_template->m_block_template->vTxFees) {
// Skip coinbase
if (fee < 0) continue;
new_fees += fee;
if (new_fees >= current_fees + fee_threshold) return block_template;
}
//
block_template.reset();
}

return block_template;
}

const CScript m_script_pub_key;
const BlockAssembler::Options m_assemble_options;

const std::unique_ptr<CBlockTemplate> m_block_template;

NodeContext* context() { return &m_node; }
ChainstateManager& chainman() { return *Assert(m_node.chainman); }
KernelNotifications& notifications() { return *Assert(m_node.notifications); }
NodeContext& m_node;
};

Expand Down Expand Up @@ -1008,7 +1076,7 @@ class MinerImpl : public Mining
{
BlockAssembler::Options assemble_options{options};
ApplyArgsManOptions(*Assert(m_node.args), assemble_options);
return std::make_unique<BlockTemplateImpl>(BlockAssembler{chainman().ActiveChainstate(), context()->mempool.get(), assemble_options}.CreateNewBlock(script_pub_key), m_node);
return std::make_unique<BlockTemplateImpl>(script_pub_key, assemble_options, BlockAssembler{chainman().ActiveChainstate(), context()->mempool.get(), assemble_options}.CreateNewBlock(script_pub_key), m_node);
}

NodeContext* context() override { return &m_node; }
Expand Down
23 changes: 18 additions & 5 deletions src/test/miner_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,12 @@ void MinerTestingSetup::TestPackageSelection(const CScript& scriptPubKey, const
tx.vout[0].nValue = 5000000000LL - 1000 - 50000 - feeToUse;
Txid hashLowFeeTx = tx.GetHash();
tx_mempool.addUnchecked(entry.Fee(feeToUse).FromTx(tx));

// waitNext() should return nullptr because there is no better template
auto should_be_nullptr = block_template->waitNext(1, MillisecondsDouble{0});
BOOST_REQUIRE(should_be_nullptr == nullptr);

// Generate a new template anyway
block_template = miner->createNewBlock(scriptPubKey);
BOOST_REQUIRE(block_template);
block = block_template->getBlock();
Expand All @@ -171,7 +177,8 @@ void MinerTestingSetup::TestPackageSelection(const CScript& scriptPubKey, const
tx.vout[0].nValue -= 2; // Now we should be just over the min relay fee
hashLowFeeTx = tx.GetHash();
tx_mempool.addUnchecked(entry.Fee(feeToUse + 2).FromTx(tx));
block_template = miner->createNewBlock(scriptPubKey);
// waitNext() should return if fees for the new template are at least 1 sat up
block_template = block_template->waitNext(1);
BOOST_REQUIRE(block_template);
block = block_template->getBlock();
BOOST_REQUIRE_EQUAL(block.vtx.size(), 6U);
Expand Down Expand Up @@ -639,7 +646,9 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity)
const int current_height{miner->getTip()->height};

// Simple block creation, nothing special yet:
block_template = miner->createNewBlock(scriptPubKey);
if (current_height % 2 == 0) {
block_template = miner->createNewBlock(scriptPubKey);
} // for odd heights the next template is created at the end of this loop
BOOST_REQUIRE(block_template);

CBlock block{block_template->getBlock()};
Expand Down Expand Up @@ -670,9 +679,13 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity)
}
// ProcessNewBlock and submitSolution do not check if the new block is
// valid. If it is, they will wait for the tip to update. Therefore the
// following check will either return immediately, or be stuck indefinately.
// It's mainly there to increase test coverage.
miner->waitTipChanged(block.hashPrevBlock);
// following checks will either return immediately, or be stuck indefinately.
// They're mainly there to increase test coverage.
if (current_height % 2 == 0) {
block_template = block_template->waitNext();
} else {
miner->waitTipChanged(block.hashPrevBlock);
}
}

LOCK(cs_main);
Expand Down

0 comments on commit bc05bc0

Please sign in to comment.