Skip to content

Commit d54d4b7

Browse files
committed
test: add fee timer blocking test coverage
Closes #67
1 parent 9a9bc7c commit d54d4b7

File tree

3 files changed

+90
-2
lines changed

3 files changed

+90
-2
lines changed

src/test/sv2_template_provider_tests.cpp

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,4 +257,89 @@ 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+
280+
node::Sv2NetMsg setup{tester.SetupConnectionMsg()};
281+
tester.receiveMessage(setup);
282+
tester.PeerReceiveBytes(); // SetupConnection.Success
283+
284+
// Send CoinbaseOutputConstraints to trigger template generation
285+
std::vector<uint8_t> coinbase_output_constraint_bytes{
286+
0x01, 0x00, 0x00, 0x00, // coinbase_output_max_additional_size
287+
0x00, 0x00 // coinbase_output_max_sigops
288+
};
289+
node::Sv2NetMsg coc_msg{node::Sv2MsgType::COINBASE_OUTPUT_CONSTRAINTS, std::move(coinbase_output_constraint_bytes)};
290+
tester.receiveMessage(coc_msg);
291+
292+
// Receive initial NewTemplate + SetNewPrevHash
293+
constexpr size_t SV2_SET_NEW_PREV_HASH_MESSAGE_SIZE = 8 + 32 + 4 + 4 + 32;
294+
constexpr size_t SV2_NEW_TEMPLATE_MESSAGE_SIZE =
295+
8 + 1 + 4 + 4 + 2 + 4 + 8 + 4 + 2 + 56 + 4 + 1;
296+
const size_t expected_set_new_prev_hash = SV2_HEADER_ENCRYPTED_SIZE + SV2_SET_NEW_PREV_HASH_MESSAGE_SIZE + Poly1305::TAGLEN;
297+
const size_t expected_new_template = SV2_HEADER_ENCRYPTED_SIZE + SV2_NEW_TEMPLATE_MESSAGE_SIZE + Poly1305::TAGLEN;
298+
const size_t expected_pair_bytes = expected_set_new_prev_hash + expected_new_template;
299+
300+
size_t initial_bytes = 0;
301+
while (initial_bytes < expected_pair_bytes) {
302+
initial_bytes += tester.PeerReceiveBytes();
303+
}
304+
BOOST_REQUIRE_EQUAL(initial_bytes, expected_pair_bytes);
305+
306+
// Timer was reset after the initial template was sent.
307+
uint64_t seq = tester.m_mining_control->GetTemplateSeq();
308+
BOOST_TEST_MESSAGE("Initial template sequence: " << seq);
309+
310+
// Immediately trigger a fee increase. The timer hasn't fired yet (just reset),
311+
// so this should be blocked (fee_delta = MAX_MONEY, threshold not met).
312+
BOOST_TEST_MESSAGE("Triggering fee increase while timer is blocking...");
313+
std::vector<CTransactionRef> blocked_fee_txs{MakeDummyTx()};
314+
tester.m_mining_control->TriggerFeeIncrease(blocked_fee_txs);
315+
316+
// Wait for the mock's waitNext timeout (fee_check_interval = 2s) plus buffer.
317+
// Should NOT get a new template because the timer blocked fee checks.
318+
bool got_template = tester.m_mining_control->WaitForTemplateSeq(seq + 1, std::chrono::milliseconds{2500});
319+
BOOST_REQUIRE_MESSAGE(!got_template, "Fee increase should be blocked when timer hasn't fired");
320+
321+
// Verify template count is still 1
322+
BOOST_REQUIRE_EQUAL(tester.GetBlockTemplateCount(), 1);
323+
324+
// Now more than fee_check_interval (2s) has passed since the timer was reset.
325+
// The timer should fire on the next iteration, allowing fee checks.
326+
BOOST_TEST_MESSAGE("Triggering fee increase after timer should have fired...");
327+
std::vector<CTransactionRef> allowed_fee_txs{MakeDummyTx()};
328+
tester.m_mining_control->TriggerFeeIncrease(allowed_fee_txs);
329+
330+
// This time we should get a template. Allow up to 3s since the TP may be
331+
// partway through a 2s waitNext cycle when we trigger the fee increase.
332+
got_template = tester.m_mining_control->WaitForTemplateSeq(seq + 1, std::chrono::milliseconds{3000});
333+
BOOST_REQUIRE_MESSAGE(got_template, "Fee increase should be allowed after timer fires");
334+
335+
// Receive the NewTemplate message
336+
size_t bytes_nt = tester.PeerReceiveBytes();
337+
BOOST_REQUIRE_EQUAL(bytes_nt, expected_new_template);
338+
339+
// Verify we now have 2 templates
340+
BOOST_REQUIRE_EQUAL(tester.GetBlockTemplateCount(), 2);
341+
342+
tester.m_mining_control->Shutdown();
343+
}
344+
260345
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)