mirror of
https://github.com/XRPLF/rippled.git
synced 2026-09-27 23:38:08 +00:00
feat(telemetry): Emit every account a transaction names on tx.process
A transaction names one or more accounts: the sender in Account, and by type a Destination, Owner, Issuer, Holder and so on. tx.process now carries each top-level account-typed field as its own attribute, keyed tx_<field> in lower snake case (tx_account, tx_destination, ...), so an account can be searched for in traces whatever role it played. Addresses are public ledger identifiers and are emitted raw. The keys live in TxAccountSpanNames.h in libxrpl, with a field-to-key table in TxAccountSpanNames.cpp. A library test walks TxFormats and the SField registry: every account field a transaction can carry has a key, and no field that only ledger entries carry has one. An empty account field is skipped rather than rendered as the zero address.
This commit is contained in:
@@ -487,24 +487,25 @@ This gives the best of both worlds: guaranteed cross-node correlation via determ
|
||||
|
||||
**Attributes added**:
|
||||
|
||||
| Span | Attribute | Type | Source |
|
||||
| ----------------- | -------------------- | ------ | ------------------------------------------------------------------- |
|
||||
| `tx.process` | `tx_type` | string | `TxFormats::getInstance().findByType(stx->getTxnType())->getName()` |
|
||||
| `tx.process` | `fee` | int64 | `stx->getFieldAmount(sfFee).xrp().drops()` |
|
||||
| `tx.process` | `sequence` | int64 | `stx->getSeqProxy().value()` |
|
||||
| `tx.process` | `ter_result` | string | `transToken(e.result)` (set after batch application) |
|
||||
| `tx.process` | `applied` | bool | `e.applied` (set after batch application) |
|
||||
| `tx.receive` | `tx_type` | string | `TxFormats::getInstance().findByType(stx->getTxnType())->getName()` |
|
||||
| `txq.enqueue` | `tx_type` | string | same pattern as above |
|
||||
| `txq.enqueue` | `txq_status` | string | `queued` / `applied_direct` / `applied` / `failed` / `rejected` |
|
||||
| `txq.enqueue` | `ter_code` | string | `transToken(directApplied->ter)` (set on the direct-apply path) |
|
||||
| `txq.enqueue` | `fee_level_paid` | int64 | `getFeeLevelPaid(view, *tx).value()` |
|
||||
| `txq.enqueue` | `required_fee_level` | int64 | `getRequiredFeeLevel(...).value()` |
|
||||
| `txq.batch_clear` | `num_cleared` | int64 | queued txs cleared ahead of the applying tx |
|
||||
| `txq.cleanup` | `expired_count` | int64 | entries dropped for passed `LastLedgerSequence` |
|
||||
| `txq.accept_tx` | `txq_status` | string | `applied` / `failed` / `retried` |
|
||||
| `txq.accept_tx` | `ter_code` | string | `transToken(txnResult)` (set before branching on the outcome) |
|
||||
| `txq.accept` | `ledger_changed` | bool | set at end of accept loop |
|
||||
| Span | Attribute | Type | Source |
|
||||
| ----------------- | -------------------- | ------ | -------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `tx.process` | `tx_type` | string | `TxFormats::getInstance().findByType(stx->getTxnType())->getName()` |
|
||||
| `tx.process` | `fee` | int64 | `stx->getFieldAmount(sfFee).xrp().drops()` |
|
||||
| `tx.process` | `sequence` | int64 | `stx->getSeqProxy().value()` |
|
||||
| `tx.process` | `tx_<account field>` | string | one per top-level `STI_ACCOUNT` field, raw r-address (`tx_account`, `tx_destination`, ...); keys in `TxAccountSpanNames.h` |
|
||||
| `tx.process` | `ter_result` | string | `transToken(e.result)` (set after batch application) |
|
||||
| `tx.process` | `applied` | bool | `e.applied` (set after batch application) |
|
||||
| `tx.receive` | `tx_type` | string | `TxFormats::getInstance().findByType(stx->getTxnType())->getName()` |
|
||||
| `txq.enqueue` | `tx_type` | string | same pattern as above |
|
||||
| `txq.enqueue` | `txq_status` | string | `queued` / `applied_direct` / `applied` / `failed` / `rejected` |
|
||||
| `txq.enqueue` | `ter_code` | string | `transToken(directApplied->ter)` (set on the direct-apply path) |
|
||||
| `txq.enqueue` | `fee_level_paid` | int64 | `getFeeLevelPaid(view, *tx).value()` |
|
||||
| `txq.enqueue` | `required_fee_level` | int64 | `getRequiredFeeLevel(...).value()` |
|
||||
| `txq.batch_clear` | `num_cleared` | int64 | queued txs cleared ahead of the applying tx |
|
||||
| `txq.cleanup` | `expired_count` | int64 | entries dropped for passed `LastLedgerSequence` |
|
||||
| `txq.accept_tx` | `txq_status` | string | `applied` / `failed` / `retried` |
|
||||
| `txq.accept_tx` | `ter_code` | string | `transToken(txnResult)` (set before branching on the outcome) |
|
||||
| `txq.accept` | `ledger_changed` | bool | set at end of accept loop |
|
||||
|
||||
**New attr keys**: `TxSpanNames.h` (`txType`, `fee`, `sequence`, `terResult`, `applied`), `TxQSpanNames.h` (`txType`).
|
||||
|
||||
|
||||
@@ -222,7 +222,8 @@ target_link_libraries(
|
||||
# opentelemetry-cpp::opentelemetry-cpp (individual component targets like
|
||||
# ::api, ::sdk are not available in the Conan package).
|
||||
#
|
||||
# Links xrpl.libxrpl.protocol PRIVATELY for sha512Half (digest.h)
|
||||
# Links xrpl.libxrpl.protocol PRIVATELY for sha512Half (digest.h) and the
|
||||
# SField table behind TxAccountSpanNames.cpp
|
||||
add_module(xrpl telemetry)
|
||||
target_link_libraries(
|
||||
xrpl.libxrpl.telemetry
|
||||
|
||||
172
include/xrpl/telemetry/TxAccountSpanNames.h
Normal file
172
include/xrpl/telemetry/TxAccountSpanNames.h
Normal 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
|
||||
45
src/libxrpl/telemetry/TxAccountSpanNames.cpp
Normal file
45
src/libxrpl/telemetry/TxAccountSpanNames.cpp
Normal file
@@ -0,0 +1,45 @@
|
||||
#include <xrpl/telemetry/TxAccountSpanNames.h>
|
||||
|
||||
#include <xrpl/protocol/SField.h>
|
||||
|
||||
#include <optional>
|
||||
#include <string_view>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace xrpl::telemetry {
|
||||
|
||||
std::optional<std::string_view>
|
||||
accountFieldAttributeKey(SField const& field)
|
||||
{
|
||||
// Built on first call, not at static initialisation: the sf* objects are
|
||||
// globals in another translation unit, so their codes are only safe to
|
||||
// read once main() has started.
|
||||
static std::unordered_map<int, std::string_view> const kTable = {
|
||||
{sfAccount.getCode(), tx_account_span::attr::account},
|
||||
{sfDestination.getCode(), tx_account_span::attr::destination},
|
||||
{sfOwner.getCode(), tx_account_span::attr::owner},
|
||||
{sfIssuer.getCode(), tx_account_span::attr::issuer},
|
||||
{sfAuthorize.getCode(), tx_account_span::attr::authorize},
|
||||
{sfUnauthorize.getCode(), tx_account_span::attr::unauthorize},
|
||||
{sfRegularKey.getCode(), tx_account_span::attr::regularKey},
|
||||
{sfNFTokenMinter.getCode(), tx_account_span::attr::nftokenMinter},
|
||||
{sfHolder.getCode(), tx_account_span::attr::holder},
|
||||
{sfDelegate.getCode(), tx_account_span::attr::delegate},
|
||||
{sfSponsor.getCode(), tx_account_span::attr::sponsor},
|
||||
{sfSponsee.getCode(), tx_account_span::attr::sponsee},
|
||||
{sfCounterparty.getCode(), tx_account_span::attr::counterparty},
|
||||
{sfCounterpartySponsor.getCode(), tx_account_span::attr::counterpartySponsor},
|
||||
{sfSubject.getCode(), tx_account_span::attr::subject},
|
||||
{sfOtherChainSource.getCode(), tx_account_span::attr::otherChainSource},
|
||||
{sfOtherChainDestination.getCode(), tx_account_span::attr::otherChainDestination},
|
||||
{sfAttestationSignerAccount.getCode(), tx_account_span::attr::attestationSignerAccount},
|
||||
{sfAttestationRewardAccount.getCode(), tx_account_span::attr::attestationRewardAccount},
|
||||
};
|
||||
|
||||
auto const it = kTable.find(field.getCode());
|
||||
if (it == kTable.end())
|
||||
return std::nullopt;
|
||||
return it->second;
|
||||
}
|
||||
|
||||
} // namespace xrpl::telemetry
|
||||
163
src/tests/libxrpl/telemetry/TxAccountSpanNames.cpp
Normal file
163
src/tests/libxrpl/telemetry/TxAccountSpanNames.cpp
Normal file
@@ -0,0 +1,163 @@
|
||||
#include <xrpl/telemetry/TxAccountSpanNames.h>
|
||||
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/TxFormats.h>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <optional>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
/**
|
||||
* Contract tests for the account-field attribute keys of the tx.process span.
|
||||
*
|
||||
* The key set is a cross-component contract: Tempo span-filter tags, the
|
||||
* naming CI check and dashboards read these exact strings. A transaction type
|
||||
* that gains an account-typed field without a key would silently emit nothing
|
||||
* for it, so the completeness tests derive the required set from TxFormats
|
||||
* and from the SField registry rather than from a copied list.
|
||||
*/
|
||||
|
||||
using namespace xrpl;
|
||||
using namespace xrpl::telemetry;
|
||||
|
||||
namespace {
|
||||
|
||||
// Every account-typed field that any transaction format carries at top level,
|
||||
// including the common fields shared by all formats.
|
||||
std::set<SField const*>
|
||||
accountFieldsInTransactionFormats()
|
||||
{
|
||||
std::set<SField const*> fields;
|
||||
for (auto const& format : TxFormats::getInstance())
|
||||
{
|
||||
for (auto const& element : format.getSOTemplate())
|
||||
{
|
||||
if (element.sField().fieldType == STI_ACCOUNT)
|
||||
fields.insert(&element.sField());
|
||||
}
|
||||
}
|
||||
return fields;
|
||||
}
|
||||
|
||||
// Every account-typed field the protocol defines, whether or not a transaction
|
||||
// carries it.
|
||||
std::set<SField const*>
|
||||
allAccountFields()
|
||||
{
|
||||
std::set<SField const*> fields;
|
||||
for (auto const& [code, field] : SField::getKnownCodeToField())
|
||||
{
|
||||
if (field->fieldType == STI_ACCOUNT)
|
||||
fields.insert(field);
|
||||
}
|
||||
return fields;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(TxAccountSpanNames, every_account_field_a_transaction_can_carry_has_a_key)
|
||||
{
|
||||
auto const fields = accountFieldsInTransactionFormats();
|
||||
// Setup: the walk over TxFormats found the one field every transaction has.
|
||||
ASSERT_TRUE(fields.contains(&sfAccount));
|
||||
|
||||
for (auto const* field : fields)
|
||||
{
|
||||
EXPECT_TRUE(accountFieldAttributeKey(*field).has_value())
|
||||
<< "no attribute key for " << field->getName();
|
||||
}
|
||||
}
|
||||
|
||||
TEST(TxAccountSpanNames, account_fields_no_transaction_carries_have_no_key)
|
||||
{
|
||||
auto const carried = accountFieldsInTransactionFormats();
|
||||
auto const all = allAccountFields();
|
||||
// Setup: the registry holds more account fields than transactions carry.
|
||||
ASSERT_TRUE(all.contains(&sfLowSponsor));
|
||||
ASSERT_FALSE(carried.contains(&sfLowSponsor));
|
||||
|
||||
for (auto const* field : all)
|
||||
{
|
||||
if (!carried.contains(field))
|
||||
EXPECT_EQ(accountFieldAttributeKey(*field), std::nullopt) << field->getName();
|
||||
}
|
||||
}
|
||||
|
||||
TEST(TxAccountSpanNames, every_key_is_tx_plus_the_field_name_in_lower_snake_case)
|
||||
{
|
||||
for (auto const* field : accountFieldsInTransactionFormats())
|
||||
{
|
||||
auto const maybeKey = accountFieldAttributeKey(*field);
|
||||
ASSERT_TRUE(maybeKey.has_value()) << field->getName();
|
||||
auto const key = maybeKey.value_or(std::string_view{});
|
||||
|
||||
// Shape: tx_ prefix, then lower_snake_case with no empty segment.
|
||||
EXPECT_TRUE(key.starts_with("tx_")) << key;
|
||||
EXPECT_FALSE(key.ends_with('_')) << key;
|
||||
EXPECT_EQ(key.find("__"), std::string_view::npos) << key;
|
||||
EXPECT_TRUE(std::ranges::all_of(key, [](unsigned char c) {
|
||||
return std::islower(c) != 0 || std::isdigit(c) != 0 || c == '_';
|
||||
})) << key;
|
||||
|
||||
// Content: the key with prefix and underscores removed is the field's
|
||||
// JSON name lowercased. Catches a misspelt or swapped key.
|
||||
std::string flattened(key.substr(3));
|
||||
std::erase(flattened, '_');
|
||||
std::string lowered = field->getName();
|
||||
std::ranges::transform(lowered, lowered.begin(), [](unsigned char c) {
|
||||
return static_cast<char>(std::tolower(c));
|
||||
});
|
||||
EXPECT_EQ(flattened, lowered) << key;
|
||||
}
|
||||
}
|
||||
|
||||
// The published contract: every carried field and its exact key. Literals are
|
||||
// deliberate here; the point is to pin underscore placement, which the shape
|
||||
// test above cannot see.
|
||||
TEST(TxAccountSpanNames, exact_key_for_every_carried_field)
|
||||
{
|
||||
std::vector<std::pair<SField const*, std::string_view>> const expected = {
|
||||
{&sfAccount, "tx_account"},
|
||||
{&sfDestination, "tx_destination"},
|
||||
{&sfOwner, "tx_owner"},
|
||||
{&sfIssuer, "tx_issuer"},
|
||||
{&sfAuthorize, "tx_authorize"},
|
||||
{&sfUnauthorize, "tx_unauthorize"},
|
||||
{&sfRegularKey, "tx_regular_key"},
|
||||
{&sfNFTokenMinter, "tx_nftoken_minter"},
|
||||
{&sfHolder, "tx_holder"},
|
||||
{&sfDelegate, "tx_delegate"},
|
||||
{&sfSponsor, "tx_sponsor"},
|
||||
{&sfSponsee, "tx_sponsee"},
|
||||
{&sfCounterparty, "tx_counterparty"},
|
||||
{&sfCounterpartySponsor, "tx_counterparty_sponsor"},
|
||||
{&sfSubject, "tx_subject"},
|
||||
{&sfOtherChainSource, "tx_other_chain_source"},
|
||||
{&sfOtherChainDestination, "tx_other_chain_destination"},
|
||||
{&sfAttestationSignerAccount, "tx_attestation_signer_account"},
|
||||
{&sfAttestationRewardAccount, "tx_attestation_reward_account"},
|
||||
};
|
||||
// Setup: this list and the TxFormats walk must name the same fields, or a
|
||||
// row is missing here.
|
||||
std::set<SField const*> listed;
|
||||
for (auto const& [field, key] : expected)
|
||||
listed.insert(field);
|
||||
ASSERT_EQ(listed, accountFieldsInTransactionFormats());
|
||||
|
||||
for (auto const& [field, key] : expected)
|
||||
EXPECT_EQ(accountFieldAttributeKey(*field).value_or(""), key) << field->getName();
|
||||
}
|
||||
|
||||
TEST(TxAccountSpanNames, non_account_fields_have_no_key)
|
||||
{
|
||||
EXPECT_EQ(accountFieldAttributeKey(sfFee), std::nullopt);
|
||||
EXPECT_EQ(accountFieldAttributeKey(sfSequence), std::nullopt);
|
||||
EXPECT_EQ(accountFieldAttributeKey(sfInvalid), std::nullopt);
|
||||
}
|
||||
@@ -119,6 +119,7 @@
|
||||
#include <xrpl/server/Manifest.h>
|
||||
#include <xrpl/shamap/SHAMap.h>
|
||||
#include <xrpl/telemetry/SpanGuard.h>
|
||||
#include <xrpl/telemetry/TxAccountSpanNames.h>
|
||||
#include <xrpl/tx/apply.h>
|
||||
|
||||
#include <boost/asio/error.hpp>
|
||||
@@ -1571,6 +1572,18 @@ NetworkOPsImp::processTransaction(
|
||||
}
|
||||
span->setAttribute(
|
||||
tx_span::attr::sequence, static_cast<std::int64_t>(stx->getSeqProxy().value()));
|
||||
// Every account the transaction names, keyed by its role
|
||||
// (tx_account, tx_destination, ...). Addresses are public ledger
|
||||
// identifiers and go out raw; see TxAccountSpanNames.h. A present
|
||||
// but empty account field is skipped rather than rendered as the
|
||||
// all-zero address.
|
||||
for (auto const& field : *stx)
|
||||
{
|
||||
if (field.getSType() != STI_ACCOUNT || field.isDefault())
|
||||
continue;
|
||||
if (auto const key = accountFieldAttributeKey(field.getFName()))
|
||||
span->setAttribute(*key, field.getText());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -85,6 +85,9 @@ inline constexpr auto fee = makeStr("fee");
|
||||
* "sequence" — transaction sequence number.
|
||||
*/
|
||||
inline constexpr auto sequence = makeStr("sequence");
|
||||
// The per-role account keys (tx_account, tx_destination, ...) that tx.process
|
||||
// also carries live in <xrpl/telemetry/TxAccountSpanNames.h>, in libxrpl, so a
|
||||
// library test can check them against TxFormats.
|
||||
/**
|
||||
* "ter_result" — engine result code after application.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user