Skip to content

Add ouroboros-consensus:tracing sublibrary - #2244

Open
jasagredo wants to merge 31 commits into
mainfrom
js/tracing-instances
Open

jasagredo wants to merge 31 commits into
mainfrom
js/tracing-instances

Conversation

@jasagredo

@jasagredo jasagredo commented Aug 27, 2026 •

Copy link
Copy Markdown
Contributor

Move the tracing instances from cardano-node to ouroboros-consensus.

@jasagredo
jasagredo force-pushed the js/tracing-instances branch 2 times, most recently from b003a0e to 7b0838e Compare August 27, 2026 13:32
@jasagredo
jasagredo force-pushed the js/tracing-instances branch from e9b2bd8 to 8f27e3f Compare September 3, 2026 15:21
Comment thread nix/haskell.nix
Comment thread tracing/test/Test/Consensus/Tracing/MetaTrace.hs Outdated
Comment thread tracing/test/Test/Consensus/Tracing/Golden.hs
Comment thread tracing/Ouroboros/Consensus/Tracing/Consensus.hs Outdated
Comment thread tracing/Ouroboros/Consensus/Tracing/Era/Shelley/Render.hs Outdated
Comment thread ouroboros-consensus.cabal
Comment thread tracing/Ouroboros/Consensus/Tracing/ChainDB.hs Outdated
@jasagredo
jasagredo force-pushed the js/tracing-instances branch 2 times, most recently from 332726c to b73febc Compare September 9, 2026 12:08
@jasagredo
jasagredo changed the base branch from main to js/cardano-config-and-keys September 16, 2026 10:44
@jasagredo
jasagredo added this pull request to stack #2296 September 16, 2026 10:44
@jasagredo
jasagredo force-pushed the js/cardano-config-and-keys branch from 40237c4 to 432c368 Compare September 16, 2026 11:10
@jasagredo
jasagredo force-pushed the js/cardano-config-and-keys branch from 432c368 to b08c4ae Compare September 16, 2026 11:28
@jasagredo
jasagredo force-pushed the js/cardano-config-and-keys branch from 8a37ec5 to 3e6b1e2 Compare September 25, 2026 14:54
Introduce a new public sublibrary 'tracing' with instances for Consensus types,
moved from cardano-node.
Moving it from cardano-node/cardano-api.
Reimplement, off cardano-api, the Shelley-era JSON rendering helpers that the
era tracing instances need: bech32 stake/reward addresses (CIP-19, via the
bech32 package), hex script hashes, and an era-generic script-purpose renderer
built on cardano-ledger-api's AnyEraScript projections (replacing cardano-api's
per-era Alonzo/Conway plutus-purpose rendering and its ShelleyBasedEra/
AlonzoEraOnwards era witnesses). Output matches cardano-api's.
Move Cardano.Node.Tracing.Era.Byron to Ouroboros.Consensus.Tracing.Era.Byron;
inline the trivial textShow helper (its only cardano-api use) and drop the
imports the old -Wno-unused-imports hack was masking.
Move Cardano.Node.Tracing.Era.Shelley to Ouroboros.Consensus.Tracing.Era.Shelley,
off cardano-api:

  - use the new Era.Shelley.Render helpers (bech32 addresses, script hashes,
    era-generic plutus purposes) instead of the cardano-api ones
  - drop the cardano-api era witnesses (ShelleyLedgerEra/IsShelleyBasedEra/
    shelleyBasedEra/forEraInEon/toScriptIndex): the affected instances are now
    phrased directly over the ledger era with an AnyEraScript constraint, and
    ExtraRedeemers renders redeemer pointers via renderScriptIndex
  - render VRF key hashes via the ledger's own hash (fromVRFVerKeyHash was
    always a ledger function, only re-exported by cardano-api)
  - inline textShow
  - re-add the ToJSON orphans for cardano-data's NonEmptySet/NonEmptyMap that
    cardano-node previously got transitively from cardano-api

Builds with no warnings.
HotKey.KESInfo and HotKey.KESEvolutionError are Consensus types, but their
LogFormatting/MetaTrace instances (and the ToJSON orphan for KESPeriod they
need) stayed behind in cardano-node's Cardano.Node.Tracing.Tracers.KESInfo,
even though the HasKESInfo/GetKESInfo classes that feed them had already moved
here. A second consumer of :tracing would have had to reimplement those
instances, and would then have clashed with cardano-node's copies.

Move the instances and traceAsKESInfo here, and split the leftover
Ouroboros.Consensus.Tracing.Queries -- a name inherited from
Cardano.Node.Queries that no longer describes its contents -- along the lines
of what the classes are actually for:

  - Ouroboros.Consensus.Tracing.KESInfo: HasKESInfo, GetKESInfo,
    traceAsKESInfo, and the instances above
  - Ouroboros.Consensus.Tracing.ConvertTxId: ConvertTxId

The instance bodies are unchanged, so the rendered messages and the EKG metric
names stay the same.
The LogFormatting/MetaTrace instances are orphans spread over the
Ouroboros.Consensus.Tracing.* modules, four of which (Era.Byron, Era.Shelley,
Era.HardFork, Formatting) export nothing else and so have to be imported for
their instances alone. Which module carries which instance is not something a
consumer should have to track: getting it wrong is silently losing an instance,
and it already forced cardano-node to sprinkle `import ... ()` lines over five
wiring modules.

Add an umbrella that re-exports the name-bearing modules and imports the
instance-only ones, so a single import brings the whole set into scope and the
internal module structure can change without breaking consumers.
renderScriptPurpose and renderScriptIndex both project a PlutusPurpose through
cardano-ledger-api's AnyEraScript pattern synonyms. Those synonyms carry no
COMPLETE pragma, so the wildcard is needed to keep the match total -- but
falling through to Aeson.Null makes a purpose the ledger has added and we do
not know about indistinguishable from one that legitimately rendered as null.

Fall through to an explicit marker instead, so an unhandled purpose is
greppable in the logs and a distinct shape in the trace schemas.

Also record that renderScriptIndex does not reproduce the JSON shape
cardano-api's toScriptIndex produced for the ExtraRedeemers field.
The instances were phrased over TraceObjectDiffusion{In,Out}bound with both
type parameters free, gated on an ObjectDiffusionMetricsPrefix class that
supplied the EKG metric prefix. That class was not exported, so the fully
general orphan both blocked anyone from writing an instance for their own
instantiation and gave them no way to extend ours.

The object type is a type family (PerasCert / PerasVote) and so cannot appear
in an instance head, which is what forced the generalisation -- but the
object-id type can, and it is what actually distinguishes the two diffusion
pipelines. Match on it directly and drop the class: a further diffusion kind
adds its own pair of instances rather than an instance of a closed class.

The metric prefixes are unchanged.
Consensus.Tracers has carried perasCertInclusionTracer and
perasVoteForgingTracer for a while, but TracePerasCertInclusionEvent and
TracePerasVoteForgingEvent had no LogFormatting/MetaTrace instances, so
cardano-node could only wire both to a no-op: the events were unobservable.

Add the instances, with a namespace, a severity and documentation per
constructor. The routine "nothing to do this slot/round" cases are Debug, the
decisions and their outcomes Info, and the environment read failure Error. The
payloads of the decisions and DB outcomes are rendered via Show, as the object
diffusion instances alongside them already do.

Also export TracePerasVoteForgingEvent (..) from Ouroboros.Consensus.Node.Tracers,
which already exported its cert inclusion counterpart, so both trace types
reach a consumer from the module that defines the record holding them.
jsonNonEmptySet/jsonNonEmptyMap were introduced with the claim that they match
what cardano-node emitted before, which cardano-node got from cardano-api.
Neither holds: cardano-api never mentions NonEmptySet, and cardano-data has no
ToJSON instance for its non-empty containers to compare against -- putting the
container back in place of the helper does not compile.

The helpers are still needed and their output is the only shape such an
instance could have; just say so rather than claiming a parity that cannot be
checked.
Nothing checked these instances: cardano-node's consistency check runs over the
assembled node configuration, in the other repo, and ouroboros-network's tracing
sublibraries have no tests at all. Writing one by hand -- as the Peras instances
just were -- means hand-listing namespaces, severities and documentation, and a
typo in any of them is silent.

The checks derive everything from allNamespaces and the namespace-indexed
methods, so they need no trace values and run over every traced type: 25 of them
here, instantiated at CardanoBlock. A namespace must be non-empty and unique,
must have a severity (without one the message cannot be configured), a privacy,
a detail level and documentation, and the whole namespace tree must satisfy the
same check trace-dispatcher applies to a node configuration.

The documentation check found 44 namespaces with no documentFor, or with an
empty one. Fixed here: ConsensusStartupException, ClientMetrics and the ten
object diffusion namespaces. The rest -- ChainDB, ImmutableDB, LedgerDB and the
forge tracer, all inherited from cardano-node -- are recorded in
knownUndocumented so that new gaps are still rejected, with a second check that
fails if an entry there becomes documented, so the list cannot go stale.
These functions were reimplemented off cardano-api when the era tracing
instances moved here, and had already drifted from it twice without anything
noticing. Pin their output as bytes so that changing it takes an explicit,
reviewed change to a golden file.

Writing it found a third divergence: renderScriptPurpose rendered the spending
purpose's TxIn through cardano-ledger's ToJSON, which shows the index newtype
and yields "<txid>#TxIx {unTxIx = 0}". cardano-api rendered "<txid>#0". Add a
renderTxIn that does the latter.

The golden file was checked by hand against cardano-api-11.5.0.0, the version
cardano-node used before the move: the ScriptWitnessIndex kind/value shapes, the
{"item": ...} wrapper on the purposes that keep their AsItem, the CIP-19 bech32
prefixes, and the txid#index form.
The list of traced types was hand-written and had drifted from Consensus.Tracers:
six top-level tracers were missing, all of them ones whose type carries a peer
or has no block parameter, which is why they were easy to overlook.

  TraceGDDEvent, Jumping.TraceEventCsj, Jumping.TraceEventDbf,
  BlockFetch.TraceFetchClientState, TraceDecisionEvent, KESAgentClientTrace

They bring 14 more undocumented namespaces into knownUndocumented, 13 of them
KESAgentClientTrace's -- an entire tracer whose messages carry no documentation
at all, and which nothing was asserting anything about until now.

The peer type is a stand-in: no MetaTrace method looks at it, so this uses ()
rather than pulling in the node's address types.
LedgerDB, ImmutableDB, VolatileDB, PerasCertDB and PerasVoteDB do not have
tracers of their own. ChainDbArgs.updateTracer derives each from the ChainDB
tracer, and ChainDB.TraceEvent's allNamespaces maps all of their namespaces in
under LedgerEvent, ImmDbEvent, VolatileDbEvent and so on -- cardano-node has no
tracer for any of them either.

Listing them alongside ChainDB.TraceEvent meant every one of their namespaces
was checked twice under two different names, and appeared twice in
knownUndocumented: once as ChunkValidation.InvalidChunkFile and again as
ImmDbEvent.ChunkValidation.InvalidChunkFile, once as
Flavor.V2.BackendTrace.LSM.LSMSnap and again as
LedgerEvent.Flavor.V2.BackendTrace.LSM.LSMSnap.

Keep ChainDB.TraceEvent, which reaches all of them, and drop the 17 duplicated
ratchet entries.
'maximumDef' was the only thing taken from it, in two severity
computations. 'foldr max' over the same list gives the same answer, and
the main library does not depend on cardano-prelude either, so this
keeps a public library out of the dependency tree entirely.
cardano-ledger-api does ship a COMPLETE pragma over the seven
AnyEra*Purpose synonyms, so the wildcard was not future-proofing: it was
reachable today, and a Dijkstra guarding redeemer rendered as
{"kind":"UnknownPlutusPurpose"} in the logs.

Match all seven and drop the wildcard, so that the next era is a compile
error here rather than a marker in an operator's logs. Guarding has no
cardano-api rendering to preserve, so its item renders directly (like
spending and rewarding) and its witness index name is ours.
'purposesByItem' only covered three of the six Conway purposes, leaving
certifying, voting and proposing unpinned -- and those are exactly the
{"item": ...}-wrapped ones the comment above warns about, so the ones
most likely to drift. 'renderTxIn' had no entry either.

Add them, plus the guarding purpose, which needs the Dijkstra era and so
also exercises the other AnyEraScript instance rather than only Conway's.
cardano-node removed the instances for
[TraceLabelPeer peer (FetchDecision [Point header])] under
IntersectMBO/cardano-node#6667: nothing emits that list, and its
Accept/Decline/EmptyPeersFetch namespaces were the stale ones the
configuration consistency check rejected. The MetaTrace instance for
FetchDecision goes with them; its metricsDocFor advertised a
connectedPeers metric that no asMetrics ever produced.

What remains is LogFormatting (FetchDecision [Point header]), which
TraceDecisionEvent's own rendering needs, matching cardano-node master.
The docstring claimed to catch a namespace left out of 'allNamespaces',
but every check iterates 'allNamespaces' itself and 'namespaceFor' is
never called, so that mistake -- and a typo appearing in both places --
still passes. Catching it needs trace values, which these types have no
'Arbitrary' or 'Enum' to produce, so narrow the wording instead.
'tracing/golden' sits outside the test component's hs-source-dirs, so
without an 'extraSrcFiles' entry haskell.nix pruned it out of the
component source, and without '--no-create' tasty-golden then created
the missing file and reported a pass. The Nix check was green without
comparing anything.

Key the golden directories by test name so that adding one is a single
line and cannot forget either half.
'tracing' is public API -- HasIssuer, ConvertTxId, and every log shape an
operator parses -- but it was not in the list the check walks, so a later
change to its .hs files would never ask for a fragment. Its path is the
directory itself, since that is its hs-source-dirs.
@jasagredo
jasagredo removed this pull request from stack #2296 September 28, 2026 09:16
@jasagredo
jasagredo changed the base branch from js/cardano-config-and-keys to main September 28, 2026 09:17

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants