Voting and Activation
← Back to Building an Amendments I: Lifecycle and Impact on Core Protocol
Introduction
The voting and activation mechanism for amendments is at the heart of XRPL's decentralized governance. This system allows the network to make collective decisions on protocol changes without a central authority, while ensuring a high level of consensus before any modification.
In this section, we will dive into the technical details of the voting process: how validators express their preferences, how these votes are collected and aggregated, how the threshold (over 80%) is calculated and verified, and finally how the network automatically activates amendments once consensus is reached.
We will continue to follow the example of Subscriptions (XLS-0078) to concretely illustrate each mechanism.
Validator Voting Mechanism
Vote Expression
A validator expresses their vote for an amendment in several ways:
1. Static configuration: Via the rippled.cfg configuration file:
[amendments]
# Vote for Subscriptions
7B73B9E8D8E6E8E8A8B8C8D8E8F8A8B8C8D8E8F8A8B8C8D8E8F8A8B8C8D8E8F8
# Vote against (or remove vote) by commenting out or removing the line
# 7B73B9E8D8E6E8E8A8B8C8D8E8F8A8B8C8D8E8F8A8B8C8D8E8F8A8B8C8D8E8F82. Dynamic RPC command: Via the admin interface:
3. Default vote: If no explicit configuration is provided, the default behavior defined in features.macro applies:
Inclusion in Validations
A validator's vote is communicated to the network via the validation messages they publish after validating each ledger.
Structure of a validation message:
Generation of amendments list: This list is generated by the doValidation() function of AmendmentTable:
Network broadcast: The signed validation message is broadcast via the overlay network to all connected peers. Each node that receives this validation extracts and stores the list of voted amendments.
Vote Collection and Aggregation
TrustedVotes Structure
The TrustedVotes class maintains a real-time count of amendment votes from trusted validators.
Internal structure:
Expiration mechanism: Votes are considered "fresh" for 5 minutes. If a validator doesn't publish a new validation within this time, their vote is removed from the count. This ensures counts reflect the current state of active validators.
AmendmentSet: Calculating the Threshold
The AmendmentSet class calculates which amendments have exceeded the required threshold for activation.
Construction:
Threshold calculation (over 80%):
The displayed threshold is calculated as floor(validations * 0.8), but the comparison uses votes > threshold (strictly greater), which ensures over 80% of validators vote for the amendment.
Examples with Subscriptions:
25 trusted validators:
26 validators:
35 validators:
Special case - 1 validator: With a single validator (test networks), the comparison becomes votes >= threshold instead of votes > threshold, allowing the single validator to activate the amendment.
doVoting Function: Vote Orchestration
The doVoting() function is called at each consensus round (before each flag ledger) to determine what actions to take for amendments.
Complete Algorithm
Returned Action Codes
The returned map associates each amendment with an action code:
tfGotMajority(0x00010000): Amendment exceeded the 80% thresholdtfLostMajority(0x00020000): Amendment fell below the threshold0: Amendment must be activated (stability period elapsed)
These codes are used to generate appropriate pseudo-transactions.
EnableAmendment Pseudo-transactions
Pseudo-transaction Generation
Pseudo-transactions are generated by the consensus engine in RCLConsensus::onAccept() after a ledger has been accepted.
Process:
Pseudo-transaction properties:
Type:
ttENABLE_AMENDMENTAccount:
rrrrrrrrrrrrrrrrrrrrrhoLvTp(special account = AccountID(0))No signature: Pseudo-transactions are not signed
No fees: No fee is deducted
TransactionIndex: Generally 0 (first transaction in ledger)
Automatically injected: Generated by the network, not submitted by a user
Processing by Change::applyAmendment
EnableAmendment pseudo-transactions are processed by a specialized function in Change.cpp:
Modified Ledger Fields
Ledger Amendments Object:
Modification during GotMajority: Amendment is added to sfMajorities with its CloseTime.
Modification during Activation: Amendment is moved from sfMajorities to sfAmendments.
Detailed Timeline: Subscriptions
Let's review the complete timeline with voting details:
2025-04-20 14:30:00 UTC (Ledger 86245750)
State before:
2025-04-20 14:32:15 UTC (Ledger 86245789)
A 29th validator activates their vote. The next flag ledger detects the threshold crossing.
Votes collected by TrustedVotes:
Verification: votes > threshold → 29 > 28 ✓ (82.9% > 80%)
doVoting() returns:
Injected pseudo-transaction:
State after:
2025-04-20 → 2025-05-04
Stability period. At each flag ledger, doVoting() checks:
2025-05-04 14:32:15 UTC (Ledger 86652345)
Exactly 2 weeks after majorityTime. The next flag ledger activates the amendment.
doVoting() returns:
Injected pseudo-transaction:
Change::applyAmendment() adds Subscriptions to sfAmendments.
Final state:
Synchronization and Consistency
doValidatedLedger: Post-Validation Update
After each validated ledger, doValidatedLedger() synchronizes the internal state:
This function ensures that even if a node temporarily misses receiving a ledger, it will resynchronize correctly when it receives the validated ledger.
Protection Against Unsupported Amendments
If an amendment is enabled but the node does not support it, the system enters "amendment blocked" mode to protect network integrity.
Detection:
Consequences:
Node ceases to participate in consensus
Node ceases to validate new ledgers
API remains accessible but signals "amendment blocked" state
Node must be updated to resume operations
Notification: A warning is displayed in logs and via the server_info API:
Last updated

