Skip to content

Commit 15e2afa

Browse files
authored
Merge pull request #96 from ajtowns/202509-inq29-sendtemplate1
net: Provide block templates to peers on request
2 parents 7e49d15 + 6406895 commit 15e2afa

15 files changed

Lines changed: 382 additions & 9 deletions

File tree

src/blockencodings.cpp

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,22 @@
1818
#include <unordered_map>
1919

2020
CBlockHeaderAndShortTxIDs::CBlockHeaderAndShortTxIDs(const CBlock& block, const uint64_t nonce) :
21-
nonce(nonce),
22-
shorttxids(block.vtx.size() - 1), prefilledtxn(1), header(block) {
21+
nonce(nonce), header(block)
22+
{
23+
const bool prefill_cb = block.vtx.size() != 0 && block.vtx[0]->IsCoinBase();
24+
2325
FillShortTxIDSelector();
26+
shorttxids.resize(block.vtx.size() - (size_t)prefill_cb);
27+
2428
//TODO: Use our mempool prior to block acceptance to predictively fill more than just the coinbase
25-
prefilledtxn[0] = {0, block.vtx[0]};
26-
for (size_t i = 1; i < block.vtx.size(); i++) {
29+
prefilledtxn.resize((size_t)prefill_cb);
30+
if (prefill_cb) {
31+
prefilledtxn[0] = {0, block.vtx[0]};
32+
}
33+
34+
for (size_t i = prefill_cb; i < block.vtx.size(); i++) {
2735
const CTransaction& tx = *block.vtx[i];
28-
shorttxids[i - 1] = GetShortID(tx.GetWitnessHash());
36+
shorttxids[i - (size_t)prefill_cb] = GetShortID(tx.GetWitnessHash());
2937
}
3038
}
3139

src/init.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -576,6 +576,7 @@ void SetupServerArgs(ArgsManager& argsman, bool can_listen_ipc)
576576
"-whitebind. "
577577
"Additional flags \"in\" and \"out\" control whether permissions apply to incoming connections and/or manual (default: incoming only). "
578578
"Can be specified multiple times.", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
579+
argsman.AddArg("-sharetemplatecount", strprintf("Number of block templates to store and share to peers (default: %d). Set to 0 to disable sharing block templates.", DEFAULT_SHARETMPL_COUNT), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::NODE_RELAY);
579580

580581
g_wallet_init_interface.AddWalletOptions(argsman);
581582

@@ -1081,6 +1082,13 @@ bool AppInitParameterInteraction(const ArgsManager& args)
10811082
}
10821083
}
10831084

1085+
if (args.GetBoolArg("-printpriority", DEFAULT_PRINT_MODIFIED_FEE)) {
1086+
if (!LogInstance().WillLogCategory(BCLog::MINER)) {
1087+
LogInstance().EnableCategory(BCLog::MINER);
1088+
LogInfo("parameter interaction: -printpriority -> setting -debug=miner\n");
1089+
}
1090+
}
1091+
10841092
// Also report errors from parsing before daemonization
10851093
{
10861094
kernel::Notifications notifications{};

src/logging.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,8 @@ static const std::map<std::string, BCLog::LogFlags, std::less<>> LOG_CATEGORIES_
201201
{"txreconciliation", BCLog::TXRECONCILIATION},
202202
{"scan", BCLog::SCAN},
203203
{"txpackages", BCLog::TXPACKAGES},
204+
{"miner", BCLog::MINER},
205+
{"sharetmpl", BCLog::SHARETMPL},
204206
};
205207

206208
static const std::unordered_map<BCLog::LogFlags, std::string> LOG_CATEGORIES_BY_FLAG{

src/logging.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,8 @@ namespace BCLog {
9494
TXRECONCILIATION = (CategoryMask{1} << 26),
9595
SCAN = (CategoryMask{1} << 27),
9696
TXPACKAGES = (CategoryMask{1} << 28),
97+
MINER = (CategoryMask{1} << 29),
98+
SHARETMPL = (CategoryMask{1} << 30),
9799
ALL = ~NONE,
98100
};
99101
enum class Level {

src/net_processing.cpp

Lines changed: 208 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#include <netbase.h>
2424
#include <netmessagemaker.h>
2525
#include <node/blockstorage.h>
26+
#include <node/miner.h>
2627
#include <node/timeoffsets.h>
2728
#include <node/txdownloadman.h>
2829
#include <node/txreconciliation.h>
@@ -165,6 +166,11 @@ static constexpr double MAX_ADDR_RATE_PER_SECOND{0.1};
165166
static constexpr size_t MAX_ADDR_PROCESSING_TOKEN_BUCKET{MAX_ADDR_TO_SEND};
166167
/** The compactblocks version we support. See BIP 152. */
167168
static constexpr uint64_t CMPCTBLOCKS_VERSION{2};
169+
/** How frequently to update templates for sharing */
170+
static constexpr std::chrono::microseconds TEMPLATE_UPDATE_INTERVAL{30s};
171+
/** Template weight limit */
172+
static constexpr unsigned int MAX_TEMPLATE_WEIGHT{8000000};
173+
static_assert(MAX_TEMPLATE_WEIGHT == 2 * MAX_BLOCK_WEIGHT);
168174

169175
// Internal stuff
170176
namespace {
@@ -176,6 +182,57 @@ struct QueuedBlock {
176182
std::unique_ptr<PartiallyDownloadedBlock> partialBlock;
177183
};
178184

185+
struct TemplateTx
186+
{
187+
CTransactionRef tx;
188+
uint32_t num_templates{0};
189+
190+
explicit TemplateTx(CTransactionRef tx) : tx{std::move(tx)} { }
191+
};
192+
using TemplateTxSet = std::map<Wtxid, TemplateTx>;
193+
using TemplateTxRefVec = std::vector<TemplateTxSet::iterator>;
194+
195+
struct MyTemplate {
196+
uint256 hash;
197+
TemplateTxRefVec txs;
198+
CBlockHeaderAndShortTxIDs compact;
199+
200+
// don't relay this template to peers whose m_last_sequence isn't at least this value
201+
uint64_t inv_sequence;
202+
uint32_t weight;
203+
};
204+
205+
class TemplateManager
206+
{
207+
public:
208+
TemplateTxSet template_txs;
209+
210+
std::deque<MyTemplate> my_templates;
211+
212+
void DiscardTxs(TemplateTxRefVec& txrv)
213+
{
214+
for (auto& it : txrv) {
215+
if (--it->second.num_templates == 0) {
216+
template_txs.erase(it);
217+
}
218+
}
219+
txrv.clear();
220+
}
221+
222+
TemplateTxRefVec AddTxs(const std::vector<CTransactionRef>& txs)
223+
{
224+
TemplateTxRefVec result;
225+
result.reserve(txs.size());
226+
for (auto& tx : txs) {
227+
const auto& wtxid = tx->GetWitnessHash();
228+
auto [it, inserted] = template_txs.try_emplace(wtxid, tx);
229+
++it->second.num_templates;
230+
result.emplace_back(it);
231+
}
232+
return result;
233+
}
234+
};
235+
179236
/**
180237
* Data structure for an individual peer. This struct is not protected by
181238
* cs_main since it does not contain validation-critical data.
@@ -286,6 +343,9 @@ struct Peer {
286343

287344
/** Minimum fee rate with which to filter transaction announcements to this node. See BIP133. */
288345
std::atomic<CAmount> m_fee_filter_received{0};
346+
347+
/** Whether this peer negotiated SENDTEMPLATE */
348+
std::atomic<bool> m_support_sendtemplate{false};
289349
};
290350

291351
/* Initializes a TxRelay struct for this peer. Can be called at most once for a peer. */
@@ -493,7 +553,7 @@ class PeerManagerImpl final : public PeerManager
493553
void FinalizeNode(const CNode& node) override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex, !m_headers_presync_mutex, !m_tx_download_mutex);
494554
bool HasAllDesirableServiceFlags(ServiceFlags services) const override;
495555
bool ProcessMessages(CNode* pfrom, std::atomic<bool>& interrupt) override
496-
EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex, !m_most_recent_block_mutex, !m_headers_presync_mutex, g_msgproc_mutex, !m_tx_download_mutex);
556+
EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex, !m_most_recent_block_mutex, !m_headers_presync_mutex, g_msgproc_mutex, !m_tx_download_mutex, !m_templatestats_mutex);
497557
bool SendMessages(CNode* pto) override
498558
EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex, !m_most_recent_block_mutex, g_msgproc_mutex, !m_tx_download_mutex);
499559

@@ -504,6 +564,7 @@ class PeerManagerImpl final : public PeerManager
504564
EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex);
505565
bool GetNodeStateStats(NodeId nodeid, CNodeStateStats& stats) const override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex);
506566
std::vector<TxOrphanage::OrphanTxBase> GetOrphanTransactions() override EXCLUSIVE_LOCKS_REQUIRED(!m_tx_download_mutex);
567+
TemplateStats GetTemplateStats() const override EXCLUSIVE_LOCKS_REQUIRED(!m_templatestats_mutex);
507568
PeerManagerInfo GetInfo() const override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex);
508569
void SendPings() override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex);
509570
void RelayTransaction(const uint256& txid, const uint256& wtxid) override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex);
@@ -1041,6 +1102,15 @@ class PeerManagerImpl final : public PeerManager
10411102

10421103
void AddAddressKnown(Peer& peer, const CAddress& addr) EXCLUSIVE_LOCKS_REQUIRED(g_msgproc_mutex);
10431104
void PushAddress(Peer& peer, const CAddress& addr) EXCLUSIVE_LOCKS_REQUIRED(g_msgproc_mutex);
1105+
1106+
TemplateManager m_templateman GUARDED_BY(g_msgproc_mutex);
1107+
NodeClock::time_point m_next_template_update GUARDED_BY(g_msgproc_mutex){NodeClock::time_point::min()};
1108+
1109+
mutable Mutex m_templatestats_mutex;
1110+
TemplateStats m_templatestats GUARDED_BY(m_templatestats_mutex);
1111+
1112+
void MaybeGenerateNewTemplate() EXCLUSIVE_LOCKS_REQUIRED(g_msgproc_mutex, !m_templatestats_mutex);
1113+
void SendTemplateTransactions(CNode& pfrom, Peer& peer, const MyTemplate& mytmp, const BlockTransactionsRequest& req) EXCLUSIVE_LOCKS_REQUIRED(g_msgproc_mutex);
10441114
};
10451115

10461116
const CNodeState* PeerManagerImpl::State(NodeId pnode) const
@@ -3473,6 +3543,12 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
34733543
MakeAndPushMessage(pfrom, NetMsgType::WTXIDRELAY);
34743544
}
34753545

3546+
if (greatest_common_version >= SENDTEMPLATE_VERSION) {
3547+
if (m_opts.share_template_count != 0) {
3548+
MakeAndPushMessage(pfrom, NetMsgType::SENDTEMPLATE);
3549+
}
3550+
}
3551+
34763552
// Signal ADDRv2 support (BIP155).
34773553
if (greatest_common_version >= 70016) {
34783554
// BIP155 defines addrv2 and sendaddrv2 for all protocol versions, but some
@@ -3694,6 +3770,20 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
36943770
return;
36953771
}
36963772

3773+
if (msg_type == NetMsgType::SENDTEMPLATE) {
3774+
if (pfrom.fSuccessfullyConnected) {
3775+
LogDebug(BCLog::NET, "sendtemplate received after verack, %s\n", pfrom.DisconnectMsg(fLogIPs));
3776+
pfrom.fDisconnect = true;
3777+
return;
3778+
}
3779+
if (m_opts.share_template_count == 0) {
3780+
return;
3781+
}
3782+
auto* tx_relay = peer->GetTxRelay();
3783+
if (tx_relay) tx_relay->m_support_sendtemplate = true;
3784+
return;
3785+
}
3786+
36973787
// BIP339 defines feature negotiation of wtxidrelay, which must happen between
36983788
// VERSION and VERACK to avoid relay problems from switching after a connection is up.
36993789
if (msg_type == NetMsgType::WTXIDRELAY) {
@@ -4084,6 +4174,16 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
40844174
return;
40854175
}
40864176

4177+
if (auto* tx_relay = peer->GetTxRelay(); tx_relay) {
4178+
for (const auto& mytmp : m_templateman.my_templates) {
4179+
if (mytmp.hash == req.blockhash && tx_relay->m_last_inv_sequence >= mytmp.inv_sequence) {
4180+
LogDebug(BCLog::SHARETMPL, "Sending requested txns for template %s peer=%d\n", mytmp.hash.ToString(), peer->m_id);
4181+
SendTemplateTransactions(pfrom, *peer, mytmp, req);
4182+
return;
4183+
}
4184+
}
4185+
}
4186+
40874187
FlatFilePos block_pos{};
40884188
{
40894189
LOCK(cs_main);
@@ -4279,6 +4379,23 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
42794379
return;
42804380
}
42814381

4382+
if (msg_type == NetMsgType::GETTEMPLATE) {
4383+
auto tx_relay = peer->GetTxRelay();
4384+
if (tx_relay == nullptr || !tx_relay->m_support_sendtemplate) return;
4385+
4386+
for (const auto& mytmp : m_templateman.my_templates) {
4387+
if (mytmp.inv_sequence <= tx_relay->m_last_inv_sequence) {
4388+
MakeAndPushMessage(pfrom, NetMsgType::TEMPLATE, mytmp.compact);
4389+
break;
4390+
}
4391+
}
4392+
return;
4393+
}
4394+
4395+
if (msg_type == NetMsgType::TEMPLATE) {
4396+
return; // ignore these for now
4397+
}
4398+
42824399
if (msg_type == NetMsgType::CMPCTBLOCK)
42834400
{
42844401
// Ignore cmpctblock received while importing
@@ -4928,6 +5045,94 @@ bool PeerManagerImpl::MaybeDiscourageAndDisconnect(CNode& pnode, Peer& peer)
49285045
return true;
49295046
}
49305047

5048+
void PeerManagerImpl::SendTemplateTransactions(CNode& pfrom, Peer& peer, const MyTemplate& mytmp, const BlockTransactionsRequest& req)
5049+
{
5050+
BlockTransactions resp(req);
5051+
unsigned int tx_requested_size = 0;
5052+
for (size_t i = 0; i < req.indexes.size(); i++) {
5053+
if (req.indexes[i] >= mytmp.txs.size()) {
5054+
Misbehaving(peer, "getblocktxn with out-of-bounds tx indices");
5055+
return;
5056+
}
5057+
resp.txn[i] = mytmp.txs[req.indexes[i]]->second.tx;
5058+
tx_requested_size += resp.txn[i]->GetTotalSize();
5059+
}
5060+
5061+
LogDebug(BCLog::SHARETMPL, "Peer %d sent us a GETBLOCKTXN for template %s, sending a BLOCKTXN with %u txns. (%u bytes)\n", pfrom.GetId(), mytmp.hash.ToString(), resp.txn.size(), tx_requested_size);
5062+
MakeAndPushMessage(pfrom, NetMsgType::BLOCKTXN, resp);
5063+
}
5064+
5065+
TemplateStats PeerManagerImpl::GetTemplateStats() const
5066+
{
5067+
LOCK(m_templatestats_mutex);
5068+
return m_templatestats;
5069+
}
5070+
5071+
void PeerManagerImpl::MaybeGenerateNewTemplate()
5072+
{
5073+
if (m_opts.share_template_count == 0) return;
5074+
if (m_next_template_update == NodeClock::time_point::min()) {
5075+
if (m_chainman.IsInitialBlockDownload() || !m_mempool.GetLoadTried()) {
5076+
return;
5077+
} else if (WITH_LOCK(cs_main, return !CanDirectFetch())) {
5078+
return;
5079+
}
5080+
}
5081+
5082+
auto now = NodeClock::now();
5083+
if (now < m_next_template_update) return;
5084+
m_next_template_update = now + TEMPLATE_UPDATE_INTERVAL;
5085+
5086+
auto& my_templates = m_templateman.my_templates;
5087+
while (my_templates.size() >= m_opts.share_template_count) {
5088+
m_templateman.DiscardTxs(my_templates.back().txs);
5089+
my_templates.pop_back();
5090+
}
5091+
5092+
const auto assemble_options = []() {
5093+
node::BlockAssembler::Options opt;
5094+
opt.nBlockMaxWeight=MAX_TEMPLATE_WEIGHT;
5095+
opt.blockMinFeeRate=CFeeRate(0);
5096+
opt.test_block_validity=false;
5097+
opt.print_modified_fee=false;
5098+
return opt;
5099+
}();
5100+
node::BlockAssembler assembler{m_chainman.ActiveChainstate(), &m_mempool, assemble_options, node::BlockAssembler::ALLOW_OVERSIZED_BLOCKS};
5101+
auto& new_template = my_templates.emplace_front();
5102+
5103+
auto block_template = assembler.CreateNewBlock();
5104+
auto& block = block_template->block;
5105+
assert(block.vtx[0]->IsCoinBase());
5106+
block.vtx.erase(block.vtx.begin());
5107+
block.nNonce = 0;
5108+
block.nTime = std::numeric_limits<uint32_t>::max();
5109+
5110+
new_template.hash = block.GetHash();
5111+
new_template.compact = CBlockHeaderAndShortTxIDs(block, FastRandomContext().rand64());
5112+
new_template.weight = 0;
5113+
for (auto& tx : block.vtx) {
5114+
new_template.weight += GetTransactionWeight(*tx);
5115+
}
5116+
new_template.txs = m_templateman.AddTxs(block.vtx);
5117+
new_template.inv_sequence = WITH_LOCK(m_mempool.cs, return m_mempool.GetSequence());
5118+
5119+
LogDebug(BCLog::SHARETMPL, "Generated template for sharing hash=%s (%d txs, %d weight)\n", new_template.hash.ToString(), new_template.txs.size(), new_template.weight);
5120+
5121+
LOCK(m_templatestats_mutex);
5122+
m_templatestats.num_templates = my_templates.size();
5123+
m_templatestats.max_templates = m_opts.share_template_count;
5124+
m_templatestats.num_transactions = m_templateman.template_txs.size();
5125+
if (!m_templateman.my_templates.empty()) {
5126+
const auto& tmp = m_templateman.my_templates.front();
5127+
m_templatestats.latest_template_weight = tmp.weight;
5128+
m_templatestats.latest_template_tx = tmp.txs.size();
5129+
} else {
5130+
m_templatestats.latest_template_weight = 0;
5131+
m_templatestats.latest_template_tx = 0;
5132+
}
5133+
m_templatestats.next_update = m_next_template_update;
5134+
}
5135+
49315136
bool PeerManagerImpl::ProcessMessages(CNode* pfrom, std::atomic<bool>& interruptMsgProc)
49325137
{
49335138
AssertLockNotHeld(m_tx_download_mutex);
@@ -4936,6 +5141,8 @@ bool PeerManagerImpl::ProcessMessages(CNode* pfrom, std::atomic<bool>& interrupt
49365141
PeerRef peer = GetPeerRef(pfrom->GetId());
49375142
if (peer == nullptr) return false;
49385143

5144+
MaybeGenerateNewTemplate();
5145+
49395146
// For outbound connections, ensure that the initial VERSION message
49405147
// has been sent first before processing any incoming messages
49415148
if (!pfrom->IsInboundConn() && !peer->m_outbound_version_message_sent) return false;

0 commit comments

Comments
 (0)