diff --git a/src/ripple/app/misc/NegativeUNLVote.cpp b/src/ripple/app/misc/NegativeUNLVote.cpp index 5c133403b..7c371cc55 100644 --- a/src/ripple/app/misc/NegativeUNLVote.cpp +++ b/src/ripple/app/misc/NegativeUNLVote.cpp @@ -99,6 +99,51 @@ NegativeUNLVote::doVoting( assert(nidToKeyMap.count(n)); addTx(seq, nidToKeyMap[n], ToReEnable, initialSet); } + + // do reporting when enabled + if (prevLedger->rules().enabled(featureXahauGenesis) && scoreTable->size() > 0) + addReportingTx(seq, *scoreTable, nidToKeyMap, initialSet); + } +} + +void +NegativeUNLVote::addReportingTx( + LedgerIndex seq, + hash_map const& scoreTable, + hash_map const& nidToKeyMap, + std::shared_ptr const& initalSet) +{ + std::vector active; + active.reserve(scoreTable.size()); + for (auto const& [n, score]: scoreTable) + { + if (score > 240) + { + active.emplace_back(sfActiveValidator); + active.back().setFieldVL(sfPublicKey, nidToKeyMap.at(n)); + } + } + + STTx repUnlTx(ttUNL_REPORT, [&](auto& obj) + { + obj.setFieldArray(sfActiveValidators, STArray(active, sfActiveValidators)); + obj.setFieldU32(sfLedgerSequence, seq); + }); + + uint256 txID = repUnlTx.getTransactionID(); + Serializer s; + repUnlTx.add(s); + if (!initalSet->addGiveItem( + SHAMapNodeType::tnTRANSACTION_NM, + std::make_shared(txID, s.slice()))) + { + JLOG(j_.warn()) << "R-UNL: ledger seq=" << seq + << ", add ttUNL_REPORT tx failed"; + } + else + { + JLOG(j_.debug()) << "R-UNL: ledger seq=" << seq + << ", add a ttUNL_REPORT Tx with txID: " << txID; } } diff --git a/src/ripple/app/misc/NegativeUNLVote.h b/src/ripple/app/misc/NegativeUNLVote.h index 6ba8b3bf2..ed496d37b 100644 --- a/src/ripple/app/misc/NegativeUNLVote.h +++ b/src/ripple/app/misc/NegativeUNLVote.h @@ -153,6 +153,16 @@ private: NegativeUNLModify modify, std::shared_ptr const& initialSet); + /** + * As above, but make a report object instead of an n-unl + */ + void + addReportingTx( + LedgerIndex seq, + hash_map const& scoreTable, + hash_map const& nidToKeyMap, + std::shared_ptr const& initalSet); + /** * Pick one candidate from a vector of candidates. * diff --git a/src/ripple/app/tx/impl/Change.cpp b/src/ripple/app/tx/impl/Change.cpp index 34e792ee8..9ccc49b54 100644 --- a/src/ripple/app/tx/impl/Change.cpp +++ b/src/ripple/app/tx/impl/Change.cpp @@ -77,6 +77,13 @@ Change::preflight(PreflightContext const& ctx) return temDISABLED; } + if (ctx.tx.getTxnType() == ttUNL_REPORT && + !ctx.rules.enabled(featureXahauGenesis)) + { + JLOG(ctx.j.warn()) << "Change: UNLReport is not enabled."; + return temDISABLED; + } + return tesSUCCESS; } @@ -134,6 +141,7 @@ Change::preclaim(PreclaimContext const& ctx) return tesSUCCESS; case ttAMENDMENT: case ttUNL_MODIFY: + case ttUNL_REPORT: case ttEMIT_FAILURE: return tesSUCCESS; default: @@ -154,12 +162,34 @@ Change::doApply() return applyUNLModify(); case ttEMIT_FAILURE: return applyEmitFailure(); + case ttUNL_REPORT: + return applyUNLReport(); default: assert(0); return tefFAILURE; } } +TER +Change::applyUNLReport() +{ + auto sle = view().peek(keylet::UNLReport()); + + bool const created = !!sle; + + if (created) + sle = std::make_shared(keylet::UNLReport()); + + sle->setFieldArray(sfActiveValidators, ctx_.tx.getFieldArray(sfActiveValidators)); + + if (created) + view().insert(sle); + else + view().update(sle); + + return tesSUCCESS; +} + void Change::preCompute() { diff --git a/src/ripple/app/tx/impl/Change.h b/src/ripple/app/tx/impl/Change.h index 50badf9b1..20c701f5c 100644 --- a/src/ripple/app/tx/impl/Change.h +++ b/src/ripple/app/tx/impl/Change.h @@ -73,6 +73,9 @@ private: TER applyEmitFailure(); + + TER + applyUNLReport(); }; } // namespace ripple diff --git a/src/ripple/app/tx/impl/applySteps.cpp b/src/ripple/app/tx/impl/applySteps.cpp index 169cf0bfd..e544cdc50 100644 --- a/src/ripple/app/tx/impl/applySteps.cpp +++ b/src/ripple/app/tx/impl/applySteps.cpp @@ -141,6 +141,7 @@ invoke_preflight(PreflightContext const& ctx) case ttAMENDMENT: case ttFEE: case ttUNL_MODIFY: + case ttUNL_REPORT: case ttEMIT_FAILURE: return invoke_preflight_helper(ctx); case ttHOOK_SET: @@ -264,6 +265,7 @@ invoke_preclaim(PreclaimContext const& ctx) case ttAMENDMENT: case ttFEE: case ttUNL_MODIFY: + case ttUNL_REPORT: case ttEMIT_FAILURE: return invoke_preclaim(ctx); case ttNFTOKEN_MINT: @@ -344,6 +346,7 @@ invoke_calculateBaseFee(ReadView const& view, STTx const& tx) case ttAMENDMENT: case ttFEE: case ttUNL_MODIFY: + case ttUNL_REPORT: case ttEMIT_FAILURE: return Change::calculateBaseFee(view, tx); case ttNFTOKEN_MINT: @@ -503,7 +506,8 @@ invoke_apply(ApplyContext& ctx) } case ttAMENDMENT: case ttFEE: - case ttUNL_MODIFY: + case ttUNL_MODIFY: + case ttUNL_REPORT: case ttEMIT_FAILURE: { Change p(ctx); return p(); diff --git a/src/ripple/protocol/Indexes.h b/src/ripple/protocol/Indexes.h index bbf271bc1..c73aef8f3 100644 --- a/src/ripple/protocol/Indexes.h +++ b/src/ripple/protocol/Indexes.h @@ -111,6 +111,9 @@ fees() noexcept; Keylet const& negativeUNL() noexcept; +Keylet const& +UNLReport() noexcept; + /** The beginning of an order book */ struct book_t { diff --git a/src/ripple/protocol/LedgerFormats.h b/src/ripple/protocol/LedgerFormats.h index 2f4afebd9..87b6c0f1e 100644 --- a/src/ripple/protocol/LedgerFormats.h +++ b/src/ripple/protocol/LedgerFormats.h @@ -172,6 +172,13 @@ enum LedgerEntryType : std::uint16_t */ ltURI_TOKEN = 0x0055, + /** A ledger object that reports on the active dUNL validators + * that were validating for more than 240 of the last 256 ledgers + * + * \sa keylet::UNLReport + */ + ltUNL_REPORT = 0x0052, + //--------------------------------------------------------------------------- /** A special type, matching any ledger entry type. diff --git a/src/ripple/protocol/SField.h b/src/ripple/protocol/SField.h index 1243f239e..85eb36328 100644 --- a/src/ripple/protocol/SField.h +++ b/src/ripple/protocol/SField.h @@ -581,6 +581,7 @@ extern SField const sfHookExecution; extern SField const sfHookDefinition; extern SField const sfHookParameter; extern SField const sfHookGrant; +extern SField const sfActiveValidator; // array of objects (common) // ARRAY/1 is reserved for end of array @@ -605,6 +606,7 @@ extern SField const sfHookParameters; extern SField const sfHooks; extern SField const sfHookGrants; extern SField const sfGenesisMints; +extern SField const sfActiveValidators; //------------------------------------------------------------------------------ diff --git a/src/ripple/protocol/TxFormats.h b/src/ripple/protocol/TxFormats.h index 84bc3915f..7999ee5b4 100644 --- a/src/ripple/protocol/TxFormats.h +++ b/src/ripple/protocol/TxFormats.h @@ -180,6 +180,7 @@ enum TxType : std::uint16_t */ ttUNL_MODIFY = 102, ttEMIT_FAILURE = 103, + ttUNL_REPORT = 104, }; // clang-format on diff --git a/src/ripple/protocol/impl/Indexes.cpp b/src/ripple/protocol/impl/Indexes.cpp index 644132bfd..42a34e100 100644 --- a/src/ripple/protocol/impl/Indexes.cpp +++ b/src/ripple/protocol/impl/Indexes.cpp @@ -71,6 +71,7 @@ enum class LedgerNameSpace : std::uint16_t { NFTOKEN_SELL_OFFERS = 'i', URI_TOKEN = 'U', IMPORT_VLSEQ = 'I', + UNL_REPORT = 'R', // No longer used or supported. Left here to reserve the space // to avoid accidental reuse. @@ -236,6 +237,14 @@ negativeUNL() noexcept return ret; } +Keylet const& +UNLReport() noexcept +{ + static Keylet const ret{ + ltUNL_REPORT, indexHash(LedgerNameSpace::UNL_REPORT)}; + return ret; +} + Keylet book_t::operator()(Book const& b) const { diff --git a/src/ripple/protocol/impl/LedgerFormats.cpp b/src/ripple/protocol/impl/LedgerFormats.cpp index 0a830299d..5fec7d797 100644 --- a/src/ripple/protocol/impl/LedgerFormats.cpp +++ b/src/ripple/protocol/impl/LedgerFormats.cpp @@ -298,6 +298,13 @@ LedgerFormats::LedgerFormats() }, commonFields); + add(jss::UNLReport, + ltUNL_REPORT, + { + {sfActiveValidators, soeREQUIRED}, + }, + commonFields); + add(jss::EmittedTxn, ltEMITTED_TXN, { diff --git a/src/ripple/protocol/impl/SField.cpp b/src/ripple/protocol/impl/SField.cpp index 33c6a0546..dde05c710 100644 --- a/src/ripple/protocol/impl/SField.cpp +++ b/src/ripple/protocol/impl/SField.cpp @@ -337,6 +337,7 @@ CONSTRUCT_UNTYPED_SFIELD(sfHookDefinition, "HookDefinition", OBJECT, CONSTRUCT_UNTYPED_SFIELD(sfHookParameter, "HookParameter", OBJECT, 23); CONSTRUCT_UNTYPED_SFIELD(sfHookGrant, "HookGrant", OBJECT, 24); CONSTRUCT_UNTYPED_SFIELD(sfGenesisMint, "GenesisMint", OBJECT, 96); +CONSTRUCT_UNTYPED_SFIELD(sfActiveValidator, "ActiveValidator", OBJECT, 95); // array of objects // ARRAY/1 is reserved for end of array @@ -357,6 +358,7 @@ CONSTRUCT_UNTYPED_SFIELD(sfDisabledValidators, "DisabledValidators", ARRAY, CONSTRUCT_UNTYPED_SFIELD(sfHookExecutions, "HookExecutions", ARRAY, 18); CONSTRUCT_UNTYPED_SFIELD(sfHookParameters, "HookParameters", ARRAY, 19); CONSTRUCT_UNTYPED_SFIELD(sfHookGrants, "HookGrants", ARRAY, 20); +CONSTRUCT_UNTYPED_SFIELD(sfActiveValidators, "ActiveValidators", ARRAY, 95); CONSTRUCT_UNTYPED_SFIELD(sfGenesisMints, "GenesisMints", ARRAY, 96); // clang-format on diff --git a/src/ripple/protocol/impl/STTx.cpp b/src/ripple/protocol/impl/STTx.cpp index e00cfa6a3..2bee74c05 100644 --- a/src/ripple/protocol/impl/STTx.cpp +++ b/src/ripple/protocol/impl/STTx.cpp @@ -564,7 +564,7 @@ isPseudoTx(STObject const& tx) return false; auto tt = safe_cast(*t); - return tt == ttAMENDMENT || tt == ttFEE || tt == ttUNL_MODIFY || tt == ttEMIT_FAILURE; + return tt == ttAMENDMENT || tt == ttFEE || tt == ttUNL_MODIFY || tt == ttEMIT_FAILURE || tt == ttUNL_REPORT; } } // namespace ripple diff --git a/src/ripple/protocol/impl/TxFormats.cpp b/src/ripple/protocol/impl/TxFormats.cpp index 7bd50f9db..3c3633d12 100644 --- a/src/ripple/protocol/impl/TxFormats.cpp +++ b/src/ripple/protocol/impl/TxFormats.cpp @@ -192,6 +192,14 @@ TxFormats::TxFormats() }, commonFields); + add(jss::UNLReport, + ttUNL_REPORT, + { + {sfLedgerSequence, soeREQUIRED}, + {sfActiveValidators, soeREQUIRED}, + }, + commonFields); + add(jss::TicketCreate, ttTICKET_CREATE, { diff --git a/src/ripple/protocol/jss.h b/src/ripple/protocol/jss.h index 2ea2ea089..3c5e36d80 100644 --- a/src/ripple/protocol/jss.h +++ b/src/ripple/protocol/jss.h @@ -117,6 +117,7 @@ JSS(RippleState); // ledger type. JSS(SLE_hit_rate); // out: GetCounts. JSS(SetFee); // transaction type. JSS(UNLModify); // transaction type. +JSS(UNLReport); // transaction type. JSS(SettleDelay); // in: TransactionSign JSS(SendMax); // in: TransactionSign JSS(Sequence); // in/out: TransactionSign; field.