# Reading the transaction ledger

> How the Transactions screen renders each submitted transaction: the recent window, the landed/dropped/dedup states, route and attribution columns, the pipeline drawer, and the CSV export.

The **Transactions** screen is a per-transaction ledger of the
`bdn.ingest → fan-out → land` pipeline. It answers one question per row:
this signature was handed to FirstByte — what happened to it, where did
it enter, and which leader did it reach?

This page explains the vocabulary on that screen. It does not cover how to
submit a transaction; it covers how to read the result.

## The recent window and what one row represents

The ledger is fed by a single procedure, `txns.list`, called with no
arguments. There is no page cursor, no time-range picker, and no
per-tenant selector on this screen — the server decides which transactions
are recent and returns that set. The subheader states the size of the set
you are currently looking at (`bdn.ingest → fan-out → land · N recent`),
so treat `N` as the window, not as your lifetime transaction count.

**Live tail** re-runs `txns.list`. It is a refetch, not a subscription:
the table updates when you press it (or when the query otherwise
refreshes), and rows that have aged out of the server's recent window
disappear on the next fetch. If you need a row to survive that, export it
(see [CSV export columns](#csv-export-columns)) or open it on chain.

One row is one submitted transaction signature, carrying:

| Column          | Meaning                                                                 |
| --------------- | ----------------------------------------------------------------------- |
| **Signature**   | The transaction signature, truncated head-and-tail in the table. The drawer shows it in full. |
| **State**       | `landed`, `dropped`, or `dedup`. See below.                              |
| **Via**         | The route taken: `tpu` or the fan-out route.                             |
| **Ingest node** | The node that accepted the transaction.                                  |
| **Leader**      | The landing leader recorded for the row.                                 |
| **Fan-out**     | Rendered as `×N` — the number of fan-out edges.                          |
| **Slot**        | The landed slot.                                                         |
| **Bytes**       | Transaction size in bytes.                                               |
| **Latency**     | Land latency in milliseconds, shown only for landed rows; otherwise `—`. |

Clicking any row opens the detail drawer.

## State vocabulary: landed, dropped, dedup

State is derived, not stored as a single field. The screen computes it
from two booleans on the row, `dedup` and `landed`, and in this order:

| State     | Condition                     | Pill colour | Reading                                                                 |
| --------- | ----------------------------- | ----------- | ------------------------------------------------------------------------ |
| `dedup`   | `dedup` is true               | secondary   | A duplicate signature was seen and suppressed. Checked first, so a dedup row is labelled `dedup` regardless of the `landed` flag. |
| `landed`  | not dedup, and `landed` true  | success     | The transaction reached a leader and was included at the recorded slot. |
| `dropped` | not dedup, and `landed` false | destructive | The transaction went through the pipeline and no leader landed it.       |

Because `dedup` wins the precedence, the three states are mutually
exclusive in the UI and the filter chips partition the window cleanly.
The **Dropped** filter is defined the same way — it keeps rows that are
neither landed nor dedup — so a suppressed duplicate never shows up as a
drop.

The summary chips above the table count against the full recent window,
not against the filtered view:

- **Landed** — `landed / total`.
- **Dedup hits** — the number of suppressed duplicates.
- **Land rate** — landed as a percentage of the window, to one decimal.
  With an empty window it renders `—` rather than a percentage, because
  there is no rate to compute.

The search box matches a case-insensitive substring against the
**signature** and the **leader** only. It does not search the ingest node,
cluster, or tenant.

## Route column: fan-out versus direct TPU

The `via` field records which path the transaction took out of the ingest
node. The table renders the raw value as a tag; the drawer spells it out
under **Route**:

| `via` value | Drawer label   | Meaning                                                        |
| ----------- | -------------- | -------------------------------------------------------------- |
| `tpu`       | `direct TPU`   | Submitted straight to the leader's TPU, bypassing the fan-out.  |
| anything else | `BDN fan-out` | Propagated across the block-distribution network's edges.       |

The **TPU** filter chip is a route filter, not a state filter: it keeps
every row whose `via` is `tpu`, whether it landed, dropped, or deduped.
Combine it with the search box to isolate direct submissions to one
leader.

The **Fan-out** column (`×N`) is the edge count for the row. Direct TPU
rows still carry a fan-out value; read it alongside the route rather than
on its own.

## Ingest node and landing leader attribution

These two columns describe the two ends of the pipeline and are attributed
from different stages:

- **Ingest node** is the node that received the transaction. The drawer's
  `ingest` pipeline stage restates it as `received @ <node>` — that stage
  is the authority for this column.
- **Leader** is the landing leader recorded on the row. For a landed
  transaction, the `land` stage pairs it with the slot
  (`landed · slot <n>`). For a dropped transaction the `land` stage reads
  `no leader landed`, and the latency cell shows `—`, so read the leader
  column on a dropped row as the leader the row was targeting or last
  associated with, not as proof of inclusion. Inclusion is established by
  the `landed` flag and the slot, or by checking the signature on chain.

Both fields are opaque identifier strings rendered verbatim. The leader is
searchable; the ingest node is not.

## The detail drawer and its four pipeline stages

Selecting a row opens a drawer with the full signature at the top, labelled
**Signature (first 64 bytes = dedup key)** — that is, deduplication keys on
the signature itself. A **Copy signature** button copies the untruncated
value.

Below that, a fixed grid of fields:

| Field          | Notes                                                              |
| -------------- | ------------------------------------------------------------------ |
| State          | Same derivation as the table pill.                                  |
| Route          | `direct TPU` or `BDN fan-out`.                                      |
| Ingest node    |                                                                     |
| Landing leader |                                                                     |
| Fan-out edges  | `×N`.                                                               |
| Landed slot    |                                                                     |
| Bytes          |                                                                     |
| Land latency   | Milliseconds when landed, otherwise `—`.                            |
| Dedup hit      | `yes · suppressed` or `no`.                                         |
| Cluster        | Drawer only — not a table column.                                   |
| Tenant         | Drawer only — not a table column.                                   |
| Timestamp      | Drawer only — not a table column.                                   |

Then the **Pipeline** block, four stages in order. Each stage shows a tick,
a lock, or a cross:

| Stage     | Marked good when      | Detail text                                                       |
| --------- | --------------------- | ------------------------------------------------------------------ |
| `ingest`  | always                | `received @ <ingest node>`                                          |
| `dedup`   | the row is not a dedup hit | `unique signature`, or `duplicate — suppressed` (shown as a lock, not a failure) |
| `fan-out` | the row is not a dedup hit | `fanned out ×N edges`, or `skipped`                            |
| `land`    | the row landed        | `landed · slot <n>`, or `no leader landed`                          |

The shape of this block is the fastest diagnosis on the screen:

- Tick, tick, tick, cross → the transaction propagated and simply was not
  included. Look at the leader and the route.
- Tick, lock, skipped, cross → it was a duplicate. Nothing was fanned out,
  so the absence of a landing is expected; the original submission is a
  different row.

## Verifying a signature on chain

The drawer footer has two exits:

- **View on explorer** opens the signature in a block explorer in a new
  tab.
- **Copy as cURL** copies a ready-to-run JSON-RPC request to your
  clipboard — a `getTransaction` call for the selected signature against
  the public mainnet-beta endpoint, with
  `maxSupportedTransactionVersion: 0`:

```bash
curl https://api.mainnet-beta.solana.com -s \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"getTransaction","params":["<signature>",{"maxSupportedTransactionVersion":0}]}'
```

Use this when the ledger says `dropped` and you want independent
confirmation, or when a row has aged out of the recent window and you still
have the signature.

## CSV export columns

**Export** downloads `bdn-transactions.csv`. Two things to know before you
rely on it:

- It exports the **currently filtered and searched rows**, not the whole
  recent window. Clear the filter chip and the search box first if you want
  everything the screen has.
- The button is disabled when the current filter matches no rows.

The file is flat, one row per transaction, with these columns:

| Column        | Source                                                             |
| ------------- | ------------------------------------------------------------------- |
| `signature`   | Full signature, untruncated.                                         |
| `state`       | `landed`, `dropped`, or `dedup` — the derived state.                 |
| `via`         | Raw route value (`tpu` or the fan-out value), not the drawer label.  |
| `ingest_node` |                                                                      |
| `leader`      |                                                                      |
| `fanout`      | Numeric, without the `×` prefix.                                     |
| `slot`        | Numeric, without thousands separators.                               |
| `bytes`       |                                                                      |
| `latency_ms`  | Numeric for landed rows; **empty** for dropped and dedup rows.       |
| `dedup`       | `yes` or `no`.                                                       |

Note the redundancy between `state` and `dedup`: a `dedup` state always has
`dedup = yes`, and `latency_ms` is blank rather than zero whenever nothing
landed. Guard for the empty string when you load this into a numeric
column.

Cluster, tenant, and timestamp appear in the drawer but are **not** in the
export.

## When the screen has no ledger to show

If the block-distribution module is not deployed, the screen replaces the
ledger with a pending state instead of an empty table. It lists what the
ledger needs before it can render:

- `mod_bdn` nodes running
- an active landed-transaction stream

Until both are in place there is no ingest, fan-out, or land data to
attribute, and the summary chips have nothing to divide by.
