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 11 commits into
base: 2024/06/sv2_connection
Choose a base branch
from
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
15 changes: 14 additions & 1 deletion 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 nullptr 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 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
95 changes: 93 additions & 2 deletions src/node/interfaces.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,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 @@ -871,7 +872,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(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 @@ -936,9 +937,99 @@ 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};

while (now <= deadline) {
bool tip_changed{false};
{
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) {
Assert(notifications().TipBlock());
tip_changed = notifications().TipBlock() != m_block_template->block.hashPrevBlock;
return tip_changed || 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);

// 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 + std::chrono::seconds(20 * 60)) {
tip_changed = true;
}
}

/**
* 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.
*
* We'll also create a new template if the tip changed during the last tick.
*/
if (options.fee_threshold < MAX_MONEY || tip_changed) {
auto block_template{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 block_template;
}

// 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 : block_template->m_block_template->vTxFees) {
// Skip coinbase
if (fee < 0) continue;
new_fees += fee;
if (new_fees >= current_fees + options.fee_threshold) return block_template;
}
}

/**
* Break out of while when using mock time. While functional tests
* can call setmocktime to move the clock and exit this loop, a
* unit tests would need to spawn a thread to achieve this.
* Making the while loop use now < deadline won't work either, because
* then there's no wait to test its internals, e.g. the 20 minute
* testnet exception.
*/
if (now == deadline) break;
now = NodeClock::now();
}

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 Down Expand Up @@ -985,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(), 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
18 changes: 18 additions & 0 deletions src/node/types.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@
#define BITCOIN_NODE_TYPES_H

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

namespace node {
enum class TransactionError {
Expand Down Expand Up @@ -61,6 +63,22 @@ 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()};

/**
* Wait until total fees in the new template exceed fees in the origal
* template by at least this amount (in sats). The default is to ignore
* fee increases and only wait for a tip change.
*/
CAmount fee_threshold{MAX_MONEY};
};

} // namespace node

#endif // BITCOIN_NODE_TYPES_H
2 changes: 2 additions & 0 deletions src/sv2/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ add_library(bitcoin_sv2 STATIC EXCLUDE_FROM_ALL
noise.cpp
transport.cpp
connman.cpp
messages.cpp
template_provider.cpp
)

target_link_libraries(bitcoin_sv2
Expand Down
35 changes: 35 additions & 0 deletions src/sv2/connman.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,41 @@ void Sv2Connman::ProcessSv2Message(const Sv2NetMsg& sv2_net_msg, Sv2Client& clie

break;
}
case Sv2MsgType::SUBMIT_SOLUTION: {
if (!client.m_setup_connection_confirmed && !client.m_coinbase_output_data_size_recv) {
client.m_disconnect_flag = true;
return;
}

node::Sv2SubmitSolutionMsg submit_solution;
try {
ss >> submit_solution;
} catch (const std::exception& e) {
LogPrintLevel(BCLog::SV2, BCLog::Level::Error, "Received invalid SubmitSolution message from client id=%zu: %e\n",
client.m_id, e.what());
return;
}

m_msgproc->SubmitSolution(submit_solution);

break;
}
case Sv2MsgType::REQUEST_TRANSACTION_DATA:
{
node::Sv2RequestTransactionDataMsg request_tx_data;

try {
ss >> request_tx_data;
} catch (const std::exception& e) {
LogPrintLevel(BCLog::SV2, BCLog::Level::Error, "Received invalid RequestTransactionData message from client id=%zu: %e\n",
client.m_id, e.what());
return;
}

m_msgproc->RequestTransactionData(client, request_tx_data);

break;
}
default: {
uint8_t msg_type[1]{uint8_t(sv2_net_msg.m_msg_type)};
LogPrintLevel(BCLog::SV2, BCLog::Level::Warning, "Received unknown message type 0x%s from client id=%zu\n",
Expand Down
18 changes: 18 additions & 0 deletions src/sv2/connman.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,12 @@ struct Sv2Client
*/
unsigned int m_coinbase_tx_outputs_size;

/**
* Tracks the best template in the Template Provider m_block_template_cache map.
* Not guaranteed to still exist.
*/
uint64_t m_best_template_id = 0;

explicit Sv2Client(size_t id, std::unique_ptr<Sv2Transport> transport) :
m_id{id}, m_transport{std::move(transport)} {};

Expand All @@ -72,6 +78,18 @@ struct Sv2Client
class Sv2EventsInterface
{
public:
/**
* We received and successfully parsed a RequestTransactionData message.
* Deal with it and respond with either RequestTransactionData.Success or
* RequestTransactionData.Error.
*/
virtual void RequestTransactionData(Sv2Client& client, node::Sv2RequestTransactionDataMsg msg) = 0;

/**
* We received and successfully parsed a SubmitSolution message.
*/
virtual void SubmitSolution(node::Sv2SubmitSolutionMsg solution) = 0;

virtual ~Sv2EventsInterface() = default;
};

Expand Down
Loading
Loading