Skip to content

Commit fffb55b

Browse files
committed
test: add fee timer blocking test coverage
Add test coverage for the fee-based rate limiting timer that was marked with a TODO in template_provider.cpp. The timer blocks fee-based template updates until fee_check_interval seconds have passed (-sv2interval flag). The test uses is_test=false to exercise the actual timer logic that is normally bypassed in tests. It verifies: - Fee increases are blocked when the timer hasn't fired - Fee increases are allowed after the timer fires Also adds a TPTester constructor overload to accept custom options, allowing tests to configure is_test and fee_check_interval independently. Closes #67
1 parent 9a9bc7c commit fffb55b

File tree

3 files changed

+91
-2
lines changed

3 files changed

+91
-2
lines changed

src/test/sv2_template_provider_tests.cpp

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,4 +257,90 @@ BOOST_AUTO_TEST_CASE(client_tests)
257257
tester.m_mining_control->Shutdown();
258258
}
259259

260+
// Test the fee-based rate limiting timer behavior.
261+
// This test uses is_test=false to exercise the actual timer logic that is
262+
// normally bypassed in tests. The timer blocks fee-based template updates
263+
// until fee_check_interval seconds have passed (-sv2interval flag).
264+
BOOST_AUTO_TEST_CASE(fee_timer_blocking_test)
265+
{
266+
// Clear mock time so the Timer uses real wall-clock time.
267+
// The test fixture sets mock time, which would prevent the timer from
268+
// advancing without explicit SetMockTime calls.
269+
SetMockTime(std::chrono::seconds{0});
270+
271+
// Use is_test=false to test actual timer behavior.
272+
// Use a short fee_check_interval (2s) to keep the test fast.
273+
Sv2TemplateProviderOptions opts;
274+
opts.is_test = false;
275+
opts.fee_check_interval = std::chrono::seconds{2};
276+
TPTester tester{opts};
277+
278+
tester.handshake();
279+
tester.handshake();
280+
281+
node::Sv2NetMsg setup{tester.SetupConnectionMsg()};
282+
tester.receiveMessage(setup);
283+
tester.PeerReceiveBytes(); // SetupConnection.Success
284+
285+
// Send CoinbaseOutputConstraints to trigger template generation
286+
std::vector<uint8_t> coinbase_output_constraint_bytes{
287+
0x01, 0x00, 0x00, 0x00, // coinbase_output_max_additional_size
288+
0x00, 0x00 // coinbase_output_max_sigops
289+
};
290+
node::Sv2NetMsg coc_msg{node::Sv2MsgType::COINBASE_OUTPUT_CONSTRAINTS, std::move(coinbase_output_constraint_bytes)};
291+
tester.receiveMessage(coc_msg);
292+
293+
// Receive initial NewTemplate + SetNewPrevHash
294+
constexpr size_t SV2_SET_NEW_PREV_HASH_MESSAGE_SIZE = 8 + 32 + 4 + 4 + 32;
295+
constexpr size_t SV2_NEW_TEMPLATE_MESSAGE_SIZE =
296+
8 + 1 + 4 + 4 + 2 + 4 + 8 + 4 + 2 + 56 + 4 + 1;
297+
const size_t expected_set_new_prev_hash = SV2_HEADER_ENCRYPTED_SIZE + SV2_SET_NEW_PREV_HASH_MESSAGE_SIZE + Poly1305::TAGLEN;
298+
const size_t expected_new_template = SV2_HEADER_ENCRYPTED_SIZE + SV2_NEW_TEMPLATE_MESSAGE_SIZE + Poly1305::TAGLEN;
299+
const size_t expected_pair_bytes = expected_set_new_prev_hash + expected_new_template;
300+
301+
size_t initial_bytes = 0;
302+
while (initial_bytes < expected_pair_bytes) {
303+
initial_bytes += tester.PeerReceiveBytes();
304+
}
305+
BOOST_REQUIRE_EQUAL(initial_bytes, expected_pair_bytes);
306+
307+
// Timer was reset after the initial template was sent.
308+
uint64_t seq = tester.m_mining_control->GetTemplateSeq();
309+
BOOST_TEST_MESSAGE("Initial template sequence: " << seq);
310+
311+
// Immediately trigger a fee increase. The timer hasn't fired yet (just reset),
312+
// so this should be blocked (fee_delta = MAX_MONEY, threshold not met).
313+
BOOST_TEST_MESSAGE("Triggering fee increase while timer is blocking...");
314+
std::vector<CTransactionRef> blocked_fee_txs{MakeDummyTx()};
315+
tester.m_mining_control->TriggerFeeIncrease(blocked_fee_txs);
316+
317+
// Wait for the mock's waitNext timeout (fee_check_interval = 2s) plus buffer.
318+
// Should NOT get a new template because the timer blocked fee checks.
319+
bool got_template = tester.m_mining_control->WaitForTemplateSeq(seq + 1, std::chrono::milliseconds{2500});
320+
BOOST_REQUIRE_MESSAGE(!got_template, "Fee increase should be blocked when timer hasn't fired");
321+
322+
// Verify template count is still 1
323+
BOOST_REQUIRE_EQUAL(tester.GetBlockTemplateCount(), 1);
324+
325+
// Now more than fee_check_interval (2s) has passed since the timer was reset.
326+
// The timer should fire on the next iteration, allowing fee checks.
327+
BOOST_TEST_MESSAGE("Triggering fee increase after timer should have fired...");
328+
std::vector<CTransactionRef> allowed_fee_txs{MakeDummyTx()};
329+
tester.m_mining_control->TriggerFeeIncrease(allowed_fee_txs);
330+
331+
// This time we should get a template. Allow up to 3s since the TP may be
332+
// partway through a 2s waitNext cycle when we trigger the fee increase.
333+
got_template = tester.m_mining_control->WaitForTemplateSeq(seq + 1, std::chrono::milliseconds{3000});
334+
BOOST_REQUIRE_MESSAGE(got_template, "Fee increase should be allowed after timer fires");
335+
336+
// Receive the NewTemplate message
337+
size_t bytes_nt = tester.PeerReceiveBytes();
338+
BOOST_REQUIRE_EQUAL(bytes_nt, expected_new_template);
339+
340+
// Verify we now have 2 templates
341+
BOOST_REQUIRE_EQUAL(tester.GetBlockTemplateCount(), 2);
342+
343+
tester.m_mining_control->Shutdown();
344+
}
345+
260346
BOOST_AUTO_TEST_SUITE_END()

src/test/sv2_tp_tester.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,10 @@ struct MockInit : public interfaces::Init {
3636
};
3737
} // namespace
3838

39-
TPTester::TPTester()
40-
: m_state{std::make_shared<MockState>()}, m_mining_control{std::make_shared<MockMining>(m_state)}
39+
TPTester::TPTester() : TPTester(Sv2TemplateProviderOptions{.is_test = true}) {}
40+
41+
TPTester::TPTester(Sv2TemplateProviderOptions opts)
42+
: m_tp_options{opts}, m_state{std::make_shared<MockState>()}, m_mining_control{std::make_shared<MockMining>(m_state)}
4143
{
4244
// Start cap'n proto event loop on a background thread
4345
std::promise<mp::EventLoop*> loop_ready;

src/test/sv2_tp_tester.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ class TPTester {
4242
std::unique_ptr<interfaces::Mining> m_mining_proxy; // IPC mining proxy
4343

4444
TPTester();
45+
explicit TPTester(Sv2TemplateProviderOptions opts);
4546
~TPTester();
4647

4748
void SendPeerBytes();

0 commit comments

Comments
 (0)