Skip to main content

zakura_network/zakura/
trace.rs

1//! Structured JSONL trace helpers for Zakura P2P.
2//!
3//! `closed.punitive` is reserved in the schema until Zakura has a punitive close
4//! path and matching metric. `closed.neutral` carries a bounded `reason` label
5//! describing the teardown cause (for example `idle_timeout`, `accept_failed`,
6//! `outbound_closed`, `bad_response`, or `cancelled`). Connection admission
7//! rejects use `rejected.admission` with a bounded `reason` label rather than
8//! one event name per rejection metric, so readers should pivot by `event` plus
9//! `reason` for those rows.
10
11use std::{
12    sync::Arc,
13    time::{Duration, Instant},
14};
15
16use blake2b_simd::Params as Blake2bParams;
17use serde_json::{Map, Number, Value};
18use zakura_jsonl_trace::{JsonlTracer, JsonlWriteEvent};
19
20use super::{ZakuraPeerId, ZakuraRejectReason};
21
22/// A Zakura JSONL trace table.
23#[derive(Copy, Clone, Debug, Eq, PartialEq)]
24pub struct ZakuraTraceTable {
25    table: &'static str,
26    file_name: &'static str,
27}
28
29impl ZakuraTraceTable {
30    /// Logical table name.
31    pub fn table(self) -> &'static str {
32        self.table
33    }
34
35    /// JSONL file name.
36    pub fn file_name(self) -> &'static str {
37        self.file_name
38    }
39}
40
41/// Legacy upgrade and control-handshake transitions.
42pub const HANDSHAKE_TABLE: ZakuraTraceTable = ZakuraTraceTable {
43    table: "handshake",
44    file_name: "handshake.jsonl",
45};
46
47/// Connection admission and close transitions.
48pub const CONN_TABLE: ZakuraTraceTable = ZakuraTraceTable {
49    table: "conn",
50    file_name: "conn.jsonl",
51};
52
53/// Per-connection stream admission transitions.
54pub const STREAM_TABLE: ZakuraTraceTable = ZakuraTraceTable {
55    table: "stream",
56    file_name: "stream.jsonl",
57};
58
59/// Discovery dialer decisions and backoff classification.
60pub const DISCOVERY_TABLE: ZakuraTraceTable = ZakuraTraceTable {
61    table: "discovery",
62    file_name: "discovery.jsonl",
63};
64
65/// Frame and message rate-limit decisions.
66pub const RATELIMIT_TABLE: ZakuraTraceTable = ZakuraTraceTable {
67    table: "ratelimit",
68    file_name: "ratelimit.jsonl",
69};
70
71/// Header-sync policy, accounting, and frontier events.
72pub const HEADER_SYNC_TABLE: ZakuraTraceTable = ZakuraTraceTable {
73    table: "header_sync",
74    file_name: "header_sync.jsonl",
75};
76
77/// Legacy compatibility request/response events.
78pub const LEGACY_REQUEST_TABLE: ZakuraTraceTable = ZakuraTraceTable {
79    table: "legacy_request",
80    file_name: "legacy_request.jsonl",
81};
82
83/// Block-sync (stream-6) scheduling, download, submit, and commit events.
84pub const BLOCK_SYNC_TABLE: ZakuraTraceTable = ZakuraTraceTable {
85    table: "block_sync",
86    file_name: "block_sync.jsonl",
87};
88
89/// Zakurad adapter boundary events for commits, state reads, and frontier mirrors.
90pub const COMMIT_STATE_TABLE: ZakuraTraceTable = ZakuraTraceTable {
91    table: "commit_state",
92    file_name: "commit_state.jsonl",
93};
94
95/// Failed non-blocking outbound queue sends for Zakura wire messages.
96pub const QUEUE_SEND_TABLE: ZakuraTraceTable = ZakuraTraceTable {
97    table: "queue_send",
98    file_name: "queue_send.jsonl",
99};
100
101/// Shared queue-send trace event names and field keys.
102pub mod queue_send_trace {
103    /// Trace row event field.
104    pub const EVENT: &str = "event";
105    /// Queue send failure event.
106    pub const QUEUE_SEND_FAILED: &str = "queue_send_failed";
107    /// Service label field (`header_sync`, `block_sync`, etc.).
108    pub const SERVICE: &str = "service";
109    /// Wire message label field (`Status`, `GetBlocks`, etc.).
110    pub const MESSAGE: &str = "message";
111    /// Peer field.
112    pub const PEER: &str = "peer";
113    /// Source peer field for forwarded messages.
114    pub const SOURCE_PEER: &str = "source_peer";
115    /// Destination peer field for forwarded messages.
116    pub const DESTINATION_PEER: &str = "destination_peer";
117    /// Bounded send error label (`full`, `closed`, or `encode`).
118    pub const ERROR: &str = "error";
119    /// Logical send reason field.
120    pub const REASON: &str = "reason";
121    /// Remaining outbound queue slots observed after the failed send.
122    pub const QUEUE_CAPACITY: &str = "queue_capacity";
123    /// Total outbound queue slots.
124    pub const QUEUE_MAX_CAPACITY: &str = "queue_max_capacity";
125    /// Range start height field.
126    pub const RANGE_START: &str = "range_start";
127    /// Range count field.
128    pub const RANGE_COUNT: &str = "range_count";
129    /// Returned response count field.
130    pub const RETURNED: &str = "returned";
131    /// Height field.
132    pub const HEIGHT: &str = "height";
133    /// Hash field.
134    pub const HASH: &str = "hash";
135}
136
137/// Shared block-sync trace event names and field keys.
138///
139/// The block-sync body pipeline has no `tracing`-macro coverage in release
140/// builds (the binary is compiled with `release_max_level_info`, which strips
141/// the `debug!` sites), so these JSONL rows are the only runtime visibility into
142/// scheduling, download, submit, and commit progress. The periodic
143/// [`BLOCK_SYNC_STATE`](block_sync_trace::BLOCK_SYNC_STATE) snapshot is the single most useful row for diagnosing a
144/// stall: it reports where the body floor, verified tip, and header tip are, how
145/// much is buffered/applying, and whether the byte budget or peer status is
146/// blocking new downloads.
147pub mod block_sync_trace {
148    /// Trace row event field.
149    pub const EVENT: &str = "event";
150    /// Peer field.
151    pub const PEER: &str = "peer";
152    /// Action/event/message kind field.
153    pub const KIND: &str = "kind";
154    /// Height field.
155    pub const HEIGHT: &str = "height";
156    /// Hash field.
157    pub const HASH: &str = "hash";
158    /// Range start height field.
159    pub const RANGE_START: &str = "range_start";
160    /// Range count field.
161    pub const RANGE_COUNT: &str = "range_count";
162    /// Expected/requested count field.
163    pub const EXPECTED_COUNT: &str = "expected_count";
164    /// Estimated byte reservation for a requested range.
165    pub const ESTIMATED_BYTES: &str = "estimated_bytes";
166    /// Serialized byte size of a received body.
167    pub const SERIALIZED_BYTES: &str = "serialized_bytes";
168    /// Attributed decoded-memory size of a block body.
169    pub const DECODED_ATTRIBUTED_MEMORY_SIZE_BYTES: &str = "decoded_attributed_memory_size_bytes";
170    /// Decoded attributed-memory bytes queued at the Sequencer input.
171    pub const SEQUENCER_INPUT_DECODED_ATTRIBUTED_MEMORY_BYTES: &str =
172        "sequencer_input_decoded_attributed_memory_bytes";
173    /// Decoded attributed-memory bytes retained in the reorder buffer.
174    pub const REORDER_DECODED_ATTRIBUTED_MEMORY_BYTES: &str =
175        "reorder_decoded_attributed_memory_bytes";
176    /// Decoded attributed-memory bytes retained in applying entries.
177    pub const APPLYING_DECODED_ATTRIBUTED_MEMORY_BYTES: &str =
178        "applying_decoded_attributed_memory_bytes";
179    /// Aggregate decoded attributed-memory bytes in the active body pipeline.
180    pub const ACTIVE_PIPELINE_DECODED_ATTRIBUTED_MEMORY_BYTES: &str =
181        "active_pipeline_decoded_attributed_memory_bytes";
182    /// End-to-end elapsed milliseconds for a traced operation.
183    pub const ELAPSED_MS: &str = "elapsed_ms";
184    /// Elapsed milliseconds before response frames were ready to send.
185    pub const PREPARE_ELAPSED_MS: &str = "prepare_elapsed_ms";
186    /// Elapsed milliseconds spent enqueueing response frames.
187    pub const SEND_ELAPSED_MS: &str = "send_elapsed_ms";
188    /// Commit result label (`committed`, `duplicate`, `rejected`, `timed_out`).
189    pub const RESULT: &str = "result";
190    /// Bounded reason field.
191    pub const REASON: &str = "reason";
192    /// Error detail field.
193    pub const ERROR: &str = "error";
194    /// Block apply token field.
195    pub const APPLY_TOKEN: &str = "apply_token";
196    /// Scheduler/query lower bound for block body requests.
197    pub const REQUEST_FLOOR: &str = "request_floor";
198    /// Highest contiguous body height already submitted for apply.
199    pub const BODY_DOWNLOAD_FLOOR: &str = "body_download_floor";
200    /// First height not yet in the contiguous body-download floor.
201    pub const FLOOR_GAP_HEIGHT: &str = "floor_gap_height";
202    /// Reactor-local classification for the first missing body height.
203    pub const FLOOR_GAP_STATE: &str = "floor_gap_state";
204    /// Peers advertising the first missing body height.
205    pub const FLOOR_GAP_SERVABLE_PEERS: &str = "floor_gap_servable_peers";
206    /// Peers with free request slots that advertise the first missing body height.
207    pub const FLOOR_GAP_AVAILABLE_PEERS: &str = "floor_gap_available_peers";
208    /// Peers with outstanding requests that include the first missing body height.
209    pub const FLOOR_GAP_OUTSTANDING_PEERS: &str = "floor_gap_outstanding_peers";
210    /// Age in milliseconds of the oldest outstanding request for the first missing body height.
211    pub const FLOOR_GAP_OLDEST_OUTSTANDING_MS: &str = "floor_gap_oldest_outstanding_ms";
212    /// Remaining milliseconds until the next outstanding request deadline for the first missing body height.
213    pub const FLOOR_GAP_NEXT_DEADLINE_MS: &str = "floor_gap_next_deadline_ms";
214    /// Highest verified (committed) block-body height.
215    pub const VERIFIED_BLOCK_TIP: &str = "verified_block_tip";
216    /// Best header tip driving the body-download target.
217    pub const BEST_HEADER_TIP: &str = "best_header_tip";
218    /// Header tip minus verified body tip.
219    pub const BODY_LAG: &str = "body_lag";
220    /// Count of blocks submitted-but-not-yet-committed (held against budget).
221    pub const APPLYING: &str = "applying";
222    /// Count of applying blocks already submitted to the verifier driver.
223    pub const SUBMITTED_APPLIES: &str = "submitted_applies";
224    /// Count of out-of-order bodies buffered awaiting a contiguous prefix.
225    pub const REORDER: &str = "reorder";
226    /// Count of outstanding (in-flight) range requests across peers.
227    pub const OUTSTANDING: &str = "outstanding";
228    /// Remaining in-flight body byte budget.
229    pub const BUDGET_AVAILABLE: &str = "budget_available";
230    /// Reserved in-flight body byte budget.
231    pub const BUDGET_RESERVED: &str = "budget_reserved";
232    /// Reserved in-flight body byte budget observed at a single body event
233    /// (e.g. immediately after a receive-path shrink or a commit-path release).
234    pub const BUDGET_RESERVED_AFTER: &str = "budget_reserved_after";
235    /// Body bytes received off the wire per second (download throughput).
236    pub const RECEIVED_BYTES_PER_SEC: &str = "received_bytes_per_sec";
237    /// Bodies received off the wire per second (download throughput).
238    pub const RECEIVED_BLOCKS_PER_SEC: &str = "received_blocks_per_sec";
239    /// Body bytes committed to the chain per second (commit throughput).
240    pub const COMMITTED_BYTES_PER_SEC: &str = "committed_bytes_per_sec";
241    /// Bodies committed to the chain per second (commit throughput).
242    pub const COMMITTED_BLOCKS_PER_SEC: &str = "committed_blocks_per_sec";
243    /// 1 when at least one peer has free request slots but the byte budget is too
244    /// low to issue another worst-case block (download is budget-limited).
245    pub const DOWNLOAD_BLOCKED_ON_BUDGET: &str = "download_blocked_on_budget";
246    /// Peers with received status that currently have free outbound request slots.
247    pub const PEERS_WANTING_SLOTS: &str = "peers_wanting_slots";
248    /// Connected block-sync peers.
249    pub const PEERS: &str = "peers";
250    /// Active reactor service sessions after this event.
251    pub const ACTIVE_CONNECTIONS: &str = "active_connections";
252    /// Connected block-sync peers whose status we have received (schedulable).
253    pub const PEERS_WITH_STATUS: &str = "peers_with_status";
254    /// Lowest height still in the body-sync `needed` set (the gap to fetch next).
255    pub const NEEDED_MIN: &str = "needed_min";
256    /// Number of heights in the body-sync `needed` set after buffer filtering.
257    pub const NEEDED_COUNT: &str = "needed_count";
258    /// Number of ranges queued in the scheduler.
259    pub const QUEUE_LEN: &str = "queue_len";
260    /// Number of block heights queued in the scheduler.
261    pub const QUEUE_BLOCKS: &str = "queue_blocks";
262    /// Lowest start height across queued scheduler ranges.
263    pub const QUEUE_MIN_START: &str = "queue_min_start";
264    /// Number of distinct assigned range keys in the scheduler.
265    pub const ASSIGNED_LEN: &str = "assigned_len";
266    /// Count of locally queued, in-flight, buffered, or applying block bodies.
267    pub const LOCAL_BODY_WORK: &str = "local_body_work";
268    /// Local body work threshold below which the reactor refills from state.
269    pub const REFILL_LOW_WATER: &str = "refill_low_water";
270    /// Highest end height across the scheduler's covered intervals.
271    pub const COVERED_MAX_END: &str = "covered_max_end";
272
273    /// Peer status received (servable body range advertised by the peer).
274    pub const BLOCK_STATUS_RECEIVED: &str = "block_status_received";
275    /// Local peer status queued for transport.
276    pub const BLOCK_STATUS_SENT: &str = "block_status_sent";
277    /// Local peer status failed to queue for transport.
278    pub const BLOCK_STATUS_SEND_FAILED: &str = "block_status_send_failed";
279    /// Block-sync peer connected to the reactor.
280    pub const BLOCK_PEER_CONNECTED: &str = "block_peer_connected";
281    /// Block-sync peer disconnected from the reactor.
282    pub const BLOCK_PEER_DISCONNECTED: &str = "block_peer_disconnected";
283    /// Block-sync peer rejected for a protocol/liveness reason.
284    pub const BLOCK_PEER_PROTOCOL_REJECT: &str = "block_peer_protocol_reject";
285    /// Body range request sent to a peer.
286    pub const BLOCK_GET_BLOCKS_SENT: &str = "block_get_blocks_sent";
287    /// Reactor accepted an inbound event.
288    pub const BLOCK_EVENT_RECEIVED: &str = "block_event_received";
289    /// Reactor accepted a decoded inbound block-sync wire message.
290    pub const BLOCK_MESSAGE_RECEIVED: &str = "block_message_received";
291    /// Reactor queued an outbound block-sync wire message for transport.
292    pub const BLOCK_MESSAGE_SENT: &str = "block_message_sent";
293    /// Reactor queued an outbound driver action.
294    pub const BLOCK_ACTION_DISPATCHED: &str = "block_action_dispatched";
295    /// Body received from a peer.
296    pub const BLOCK_BODY_RECEIVED: &str = "block_body_received";
297    /// Body handed from a peer routine to the sequencer input channel.
298    pub const BLOCK_BODY_SEQUENCER_SENT: &str = "block_body_sequencer_sent";
299    /// A peer routine acquired body input capacity before decoding a body.
300    pub const BLOCK_BODY_DECODE_PERMIT: &str = "block_body_decode_permit";
301    /// Sequencer task accepted a received body from its input channel.
302    pub const BLOCK_BODY_ACCEPTED: &str = "block_body_accepted";
303    /// Reactor attempted or completed a control send to the Sequencer task.
304    pub const BLOCK_SEQUENCER_CONTROL_SENT: &str = "block_sequencer_control_sent";
305    /// Body submitted to the verifier for commit.
306    pub const BLOCK_BODY_SUBMITTED: &str = "block_body_submitted";
307    /// Verifier submission could not enter the shared action channel.
308    pub const BLOCK_BODY_SUBMISSION_RETRY_SCHEDULED: &str = "block_body_submission_retry_scheduled";
309    /// Verifier finished applying a submitted body.
310    pub const BLOCK_APPLY_FINISHED: &str = "block_apply_finished";
311    /// Peer reported a requested range as unavailable.
312    pub const BLOCK_RANGE_UNAVAILABLE: &str = "block_range_unavailable";
313    /// Local node queued a block range response for transport.
314    pub const BLOCK_RANGE_RESPONSE_SENT: &str = "block_range_response_sent";
315    /// Work-queue producer added needed heights to the pending set.
316    pub const BLOCK_WORK_EXTENDED: &str = "block_work_extended";
317    /// A peer claimed a contiguous chunk of pending work for issuance.
318    pub const BLOCK_WORK_TAKEN: &str = "block_work_taken";
319    /// A request cleanup found an anomalous outcome returning work to `pending`.
320    pub const BLOCK_WORK_RETURNED: &str = "block_work_returned";
321    /// The floor watchdog force-cancelled an expired request claim.
322    pub const BLOCK_FLOOR_WATCHDOG_CANCELLED: &str = "block_floor_watchdog_cancelled";
323    /// A `try_fill` pass ended having issued no request: the routine went idle this wake.
324    /// Carries [`FILL_STOP_REASON`] plus the slot/budget/work snapshot so a trace can
325    /// attribute carrier idle ("bubble") time to a cause rather than inferring it.
326    pub const BLOCK_FILL_STOP: &str = "block_fill_stop";
327    /// Why a `try_fill` pass stopped issuing requests (`no_status` / `cwnd_saturated` /
328    /// `no_work` / `lookahead_cap` / `retry_avoid` / `budget` / `outbound_full` /
329    /// `send_error` / `internal`).
330    pub const FILL_STOP_REASON: &str = "fill_stop_reason";
331    /// Requests this `try_fill` pass issued before stopping (0 = a candidate bubble).
332    pub const FILL_SENT: &str = "fill_sent";
333    /// Verified body frontier advanced from state.
334    pub const BLOCK_FRONTIERS_CHANGED: &str = "block_frontiers_changed";
335    /// Chain tip reset rolled the body frontier back.
336    pub const BLOCK_CHAIN_TIP_RESET: &str = "block_chain_tip_reset";
337    /// Sequencer classified a frontier reset as growth, preservation, or destructive.
338    pub const BLOCK_FRONTIER_RESET_CLASSIFIED: &str = "block_frontier_reset_classified";
339    /// Periodic reactor state snapshot (the key stall-diagnosis row).
340    pub const BLOCK_SYNC_STATE: &str = "block_sync_state";
341    /// Periodic per-peer BBR controller heartbeat, emitted on a fixed cadence even while
342    /// the peer is idle (unlike the per-delivery `block_body_received` row), so a trace can
343    /// tell a settled controller (cwnd stable, reliability ≈ 1.0) from an oscillating one.
344    pub const BLOCK_PEER_BBR: &str = "block_peer_bbr";
345}
346
347/// Shared discovery trace event names and field keys.
348pub mod discovery_trace {
349    /// Trace row event field.
350    pub const EVENT: &str = "event";
351    /// Peer field.
352    pub const PEER: &str = "peer";
353    /// Dial result label.
354    pub const RESULT: &str = "result";
355
356    /// A discovery dial worker completed and was classified for backoff.
357    pub const DISCOVERY_DIAL_RESULT: &str = "discovery_dial_result";
358}
359
360/// Shared header-sync trace event names and field keys.
361pub mod header_sync_trace {
362    /// Trace row event field.
363    pub const EVENT: &str = "event";
364    /// Peer field.
365    pub const PEER: &str = "peer";
366    /// Action/event/message kind field.
367    pub const KIND: &str = "kind";
368    /// Source peer field for forwarded full-block floods.
369    pub const SOURCE_PEER: &str = "source_peer";
370    /// Height field.
371    pub const HEIGHT: &str = "height";
372    /// Hash field.
373    pub const HASH: &str = "hash";
374    /// Header anchor hash field.
375    pub const ANCHOR_HASH: &str = "anchor_hash";
376    /// Range start height field.
377    pub const RANGE_START: &str = "range_start";
378    /// Range count field.
379    pub const RANGE_COUNT: &str = "range_count";
380    /// Header validation stage field.
381    pub const VALIDATION_STAGE: &str = "validation_stage";
382    /// Concrete validation error kind field.
383    pub const ERROR_KIND: &str = "error_kind";
384    /// Advertised peer range cap field.
385    pub const ADVERTISED_CAP: &str = "advertised_cap";
386    /// Expected header count field.
387    pub const EXPECTED_COUNT: &str = "expected_count";
388    /// In-flight request count field.
389    pub const IN_FLIGHT_COUNT: &str = "in_flight_count";
390    /// Whether header-carried tree-aux roots were requested for this range.
391    pub const WANT_TREE_AUX_ROOTS: &str = "want_tree_aux_roots";
392    /// Whether the range is expected to terminate at a checkpoint.
393    pub const FINALIZED: &str = "finalized";
394    /// Header scheduler priority label (`forward` or `backward`).
395    pub const RANGE_PRIORITY: &str = "range_priority";
396    /// Highest verified full-block/body height observed by the header scheduler.
397    pub const VERIFIED_BLOCK_TIP: &str = "verified_block_tip";
398    /// Durable finalized height observed by the header scheduler.
399    pub const FINALIZED_HEIGHT: &str = "finalized_height";
400    /// Durable best header tip observed by the header scheduler.
401    pub const BEST_HEADER_TIP: &str = "best_header_tip";
402    /// Number of header-carried tree-aux roots present on this send/receive.
403    pub const TREE_AUX_ROOTS_LEN: &str = "tree_aux_roots_len";
404    /// First encoded tree-aux root height.
405    pub const FIRST_ROOT_HEIGHT: &str = "first_root_height";
406    /// Last encoded tree-aux root height.
407    pub const LAST_ROOT_HEIGHT: &str = "last_root_height";
408    /// Zero-based index of the first mismatching root.
409    pub const ROOT_MISMATCH_OFFSET: &str = "root_mismatch_offset";
410    /// Expected height of the first mismatching root.
411    pub const EXPECTED_ROOT_HEIGHT: &str = "expected_root_height";
412    /// Actual height of the first mismatching root.
413    pub const ACTUAL_ROOT_HEIGHT: &str = "actual_root_height";
414    /// Destination peer count field.
415    pub const DESTINATION_PEER_COUNT: &str = "destination_peer_count";
416    /// Active reactor service sessions after this event.
417    pub const ACTIVE_CONNECTIONS: &str = "active_connections";
418    /// Ordered-stream generation that owns this header-sync event.
419    pub const SESSION_ID: &str = "session_id";
420    /// Header-sync v7 request identifier, when present.
421    pub const REQUEST_ID: &str = "request_id";
422    /// Negotiated header-sync stream version for this peer session.
423    pub const STREAM_VERSION: &str = "stream_version";
424    /// Bounded reason field.
425    pub const REASON: &str = "reason";
426
427    /// Reactor accepted an inbound event.
428    pub const HEADER_EVENT_RECEIVED: &str = "header_event_received";
429    /// Reactor queued an outbound driver action.
430    pub const HEADER_ACTION_DISPATCHED: &str = "header_action_dispatched";
431    /// Local status sent to a peer.
432    pub const HEADER_STATUS_SENT: &str = "header_status_sent";
433    /// Peer status received.
434    pub const HEADER_STATUS_RECEIVED: &str = "header_status_received";
435    /// Header-sync peer connected to the reactor.
436    pub const HEADER_PEER_CONNECTED: &str = "header_peer_connected";
437    /// Header-sync peer disconnected from the reactor.
438    pub const HEADER_PEER_DISCONNECTED: &str = "header_peer_disconnected";
439    /// Header range request sent.
440    pub const HEADER_GET_HEADERS_SENT: &str = "header_get_headers_sent";
441    /// Header range response received.
442    pub const HEADER_HEADERS_RECEIVED: &str = "header_headers_received";
443    /// Header range response served from local state.
444    pub const HEADER_HEADERS_SERVED: &str = "header_headers_served";
445    /// Header range committed.
446    pub const HEADER_RANGE_COMMITTED: &str = "header_range_committed";
447    /// Header range rejected.
448    pub const HEADER_RANGE_REJECTED: &str = "header_range_rejected";
449    /// NewBlock tip flood received.
450    pub const HEADER_NEW_BLOCK_RECEIVED: &str = "header_new_block_received";
451    /// NewBlock tip flood forwarded.
452    pub const HEADER_NEW_BLOCK_FORWARDED: &str = "header_new_block_forwarded";
453    /// NewBlock tip flood deduped.
454    pub const HEADER_NEW_BLOCK_DEDUPED: &str = "header_new_block_deduped";
455    /// Peer violation observed.
456    pub const HEADER_PEER_VIOLATION: &str = "header_peer_violation";
457    /// Peer violation recorded without disconnecting the peer.
458    pub const HEADER_PEER_VIOLATION_RECORDED: &str = "header_peer_violation_recorded";
459    /// Header frontier advanced.
460    pub const HEADER_FRONTIER_ADVANCED: &str = "header_frontier_advanced";
461    /// Header frontier re-anchored down to the verified block frontier.
462    pub const HEADER_FRONTIER_REANCHORED: &str = "header_frontier_reanchored";
463    /// Missing block bodies reported.
464    pub const HEADER_MISSING_BODIES_REPORTED: &str = "header_missing_bodies_reported";
465    /// The reactor woke because a real retry, timeout, or status deadline expired.
466    pub const HEADER_MAINTENANCE_WAKEUP: &str = "header_maintenance_wakeup";
467}
468
469/// Shared commit/frontier adapter trace event names and field keys.
470pub mod commit_state_trace {
471    /// Trace row event field.
472    pub const EVENT: &str = "event";
473    /// Source driver/subsystem field.
474    pub const SOURCE: &str = "source";
475    /// Height field.
476    pub const HEIGHT: &str = "height";
477    /// Hash field.
478    pub const HASH: &str = "hash";
479    /// Range start height field.
480    pub const RANGE_START: &str = "range_start";
481    /// Range count field.
482    pub const RANGE_COUNT: &str = "range_count";
483    /// Number of header-carried tree-aux roots supplied to this commit.
484    pub const TREE_AUX_ROOTS_LEN: &str = "tree_aux_roots_len";
485    /// Result label field.
486    pub const RESULT: &str = "result";
487    /// Stable state error variant label field.
488    pub const ERROR_VARIANT: &str = "error_variant";
489    /// Debug-formatted state error field.
490    pub const ERROR_DEBUG: &str = "error_debug";
491    /// Bounded reason field.
492    pub const REASON: &str = "reason";
493    /// Reactor-local block apply token field.
494    pub const APPLY_TOKEN: &str = "apply_token";
495    /// Apply class field (`checkpoint` or `full`).
496    pub const APPLY_CLASS: &str = "apply_class";
497    /// Finalized height observed from state.
498    pub const FINALIZED_HEIGHT: &str = "finalized_height";
499    /// Verified full-block/body tip height.
500    pub const VERIFIED_BLOCK_TIP: &str = "verified_block_tip";
501    /// Verified full-block/body tip hash.
502    pub const VERIFIED_BLOCK_HASH: &str = "verified_block_hash";
503    /// Best header tip height.
504    pub const BEST_HEADER_TIP: &str = "best_header_tip";
505    /// Elapsed milliseconds field.
506    pub const ELAPSED_MS: &str = "elapsed_ms";
507    /// Diagnostic attribution of why a commit was still pending at the stall deadline
508    /// (see [`COMMIT_STALL_CONTIGUOUS_HEAD`] / [`COMMIT_STALL_BEHIND_PREFIX`]). Purely a
509    /// trace label — it never gates the commit.
510    pub const COMMIT_STALL_REASON: &str = "commit_stall_reason";
511    /// Highest contiguously-committed height the committer had reached at the stall.
512    pub const COMMITTED_MARKER: &str = "committed_marker";
513    /// Highest height the committer had fired a commit for at the stall (submission
514    /// high-water — lets analysis see whether the range above the stalled block was
515    /// even submitted to the verifier yet).
516    pub const FIRED_HIGH_WATER: &str = "fired_high_water";
517    /// Number of fired-but-unresolved commits at the stall (concurrency / batch depth).
518    pub const COMMITS_IN_FLIGHT: &str = "commits_in_flight";
519    /// Peer field.
520    pub const PEER: &str = "peer";
521    /// Queue length field.
522    pub const QUEUE_LEN: &str = "queue_len";
523    /// In-flight count field.
524    pub const IN_FLIGHT_COUNT: &str = "in_flight_count";
525    /// Action kind field.
526    pub const ACTION: &str = "action";
527    /// Whether an optional frontier was present.
528    pub const LOCAL_FRONTIER: &str = "local_frontier";
529
530    /// Driver received a reactor action.
531    pub const ACTION_RECEIVED: &str = "action_received";
532    /// State read started.
533    pub const STATE_READ_START: &str = "state_read_start";
534    /// State read completed successfully.
535    pub const STATE_READ_SUCCESS: &str = "state_read_success";
536    /// State read failed or returned an unexpected response.
537    pub const STATE_READ_ERROR: &str = "state_read_error";
538    /// State read timed out.
539    pub const STATE_READ_TIMEOUT: &str = "state_read_timeout";
540    /// Block submit was queued in the driver.
541    pub const BLOCK_SUBMIT_QUEUED: &str = "block_submit_queued";
542    /// Verifier commit started.
543    pub const COMMIT_START: &str = "commit_start";
544    /// Verifier commit exceeded the driver timeout but is still being awaited.
545    pub const COMMIT_STALLED: &str = "commit_stalled";
546    /// [`COMMIT_STALL_REASON`] value: the committed tip sat immediately below the stalled
547    /// block, so it was the contiguous head — the gate is downstream of submission (the
548    /// checkpoint batch above it is still filling in the verifier, or verify+persist on
549    /// the head is slow). This is the "make commit faster" signal.
550    pub const COMMIT_STALL_CONTIGUOUS_HEAD: &str = "contiguous_head";
551    /// [`COMMIT_STALL_REASON`] value: a lower contiguous block had not committed yet, so
552    /// the stalled block was blocked behind the un-committed prefix (floor / range
553    /// head-of-line).
554    pub const COMMIT_STALL_BEHIND_PREFIX: &str = "behind_committed_prefix";
555    /// Verifier commit finished.
556    pub const COMMIT_FINISH: &str = "commit_finish";
557    /// Post-commit frontier query started.
558    pub const FRONTIER_QUERY_START: &str = "frontier_query_start";
559    /// Post-commit frontier query finished.
560    pub const FRONTIER_QUERY_FINISH: &str = "frontier_query_finish";
561    /// Driver sent an event back to a reactor.
562    pub const REACTOR_EVENT_SENT: &str = "reactor_event_sent";
563    /// Delayed checkpoint frontier refresh attempted.
564    pub const CHECKPOINT_REFRESH_ATTEMPT: &str = "checkpoint_refresh_attempt";
565    /// Delayed checkpoint frontier refresh sent a frontier event.
566    pub const CHECKPOINT_REFRESH_SENT: &str = "checkpoint_refresh_sent";
567    /// Header-sync driver notified block sync about a header tip.
568    pub const BLOCK_SYNC_NOTIFY_SENT: &str = "block_sync_notify_sent";
569    /// Chain-tip mirror observed a watch action.
570    pub const CHAIN_TIP_ACTION: &str = "chain_tip_action";
571    /// Chain-tip mirror derived local frontiers.
572    pub const FRONTIER_DERIVED: &str = "frontier_derived";
573    /// Shared sync exchange accepted or ignored a frontier update.
574    pub const SYNC_FRONTIER_TRANSITION: &str = "sync_frontier_transition";
575    /// Monotonic shared sync exchange transition sequence.
576    pub const SEQUENCE: &str = "sequence";
577    /// Shared sync exchange transition cause.
578    pub const CAUSE: &str = "cause";
579    /// Previous finalized frontier height.
580    pub const OLD_FINALIZED_HEIGHT: &str = "old_finalized_height";
581    /// Previous finalized frontier hash.
582    pub const OLD_FINALIZED_HASH: &str = "old_finalized_hash";
583    /// Previous verified body frontier height.
584    pub const OLD_VERIFIED_BODY_HEIGHT: &str = "old_verified_body_height";
585    /// Previous verified body frontier hash.
586    pub const OLD_VERIFIED_BODY_HASH: &str = "old_verified_body_hash";
587    /// Previous best header frontier height.
588    pub const OLD_BEST_HEADER_HEIGHT: &str = "old_best_header_height";
589    /// Previous best header frontier hash.
590    pub const OLD_BEST_HEADER_HASH: &str = "old_best_header_hash";
591    /// New finalized frontier height.
592    pub const NEW_FINALIZED_HEIGHT: &str = "new_finalized_height";
593    /// New finalized frontier hash.
594    pub const NEW_FINALIZED_HASH: &str = "new_finalized_hash";
595    /// New verified body frontier height.
596    pub const NEW_VERIFIED_BODY_HEIGHT: &str = "new_verified_body_height";
597    /// New verified body frontier hash.
598    pub const NEW_VERIFIED_BODY_HASH: &str = "new_verified_body_hash";
599    /// New best header frontier height.
600    pub const NEW_BEST_HEADER_HEIGHT: &str = "new_best_header_height";
601    /// New best header frontier hash.
602    pub const NEW_BEST_HEADER_HASH: &str = "new_best_header_hash";
603}
604
605/// Cloneable Zakura trace emitter.
606#[derive(Clone, Debug)]
607pub struct ZakuraTrace {
608    tracer: JsonlTracer,
609    node: Arc<str>,
610    started: Instant,
611}
612
613impl ZakuraTrace {
614    /// Create a no-op trace emitter.
615    pub fn noop() -> Self {
616        Self::new(JsonlTracer::noop(), zakura_jsonl_trace::node_id())
617    }
618
619    /// Create a trace emitter with an explicit node label.
620    pub fn new(tracer: JsonlTracer, node: impl Into<Arc<str>>) -> Self {
621        Self {
622            tracer,
623            node: node.into(),
624            started: Instant::now(),
625        }
626    }
627
628    /// Return the underlying JSONL tracer.
629    pub fn tracer(&self) -> &JsonlTracer {
630        &self.tracer
631    }
632
633    /// Return true when this emitter will attempt to write rows.
634    pub fn is_enabled(&self) -> bool {
635        self.tracer.is_enabled()
636    }
637
638    /// Emit one event row without awaiting or back-pressuring the caller.
639    pub fn emit(&self, table: ZakuraTraceTable, event: ZakuraTraceEvent<'_>) {
640        self.emit_with(table, |row| event.insert_into(row));
641    }
642
643    /// Emit one event row, building the row only when a queue slot is reserved.
644    ///
645    /// Reserving the bounded channel slot first means the `build` closure and
646    /// serialization never run when the queue is full or the writer has closed,
647    /// keeping attacker-rate emit sites cheap once the writer falls behind.
648    pub fn emit_with(&self, table: ZakuraTraceTable, build: impl FnOnce(&mut Map<String, Value>)) {
649        let Ok(permit) = self.tracer.try_reserve() else {
650            return;
651        };
652
653        let mut row = Map::new();
654        row.insert("ts".to_string(), elapsed_micros(self.started.elapsed()));
655        row.insert("node".to_string(), Value::String(self.node.to_string()));
656        build(&mut row);
657
658        if let Ok(line) = serde_json::to_vec(&Value::Object(row)) {
659            permit.send(JsonlWriteEvent {
660                table: table.table(),
661                file_name: table.file_name(),
662                line,
663            });
664        }
665    }
666}
667
668impl Default for ZakuraTrace {
669    fn default() -> Self {
670        Self::noop()
671    }
672}
673
674/// A single Zakura trace event before serialization.
675#[derive(Clone, Debug)]
676pub struct ZakuraTraceEvent<'a> {
677    event: &'static str,
678    conn: Option<u64>,
679    stream: Option<u64>,
680    payload_len: Option<u64>,
681    frame_len: Option<u64>,
682    max_frame_bytes: Option<u64>,
683    peer: Option<&'a str>,
684    role: Option<&'static str>,
685    phase: Option<&'static str>,
686    reason: Option<&'static str>,
687    selected_protocol: Option<u16>,
688    direction: Option<&'static str>,
689    stream_kind: Option<&'static str>,
690    network: Option<&'static str>,
691}
692
693impl<'a> ZakuraTraceEvent<'a> {
694    /// Create an event row with the required dotted event name.
695    pub fn new(event: &'static str) -> Self {
696        Self {
697            event,
698            conn: None,
699            stream: None,
700            payload_len: None,
701            frame_len: None,
702            max_frame_bytes: None,
703            peer: None,
704            role: None,
705            phase: None,
706            reason: None,
707            selected_protocol: None,
708            direction: None,
709            stream_kind: None,
710            network: None,
711        }
712    }
713
714    /// Attach a local connection id.
715    pub fn conn(mut self, conn: u64) -> Self {
716        self.conn = Some(conn);
717        self
718    }
719
720    /// Attach a local stream id.
721    pub fn stream(mut self, stream: u64) -> Self {
722        self.stream = Some(stream);
723        self
724    }
725
726    /// Attach a declared frame payload length.
727    pub fn payload_len(mut self, payload_len: u64) -> Self {
728        self.payload_len = Some(payload_len);
729        self
730    }
731
732    /// Attach an encoded frame length.
733    pub fn frame_len(mut self, frame_len: u64) -> Self {
734        self.frame_len = Some(frame_len);
735        self
736    }
737
738    /// Attach the effective frame byte cap used by the receiver.
739    pub fn max_frame_bytes(mut self, max_frame_bytes: u64) -> Self {
740        self.max_frame_bytes = Some(max_frame_bytes);
741        self
742    }
743
744    /// Attach a bounded peer label.
745    pub fn peer(mut self, peer: &'a str) -> Self {
746        self.peer = Some(peer);
747        self
748    }
749
750    /// Attach a bounded peer label when one is available.
751    pub fn maybe_peer(mut self, peer: Option<&'a str>) -> Self {
752        self.peer = peer;
753        self
754    }
755
756    /// Attach a control role label.
757    pub fn role(mut self, role: &'static str) -> Self {
758        self.role = Some(role);
759        self
760    }
761
762    /// Attach a handshake phase label.
763    pub fn phase(mut self, phase: &'static str) -> Self {
764        self.phase = Some(phase);
765        self
766    }
767
768    /// Attach a bounded reason label.
769    pub fn reason(mut self, reason: &'static str) -> Self {
770        self.reason = Some(reason);
771        self
772    }
773
774    /// Attach the selected Zakura protocol version.
775    pub fn selected_protocol(mut self, selected_protocol: u16) -> Self {
776        self.selected_protocol = Some(selected_protocol);
777        self
778    }
779
780    /// Attach a connection direction label.
781    pub fn direction(mut self, direction: &'static str) -> Self {
782        self.direction = Some(direction);
783        self
784    }
785
786    /// Attach a stream kind label.
787    pub fn stream_kind(mut self, stream_kind: &'static str) -> Self {
788        self.stream_kind = Some(stream_kind);
789        self
790    }
791
792    /// Attach a network label.
793    pub fn network(mut self, network: &'static str) -> Self {
794        self.network = Some(network);
795        self
796    }
797
798    fn insert_into(self, row: &mut Map<String, Value>) {
799        row.insert("event".to_string(), Value::String(self.event.to_string()));
800        insert_optional_u64(row, "conn", self.conn);
801        insert_optional_u64(row, "stream", self.stream);
802        insert_optional_u64(row, "payload_len", self.payload_len);
803        insert_optional_u64(row, "frame_len", self.frame_len);
804        insert_optional_u64(row, "max_frame_bytes", self.max_frame_bytes);
805        insert_optional_str(row, "peer", self.peer);
806        insert_optional_str(row, "role", self.role);
807        insert_optional_str(row, "phase", self.phase);
808        insert_optional_str(row, "reason", self.reason);
809        insert_optional_u64(
810            row,
811            "selected_protocol",
812            self.selected_protocol.map(u64::from),
813        );
814        insert_optional_str(row, "direction", self.direction);
815        insert_optional_str(row, "stream_kind", self.stream_kind);
816        insert_optional_str(row, "network", self.network);
817    }
818}
819
820/// Return a stable, bounded label for a peer id.
821pub fn peer_label(peer_id: &ZakuraPeerId) -> String {
822    let hash = Blake2bParams::new()
823        .hash_length(8)
824        .personal(b"zakura-peer-lbl")
825        .hash(peer_id.as_bytes());
826    format!("peer:{}", hex::encode(hash.as_bytes()))
827}
828
829/// Return the low-cardinality trace label for a rejection reason.
830pub fn reject_reason_label(reason: ZakuraRejectReason) -> &'static str {
831    match reason {
832        ZakuraRejectReason::UnsupportedPreludeVersion => "unsupported_prelude_version",
833        ZakuraRejectReason::IncompatibleZakuraProtocol => "incompatible_zakura_protocol",
834        ZakuraRejectReason::WrongNetwork => "wrong_network",
835        ZakuraRejectReason::WrongChain => "wrong_chain",
836        ZakuraRejectReason::MissingRequiredCapability => "missing_required_capability",
837        ZakuraRejectReason::ResourceLimit => "resource_limit",
838        ZakuraRejectReason::AlreadyConnected => "already_connected",
839        ZakuraRejectReason::TemporaryUnavailable => "temporary_unavailable",
840    }
841}
842
843/// Return a stable, bounded label for non-blocking ordered-stream send errors.
844pub(crate) fn ordered_send_error_label(
845    error: &crate::zakura::transport::OrderedSendError,
846) -> &'static str {
847    match error {
848        crate::zakura::transport::OrderedSendError::Full => "full",
849        crate::zakura::transport::OrderedSendError::Closed => "closed",
850        crate::zakura::transport::OrderedSendError::Encode(_) => "encode",
851    }
852}
853
854fn elapsed_micros(elapsed: Duration) -> Value {
855    let micros = u64::try_from(elapsed.as_micros()).unwrap_or(u64::MAX);
856    Value::Number(Number::from(micros))
857}
858
859fn insert_optional_str(row: &mut Map<String, Value>, key: &'static str, value: Option<&str>) {
860    row.insert(
861        key.to_string(),
862        value.map_or(Value::Null, |value| Value::String(value.to_string())),
863    );
864}
865
866fn insert_optional_u64(row: &mut Map<String, Value>, key: &'static str, value: Option<u64>) {
867    row.insert(
868        key.to_string(),
869        value.map_or(Value::Null, |value| Value::Number(Number::from(value))),
870    );
871}
872
873#[cfg(test)]
874mod tests {
875    use std::sync::{
876        atomic::{AtomicBool, Ordering},
877        Arc,
878    };
879
880    use tokio::sync::mpsc;
881
882    use super::*;
883
884    #[test]
885    fn noop_trace_does_not_build_rows() {
886        let trace = ZakuraTrace::noop();
887        let called = Arc::new(AtomicBool::new(false));
888        let called_in_emit = called.clone();
889
890        trace.emit_with(CONN_TABLE, |_| {
891            called_in_emit.store(true, Ordering::SeqCst);
892        });
893
894        assert!(!called.load(Ordering::SeqCst));
895    }
896
897    #[test]
898    fn full_queue_does_not_build_rows() {
899        // Capacity 1, pre-filled so the next reserve fails with `Full`.
900        let (tx, _rx) = mpsc::channel(1);
901        let tracer = JsonlTracer::new(tx);
902        let trace = ZakuraTrace::new(tracer, "node-full");
903
904        trace.emit(CONN_TABLE, ZakuraTraceEvent::new("conn.fill"));
905
906        let called = Arc::new(AtomicBool::new(false));
907        let called_in_emit = called.clone();
908        trace.emit_with(CONN_TABLE, |_| {
909            called_in_emit.store(true, Ordering::SeqCst);
910        });
911
912        assert!(
913            !called.load(Ordering::SeqCst),
914            "build closure must not run when the queue is full"
915        );
916    }
917
918    #[test]
919    fn closed_queue_does_not_build_rows() {
920        // Dropping the receiver closes the channel; reserve fails with `Closed`.
921        let (tx, rx) = mpsc::channel(1);
922        let tracer = JsonlTracer::new(tx);
923        let trace = ZakuraTrace::new(tracer, "node-closed");
924        drop(rx);
925
926        let called = Arc::new(AtomicBool::new(false));
927        let called_in_emit = called.clone();
928        trace.emit_with(CONN_TABLE, |_| {
929            called_in_emit.store(true, Ordering::SeqCst);
930        });
931
932        assert!(
933            !called.load(Ordering::SeqCst),
934            "build closure must not run when the queue is closed"
935        );
936        assert!(
937            !trace.is_enabled(),
938            "trace must report disabled once the receiver is dropped"
939        );
940    }
941}