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