Compare commits

...

5 Commits

Author SHA1 Message Date
Nicholas Dudfield
4cf2be8e24 fix: add fatal log on amendment-blocked shutdown 2026-03-09 09:21:10 +07:00
Nicholas Dudfield
277e9f26bc fix: rethrow runtime_error if not amendment blocked 2026-03-08 10:33:28 +07:00
Nicholas Dudfield
ffcc58c8aa fix: narrow catch to std::runtime_error in switchLastClosedLedger 2026-03-08 10:32:36 +07:00
Nicholas Dudfield
9246677e9c fix: skip signalStop in standalone mode for test compatibility 2026-03-08 09:51:30 +07:00
Nicholas Dudfield
1f8418a58b fix: fail fast when amendment blocked instead of zombie state
- signalStop() for graceful shutdown when unsupported amendment activates
- early shutdown ~1 minute before expected activation to avoid race
- try/catch in switchLastClosedLedger to survive unknown field crashes
  during shutdown window
- show amendment warning to all RPC users, not just admin

Fixes: #706
2026-03-08 08:41:20 +07:00
2 changed files with 52 additions and 9 deletions

View File

@@ -310,12 +310,28 @@ LedgerMaster::setValidLedger(std::shared_ptr<Ledger const> const& l)
if (auto const first = if (auto const first =
app_.getAmendmentTable().firstUnsupportedExpected()) app_.getAmendmentTable().firstUnsupportedExpected())
{ {
JLOG(m_journal.error()) << "One or more unsupported amendments " using namespace std::chrono_literals;
"reached majority. Upgrade before " auto const now = app_.timeKeeper().closeTime();
<< to_string(*first) if (*first > now && (*first - now) <= 1min)
<< " to prevent your server from " {
"becoming amendment blocked."; // Shut down just before the amendment activates to
app_.getOPs().setAmendmentWarned(); // avoid processing ledgers with unknown fields.
JLOG(m_journal.error())
<< "Unsupported amendment activating imminently "
"at "
<< to_string(*first) << ". Shutting down.";
app_.getOPs().setAmendmentBlocked();
}
else
{
JLOG(m_journal.error())
<< "One or more unsupported amendments "
"reached majority. Upgrade before "
<< to_string(*first)
<< " to prevent your server from "
"becoming amendment blocked.";
app_.getOPs().setAmendmentWarned();
}
} }
else else
app_.getOPs().clearAmendmentWarned(); app_.getOPs().clearAmendmentWarned();

View File

@@ -1634,6 +1634,16 @@ NetworkOPsImp::setAmendmentBlocked()
{ {
amendmentBlocked_ = true; amendmentBlocked_ = true;
setMode(OperatingMode::CONNECTED); setMode(OperatingMode::CONNECTED);
if (!app_.config().standalone())
{
JLOG(m_journal.fatal())
<< "One or more unsupported amendments activated. "
"Shutting down. Upgrade the server to remain "
"compatible with the network.";
app_.signalStop(
"One or more unsupported amendments activated. "
"Server must be upgraded to remain compatible with the network.");
}
} }
inline bool inline bool
@@ -1789,8 +1799,23 @@ NetworkOPsImp::switchLastClosedLedger(
clearNeedNetworkLedger(); clearNeedNetworkLedger();
// Update fee computations. // Update fee computations. May throw if the ledger contains
app_.getTxQ().processClosedLedger(app_, *newLCL, true); // transactions with fields unknown to this binary (e.g. after an
// unsupported amendment activates). Catch to allow graceful shutdown.
//@@start process-closed-ledger-catch
try
{
app_.getTxQ().processClosedLedger(app_, *newLCL, true);
}
catch (std::runtime_error const& e)
{
if (!amendmentBlocked_)
throw;
JLOG(m_journal.error())
<< "Failed to process closed ledger: " << e.what();
return;
}
//@@end process-closed-ledger-catch
// Caller must own master lock // Caller must own master lock
{ {
@@ -2449,7 +2474,7 @@ NetworkOPsImp::getServerInfo(bool human, bool admin, bool counters)
"may be incorrectly configured or some [validator_list_sites] " "may be incorrectly configured or some [validator_list_sites] "
"may be unreachable."; "may be unreachable.";
} }
if (admin && isAmendmentWarned()) if (isAmendmentWarned())
{ {
Json::Value& w = warnings.append(Json::objectValue); Json::Value& w = warnings.append(Json::objectValue);
w[jss::id] = warnRPC_UNSUPPORTED_MAJORITY; w[jss::id] = warnRPC_UNSUPPORTED_MAJORITY;
@@ -2893,6 +2918,7 @@ NetworkOPsImp::pubLedger(std::shared_ptr<ReadView const> const& lpAccepted)
// Ledgers are published only when they acquire sufficient validations // Ledgers are published only when they acquire sufficient validations
// Holes are filled across connection loss or other catastrophe // Holes are filled across connection loss or other catastrophe
//@@start pubLedger-accepted-ledger-construction
std::shared_ptr<AcceptedLedger> alpAccepted = std::shared_ptr<AcceptedLedger> alpAccepted =
app_.getAcceptedLedgerCache().fetch(lpAccepted->info().hash); app_.getAcceptedLedgerCache().fetch(lpAccepted->info().hash);
if (!alpAccepted) if (!alpAccepted)
@@ -2901,6 +2927,7 @@ NetworkOPsImp::pubLedger(std::shared_ptr<ReadView const> const& lpAccepted)
app_.getAcceptedLedgerCache().canonicalize_replace_client( app_.getAcceptedLedgerCache().canonicalize_replace_client(
lpAccepted->info().hash, alpAccepted); lpAccepted->info().hash, alpAccepted);
} }
//@@end pubLedger-accepted-ledger-construction
XRPL_ASSERT( XRPL_ASSERT(
alpAccepted->getLedger().get() == lpAccepted.get(), alpAccepted->getLedger().get() == lpAccepted.get(),