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:
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:
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:
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:
Do 80% agree with OUR position? → Yes
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:
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:
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:
Timer-driven: Periodic events drive state transitions
Iterative: Establish phase loops until consensus
Convergent: Avalanche mechanism's rising thresholds force consensus
Resilient: Handles network issues and recovery
Continuous: Rounds follow each other seamlessly
In the next chapter, we'll explore the ledger architecture that stores the results of consensus.
Last updated

