diff --git a/libraries/net/include/psibase/shortest_path_routing.hpp b/libraries/net/include/psibase/shortest_path_routing.hpp index cc7f5e5a3..cf8dc7075 100644 --- a/libraries/net/include/psibase/shortest_path_routing.hpp +++ b/libraries/net/include/psibase/shortest_path_routing.hpp @@ -10,6 +10,7 @@ #include #include +#include #include #include #include @@ -156,7 +157,7 @@ namespace psibase::net std::chrono::steady_clock::time_point time; }; - explicit shortest_path_routing(boost::asio::io_context& ctx) : base_type(ctx) + explicit shortest_path_routing(boost::asio::io_context& ctx) : base_type(ctx), seqnoTimer(ctx) { logger.add_attribute("Channel", boost::log::attributes::constant(std::string("p2p"))); } @@ -480,6 +481,18 @@ namespace psibase::net multicast(RouteUpdateMessage{consensus().producer_name(), seqno, 0}); lastSeqnoUpdate = now; } + else + { + seqnoTimer.expires_at(lastSeqnoUpdate + minSeqnoUpdateInterval); + seqnoTimer.async_wait( + [this](const std::error_code& e) + { + if (!e) + { + incrementSeqno(); + } + }); + } } bool cacheSeqnoRequest(const RouteSeqnoRequest& msg) { @@ -537,6 +550,7 @@ namespace psibase::net std::map selectedRoutes; std::map recentSeqnoRequests; + boost::asio::steady_timer seqnoTimer; loggers::common_logger logger; }; diff --git a/programs/psinode/tests/test_routing.py b/programs/psinode/tests/test_routing.py index 16377701d..82a41f40a 100755 --- a/programs/psinode/tests/test_routing.py +++ b/programs/psinode/tests/test_routing.py @@ -55,23 +55,23 @@ def test_change_route(self, cluster): @testutil.psinode_test def test_bft(self, cluster): - (a, b, c, d, e, f, g) = cluster.line('a', 'b', 'c', 'd', 'e', 'f', 'g') - # This isn't working. The problem is that the routing algorithm - # doesn't respect the order dependency between blocks and consensus - # messages related to said blocks. - testutil.boot_with_producers([a, b, c, d, e, f, g], 'bft', timeout=15) + (a, b, c, d, e, f, g) = cluster.ring('a', 'b', 'c', 'd', 'e', 'f', 'g') + testutil.boot_with_producers([a, b, c, d, e, f, g], 'bft') # wait for irreversibility to advance a.wait(new_block()) a.wait(irreversible(a.get_block_header()['blockNum'])) + a.disconnect(g) + # Switch between two different configurations print('checking blocks') + testutil.sleep(0.0, 0.5) for i in range(10): header = a.get_block_header() print(header) self.assertEqual(header['commitNum'] + 2, header['blockNum']) - time.sleep(1) + testutil.sleep(0.5, 0.5) if i % 2 == 0: a.connect(g) a.disconnect(b) diff --git a/programs/psinode/tests/testutil.py b/programs/psinode/tests/testutil.py index 570f0db1d..f93b7174a 100644 --- a/programs/psinode/tests/testutil.py +++ b/programs/psinode/tests/testutil.py @@ -3,6 +3,8 @@ import argparse import unittest import sys +import time +import math def psinode_test(f): def result(self): @@ -33,6 +35,16 @@ def boot_with_producers(nodes, algorithm=None, timeout=10): p.wait(predicates.producers_are(nodes), timeout=timeout) return p +def sleep(secs, end_at): + '''Like time.sleep, but waits additional time until the fractional seconds are equal to end_at''' + ticks_per_sec = 1000000000 + current = (time.time_ns() % ticks_per_sec) / ticks_per_sec + extra = end_at - (current + math.modf(secs)[0]) + if extra < 0: + extra += 1 + + time.sleep(secs + extra) + def main(argv=sys.argv): parser = argparse.ArgumentParser() parser.add_argument("--psinode", default="psinode", help="The path to the psinode executable")