Consensus Lifecycle

← Back to Consensus I: Node, Consensus, and Ledger Fundamentals


Introduction

This chapter traces through the complete consensus lifecycle, from the initiation of a new round to the acceptance of a validated ledger. Understanding this flow is essential for debugging consensus issues, implementing protocol changes, and reasoning about network behavior.

We follow the execution path through the actual codebase, showing how each component interacts to achieve distributed agreement.

High-Level Flow

┌──────────────────────────────────────────────────────────────────────┐
│                     CONSENSUS LIFECYCLE                              │
│                                                                      │
│  Application.cpp                                                     │
│       │                                                              │
│       ▼                                                              │
│  NetworkOPs::beginConsensus()                                        │
│       │                                                              │
│       ▼                                                              │
│  ┌─────────────────────────────────────────────────────────────┐     │
│  │                    CONSENSUS ROUND                          │     │
│  │                                                             │     │
│  │   preStartRound() ──► startRound() ──► startRoundInternal() │     │
│  │         │                                                   │     │
│  │         ▼                                                   │     │
│  │   ┌──────────┐    ┌────────────┐    ┌──────────┐            │     │
│  │   │   OPEN   │───►│ ESTABLISH  │───►│ ACCEPTED │            │     │
│  │   └──────────┘    └────────────┘    └──────────┘            │     │
│  │         │               │                │                  │     │
│  │     timerEntry()    timerEntry()     onAccept()             │     │
│  └─────────────────────────────────────────────────────────────┘     │
│       │                                                              │
│       ▼                                                              │
│  NetworkOPs::endConsensus()                                          │
│       │                                                              │
│       ▼                                                              │
│  Loop back to beginConsensus()                                       │
└──────────────────────────────────────────────────────────────────────┘

Stage 1: Initiating Consensus

Entry Point: Application.cpp

Consensus begins at application startup:

Key Actions:

  • Passes the hash of the last closed ledger

  • NetworkOPs coordinates the consensus initiation

  • Failure to start consensus is fatal

Stage 2: Pre-Start Preparation

Function: RCLConsensus::Adaptor::preStartRound

Before a round begins, the system prepares:

Checks Performed:

  • Is the node synchronized with the network?

  • Is the previous ledger valid and complete?

  • Are all required data structures ready?

Stage 3: Starting the Round

Function: Consensus::startRound

This function establishes the round parameters:

Parameters Established:

  • Previous ledger hash and object

  • Initial mode (proposing or observing)

  • Timer initialization

  • Peer position tracking

Stage 4: Open Phase

Function: Consensus::startRoundInternal → ConsensusPhase::open

Key Timing Parameters:

Parameter
Value
Purpose

ledgerIDLE_INTERVAL

15s

Max time ledger stays open with no transactions

ledgerMIN_CLOSE

2s

Minimum time ledger must stay open

prevRoundTime/2

Dynamic

Must stay open ≥ half of previous consensus time

proposeFRESHNESS

20s

How long peer proposals remain valid

proposeINTERVAL

12s

How often we must refresh our proposal

The ledger opens to accept transactions:

Key Activities:

playbackProposals():

  • Replays peer proposals received for this ledger that arrived early

  • Recovers proposals from up to 10 recent proposals per peer

  • Filters by prevLedgerID to find relevant proposals

  • Allows fast catch-up when switching ledgers

Example:

timerEntry() in open phase:

  • Called periodically (~1 second via ledgerGRANULARITY)

  • Evaluates close conditions via checkLedger()

  • Calls shouldCloseLedger() to determine if ledger should close

Example:

Close conditions checked:

  • Minimum time elapsed (2s)

  • Has transactions OR idle timeout

  • Not opening too fast (≥ prevRoundTime/2)

  • Enough peers have closed

Stage 5: Close Decision

Function: shouldCloseLedger

Determines if the ledger should close:

Close Decision Process:

Step 1: Safety Checks

  • If timing is unusual (prevRoundTime or timeSincePrevClose out of range) → CLOSE immediately

Step 2: Network Coordination

  • If (proposersClosed + proposersValidated) > prevProposers/2 → CLOSE (follow majority)

Step 3: Transaction-Based Decision

Path A: No Transactions

  • Close only if: timeSincePrevClose ≥ ledgerIDLE_INTERVAL (15s)

  • Purpose: Maintain regular ledger cadence even when idle

Path B: Has Transactions

  • Must satisfy: openTime ≥ ledgerMIN_CLOSE (2s)

  • Must satisfy: openTime ≥ prevRoundTime/2

  • If both pass → CLOSE

Key Parameters:

Parameter
Value
Purpose

ledgerMIN_CLOSE

2s

Minimum time ledger must stay open

ledgerIDLE_INTERVAL

15s

Maximum idle time before forcing close

prevRoundTime/2

Dynamic

Throttle: ledger must stay open ≥ half of previous round time

Stage 6: Closing the Ledger

Function: Consensus::closeLedger

Transitions from open to establish phase:

Actions:

  • Finalizes transaction set for the ledger

  • Creates initial consensus proposal

  • Broadcasts closure to peers

  • Transitions to establish phase

Stage 7: Establish Phase

Function: Consensus::phaseEstablish

Key Timing Parameters:

Parameter
Value
Purpose

ledgerMIN_CONSENSUS

1.95s

Minimum time before consensus can be declared

ledgerMAX_CONSENSUS

15s

Maximum time to wait for lagging validators

ledgerABANDON_CONSENSUS

120s

Absolute maximum round time

ledgerABANDON_CONSENSUS_FACTOR

10×

Dynamic abandonment: min(prevTime × 10, 120s)

avMIN_CONSENSUS_TIME

5s

Minimum time used for avalanche calculations

Avalanche Threshold Progression:

The core of consensus—exchanging proposals and resolving disputes:

Key Functions:

updateOurPositions():

  • Processes peer proposals

  • Adjusts votes on disputed transactions

  • Updates local candidate transaction set

shouldPause():

  • Checks if consensus should pause

  • Handles lagging or non-participating peers

  • Prevents premature advancement

haveConsensus() → checkConsensus() → checkConsensusReached():

  • Evaluates if agreement threshold met

  • Considers timing constraints

  • Returns consensus state (Yes/No/MovedOn/Expired)

Stage 8: Consensus Checking

Consensus Evaluation Logic:

Agreement Threshold:

Required: 80% (fixed - minCONSENSUS_PCT)

This checks if 80% of validators have the SAME complete transaction set (same hash).

Two checks performed:

  1. Do 80% agree with OUR position? → Yes

  2. Did 80% move to next ledger? → MovedOn

Important Distinction:

The 80% threshold here is for final consensus (comparing complete transaction set hashes).

This is separate from avalanche voting (50%→65%→70%→95%) which determines which individual transactions to include during updateOurPositions().

Consensus States:

State
Meaning
Action

No

Not enough agreement yet

Continue voting

Yes

80%+ agree with us

Accept consensus!

MovedOn

80%+ moved to next ledger

We're behind, catch up

Expired

Took too long

Bow out of consensus

Stage 9: Acceptance

Function: RCLConsensus::Adaptor::onAccept

When consensus is reached:

Sub-Functions:

buildLCL():

  • Constructs Last Closed Ledger from agreed transactions

  • Applies transactions in canonical order

  • Produces validated ledger object

LedgerMaster::consensusBuilt():

  • Updates system's view of current ledger

  • Triggers downstream processing

Apply Disputed Transactions:

  • Attempts to apply transactions that weren't included

  • Queues for next round if applicable

Stage 10: Final Acceptance

Function: RCLConsensus::Adaptor::doAccept

Finalizes the acceptance process:

Key Actions:

BuildLedger::buildLedger:

  • Final ledger construction

  • State persistence

app_.getTxQ().processClosedLedger:

  • Updates fee metrics

  • Removes included transactions

  • Manages deferred transactions

app_.openLedger().accept:

  • Applies local transactions not in closed ledger

  • Prepares open ledger for next round

Stage 11: End Consensus

Function: NetworkOPsImp::endConsensus

Completes the round and prepares for the next:

Stage 12: Begin Next Round

Function: NetworkOPsImp::beginConsensus

The cycle continues:

Complete Lifecycle Diagram

Summary

Lifecycle Stages:

Stage
Function
Purpose

1

Application.cpp

Entry point

2

preStartRound

Preparation and validation

3

startRound

Parameter initialization

4

Open phase

Transaction collection

5

shouldCloseLedger

Close decision

6

closeLedger

Transition to establish

7

Establish phase

Proposal exchange

8

checkConsensus

Agreement evaluation

9

onAccept

Consensus acceptance

10

doAccept

Final processing

11

endConsensus

Round completion

12

beginConsensus

Next round

Key Takeaways:

  1. Timer-driven: Periodic events drive state transitions

  2. Iterative: Establish phase loops until consensus

  3. Convergent: Avalanche mechanism's rising thresholds force consensus

  4. Resilient: Handles network issues and recovery

  5. Continuous: Rounds follow each other seamlessly

In the next chapter, we'll explore the ledger architecture that stores the results of consensus.

Last updated