Lifecycle — Block Stage Transitions

Subscribe

{"subscribe": ["Lifecycle"]}

Message Format

{
  "seq": 46,
  "Lifecycle": {
    "block_hash": "0xabc123...",
    "block_number": 56147820,
    "from_stage": "Proposed",
    "to_stage": "Voted",
    "time_in_previous_stage_ms": 412.5,
    "block_age_ms": 412.5,
    "txn_count": 150,
    "gas_used": null
  }
}

Fields

FieldTypeDescription
block_hashstringConsensus block ID (0x-prefixed)
block_numberu64Block number
from_stagestring | nullPrevious stage (null for initial Proposed)
to_stagestringNew stage: Proposed, Voted, Finalized, Verified, Rejected
time_in_previous_stage_msf64 | nullTime spent in previous stage (ms)
block_age_msf64Total time since Proposed (ms)
txn_countusizeNumber of transactions in the block
gas_usedu64 | nullGas used (available after BlockEnd)

MonadBFT Stages

Proposed (~0 ms) → Voted (~400 ms) → Finalized (~800 ms) → Verified (terminal)
                                                             │
                   (any stage) ──────────────────────────► Rejected (terminal)
StageTrigger EventLatencyGuarantee
ProposedBlockStart~0 msSpeculative
VotedBlockQC~400 msSpeculative finality (2/3+ validators)
FinalizedBlockFinalized~800 msFull finality — irreversible
VerifiedBlockVerifiedAfter finalizationState root verified
RejectedBlockRejectVariesBlock dropped

Description

Lifecycle is a high-level metric tracking block transitions between consensus stages. Each transition emits one message.

Typical sequence for a block:

  1. Lifecycle: Proposed (block_age_ms ≈ 0)
  2. Lifecycle: Voted (block_age_ms ≈ 400)
  3. Lifecycle: Finalized (block_age_ms ≈ 800)
  4. Lifecycle: Verified (block_age_ms ≈ 900+)

Usage Examples

Finality Monitoring

{"subscribe": ["Lifecycle"]}
ws.onmessage = (event) => {
  const msg = JSON.parse(event.data);
  if (msg.lifecycle) {
    const lc = msg.Lifecycle;
    console.log(`Block ${lc.block_number}: ${lc.from_stage || "new"} → ${lc.to_stage} (${lc.block_age_ms.toFixed(0)} ms)`);
  }
};

Pattern: Buffering + Finality Confirmation

For applications requiring finality (bridge, exchange):

const pendingBlocks = new Map(); // block_number → events[]

ws.onmessage = (event) => {
  const msg = JSON.parse(event.data);

  if (msg.Events) {
    for (const e of msg.Events) {
      if (!pendingBlocks.has(e.block_number)) pendingBlocks.set(e.block_number, []);
      pendingBlocks.get(e.block_number).push(e);
    }
  }

  if (msg.lifecycle) {
    const lc = msg.Lifecycle;
    if (lc.to_stage === "Finalized") {
      const events = pendingBlocks.get(lc.block_number);
      if (events) {
        processConfirmedEvents(events); // safe to process
        pendingBlocks.delete(lc.block_number);
      }
    }
    if (lc.to_stage === "Rejected") {
      pendingBlocks.delete(lc.block_number); // discard
    }
  }
};

Metrics Dashboard

{"subscribe": ["TPS", "Lifecycle", "ContentionData"]}

3 items = within default subscription limit.

REST Equivalent

# All tracked blocks
curl http://localhost:8443/v1/blocks/lifecycle

# Specific block
curl http://localhost:8443/v1/blocks/56147820/lifecycle

Frequency

2-4 messages per block (one per stage transition). At 400 ms block time, ~5-10 messages per second.