Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stratum v2 Template Provider common functionality #49

Open
wants to merge 16 commits into
base: 2024/06/sv2_connection
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions src/chainparamsbase.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,15 @@ std::unique_ptr<CBaseChainParams> CreateBaseChainParams(const ChainType chain)
{
switch (chain) {
case ChainType::MAIN:
return std::make_unique<CBaseChainParams>("", 8332);
return std::make_unique<CBaseChainParams>("", 8332, 8336);
case ChainType::TESTNET:
return std::make_unique<CBaseChainParams>("testnet3", 18332);
return std::make_unique<CBaseChainParams>("testnet3", 18332, 18336);
case ChainType::TESTNET4:
return std::make_unique<CBaseChainParams>("testnet4", 48332);
return std::make_unique<CBaseChainParams>("testnet4", 48332, 48336);
case ChainType::SIGNET:
return std::make_unique<CBaseChainParams>("signet", 38332);
return std::make_unique<CBaseChainParams>("signet", 38332, 38336);
case ChainType::REGTEST:
return std::make_unique<CBaseChainParams>("regtest", 18443);
return std::make_unique<CBaseChainParams>("regtest", 18443, 18447);
}
assert(false);
}
Expand Down
6 changes: 4 additions & 2 deletions src/chainparamsbase.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,15 @@ class CBaseChainParams
public:
const std::string& DataDir() const { return strDataDir; }
uint16_t RPCPort() const { return m_rpc_port; }
uint16_t Sv2Port() const { return m_sv2_port; }

CBaseChainParams() = delete;
CBaseChainParams(const std::string& data_dir, uint16_t rpc_port)
: m_rpc_port(rpc_port), strDataDir(data_dir) {}
CBaseChainParams(const std::string& data_dir, uint16_t rpc_port, uint16_t sv2_port)
: m_rpc_port(rpc_port), m_sv2_port(sv2_port), strDataDir(data_dir) {}

private:
const uint16_t m_rpc_port;
const uint16_t m_sv2_port;
std::string strDataDir;
};

Expand Down
32 changes: 25 additions & 7 deletions src/interfaces/mining.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

#include <consensus/amount.h> // for CAmount
#include <interfaces/types.h> // for BlockRef
#include <node/types.h> // for BlockCreateOptions
#include <node/types.h> // for BlockCreateOptions, BlockWaitOptions
#include <primitives/block.h> // for CBlock, CBlockHeader
#include <primitives/transaction.h> // for CTransactionRef
#include <stdint.h> // for int64_t
Expand Down Expand Up @@ -56,6 +56,19 @@ class BlockTemplate
* @returns if the block was processed, independent of block validity
*/
virtual bool submitSolution(uint32_t version, uint32_t timestamp, uint32_t nonce, CTransactionRef coinbase) = 0;

/**
* Waits for fees in the next block to rise, a new tip or the timeout.
*
* @param[in] options Control the timeout (default forever) and by how much total fees
* for the next block should rise (default infinite).
*
* @returns a new BlockTemplate or nothing if the timeout occurs.
*
* On testnet this will additionally return a template with difficulty 1 if
* the tip is more than 20 minutes old.
*/
virtual std::unique_ptr<BlockTemplate> waitNext(const node::BlockWaitOptions options = {}) = 0;
};

//! Interface giving clients (RPC, Stratum v2 Template Provider in the future)
Expand All @@ -76,20 +89,25 @@ class Mining

/**
* Waits for the connected tip to change. During node initialization, this will
* wait until the tip is connected.
* wait until the tip is connected (regardless of `timeout`).
*
* @param[in] current_tip block hash of the current chain tip. Function waits
* for the chain tip to differ from this.
* @param[in] timeout how long to wait for a new tip
* @returns Hash and height of the current chain tip after this call.
* @param[in] timeout how long to wait for a new tip (default is forever)
*
* @retval BlockRef hash and height of the current chain tip after this call.
* @retval std::nullopt if the node is shut down.
*/
virtual BlockRef waitTipChanged(uint256 current_tip, MillisecondsDouble timeout = MillisecondsDouble::max()) = 0;
virtual std::optional<BlockRef> waitTipChanged(uint256 current_tip, MillisecondsDouble timeout = MillisecondsDouble::max()) = 0;

/**
* Construct a new block template
* Construct a new block template.
*
* During node initialization, this will wait until the tip is connected.
*
* @param[in] options options for creating the block
* @returns a block template
* @retval BlockTemplate a block template.
* @retval std::nullptr if the node is shut down.
*/
virtual std::unique_ptr<BlockTemplate> createNewBlock(const node::BlockCreateOptions& options = {}) = 0;

Expand Down
6 changes: 6 additions & 0 deletions src/ipc/capnp/mining.capnp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ interface BlockTemplate $Proxy.wrap("interfaces::BlockTemplate") {
getWitnessCommitmentIndex @7 (context: Proxy.Context) -> (result: Int32);
getCoinbaseMerklePath @8 (context: Proxy.Context) -> (result: List(Data));
submitSolution @9 (context: Proxy.Context, version: UInt32, timestamp: UInt32, nonce: UInt32, coinbase :Data) -> (result: Bool);
waitNext @10 (context: Proxy.Context, options: BlockWaitOptions) -> (result: BlockTemplate);
}

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

struct BlockWaitOptions $Proxy.wrap("node::BlockWaitOptions") {
timeout @0 : Float64 $Proxy.name("timeout");
feeThreshold @1 : Int64 $Proxy.name("fee_threshold");
}

# Note: serialization of the BlockValidationState C++ type is somewhat fragile
# and using the struct can be awkward. It would be good if testBlockValidity
# method were changed to return validity information in a simpler format.
Expand Down
26 changes: 26 additions & 0 deletions src/key.h
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@ class CKey
ECDHSecret ComputeBIP324ECDHSecret(const EllSwiftPubKey& their_ellswift,
const EllSwiftPubKey& our_ellswift,
bool initiating) const;

/** Compute a KeyPair
*
* Wraps a `secp256k1_keypair` type.
Expand All @@ -220,6 +221,31 @@ class CKey
* Merkle root of the script tree).
*/
KeyPair ComputeKeyPair(const uint256* merkle_root) const;

/** Straight-forward serialization of key bytes (and compressed flag).
* Use GetPrivKey() for OpenSSL compatible DER encoding.
*/
template <typename Stream>
void Serialize(Stream& s) const
{
if (!IsValid()) {
throw std::ios_base::failure("invalid key");
}
s << fCompressed;
::Serialize(s, Span{*this});
}

template <typename Stream>
void Unserialize(Stream& s)
{
s >> fCompressed;
MakeKeyData();
s >> Span{*keydata};
if (!Check(keydata->data())) {
ClearKeyData();
throw std::ios_base::failure("invalid key");
}
}
};

CKey GenerateRandomKey(bool compressed = true) noexcept;
Expand Down
1 change: 1 addition & 0 deletions src/node/context.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ class ChainstateManager;
class ECC_Context;
class NetGroupManager;
class PeerManager;
class Sv2TemplateProvider;
namespace interfaces {
class Chain;
class ChainClient;
Expand Down
136 changes: 126 additions & 10 deletions src/node/interfaces.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ using interfaces::Mining;
using interfaces::Node;
using interfaces::WalletLoader;
using node::BlockAssembler;
using node::BlockWaitOptions;
using util::Join;

namespace node {
Expand Down Expand Up @@ -877,7 +878,11 @@ 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(BlockAssembler::Options assemble_options,
std::unique_ptr<CBlockTemplate> block_template,
NodeContext& node) : 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 @@ -942,9 +947,103 @@ class BlockTemplateImpl : public BlockTemplate
return chainman().ProcessNewBlock(block_ptr, /*force_processing=*/true, /*min_pow_checked=*/true, /*new_block=*/nullptr);
}

std::unique_ptr<BlockTemplate> waitNext(BlockWaitOptions options) override
{
// 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{NodeClock::now()};
const auto deadline = now + options.timeout;
const MillisecondsDouble tick{1000};
const bool allow_min_difficulty{chainman().GetParams().GetConsensus().fPowAllowMinDifficultyBlocks};

do {
bool tip_changed{false};
{
WAIT_LOCK(notifications().m_tip_block_mutex, lock);
// Note that wait_until() checks the predicate before waiting
notifications().m_tip_block_cv.wait_until(lock, std::min(now + tick, deadline), [&]() EXCLUSIVE_LOCKS_REQUIRED(notifications().m_tip_block_mutex) {
AssertLockHeld(notifications().m_tip_block_mutex);
const auto tip_block{notifications().TipBlock()};
// We assume tip_block is set, because this is an instance
// method on BlockTemplate and no template could have been
// generated before a tip exists.
tip_changed = Assume(tip_block) && tip_block != m_block_template->block.hashPrevBlock;
return tip_changed || chainman().m_interrupt;
});
}

if (chainman().m_interrupt) return nullptr;
// At this point the tip changed, a full tick went by or we reached
// the deadline.

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

// On test networks return a minimum difficulty block after 20 minutes
if (!tip_changed && allow_min_difficulty) {
const NodeClock::time_point tip_time{std::chrono::seconds{chainman().ActiveChain().Tip()->GetBlockTime()}};
if (now > tip_time + 20min) {
tip_changed = true;
}
}

/**
* We determine if fees increased compared to the previous template by generating
* a fresh template. There may be more efficient ways to determine how much
* (approximate) fees for the next block increased, perhaps more so after
* Cluster Mempool.
*
* We'll also create a new template if the tip changed during this iteration.
*/
if (options.fee_threshold < MAX_MONEY || tip_changed) {
auto tmpl{std::make_unique<BlockTemplateImpl>(m_assemble_options,
BlockAssembler{
chainman().ActiveChainstate(),
context()->mempool.get(),
m_assemble_options}
.CreateNewBlock(),
m_node)};

// If the tip changed, return the new template regardless of its fees.
if (tip_changed) return tmpl;

// Calculate the original template total fees if we haven't already
if (current_fees == -1) {
current_fees = 0;
for (CAmount fee : m_block_template->vTxFees) {
// Skip coinbase
if (fee < 0) continue;
current_fees += fee;
}
}

CAmount new_fees = 0;
for (CAmount fee : tmpl->m_block_template->vTxFees) {
// Skip coinbase
if (fee < 0) continue;
new_fees += fee;
Assume(options.fee_threshold != MAX_MONEY);
if (new_fees >= current_fees + options.fee_threshold) return tmpl;
}
}

now = NodeClock::now();
} while (now < deadline);

return nullptr;
}

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 All @@ -971,27 +1070,44 @@ class MinerImpl : public Mining
return BlockRef{tip->GetBlockHash(), tip->nHeight};
}

BlockRef waitTipChanged(uint256 current_tip, MillisecondsDouble timeout) override
std::optional<BlockRef> waitTipChanged(uint256 current_tip, MillisecondsDouble timeout) override
{
Assume(timeout >= 0ms); // No internal callers should use a negative timeout
if (timeout < 0ms) timeout = 0ms;
if (timeout > std::chrono::years{100}) timeout = std::chrono::years{100}; // Upper bound to avoid UB in std::chrono
auto deadline{std::chrono::steady_clock::now() + timeout};
{
WAIT_LOCK(notifications().m_tip_block_mutex, lock);
notifications().m_tip_block_cv.wait_for(lock, timeout, [&]() EXCLUSIVE_LOCKS_REQUIRED(notifications().m_tip_block_mutex) {
// We need to wait for m_tip_block to be set AND for the value
// to differ from the current_tip value.
return (notifications().TipBlock() && notifications().TipBlock() != current_tip) || chainman().m_interrupt;
// For callers convenience, wait longer than the provided timeout
// during startup for the tip to be non-null. That way this function
// always returns valid tip information when possible and only
// returns null when shutting down, not when timing out.
notifications().m_tip_block_cv.wait(lock, [&]() EXCLUSIVE_LOCKS_REQUIRED(notifications().m_tip_block_mutex) {
return notifications().TipBlock() || chainman().m_interrupt;
});
if (chainman().m_interrupt) return {};
// At this point TipBlock is set, so continue to wait until it is
// different then `current_tip` provided by caller.
notifications().m_tip_block_cv.wait_until(lock, deadline, [&]() EXCLUSIVE_LOCKS_REQUIRED(notifications().m_tip_block_mutex) {
return Assume(notifications().TipBlock()) != current_tip || chainman().m_interrupt;
});
}
// Must release m_tip_block_mutex before locking cs_main, to avoid deadlocks.
LOCK(::cs_main);
return BlockRef{chainman().ActiveChain().Tip()->GetBlockHash(), chainman().ActiveChain().Tip()->nHeight};

if (chainman().m_interrupt) return {};

// Must release m_tip_block_mutex before getTip() locks cs_main, to
// avoid deadlocks.
return getTip();
}

std::unique_ptr<BlockTemplate> createNewBlock(const BlockCreateOptions& options) override
{
// Ensure m_tip_block is set so consumers of BlockTemplate can rely on that.
if (!waitTipChanged(uint256::ZERO, MillisecondsDouble::max())) return {};

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(), m_node);
return std::make_unique<BlockTemplateImpl>(assemble_options, BlockAssembler{chainman().ActiveChainstate(), context()->mempool.get(), assemble_options}.CreateNewBlock(), m_node);
}

NodeContext* context() override { return &m_node; }
Expand Down
24 changes: 24 additions & 0 deletions src/node/types.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@
#ifndef BITCOIN_NODE_TYPES_H
#define BITCOIN_NODE_TYPES_H

#include <consensus/amount.h>
#include <cstddef>
#include <policy/policy.h>
#include <script/script.h>
#include <util/time.h>

namespace node {
enum class TransactionError {
Expand Down Expand Up @@ -61,6 +63,28 @@ struct BlockCreateOptions {
*/
CScript coinbase_output_script{CScript() << OP_TRUE};
};

struct BlockWaitOptions {
/**
* How long to wait before returning nullptr instead of a new template.
* Default is to wait forever.
*/
MillisecondsDouble timeout{MillisecondsDouble::max()};

/**
* The wait method will not return a new template unless it has fees at
* least fee_threshold sats higher than the current template, or unless
* the chain tip changes and the previous template is no longer valid.
*
* A caller may not be interested in templates with higher fees, and
* determining whether fee_threshold is reached is also expensive. So as
* an optimization, when fee_threshold is set to MAX_MONEY (default), the
* implementation is able to be much more efficient, skipping expensive
* checks and only returning new templates when the chain tip changes.
*/
CAmount fee_threshold{MAX_MONEY};
};

} // namespace node

#endif // BITCOIN_NODE_TYPES_H
Loading
Loading