From a0866d1559e786a6776c17b406bec2db3d8d56b9 Mon Sep 17 00:00:00 2001 From: Nicholas Dudfield Date: Sun, 28 Jun 2026 19:45:36 +0700 Subject: [PATCH] fix(import): use ledger time for VL freshness --- src/xrpld/app/tx/detail/Import.cpp | 45 ++++++++++++++++++++---------- 1 file changed, 30 insertions(+), 15 deletions(-) diff --git a/src/xrpld/app/tx/detail/Import.cpp b/src/xrpld/app/tx/detail/Import.cpp index 1c10dc1f4..cdcf012bf 100644 --- a/src/xrpld/app/tx/detail/Import.cpp +++ b/src/xrpld/app/tx/detail/Import.cpp @@ -540,7 +540,6 @@ Import::preflight(PreflightContext const& ctx) list.isMember(jss::effective) ? list[jss::effective].asUInt() : 0}}; auto const validUntil = TimeKeeper::time_point{ TimeKeeper::duration{list[jss::expiration].asUInt()}}; - auto const now = ctx.app.timeKeeper().now(); if (validUntil <= validFrom) { JLOG(ctx.j.warn()) << "Import: unl blob validUntil <= validFrom " @@ -548,20 +547,6 @@ Import::preflight(PreflightContext const& ctx) return temMALFORMED; } - if (validUntil <= now) - { - JLOG(ctx.j.warn()) << "Import: unl blob expired " - << tx.getTransactionID(); - return temMALFORMED; - } - - if (validFrom > now) - { - JLOG(ctx.j.warn()) << "Import: unl blob not yet valid " - << tx.getTransactionID(); - return temMALFORMED; - } - auto const sig = strUnHex((*xpop)[jss::validation][jss::unl][jss::signature].asString()); if (!sig || !ripple::verify(*signingKey, makeSlice(data), makeSlice(*sig))) @@ -1137,6 +1122,36 @@ Import::preclaim(PreclaimContext const& ctx) return tefINTERNAL; } + auto const data = + base64_decode((*xpop)[jss::validation][jss::unl][jss::blob].asString()); + Json::Reader r; + Json::Value list; + if (!r.parse(data, list)) + { + JLOG(ctx.j.warn()) + << "Import: during preclaim could not parse unl blob, bailing."; + return tefINTERNAL; + } + + auto const validFrom = TimeKeeper::time_point{TimeKeeper::duration{ + list.isMember(jss::effective) ? list[jss::effective].asUInt() : 0}}; + auto const validUntil = TimeKeeper::time_point{ + TimeKeeper::duration{list[jss::expiration].asUInt()}}; + auto const now = ctx.view.parentCloseTime(); + if (validUntil <= now) + { + JLOG(ctx.j.warn()) + << "Import: unl blob expired at parent ledger close time."; + return temMALFORMED; + } + + if (validFrom > now) + { + JLOG(ctx.j.warn()) + << "Import: unl blob not yet valid at parent ledger close time."; + return temMALFORMED; + } + // Shared XPOP verification includes the source VL anti-downgrade ratchet. // A shadow ticket is the callback replay latch; it does not replace the // requirement that imports use the newest validator list this chain has