Skip to content

Commit f080f7e

Browse files
committed
Incrementally update sv2 block template
1 parent 160693f commit f080f7e

File tree

3 files changed

+136
-9
lines changed

3 files changed

+136
-9
lines changed

src/sv2/template_provider.cpp

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,31 @@ void Sv2TemplateProvider::StopThreads()
7474
}
7575
}
7676

77+
class Timer {
78+
private:
79+
std::chrono::seconds m_interval;
80+
std::chrono::seconds m_last_triggered;
81+
82+
public:
83+
Timer(std::chrono::seconds interval) : m_interval(interval) {
84+
reset();
85+
}
86+
87+
bool trigger() {
88+
auto now{GetTime<std::chrono::seconds>()};
89+
if (now - m_last_triggered >= m_interval) {
90+
m_last_triggered = now;
91+
return true;
92+
}
93+
return false;
94+
}
95+
96+
void reset() {
97+
auto now{GetTime<std::chrono::seconds>()};
98+
m_last_triggered = now;
99+
}
100+
};
101+
77102
void Sv2TemplateProvider::ThreadSv2Handler()
78103
{
79104
// Wait for the node chainstate to be ready if needed.
@@ -96,6 +121,8 @@ void Sv2TemplateProvider::ThreadSv2Handler()
96121
std::this_thread::sleep_for(1000ms);
97122
}
98123

124+
Timer timer(m_options.fee_check_interval);
125+
99126
while (!m_flag_interrupt_sv2) {
100127
// We start with one template per client, which has an interface through
101128
// which we monitor for better templates.
@@ -144,23 +171,27 @@ void Sv2TemplateProvider::ThreadSv2Handler()
144171
}
145172
});
146173

174+
// Do not send templates with improved fees more frequently than the fee check interval
175+
const bool check_fees{timer.trigger()};
147176
bool new_template{false};
148177

149178
// Delay event loop is no client if fully connected
150179
if (!first_client_id) std::this_thread::sleep_for(1000ms);
151180

152-
// For the first connected client, wait for a new chaintip
153-
m_connman->ForEachClient([this, first_client_id, &new_template](Sv2Client& client) {
154-
if (!first_client_id || client.m_id != first_client_id.value()) return;
181+
// For the first connected client, wait for fees to rise.
182+
m_connman->ForEachClient([this, first_client_id, check_fees, &new_template](Sv2Client& client) {
183+
if (!first_client_id || client.m_id != first_client_id) return;
155184
Assert(client.m_coinbase_output_data_size_recv);
156185

157186
LOCK(m_tp_mutex);
158187
auto block_template_entry{m_block_template_cache.find(client.m_best_template_id)};
159188

189+
CAmount fee_delta{check_fees ? m_options.fee_delta : MAX_MONEY};
190+
160191
// We give waitNext() a timeout of 1 second to prevent it from generating
161192
// new templates too quickly. During this wait we're not serving newly connected clients.
162193
// This can be cleaned up by having every client run its own thread.
163-
auto block_template = block_template_entry->second->waitNext(MAX_MONEY, MillisecondsDouble{1000});
194+
auto block_template = block_template_entry->second->waitNext(fee_delta, MillisecondsDouble{1000});
164195
if (block_template) {
165196
new_template = true;
166197

@@ -322,6 +353,7 @@ void Sv2TemplateProvider::SubmitSolution(node::Sv2SubmitSolutionMsg solution)
322353
block_template->submitSolution(solution.m_version, solution.m_header_timestamp, solution.m_header_nonce, solution.m_coinbase_tx);
323354
}
324355

356+
// TODO: get rid of this helper function
325357
Sv2TemplateProvider::NewWorkSet Sv2TemplateProvider::BuildNewWorkSet(bool future_template, unsigned int coinbase_output_max_additional_size)
326358
{
327359
AssertLockHeld(m_tp_mutex);

src/sv2/template_provider.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,16 @@ struct Sv2TemplateProviderOptions
2424
* The listening port for the server.
2525
*/
2626
uint16_t port{8336};
27+
28+
/**
29+
* Minimum fee delta to send new template upstream
30+
*/
31+
CAmount fee_delta{1000};
32+
33+
/**
34+
* Block template update interval (to check for increased fees)
35+
*/
36+
std::chrono::seconds fee_check_interval{30};
2737
};
2838

2939
/**

src/test/sv2_template_provider_tests.cpp

Lines changed: 90 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
1+
#include <addresstype.h>
12
#include <boost/test/unit_test.hpp>
23
#include <interfaces/mining.h>
34
#include <node/miner.h>
5+
#include <node/transaction.h>
46
#include <sv2/messages.h>
57
#include <sv2/template_provider.h>
68
#include <test/util/net.h>
79
#include <test/util/setup_common.h>
10+
#include <test/util/transaction_utils.h>
811
#include <util/sock.h>
12+
#include <util/strencodings.h>
913

1014
#include <memory>
1115

@@ -164,7 +168,47 @@ BOOST_AUTO_TEST_CASE(client_tests)
164168
// There should now be one template
165169
BOOST_REQUIRE_EQUAL(tester.GetBlockTemplateCount(), 1);
166170

167-
// Get the template id
171+
// Move mock time
172+
// If the mempool doesn't change, no new template is generated.
173+
SetMockTime(GetMockTime() + std::chrono::seconds{10});
174+
BOOST_REQUIRE_EQUAL(tester.GetBlockTemplateCount(), 1);
175+
176+
// Create a transaction with a large fee
177+
size_t tx_size;
178+
CKey key = GenerateRandomKey();
179+
CScript locking_script = GetScriptForDestination(PKHash(key.GetPubKey()));
180+
// Don't hold on to the transaction
181+
{
182+
LOCK(cs_main);
183+
BOOST_REQUIRE_EQUAL(m_node.mempool->size(), 0);
184+
185+
auto mtx = CreateValidMempoolTransaction(/*input_transaction=*/m_coinbase_txns[0], /*input_vout=*/0,
186+
/*input_height=*/0, /*input_signing_key=*/coinbaseKey,
187+
/*output_destination=*/locking_script,
188+
/*output_amount=*/CAmount(49 * COIN), /*submit=*/true);
189+
CTransactionRef tx = MakeTransactionRef(mtx);
190+
191+
// Get serialized transaction size
192+
DataStream ss;
193+
ss << TX_WITH_WITNESS(tx);
194+
tx_size = ss.size();
195+
196+
BOOST_REQUIRE_EQUAL(m_node.mempool->size(), 1);
197+
}
198+
199+
// Move mock time
200+
SetMockTime(GetMockTime() + std::chrono::seconds{tester.m_tp_options.fee_check_interval});
201+
202+
// Briefly wait for block creation
203+
UninterruptibleSleep(std::chrono::milliseconds{200});
204+
205+
// Expect our peer to receive a NewTemplate message
206+
// This time it should contain the 32 byte prevhash (unchanged)
207+
constexpr size_t expected_len = SV2_HEADER_ENCRYPTED_SIZE + 91 + 32 + Poly1305::TAGLEN;
208+
BOOST_TEST_MESSAGE("Receive NewTemplate");
209+
BOOST_REQUIRE_EQUAL(tester.PeerReceiveBytes(), expected_len);
210+
211+
// Get the latest template id
168212
uint64_t template_id = 0;
169213
{
170214
LOCK(tester.m_tp->m_tp_mutex);
@@ -175,6 +219,10 @@ BOOST_AUTO_TEST_CASE(client_tests)
175219
}
176220
}
177221

222+
BOOST_REQUIRE_EQUAL(template_id, 2);
223+
224+
UninterruptibleSleep(std::chrono::milliseconds{200});
225+
178226
// Have the peer send us RequestTransactionData
179227
// We should reply with RequestTransactionData.Success
180228
node::Sv2NetHeader req_tx_data_header{node::Sv2MsgType::REQUEST_TRANSACTION_DATA, 8};
@@ -188,15 +236,49 @@ BOOST_AUTO_TEST_CASE(client_tests)
188236
tester.receiveMessage(msg);
189237
const size_t template_id_size = 8;
190238
const size_t excess_data_size = 2 + 32;
191-
size_t tx_list_size = 2; // no transactions, so transaction_list is 0x0100
239+
size_t tx_list_size = 2 + 3 + tx_size;
240+
BOOST_TEST_MESSAGE("Receive RequestTransactionData.Success");
241+
BOOST_REQUIRE_EQUAL(tester.PeerReceiveBytes(), SV2_HEADER_ENCRYPTED_SIZE + template_id_size + excess_data_size + tx_list_size + Poly1305::TAGLEN);
242+
{
243+
LOCK(cs_main);
244+
245+
// RBF the transaction with with > DEFAULT_SV2_FEE_DELTA
246+
CreateValidMempoolTransaction(/*input_transaction=*/m_coinbase_txns[0], /*input_vout=*/0,
247+
/*input_height=*/0, /*input_signing_key=*/coinbaseKey,
248+
/*output_destination=*/locking_script,
249+
/*output_amount=*/CAmount(48 * COIN), /*submit=*/true);
250+
251+
BOOST_REQUIRE_EQUAL(m_node.mempool->size(), 1);
252+
}
253+
254+
// Move mock time
255+
SetMockTime(GetMockTime() + std::chrono::seconds{tester.m_tp_options.fee_check_interval});
256+
257+
// Briefly wait for the timer in ThreadSv2Handler and block creation
258+
UninterruptibleSleep(std::chrono::milliseconds{200});
259+
260+
// Wait a bit more for macOS native CI
261+
UninterruptibleSleep(std::chrono::milliseconds{1000});
262+
263+
// Expect our peer to receive a NewTemplate message
264+
BOOST_REQUIRE_EQUAL(tester.PeerReceiveBytes(), SV2_HEADER_ENCRYPTED_SIZE + 91 + 32 + Poly1305::TAGLEN);
265+
266+
// Check that there's a new template
267+
BOOST_REQUIRE_EQUAL(tester.GetBlockTemplateCount(), 3);
268+
269+
// Have the peer send us RequestTransactionData for the old template
270+
// We should reply with RequestTransactionData.Success, and the original
271+
// (replaced) transaction
272+
tester.receiveMessage(msg);
273+
tx_list_size = 2 + 3 + tx_size;
192274
BOOST_REQUIRE_EQUAL(tester.PeerReceiveBytes(), SV2_HEADER_ENCRYPTED_SIZE + template_id_size + excess_data_size + tx_list_size + Poly1305::TAGLEN);
193275

194-
// Create a new block
276+
BOOST_TEST_MESSAGE("Create a new block");
195277
mineBlocks(1);
196278

197279
// We should send out another NewTemplate and SetNewPrevHash
198280
// The new template contains the new prevhash.
199-
BOOST_REQUIRE_EQUAL(tester.PeerReceiveBytes(), 2 * SV2_HEADER_ENCRYPTED_SIZE + 91 + 80 + 2 * Poly1305::TAGLEN);
281+
BOOST_REQUIRE_EQUAL(tester.PeerReceiveBytes(), 2 * SV2_HEADER_ENCRYPTED_SIZE + 91 + 32 + 80 + 2 * Poly1305::TAGLEN);
200282
// The SetNewPrevHash message is redundant
201283
// TODO: don't send it?
202284
// Background: in the future we want to send an empty or optimistic template
@@ -205,7 +287,7 @@ BOOST_AUTO_TEST_CASE(client_tests)
205287
// a new block, and construct a better template _after_ that.
206288

207289
// Templates are briefly preserved
208-
BOOST_REQUIRE_EQUAL(tester.GetBlockTemplateCount(), 2);
290+
BOOST_REQUIRE_EQUAL(tester.GetBlockTemplateCount(), 4);
209291

210292
// Do not provide transactions for stale templates
211293
// TODO
@@ -217,6 +299,9 @@ BOOST_AUTO_TEST_CASE(client_tests)
217299
SetMockTime(GetMockTime() + std::chrono::seconds{15});
218300
UninterruptibleSleep(std::chrono::milliseconds{1100});
219301
BOOST_REQUIRE_EQUAL(tester.GetBlockTemplateCount(), 1);
302+
303+
// Mine a block in order to interrupt waitNext()
304+
mineBlocks(1);
220305
}
221306

222307
BOOST_AUTO_TEST_SUITE_END()

0 commit comments

Comments
 (0)