Merge branch 'pratik/otel-sync-diagnostics' into pratik/otel-sync-diagnostics-freshen-fix

This commit is contained in:
Pratik Mankawde
2026-09-23 18:51:01 +01:00
128 changed files with 6515 additions and 2005 deletions

View File

@@ -21,6 +21,10 @@ namespace beast::insight {
* as desired (counters, events, gauges, meters, and an optional hook)
* using the interface.
*
* Create them there, before the application calls onCollectionReady().
* That call is when a collector starts polling and arms its observable
* instruments, and it runs once.
*
* @see Counter, Event, Gauge, Hook, Meter
* @see NullCollector, StatsDCollector
*/
@@ -140,6 +144,9 @@ public:
/**
* Create a gauge with the specified name.
*
* Create it before onCollectionReady(); a gauge made after that is
* never armed, so it is never exported.
* @see Gauge
*/
/** @{ */

View File

@@ -59,7 +59,9 @@
* | Attrs: proposers, round_time_ms, quorum
* | |
* | +-- consensus.accept.apply [jtACCEPT thread, child of accept]
* | Created: Adaptor::doAccept()
* | Created: Adaptor::doAccept(), scoped: the txq spans doAccept
* | goes on to create nest under it; the tx apply-stage
* | spans are hash-derived roots and do not
* | Attrs: ledger_seq, close_time_ripple_epoch_s, close_time_correct,
* | close_resolution_ms, consensus_state, proposing, round_time_ms,
* | parent_close_time_ripple_epoch_s, close_time_self_ripple_epoch_s,
@@ -71,7 +73,7 @@
* | Attrs: ledger_seq, proposing
* |
* +-- consensus.mode_change [main thread]
* Created: Adaptor::onModeChange()
* Created: Adaptor::onModeChange(), only when the mode moves
* Attrs: mode_old, mode_new
*
* Standalone spans (no parent, created per-message in overlay):

View File

@@ -347,6 +347,30 @@ isAuditorMirrorCurrent(SLE const& issuance, SLE const& mptoken);
[[nodiscard]] bool
areMirrorsCurrent(SLE const& issuance, SLE const& mptoken);
/**
* @brief Set the holder's issuer mirror epoch to match the issuance's current issuer key epoch.
*
* Call this after writing the issuer mirror ciphertext under the issuance's
* currently registered issuer key, so that the mirror reads as current afterwards.
*
* @param issuance The MPTokenIssuance ledger object.
* @param mptoken The holder's MPToken ledger entry to update.
*/
void
setIssuerMirrorEpoch(SLE const& issuance, SLE& mptoken);
/**
* @brief Set the holder's auditor mirror epoch to match the issuance's current auditor key epoch.
*
* Call this after writing the auditor mirror ciphertext under the issuance's
* currently registered auditor key. Does nothing when the holder has no auditor mirror.
*
* @param issuance The MPTokenIssuance ledger object.
* @param mptoken The holder's MPToken ledger entry to update.
*/
void
setAuditorMirrorEpoch(SLE const& issuance, SLE& mptoken);
/**
* @brief Set the holder's MPToken mirror epochs to match the issuance's current key epochs.
*

View File

@@ -540,6 +540,11 @@ constexpr std::size_t kEcConvertBackProofLength =
*/
constexpr std::size_t kEcClawbackProofLength = SECP256K1_COMPACT_CLAWBACK_PROOF_SIZE;
/**
* Length of compact equality proof.
*/
constexpr std::size_t kEcEqualityProofLength = 128;
/**
* Extra base fee multiplier charged to confidential MPT transactions.
*/

View File

@@ -1134,6 +1134,19 @@ TRANSACTION(ttSPONSORSHIP_SET, 91, SponsorshipSet,
{sfRemainingOwnerCountDelta, SoeOptional},
}))
#if TRANSACTION_INCLUDE
# include <xrpl/tx/transactors/token/ConfidentialMPTMirrorUpdate.h>
#endif
TRANSACTION(ttCONFIDENTIAL_MPT_MIRROR_UPDATE, 92, ConfidentialMPTMirrorUpdate,
({.delegable = Delegation::Delegable, .amendment = featureConfidentialMPTKeyRotation}),
({
{sfMPTokenIssuanceID, SoeRequired},
{sfHolder, SoeOptional},
{sfIssuerEncryptedAmount, SoeOptional},
{sfAuditorEncryptedAmount, SoeOptional},
{sfZKProof, SoeRequired},
}))
/** This system-generated transaction type is used to update the status of the various amendments.
For details, see: https://xrpl.org/amendments.html

View File

@@ -0,0 +1,266 @@
// This file is auto-generated. Do not edit.
#pragma once
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/STParsedJSON.h>
#include <xrpl/protocol/jss.h>
#include <xrpl/protocol_autogen/TransactionBase.h>
#include <xrpl/protocol_autogen/TransactionBuilderBase.h>
#include <xrpl/json/json_value.h>
#include <stdexcept>
#include <optional>
namespace xrpl::transactions {
class ConfidentialMPTMirrorUpdateBuilder;
/**
* @brief Transaction: ConfidentialMPTMirrorUpdate
*
* Type: ttCONFIDENTIAL_MPT_MIRROR_UPDATE (92)
* Delegable: Delegation::Delegable
* Amendment: featureConfidentialMPTKeyRotation
* Privileges: Privilege::NoPriv
*
* Immutable wrapper around STTx providing type-safe field access.
* Use ConfidentialMPTMirrorUpdateBuilder to construct new transactions.
*/
class ConfidentialMPTMirrorUpdate : public TransactionBase
{
public:
static constexpr xrpl::TxType txType = ttCONFIDENTIAL_MPT_MIRROR_UPDATE;
/**
* @brief Construct a ConfidentialMPTMirrorUpdate transaction wrapper from an existing STTx object.
* @throws std::runtime_error if the transaction type doesn't match.
*/
explicit ConfidentialMPTMirrorUpdate(std::shared_ptr<STTx const> tx)
: TransactionBase(std::move(tx))
{
// Verify transaction type
if (tx_->getTxnType() != txType)
{
throw std::runtime_error("Invalid transaction type for ConfidentialMPTMirrorUpdate");
}
}
// Transaction-specific field getters
/**
* @brief Get sfMPTokenIssuanceID (SoeRequired)
* @return The field value.
*/
[[nodiscard]]
SF_UINT192::type::value_type
getMPTokenIssuanceID() const
{
return this->tx_->at(sfMPTokenIssuanceID);
}
/**
* @brief Get sfHolder (SoeOptional)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_ACCOUNT::type::value_type>
getHolder() const
{
if (hasHolder())
{
return this->tx_->at(sfHolder);
}
return std::nullopt;
}
/**
* @brief Check if sfHolder is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasHolder() const
{
return this->tx_->isFieldPresent(sfHolder);
}
/**
* @brief Get sfIssuerEncryptedAmount (SoeOptional)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_VL::type::value_type>
getIssuerEncryptedAmount() const
{
if (hasIssuerEncryptedAmount())
{
return this->tx_->at(sfIssuerEncryptedAmount);
}
return std::nullopt;
}
/**
* @brief Check if sfIssuerEncryptedAmount is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasIssuerEncryptedAmount() const
{
return this->tx_->isFieldPresent(sfIssuerEncryptedAmount);
}
/**
* @brief Get sfAuditorEncryptedAmount (SoeOptional)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_VL::type::value_type>
getAuditorEncryptedAmount() const
{
if (hasAuditorEncryptedAmount())
{
return this->tx_->at(sfAuditorEncryptedAmount);
}
return std::nullopt;
}
/**
* @brief Check if sfAuditorEncryptedAmount is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasAuditorEncryptedAmount() const
{
return this->tx_->isFieldPresent(sfAuditorEncryptedAmount);
}
/**
* @brief Get sfZKProof (SoeRequired)
* @return The field value.
*/
[[nodiscard]]
SF_VL::type::value_type
getZKProof() const
{
return this->tx_->at(sfZKProof);
}
};
/**
* @brief Builder for ConfidentialMPTMirrorUpdate transactions.
*
* Provides a fluent interface for constructing transactions with method chaining.
* Uses STObject internally for flexible transaction construction.
* Inherits common field setters from TransactionBuilderBase.
*/
class ConfidentialMPTMirrorUpdateBuilder : public TransactionBuilderBase<ConfidentialMPTMirrorUpdateBuilder>
{
public:
/**
* @brief Construct a new ConfidentialMPTMirrorUpdateBuilder with required fields.
* @param account The account initiating the transaction.
* @param mPTokenIssuanceID The sfMPTokenIssuanceID field value.
* @param zKProof The sfZKProof field value.
* @param sequence Optional sequence number for the transaction.
* @param fee Optional fee for the transaction.
*/
ConfidentialMPTMirrorUpdateBuilder(SF_ACCOUNT::type::value_type account,
std::decay_t<typename SF_UINT192::type::value_type> const& mPTokenIssuanceID, std::decay_t<typename SF_VL::type::value_type> const& zKProof, std::optional<SF_UINT32::type::value_type> sequence = std::nullopt,
std::optional<SF_AMOUNT::type::value_type> fee = std::nullopt
)
: TransactionBuilderBase<ConfidentialMPTMirrorUpdateBuilder>(ttCONFIDENTIAL_MPT_MIRROR_UPDATE, account, sequence, fee)
{
setMPTokenIssuanceID(mPTokenIssuanceID);
setZKProof(zKProof);
}
/**
* @brief Construct a ConfidentialMPTMirrorUpdateBuilder from an existing STTx object.
* @param tx The existing transaction to copy from.
* @throws std::runtime_error if the transaction type doesn't match.
*/
ConfidentialMPTMirrorUpdateBuilder(std::shared_ptr<STTx const> tx)
{
if (tx->getTxnType() != ttCONFIDENTIAL_MPT_MIRROR_UPDATE)
{
throw std::runtime_error("Invalid transaction type for ConfidentialMPTMirrorUpdateBuilder");
}
object_ = *tx;
}
/**
* @brief Transaction-specific field setters
*/
/**
* @brief Set sfMPTokenIssuanceID (SoeRequired)
* @return Reference to this builder for method chaining.
*/
ConfidentialMPTMirrorUpdateBuilder&
setMPTokenIssuanceID(std::decay_t<typename SF_UINT192::type::value_type> const& value)
{
object_[sfMPTokenIssuanceID] = value;
return *this;
}
/**
* @brief Set sfHolder (SoeOptional)
* @return Reference to this builder for method chaining.
*/
ConfidentialMPTMirrorUpdateBuilder&
setHolder(std::decay_t<typename SF_ACCOUNT::type::value_type> const& value)
{
object_[sfHolder] = value;
return *this;
}
/**
* @brief Set sfIssuerEncryptedAmount (SoeOptional)
* @return Reference to this builder for method chaining.
*/
ConfidentialMPTMirrorUpdateBuilder&
setIssuerEncryptedAmount(std::decay_t<typename SF_VL::type::value_type> const& value)
{
object_[sfIssuerEncryptedAmount] = value;
return *this;
}
/**
* @brief Set sfAuditorEncryptedAmount (SoeOptional)
* @return Reference to this builder for method chaining.
*/
ConfidentialMPTMirrorUpdateBuilder&
setAuditorEncryptedAmount(std::decay_t<typename SF_VL::type::value_type> const& value)
{
object_[sfAuditorEncryptedAmount] = value;
return *this;
}
/**
* @brief Set sfZKProof (SoeRequired)
* @return Reference to this builder for method chaining.
*/
ConfidentialMPTMirrorUpdateBuilder&
setZKProof(std::decay_t<typename SF_VL::type::value_type> const& value)
{
object_[sfZKProof] = value;
return *this;
}
/**
* @brief Build and return the ConfidentialMPTMirrorUpdate wrapper.
* @param publicKey The public key for signing.
* @param secretKey The secret key for signing.
* @return The constructed transaction wrapper.
*/
ConfidentialMPTMirrorUpdate
build(PublicKey const& publicKey, SecretKey const& secretKey)
{
sign(publicKey, secretKey);
return ConfidentialMPTMirrorUpdate{std::make_shared<STTx>(std::move(object_))};
}
};
} // namespace xrpl::transactions

View File

@@ -56,8 +56,7 @@
* // Edge case -- an expensive value still needs a block guard,
* // because arguments are evaluated even when the method is a no-op.
* if constexpr (telemetry::kEnabled)
* span.setAttribute(
* pathfind_span::attr::sourceAccount, redactAccount(account));
* span.setAttribute(tx_span::attr::txHash, to_string(txId));
* @endcode
*/

View File

@@ -1,34 +1,30 @@
#pragma once
/**
* Account-address redaction for telemetry span attributes.
* Account-address redaction helper for telemetry span attributes.
*
* Path-finding RPC handlers would otherwise emit the caller's raw
* account addresses as span attributes. To keep plaintext addresses out
* of the telemetry backend, they are hashed at the point of emission.
* This header exposes a single pure helper that turns an address into a
* short, stable, obfuscated token.
* A single pure helper that turns a string into a short, stable,
* obfuscated token, for a span attribute whose value should not be stored
* in the clear and is hard to guess.
*
* Data flow:
*
* handler -> redactAccount(addr) -> span attribute -> OTLP export
* Not applied to any span today. Account addresses are public ledger
* identifiers, so the path-finding spans emit them raw (see
* PathFindSpanNames.h) and no collector processor hashes them. Use this
* helper only for a value that is genuinely private, and document the
* reason at the attribute constant.
*
* The returned token is the first 16 hex characters (lowercase) of the
* SHA-512Half digest of the address. It is deterministic (same address
* always maps to the same token) so operators can still correlate spans
* for a given account across nodes and restarts.
* SHA-512Half digest of the input. It is deterministic (same input
* always maps to the same token) so spans for one value still correlate
* across nodes and restarts.
*
* The hash is unsalted, so it is obfuscation, not a secrecy guarantee:
* XRP account addresses are a public, enumerable set, so a determined
* observer with the telemetry stream could rebuild the address->token
* mapping. The goal here is to keep plaintext addresses out of traces
* and dashboards, not to defend against a precomputation attack. A salt
* is intentionally omitted because it would break cross-node/restart
* correlation, which is the reason for hashing rather than dropping.
*
* A second, independent hashing layer runs in the OpenTelemetry
* Collector (an `attributes/hash` processor) as defense-in-depth for
* any node that emits a raw value.
* The hash is unsalted, so it is obfuscation, not a secrecy guarantee.
* It hides a value only when that value is hard to guess: for an input
* drawn from a small or enumerable set, such as an account address, an
* observer can rebuild the value->token mapping by lookup, which is why
* account addresses are emitted raw instead. A salt is intentionally
* omitted because it would break cross-node/restart correlation, which is
* the reason for hashing rather than dropping.
*
* @note This function is pure and reentrant: it holds no global state,
* performs no I/O, and is safe to call concurrently from any thread.
@@ -38,8 +34,7 @@
* #include <xrpl/telemetry/Redaction.h>
* using namespace xrpl::telemetry;
*
* span.setAttribute(
* pathfind_span::attr::sourceAccount, redactAccount(src.asString()));
* auto const token = redactAccount(value); // 16 lowercase hex chars
* @endcode
*
* Edge case (empty input yields empty output):
@@ -54,9 +49,10 @@
namespace xrpl::telemetry {
/**
* Hash an account address into a short, stable, obfuscated token.
* Hash a value into a short, stable, obfuscated token.
*
* @param addr The account address to redact (e.g. an r-address).
* @param addr The value to redact. Named for its original use on account
* addresses; any string can be passed.
* @return The first 16 lowercase hex characters of sha512Half(addr),
* or an empty string when @p addr is empty.
*/

View File

@@ -574,7 +574,14 @@ public:
setAttribute(std::string_view key, std::string_view value) noexcept;
/**
* Set a string attribute (C-string overload). No-op on a null guard.
* Set a string attribute from a C string. No-op on a null guard.
*
* @param key Attribute key.
* @param value Null-terminated text. A null pointer records nothing, since
* an empty value is already a meaningful value here.
* @note This overload is required, not a convenience. Without it a string
* literal binds to the bool overload, because pointer-to-bool is a standard
* conversion and beats the std::string_view one.
*/
void
setAttribute(std::string_view key, char const* value) noexcept;
@@ -875,7 +882,14 @@ public:
setAttribute(std::string_view key, std::string_view value) noexcept;
/**
* Set a string attribute (C-string overload). No-op on a null guard.
* Set a string attribute from a C string. No-op on a null guard.
*
* @param key Attribute key.
* @param value Null-terminated text. A null pointer records nothing, since
* an empty value is already a meaningful value here.
* @note This overload is required, not a convenience. Without it a string
* literal binds to the bool overload, because pointer-to-bool is a standard
* conversion and beats the std::string_view one.
*/
void
setAttribute(std::string_view key, char const* value) noexcept;
@@ -918,6 +932,15 @@ public:
void
addEvent(std::string_view name) noexcept;
/**
* Add a named event with key-value attributes to the span's timeline.
* No-op on a null guard.
* @param name Event name.
* @param attrs Attribute pairs (all string_view for simplicity).
*/
void
addEvent(std::string_view name, std::initializer_list<EventAttribute> attrs) noexcept;
/**
* Record an exception as a span event and mark status as error.
* No-op on a null guard.
@@ -1355,6 +1378,10 @@ public:
{
}
void
addEvent(std::string_view, std::initializer_list<EventAttribute>) noexcept
{
}
void
recordException(std::exception const&) noexcept
{
}

View File

@@ -168,6 +168,16 @@ inline constexpr auto kDefaultMetricExportInterval = std::chrono::milliseconds{1
*/
inline constexpr auto kDefaultMetricExportTimeout = std::chrono::milliseconds{500};
/**
* Default OTLP/HTTP URL for metrics, signal path included.
*
* The collector's standard port on the same host. Declared here so the Setup
* member, the config parser's default and the collector's startup log all name
* one string. Outside the telemetry #ifdef, because the config parser reads it
* in every build.
*/
inline constexpr char const* kDefaultMetricsEndpoint = "http://localhost:4318/v1/metrics";
/**
* How a consensus round span picks its trace id.
*
@@ -316,7 +326,7 @@ public:
* point the two signals at different collectors, or at one whose OTLP
* paths are not the defaults.
*/
std::string metricsEndpoint = "http://localhost:4318/v1/metrics";
std::string metricsEndpoint = kDefaultMetricsEndpoint;
/**
* Whether to use TLS for the exporter connection.

View File

@@ -0,0 +1,172 @@
#pragma once
/**
* Span attribute keys for the account-typed fields of a transaction.
*
* A transaction names one or more accounts: the sender in `Account`, and
* depending on the type a `Destination`, `Owner`, `Issuer`, `Holder` and so
* on. The tx.process span emits every one it finds as its own attribute, so
* an account can be searched for in traces whatever role it played. An
* account address is a public ledger identifier, so each is emitted as the
* raw r-address and never hashed.
*
* One key per protocol field: `tx_` followed by the field's JSON name in
* lower snake case. The full table is the initializer in
* src/libxrpl/telemetry/TxAccountSpanNames.cpp; the common ones are
*
* STTx field span attribute key
* ----------------- ------------------
* Account tx_account
* Destination tx_destination
* Owner tx_owner
* Issuer tx_issuer
* RegularKey tx_regular_key
* NFTokenMinter tx_nftoken_minter
*
* Only fields that some transaction format carries at top level have a key.
* Account-typed fields that appear only in ledger entries or inner objects
* (LowSponsor, LockingChainDoor, ...) map to nullopt. A library test walks
* TxFormats and fails when a format gains an account field with no key.
*
* Why this header lives in libxrpl rather than beside TxSpanNames.h: the
* mapping is keyed by protocol fields and its completeness is checked from
* TxFormats, which a library test can reach and a daemon header cannot.
*
* Data flow:
*
* NetworkOPs::processTransaction (src/xrpld)
* │ for each top-level field with getSType() == STI_ACCOUNT
* ▼
* accountFieldAttributeKey(field.getFName()) (this header)
* │ the key, or nullopt for a field with no key
* ▼
* span->setAttribute(key, field.getText())
*
* @code
* // Primary use: emit every account the transaction names. An empty
* // account field is skipped so it is not rendered as the zero address.
* for (auto const& field : stx)
* {
* if (field.getSType() != STI_ACCOUNT || field.isDefault())
* continue;
* if (auto const key = telemetry::accountFieldAttributeKey(field.getFName()))
* span.setAttribute(*key, toBase58(stx.getAccountID(field.getFName())));
* }
* @endcode
*
* @code
* // Edge case: a field that is not a top-level transaction account has
* // no key, so a caller must test the optional before using it.
* accountFieldAttributeKey(sfFee); // == std::nullopt
* accountFieldAttributeKey(sfLowSponsor); // == std::nullopt
* @endcode
*
* @note Only top-level fields are covered. Accounts nested in Signers, in a
* Batch's inner transactions, or as the issuer inside an Amount are not
* emitted.
* @note accountFieldAttributeKey() is thread-safe. Its table is built once
* on first use and is read-only afterwards.
*/
#include <xrpl/telemetry/SpanNames.h>
#include <optional>
#include <string_view>
namespace xrpl {
class SField;
} // namespace xrpl
namespace xrpl::telemetry {
namespace tx_account_span::attr {
/**
* "tx_account" — the sending account (`Account`). Every transaction has one.
*/
inline constexpr auto account = makeStr("tx_account");
/**
* "tx_destination" — the receiving account (`Destination`).
*/
inline constexpr auto destination = makeStr("tx_destination");
/**
* "tx_owner" — the owner of the object acted on (`Owner`).
*/
inline constexpr auto owner = makeStr("tx_owner");
/**
* "tx_issuer" — the issuer named by the transaction (`Issuer`).
*/
inline constexpr auto issuer = makeStr("tx_issuer");
/**
* "tx_authorize" — the account being authorised (`Authorize`).
*/
inline constexpr auto authorize = makeStr("tx_authorize");
/**
* "tx_unauthorize" — the account whose authorisation is removed (`Unauthorize`).
*/
inline constexpr auto unauthorize = makeStr("tx_unauthorize");
/**
* "tx_regular_key" — the regular key being set (`RegularKey`).
*/
inline constexpr auto regularKey = makeStr("tx_regular_key");
/**
* "tx_nftoken_minter" — the authorised NFToken minter (`NFTokenMinter`).
*/
inline constexpr auto nftokenMinter = makeStr("tx_nftoken_minter");
/**
* "tx_holder" — the token holder acted on (`Holder`).
*/
inline constexpr auto holder = makeStr("tx_holder");
/**
* "tx_delegate" — the delegate signing on the sender's behalf (`Delegate`).
*/
inline constexpr auto delegate = makeStr("tx_delegate");
/**
* "tx_sponsor" — the account paying the fee or reserve (`Sponsor`).
*/
inline constexpr auto sponsor = makeStr("tx_sponsor");
/**
* "tx_sponsee" — the account being sponsored (`Sponsee`).
*/
inline constexpr auto sponsee = makeStr("tx_sponsee");
/**
* "tx_counterparty" — the other party to a loan (`Counterparty`).
*/
inline constexpr auto counterparty = makeStr("tx_counterparty");
/**
* "tx_counterparty_sponsor" — the counterparty's sponsor (`CounterpartySponsor`).
*/
inline constexpr auto counterpartySponsor = makeStr("tx_counterparty_sponsor");
/**
* "tx_subject" — the subject of a credential (`Subject`).
*/
inline constexpr auto subject = makeStr("tx_subject");
/**
* "tx_other_chain_source" — the source account on the other chain (`OtherChainSource`).
*/
inline constexpr auto otherChainSource = makeStr("tx_other_chain_source");
/**
* "tx_other_chain_destination" — destination on the other chain (`OtherChainDestination`).
*/
inline constexpr auto otherChainDestination = makeStr("tx_other_chain_destination");
/**
* "tx_attestation_signer_account" — the attestation signer (`AttestationSignerAccount`).
*/
inline constexpr auto attestationSignerAccount = makeStr("tx_attestation_signer_account");
/**
* "tx_attestation_reward_account" — attestation reward account (`AttestationRewardAccount`).
*/
inline constexpr auto attestationRewardAccount = makeStr("tx_attestation_reward_account");
} // namespace tx_account_span::attr
/**
* Look up the span attribute key for an account-typed transaction field.
*
* @param field The protocol field, as returned by STBase::getFName().
* @return The `tx_*` key for a top-level transaction account field, or
* nullopt when the field is not account-typed or is carried only by ledger
* entries and inner objects.
*/
[[nodiscard]] std::optional<std::string_view>
accountFieldAttributeKey(SField const& field);
} // namespace xrpl::telemetry

View File

@@ -0,0 +1,98 @@
#pragma once
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/XRPAmount.h>
#include <xrpl/tx/ApplyContext.h>
#include <xrpl/tx/Transactor.h>
namespace xrpl {
/**
* @brief Updates the encrypted mirror balances of a Confidential MPToken.
*
* @details
* This transaction updates a single holder's mirrored confidential balances
* (`sfIssuerEncryptedBalance` and/or `sfAuditorEncryptedBalance`) with the latest
* ElGamal public keys defined on the `MPTokenIssuance`.
*
* It supports both issuer and holder self-migration modes, each mode supports multiple flows:
* - Issuer mode: Submitted by the issuer.
* 1. Issuer Key Rotation Migration: Re-encrypts the
* holder's `sfIssuerEncryptedBalance` under the issuer's new ElGamal public key.
*
* 2. Auditor Key Rotation Migration: Re-encrypts the
* holder's `sfAuditorEncryptedBalance` under the auditor's new ElGamal public key.
*
* 3. Simultaneous Rotation Migration: Updates both the issuer
* and auditor encrypted balances in a single transaction to optimize network throughput.
*
* 4. Auditor Late-Registration Migration: When the issuer ElGamal
* public key is already registered on the `MPTokenIssuance` object, the issuer can
* register an auditor key at a later time through `MPTokenIssuanceSet`. Then the issuer uses this
* flow to set the holder's initial `sfAuditorEncryptedBalance` on `MPToken` object.
*
* - Holder self-migration mode: Submitted by the holder. The holder decrypts their own
* `sfConfidentialBalanceSpending` with holder's private key to recover the balance and
* re-encrypts it under the relevant new ElGamal public key(s). This mode is always
* available to the holder and is not conditioned on the issuer being unable to migrate
* them: the ledger cannot verify whether an issuer has really lost its private key. That
* loss is only the expected motivation, since an issuer that still holds its key can
* migrate holders itself in issuer mode.
* @note All holder migration flows strictly require the holder's
* `sfConfidentialBalanceInbox` to be canonically zero; the holder must run
* `ConfidentialMPTMergeInbox` first so the spending balance reflects the
* full balance.
*
* 5. Holder Issuer-Mirror Migration: Re-encrypts the holder's
* `sfIssuerEncryptedBalance` under the issuer's new ElGamal public key.
*
* 6. Holder Auditor-Mirror Migration: Re-encrypts the holder's
* `sfAuditorEncryptedBalance` under the auditor's new ElGamal public key, or
* sets it for the first time when the auditor key was late-registered. This is the
* holder-driven counterpart to flows 2 and 4, for when the issuer does not migrate
* the holder itself.
*
* 7. Simultaneous Holder Self-Migration: Updates both the issuer and auditor
* encrypted balances in a single transaction (both keys have rotated).
*/
class ConfidentialMPTMirrorUpdate : public Transactor
{
public:
static constexpr auto kConsequencesFactory = ConsequencesFactoryType::Normal;
explicit ConfidentialMPTMirrorUpdate(ApplyContext& ctx) : Transactor(ctx)
{
}
static bool
checkExtraFeatures(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);
static XRPAmount
calculateBaseFee(ReadView const& view, STTx const& tx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
void
visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override;
[[nodiscard]] bool
finalizeInvariants(
STTx const& tx,
TER result,
XRPAmount fee,
ReadView const& view,
beast::Journal const& j) override;
};
} // namespace xrpl