diff --git a/src/herder/HerderImpl.cpp b/src/herder/HerderImpl.cpp index 983a0ee523..0ab9b0df09 100644 --- a/src/herder/HerderImpl.cpp +++ b/src/herder/HerderImpl.cpp @@ -1229,16 +1229,69 @@ HerderImpl::setupTriggerNextLedger() // bootstrap with a pessimistic estimate of when // the ballot protocol started last auto now = mApp.getClock().now(); - auto lastBallotStart = now - milliseconds; - auto lastStart = mHerderSCPDriver.getPrepareStart(lastIndex); - if (lastStart) + auto lastLedgerStatingPoint = now - milliseconds; + +#ifdef BUILD_TESTS + if (mApp.getConfig().EXPERIMENTAL_TRIGGER_TIMER && + mApp.getClock().getMode() == VirtualClock::REAL_TIME) + { + auto consensusCloseTime = trackingConsensusCloseTime(); + + // Bootstrap to pessimistic estimate on startup + if (consensusCloseTime == 0) + { + CLOG_WARNING( + Herder, + "Consensus close time is 0, using pessimistic estimate"); + // Keep the existing lastLedgerStatingPoint + } + else + { + // The externalized close time is a unix timestamp. We convert it to + // steady_clock time by: + // 1. Converting unix timestamp to system_clock::time_point (wall + // clock time) + // 2. Calculating how long ago that was from current system time + // 3. Subtracting that duration from current steady_clock time + auto externalizedSystemTime = + VirtualClock::from_time_t(consensusCloseTime); + auto currentSystemTime = mApp.getClock().system_now(); + + // Handle clock drift: if externalized time is in the future, + // fall back to pessimistic estimate + if (externalizedSystemTime >= currentSystemTime) + { + CLOG_WARNING(Herder, + "Externalized closeTime {} is in the future " + "(current time {}), " + "using pessimistic estimate", + consensusCloseTime, + VirtualClock::to_time_t(currentSystemTime)); + // Keep the existing lastLedgerStatingPoint which is already set + // to the pessimistic estimate (now - milliseconds) + } + else + { + // Calculate how long ago the externalized closeTime was + auto timeSinceExternalized = + currentSystemTime - externalizedSystemTime; + lastLedgerStatingPoint = now - timeSinceExternalized; + } + } + } + else +#endif { - lastBallotStart = *lastStart; + auto lastStart = mHerderSCPDriver.getPrepareStart(lastIndex); + if (lastStart) + { + lastLedgerStatingPoint = *lastStart; + } } // Adjust trigger time in case node's clock has drifted. // This ensures that next value to nominate is valid - auto triggerTime = lastBallotStart + milliseconds; + auto triggerTime = lastLedgerStatingPoint + milliseconds; if (triggerTime < now) { diff --git a/src/main/Config.cpp b/src/main/Config.cpp index 6322c18c47..f8e5fd4a08 100644 --- a/src/main/Config.cpp +++ b/src/main/Config.cpp @@ -357,6 +357,7 @@ Config::Config() : NODE_SEED(SecretKey::random()) CATCHUP_SKIP_KNOWN_RESULTS_FOR_TESTING = false; MODE_USES_IN_MEMORY_LEDGER = false; SKIP_HIGH_CRITICAL_VALIDATOR_CHECKS_FOR_TESTING = false; + EXPERIMENTAL_TRIGGER_TIMER = false; #endif #ifdef BEST_OFFER_DEBUGGING @@ -1131,6 +1132,8 @@ Config::processConfig(std::shared_ptr t) EXPERIMENTAL_BACKGROUND_TX_SIG_VERIFICATION = readBool(item); }}, + {"EXPERIMENTAL_TRIGGER_TIMER", + [&]() { EXPERIMENTAL_TRIGGER_TIMER = readBool(item); }}, {"ARTIFICIALLY_DELAY_LEDGER_CLOSE_FOR_TESTING", [&]() { ARTIFICIALLY_DELAY_LEDGER_CLOSE_FOR_TESTING = diff --git a/src/main/Config.h b/src/main/Config.h index f6c5906c9d..19491a8ba3 100644 --- a/src/main/Config.h +++ b/src/main/Config.h @@ -848,6 +848,11 @@ class Config : public std::enable_shared_from_this size_t TESTING_MAX_SOROBAN_BYTE_ALLOWANCE; size_t TESTING_MAX_CLASSIC_BYTE_ALLOWANCE; + // Experimental flag to use externalized close time for trigger timer + // calculation instead of prepare start time. Should only be used for + // testing. + bool EXPERIMENTAL_TRIGGER_TIMER; + // Set QUORUM_SET using automatic quorum set configuration based on // `validators`. void diff --git a/src/test/test.cpp b/src/test/test.cpp index f0a49ce396..df1562db87 100644 --- a/src/test/test.cpp +++ b/src/test/test.cpp @@ -251,6 +251,7 @@ getTestConfig(int instanceNumber, Config::TestDbMode mode) thisConfig.MANUAL_CLOSE = true; thisConfig.TEST_CASES_ENABLED = true; + thisConfig.EXPERIMENTAL_TRIGGER_TIMER = true; thisConfig.PEER_PORT = static_cast(DEFAULT_PEER_PORT + instanceNumber * 2);