mirror of
https://github.com/Xahau/xahaud.git
synced 2026-07-31 11:00:10 +00:00
refactor(export): name admission lifecycle precisely
This commit is contained in:
@@ -54,7 +54,7 @@ struct ExportLimits
|
||||
// Maximum admission window requested through the mandatory outer
|
||||
// LastLedgerSequence. This bounds how long an Export may remain queued
|
||||
// before entering a ledger; it does not bound post-validation release.
|
||||
static constexpr std::uint32_t maxRetryLedgers = 5;
|
||||
static constexpr std::uint32_t maxAdmissionWindowLedgers = 5;
|
||||
|
||||
// Fixed source-ledger window for post-validation share publication and
|
||||
// witness materialization, measured from the ledger that admits the
|
||||
|
||||
@@ -132,7 +132,7 @@ struct Export_test : public beast::unit_test::suite
|
||||
jvExport[jss::TransactionType] = jss::Export;
|
||||
jvExport[jss::Account] = alice.human();
|
||||
jvExport[jss::LastLedgerSequence] =
|
||||
xahau.current()->seq() + ExportLimits::maxRetryLedgers;
|
||||
xahau.current()->seq() + ExportLimits::maxAdmissionWindowLedgers;
|
||||
jvExport[sfExportedTxn.jsonName] = innerObj.getJson(JsonOptions::none);
|
||||
|
||||
auto const& valKeys = xahau.app().getValidatorKeys();
|
||||
@@ -884,7 +884,7 @@ struct Export_test : public beast::unit_test::suite
|
||||
BEAST_EXPECT(it->second.size() == 1);
|
||||
std::shared_ptr<STTx const> witness;
|
||||
for (std::size_t attempt = 0;
|
||||
attempt < ExportLimits::maxRetryLedgers && !witness;
|
||||
attempt < ExportLimits::maxAdmissionWindowLedgers && !witness;
|
||||
++attempt)
|
||||
{
|
||||
env.close();
|
||||
@@ -1160,12 +1160,13 @@ struct Export_test : public beast::unit_test::suite
|
||||
alice.id(),
|
||||
carol.id(),
|
||||
seq + 1,
|
||||
seq + ExportLimits::maxRetryLedgers);
|
||||
seq + ExportLimits::maxAdmissionWindowLedgers);
|
||||
|
||||
Json::Value jv;
|
||||
jv[jss::TransactionType] = jss::Export;
|
||||
jv[jss::Account] = alice.human();
|
||||
jv[jss::LastLedgerSequence] = seq + ExportLimits::maxRetryLedgers;
|
||||
jv[jss::LastLedgerSequence] =
|
||||
seq + ExportLimits::maxAdmissionWindowLedgers;
|
||||
bindExportAuthority(env, jv);
|
||||
jv[sfExportedTxn.jsonName] = innerObj.getJson(JsonOptions::none);
|
||||
|
||||
@@ -1200,7 +1201,7 @@ struct Export_test : public beast::unit_test::suite
|
||||
|
||||
auto const seq = env.current()->seq();
|
||||
auto const ticketSeq = std::uint32_t{1};
|
||||
auto const lls = seq + ExportLimits::maxRetryLedgers;
|
||||
auto const lls = seq + ExportLimits::maxAdmissionWindowLedgers;
|
||||
auto innerObj = buildExportedPayment(
|
||||
alice.id(), carol.id(), seq + 1, lls, ticketSeq);
|
||||
|
||||
@@ -1817,11 +1818,12 @@ struct Export_test : public beast::unit_test::suite
|
||||
alice.id(),
|
||||
carol.id(),
|
||||
seq + 1,
|
||||
seq + ExportLimits::maxRetryLedgers);
|
||||
seq + ExportLimits::maxAdmissionWindowLedgers);
|
||||
Json::Value intent;
|
||||
intent[jss::TransactionType] = jss::Export;
|
||||
intent[jss::Account] = alice.human();
|
||||
intent[jss::LastLedgerSequence] = seq + ExportLimits::maxRetryLedgers;
|
||||
intent[jss::LastLedgerSequence] =
|
||||
seq + ExportLimits::maxAdmissionWindowLedgers;
|
||||
intent[sfExportedTxn.jsonName] = innerObj.getJson(JsonOptions::none);
|
||||
intent[sfExportCommitteeHash.jsonName] = to_string(digest);
|
||||
env(intent, fee(XRP(1)), ter(tecEXPORT_COMMITTEE_UNAVAILABLE));
|
||||
@@ -1847,7 +1849,7 @@ struct Export_test : public beast::unit_test::suite
|
||||
|
||||
auto const seq = env.current()->seq();
|
||||
auto const ticketSeq = std::uint32_t{1};
|
||||
auto const lls = seq + ExportLimits::maxRetryLedgers;
|
||||
auto const lls = seq + ExportLimits::maxAdmissionWindowLedgers;
|
||||
auto innerObj = buildExportedPayment(
|
||||
alice.id(), carol.id(), seq + 1, lls, ticketSeq);
|
||||
auto jt = makeExportJTx(env, alice, innerObj, lls);
|
||||
@@ -1944,13 +1946,14 @@ struct Export_test : public beast::unit_test::suite
|
||||
alice.id(),
|
||||
carol.id(),
|
||||
seq + 1,
|
||||
seq + ExportLimits::maxRetryLedgers,
|
||||
seq + ExportLimits::maxAdmissionWindowLedgers,
|
||||
ticketSeq);
|
||||
|
||||
Json::Value jv;
|
||||
jv[jss::TransactionType] = jss::Export;
|
||||
jv[jss::Account] = alice.human();
|
||||
jv[jss::LastLedgerSequence] = seq + ExportLimits::maxRetryLedgers;
|
||||
jv[jss::LastLedgerSequence] =
|
||||
seq + ExportLimits::maxAdmissionWindowLedgers;
|
||||
jv[sfExportedTxn.jsonName] = innerObj.getJson(JsonOptions::none);
|
||||
bindExportAuthority(env, jv);
|
||||
|
||||
@@ -1994,13 +1997,14 @@ struct Export_test : public beast::unit_test::suite
|
||||
alice.id(),
|
||||
carol.id(),
|
||||
seq + 1,
|
||||
seq + ExportLimits::maxRetryLedgers,
|
||||
seq + ExportLimits::maxAdmissionWindowLedgers,
|
||||
ticketSeq);
|
||||
|
||||
Json::Value jv;
|
||||
jv[jss::TransactionType] = jss::Export;
|
||||
jv[jss::Account] = alice.human();
|
||||
jv[jss::LastLedgerSequence] = seq + ExportLimits::maxRetryLedgers;
|
||||
jv[jss::LastLedgerSequence] =
|
||||
seq + ExportLimits::maxAdmissionWindowLedgers;
|
||||
jv[sfExportedTxn.jsonName] = innerObj.getJson(JsonOptions::none);
|
||||
bindExportAuthority(env, jv);
|
||||
|
||||
@@ -2058,13 +2062,14 @@ struct Export_test : public beast::unit_test::suite
|
||||
alice.id(),
|
||||
carol.id(),
|
||||
seq + 1,
|
||||
seq + ExportLimits::maxRetryLedgers,
|
||||
seq + ExportLimits::maxAdmissionWindowLedgers,
|
||||
ticketSeq);
|
||||
|
||||
Json::Value jv;
|
||||
jv[jss::TransactionType] = jss::Export;
|
||||
jv[jss::Account] = alice.human();
|
||||
jv[jss::LastLedgerSequence] = seq + ExportLimits::maxRetryLedgers;
|
||||
jv[jss::LastLedgerSequence] =
|
||||
seq + ExportLimits::maxAdmissionWindowLedgers;
|
||||
jv[sfExportedTxn.jsonName] = innerObj.getJson(JsonOptions::none);
|
||||
bindExportAuthority(env, jv);
|
||||
|
||||
@@ -2145,13 +2150,14 @@ struct Export_test : public beast::unit_test::suite
|
||||
alice.id(),
|
||||
carol.id(),
|
||||
seq + 1,
|
||||
seq + ExportLimits::maxRetryLedgers,
|
||||
seq + ExportLimits::maxAdmissionWindowLedgers,
|
||||
ticketSeq);
|
||||
|
||||
Json::Value jvExport;
|
||||
jvExport[jss::TransactionType] = jss::Export;
|
||||
jvExport[jss::Account] = alice.human();
|
||||
jvExport[jss::LastLedgerSequence] = seq + ExportLimits::maxRetryLedgers;
|
||||
jvExport[jss::LastLedgerSequence] =
|
||||
seq + ExportLimits::maxAdmissionWindowLedgers;
|
||||
jvExport[sfExportedTxn.jsonName] = innerObj.getJson(JsonOptions::none);
|
||||
auto const committeeRoster = defaultExportCommittee(env);
|
||||
auto const committeeDigest =
|
||||
@@ -2288,13 +2294,14 @@ struct Export_test : public beast::unit_test::suite
|
||||
alice.id(),
|
||||
carol.id(),
|
||||
seq + 1,
|
||||
seq + ExportLimits::maxRetryLedgers,
|
||||
seq + ExportLimits::maxAdmissionWindowLedgers,
|
||||
std::nullopt);
|
||||
|
||||
Json::Value jv;
|
||||
jv[jss::TransactionType] = jss::Export;
|
||||
jv[jss::Account] = alice.human();
|
||||
jv[jss::LastLedgerSequence] = seq + ExportLimits::maxRetryLedgers;
|
||||
jv[jss::LastLedgerSequence] =
|
||||
seq + ExportLimits::maxAdmissionWindowLedgers;
|
||||
jv[sfExportedTxn.jsonName] = innerObj.getJson(JsonOptions::none);
|
||||
bindExportAuthority(env, jv);
|
||||
|
||||
@@ -2326,7 +2333,7 @@ struct Export_test : public beast::unit_test::suite
|
||||
alice.id(),
|
||||
carol.id(),
|
||||
seq + 1,
|
||||
seq + ExportLimits::maxRetryLedgers);
|
||||
seq + ExportLimits::maxAdmissionWindowLedgers);
|
||||
|
||||
Json::Value jv;
|
||||
jv[jss::TransactionType] = jss::Export;
|
||||
@@ -2356,13 +2363,14 @@ struct Export_test : public beast::unit_test::suite
|
||||
alice.id(),
|
||||
carol.id(),
|
||||
seq + 1,
|
||||
seq + ExportLimits::maxRetryLedgers);
|
||||
seq + ExportLimits::maxAdmissionWindowLedgers);
|
||||
innerObj.setFieldVL(sfSigningPubKey, alice.pk().slice());
|
||||
|
||||
Json::Value jv;
|
||||
jv[jss::TransactionType] = jss::Export;
|
||||
jv[jss::Account] = alice.human();
|
||||
jv[jss::LastLedgerSequence] = seq + ExportLimits::maxRetryLedgers;
|
||||
jv[jss::LastLedgerSequence] =
|
||||
seq + ExportLimits::maxAdmissionWindowLedgers;
|
||||
jv[sfExportedTxn.jsonName] = innerObj.getJson(JsonOptions::none);
|
||||
bindExportAuthority(env, jv);
|
||||
|
||||
@@ -2385,7 +2393,7 @@ struct Export_test : public beast::unit_test::suite
|
||||
env.close();
|
||||
|
||||
auto const seq = env.current()->seq();
|
||||
auto const lls = seq + ExportLimits::maxRetryLedgers + 1;
|
||||
auto const lls = seq + ExportLimits::maxAdmissionWindowLedgers + 1;
|
||||
auto innerObj =
|
||||
buildExportedPayment(alice.id(), carol.id(), seq + 1, lls);
|
||||
|
||||
|
||||
@@ -212,7 +212,7 @@ public:
|
||||
BEAST_EXPECT(st.getFieldU32(sfFirstLedgerSequence) == seq + 1);
|
||||
BEAST_EXPECT(
|
||||
st.getFieldU32(sfLastLedgerSequence) ==
|
||||
seq + ExportLimits::maxRetryLedgers);
|
||||
seq + ExportLimits::maxAdmissionWindowLedgers);
|
||||
BEAST_EXPECT(st.isFieldPresent(sfEmitDetails));
|
||||
|
||||
auto const result2 =
|
||||
@@ -243,7 +243,7 @@ public:
|
||||
obj[sfSigningPubKey] = Slice{};
|
||||
obj[sfFirstLedgerSequence] = env.closed()->seq() + 1;
|
||||
obj[sfLastLedgerSequence] =
|
||||
env.closed()->seq() + ExportLimits::maxRetryLedgers;
|
||||
env.closed()->seq() + ExportLimits::maxAdmissionWindowLedgers;
|
||||
obj[sfFee] = env.closed()->fees().base;
|
||||
|
||||
auto& emitDetails = obj.peekFieldObject(sfEmitDetails);
|
||||
@@ -260,7 +260,7 @@ public:
|
||||
obj[sfSigningPubKey] = Slice{};
|
||||
obj[sfFirstLedgerSequence] = env.closed()->seq() + 1;
|
||||
obj[sfLastLedgerSequence] =
|
||||
env.closed()->seq() + ExportLimits::maxRetryLedgers;
|
||||
env.closed()->seq() + ExportLimits::maxAdmissionWindowLedgers;
|
||||
obj[sfFee] = env.closed()->fees().base;
|
||||
STObject hookobj(sfHook);
|
||||
auto& hooks = obj.peekFieldArray(sfHooks);
|
||||
@@ -602,7 +602,7 @@ public:
|
||||
auto const currentSeq = applyCtx.view().info().seq;
|
||||
tx.setFieldU32(
|
||||
sfLastLedgerSequence,
|
||||
currentSeq + ExportLimits::maxRetryLedgers + 1);
|
||||
currentSeq + ExportLimits::maxAdmissionWindowLedgers + 1);
|
||||
auto const result = api.emit(tx.getSerializer().slice());
|
||||
BEAST_EXPECT(result.error() == EMISSION_FAILURE);
|
||||
}
|
||||
|
||||
@@ -169,7 +169,7 @@ public:
|
||||
BEAST_EXPECT(wrapper.getFieldU32(sfFirstLedgerSequence) == 11);
|
||||
BEAST_EXPECT(
|
||||
wrapper.getFieldU32(sfLastLedgerSequence) ==
|
||||
10 + ExportLimits::maxRetryLedgers);
|
||||
10 + ExportLimits::maxAdmissionWindowLedgers);
|
||||
BEAST_EXPECT(wrapper.getFieldAmount(sfFee) == STAmount{12345});
|
||||
BEAST_EXPECT(wrapper.getFieldVL(sfSigningPubKey).empty());
|
||||
BEAST_EXPECT(
|
||||
|
||||
@@ -35,8 +35,9 @@ agreement is needed. The result is explicitly labeled
|
||||
(`EntropyTier = consensus_fallback`, `EntropyCount = 0`) — it is
|
||||
user-influenceable via transaction submission and must never be presented
|
||||
under validator-entropy semantics. Export has no equivalent fallback value:
|
||||
without quorum-aligned verified export signatures, the export must not be
|
||||
treated as complete and must retry or expire under transaction rules.
|
||||
without quorum-aligned verified export signatures, the admitted latch remains
|
||||
pending until a later witness or publication expiry. The source transaction
|
||||
does not retry.
|
||||
|
||||
The fallback/non-fallback decision is itself ledger-defining. A local node may
|
||||
diagnose that progress looks unlikely from its current peer view, but it must
|
||||
@@ -502,6 +503,13 @@ link, and pending-work link. Lack of signatures does not make the source
|
||||
transaction retry. The pending directory is durable scheduling state across
|
||||
rounds and restarts.
|
||||
|
||||
An owner may have at most one live latch for a destination TicketSequence,
|
||||
regardless of `W`. Flagless control and publication expiry retain the latch, so
|
||||
they also retain that one-shot destination-authority slot. Reissuing the same
|
||||
normalized target after terminal erasure creates a new outer transaction and a
|
||||
new `W`; it does not recreate the old latch. Clients must not reissue a target
|
||||
TicketSequence that the destination chain has already consumed.
|
||||
|
||||
Honest signature release is keyed to fully validated history, not to the open
|
||||
ledger or current transaction candidate set. When an exact origin ledger becomes
|
||||
validated, eligible committee members reconstruct the target payload, append
|
||||
|
||||
@@ -132,6 +132,14 @@ the normalized identity-form target, not a signer-subset-dependent final target
|
||||
transaction ID. Any valid qC subset for the exact stamped intent may therefore
|
||||
execute and return without orphaning source state.
|
||||
|
||||
At most one live latch may name a given `(owner, destination TicketSequence)`,
|
||||
even when distinct outer transactions would produce distinct `W` values. A
|
||||
retained canceled or publication-expired latch therefore blocks another
|
||||
issuance for that TicketSequence until terminal erasure. Reissuing the same
|
||||
normalized intent after erasure creates a new outer transaction and new `W`;
|
||||
it never recreates the old latch. A client must not reissue a destination Ticket
|
||||
that has already been consumed.
|
||||
|
||||
Witness and XPOP are independent monotonic facts. Whichever arrives second
|
||||
symmetrically erases the latch and releases reserve. Flagless lifecycle control
|
||||
names exact `W`, unlinks pending work, and retains callback readiness because
|
||||
|
||||
@@ -431,7 +431,7 @@ HookAPI::prepare(Slice const& txBlob) const
|
||||
|
||||
if (!json.isMember(jss::LastLedgerSequence))
|
||||
json[jss::LastLedgerSequence] =
|
||||
Json::Value(seq + ExportLimits::maxRetryLedgers);
|
||||
Json::Value(seq + ExportLimits::maxAdmissionWindowLedgers);
|
||||
|
||||
uint8_t details[512];
|
||||
if (!json.isMember(jss::EmitDetails))
|
||||
@@ -745,12 +745,12 @@ HookAPI::emit(Slice const& txBlob) const
|
||||
return Unexpected(EMISSION_FAILURE);
|
||||
}
|
||||
|
||||
if (tx_lls > ledgerSeq + ExportLimits::maxRetryLedgers)
|
||||
if (tx_lls > ledgerSeq + ExportLimits::maxAdmissionWindowLedgers)
|
||||
{
|
||||
JLOG(j.trace())
|
||||
<< "HookEmit[" << HC_ACC()
|
||||
<< "]: sfLastLedgerSequence cannot be greater than current seq + "
|
||||
<< ExportLimits::maxRetryLedgers;
|
||||
<< ExportLimits::maxAdmissionWindowLedgers;
|
||||
return Unexpected(EMISSION_FAILURE);
|
||||
}
|
||||
|
||||
|
||||
@@ -78,7 +78,7 @@ build(Input const& input)
|
||||
exportObj.setFieldVL(sfSigningPubKey, Blob{});
|
||||
exportObj[sfFirstLedgerSequence] = input.ledgerSeq + 1;
|
||||
exportObj[sfLastLedgerSequence] =
|
||||
input.ledgerSeq + ExportLimits::maxRetryLedgers;
|
||||
input.ledgerSeq + ExportLimits::maxAdmissionWindowLedgers;
|
||||
exportObj[sfFee] = STAmount{0};
|
||||
exportObj.setFieldH256(sfExportCommitteeHash, input.committeeHash);
|
||||
|
||||
|
||||
@@ -138,8 +138,8 @@ Export::preclaim(PreclaimContext const& ctx)
|
||||
!isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
if (auto ter =
|
||||
ExportLedgerOps::validateRetryWindow(ctx.tx, ctx.view.seq(), ctx.j);
|
||||
if (auto ter = ExportLedgerOps::validateAdmissionWindow(
|
||||
ctx.tx, ctx.view.seq(), ctx.j);
|
||||
!isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
|
||||
@@ -720,7 +720,10 @@ checkExportTxnLimit(ReadView const& view, beast::Journal j)
|
||||
}
|
||||
|
||||
inline TER
|
||||
validateRetryWindow(STTx const& stx, LedgerIndex ledgerSeq, beast::Journal j)
|
||||
validateAdmissionWindow(
|
||||
STTx const& stx,
|
||||
LedgerIndex ledgerSeq,
|
||||
beast::Journal j)
|
||||
{
|
||||
if (!stx.isFieldPresent(sfLastLedgerSequence))
|
||||
{
|
||||
@@ -730,14 +733,15 @@ validateRetryWindow(STTx const& stx, LedgerIndex ledgerSeq, beast::Journal j)
|
||||
}
|
||||
|
||||
auto const lls = stx.getFieldU32(sfLastLedgerSequence);
|
||||
auto const maxLLS =
|
||||
static_cast<std::uint64_t>(ledgerSeq) + ExportLimits::maxRetryLedgers;
|
||||
auto const maxLLS = static_cast<std::uint64_t>(ledgerSeq) +
|
||||
ExportLimits::maxAdmissionWindowLedgers;
|
||||
if (lls > maxLLS)
|
||||
{
|
||||
JLOG(j.warn()) << "ExportLedgerOps: export LastLedgerSequence too far "
|
||||
"ahead ledgerSeq="
|
||||
<< ledgerSeq << " lastLedgerSequence=" << lls
|
||||
<< " maxRetryLedgers=" << ExportLimits::maxRetryLedgers;
|
||||
<< " maxAdmissionWindowLedgers="
|
||||
<< ExportLimits::maxAdmissionWindowLedgers;
|
||||
return temMALFORMED;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user