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
# 7B73B9E8D8E6E8E8A8B8C8D8E8F8A8B8C8D8E8F8A8B8C8D8E8F8A8B8C8D8E8F8

2. 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% threshold

  • tfLostMajority (0x00020000): Amendment fell below the threshold

  • 0: 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_AMENDMENT

  • Account: 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 > threshold29 > 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