@@ -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+
260345BOOST_AUTO_TEST_SUITE_END ()
0 commit comments