- 16 Feb 2024
- 10 Minutes to read
- DarkLight
- PDF
Consensus Manager
- Updated on 16 Feb 2024
- 10 Minutes to read
- DarkLight
- PDF
This document offers a description of the design and implementation of the Consensus Manager component: its state, public API, and its system-triggered, internal responsibilities.
Low-level details below
If you are only looking for the Consensus Manager’s public interface, please skip directly to the API reference.
Background
In Radix, the Consensus Manager native component is responsible for bridging the Ledger state with the world of distributed Consensus run by the Validator Nodes. This means:
Storing the current leader Validator, Round number and wall-clock time reported by the Consensus algorithm.
Triggering the Epoch changes.
Tracking all registered Validators and picking the top-staked Active Validator Set to run the Consensus.
Calculating and applying the Validator emissions/penalties according to their tracked performance statistics.
Keeping the global settings of all the rules governing the Network’s behaviors mentioned above.
Network-wide Singleton
The singleton Consensus Manager’s instance is created and started by the system during Genesis (i.e. it is not supposed to be instantiated by users). You can find its well-known address for each Network here.
The Consensus Manager’s internal responsibilities (i.e. related to Consensus progress) are performed at the beginning of each Consensus Round, when a special system transaction calls the protected ConsensusManager::next_round()
method.
Apart from that, the Consensus Manager exposes public methods for:
Using the Consensus wall-clock.
Creating a new Validator.
On-ledger State
The ConsensusManager
blueprint defines the following fields:
config
, holding the Network’s Consensus-related configuration, e.g.:maximum number of Active Validators,
target round count and duration of an Epoch,
timeouts for unstaking / unlocking / fee changes,
amount of XRD emitted for Validators on each Epoch change,
cost of creating a Validator,
minimum reliability expected from a Validator (according to which the Network Emission penalties are applied).
state
, holding the Consensus’ progress:current Epoch and Round,
timestamps of the current Epoch’s start:
the “effective start, used to maintain the Epoch’s target duration;
the “actual start”, used only to measure the potential drift of the Epoch’s start.
a reference to the current Leader (among the Active Validator Set).
validator_rewards
containing:the actual XRD Vault holding all Network Fees and Tips collected during the current Epoch (to be distributed on the Epoch change),
accounting information tracking the Rewards assigned to individual Leaders for transactions they proposed (used to calculate the Rewards distribution from the Vault mentioned above).
current_validator_set
, which simply represents the Active Validator Set of the current Epoch, as a list of component addresses, public keys and stakes (captured at the Epoch’s start).current_proposal_statistic
, which tracks a number of successful vs missed proposals of each Validator of the current Epoch (for the Rounds during which it was a Leader).proposer_minute_timestamp
holding a minute-resolution counterpart of theproposer_milli_timestamp
described below.proposer_milli_timestamp
holding a millisecond-resolution Unix timestamp captured by the Leader at the moment of proposing the most recently committed Round. Note: the Engine’s logic ensures that this number is non-decreasing (even if there is a clock skew between consecutive Leaders).
And a single collection:
registered_validators_by_stake
- a sorted index of all currently registered Validators, by their stake (descending):The sort key is calculated as a scaled down and inverted stake of a Validator. This is done so that this single 16-bit integer represents an approximate magnitude of a stake, with the highest stakes being first (in the natural ordering).
This structure ensures convenient and performant selection of next Active Validator Set.
Internal responsibilities
The public-facing services provided by the Consensus Manager are quite modest (accessing the wall-clock and creating new validator components). However, apart from those, it has an important internal Consensus-related responsibility: on each Round, the Consensus Leader automatically triggers the ConsensusManager
’s protected next_round()
method, which progresses the Consensus’ Rounds and Epochs.
Round Change
The Leader of the Round starts it with a special transaction, which provides the details of the new Round (i.e. as inputs to the next_round()
method being called).
This method then:
Updates the wall-clock (i.e. the
proposer_milli_timestamp
andproposer_minute_timestamp
substates) with the timestamp provided in the input.The new timestamp is validated: it must be greater or equal to the previous one, or the transaction will fail.
Updates the proposal statistics of the current Epoch (i.e. the
current_proposal_statistic
).In most cases, on a healthy network, this will simply increment a number of successful rounds of the current Leader.
However, it may happen that one or more Rounds were skipped before the one being started. In such case, the current Leader will provide the list of past Leaders which missed their Rounds (and their “miss counters” will be incremented accordingly).
The statistics are accumulated this way throughout the Epoch - they will be used for calculating potential Emission penalties of each Validator, at the end of the Epoch.
Updates the current Leader reference (within the
state
substate).Determines whether the current Epoch should end (i.e. whether the currently started Round should in fact become the first round of a newly-started Epoch). This simply evaluates the Epoch change condition stored within the
config
substate:If the started Round’s number is strictly less than the configured minimum Round count (currently on mainnet:
500
), the Epoch definitely continues.If the started Round’s number is greater or equal to the configured maximum Round count (currently on mainnet:
3000
), the Epoch definitely ends.If the above hard-limitting special cases do not occur, then the Epoch ends as soon as it lasted for the configured target Epoch duration (currently on mainnet:
5 minutes
).
Applies the actual Round/Epoch change:
If the Epoch is not supposed to end, then simply a new Round’s number is updated (within the
state
substate), and aRoundChangeEvent
is emitted.Otherwise, an Epoch Change is applied, as detailed in the section below.
Epoch Change
When the Round Change logic (i.e. point 4. described above) determines the end of the current Epoch, the Consensus Manager:
Calculates and distributes the Network Emissions to Validators belonging to the ended Epoch’s Active Validator Set:
First, for each Validator, we calculate its “reliability factor”. This is simply rescaling its absolute successful proposal ratio (recorded within
current_proposal_statistic
) into a value relative to the minimum required reliability (set in theconfig
).Example
Let’s assume a Validator had
7
successful and3
missed Rounds.Then, its absolute reliability is
0.7
.If the minimum required reliability is configured as
0.6
, then the rescaling(0.7 - 0.6) / (1.0 - 0.6)
gives us the reliability factor of0.25
.Mainnet configuration note
Currently on mainnet, the minimum required reliability is set to
1.0
. This means that effectively, the reliability factor of any Validator may either be1.0
(if it did not miss any Round during an Epoch) or0.0
(if it missed even one).Then, a configured amount of XRD is minted for Network Emissions purposes (currently on mainnet:
~2853.9
per Epoch, which is approximately300M XRD
per year).Each Validator is assigned a fraction of the above Emission, proportional to its stake (compared to the total stake of the entire Active Validator Set).
However, the actually received amount is multiplied by the Validator’s reliability factor. This means that some of the Emission may not be distributed at all.
Example
Let’s assume a Validator with a
200 XRD
stake, and an Active Validator Set with a total stake of1000 XRD
.For simplicity, let’s say that
100 XRD
is minted for Network Emissions each Epoch.In theory, our Validator is entitled to 20% of this Emission (i.e.
20 XRD
).However, if the Validator missed some of its Rounds, and has reliability factor of
0.25
, then it will actually receive only4 XRD
. The remaining16 XRD
will not be distributed (neither to this Validator, nor to any other from the Active Validator Set).As an implementation detail, the “not distributed” part of the Emission is not explicitly burned, but simply calculated upfront (i.e. appropriately lower Emission is actually minted).
Distributes Proposer Rewards to the ended Epoch’s Active Validator Set:
These Rewards are already calculated (on a per-Validator basis) and stored directly in the
validator_rewards
substate.For context: they contain 100% of the voluntary Tips and 25% of the Network Fees charged for executing the transactions proposed by particular Validator.
Unlike Emissions, they are received in full, regardless of the Validator’s reliability.
Calculates and distributes the remaining shared Rewards to the ended Epoch’s Active Validator Set:
This is supposed to distribute the amount remaining in the
validator_rewards
Vault proportionally among all Active Validators.For context: it comes from 25% of the Network Fees charged for executing all transactions during the ended Epoch. As a side note, one can correctly observe that 50% of those Fees are burned during the transaction execution itself.
These rewards are divided according to each Validator’s effective stake: i.e. its stake multiplied by the reliability factor (the same one as used for Network Emissions distribution).
Example
Let’s assume that the Vault within
validator_rewards
substate contained100 XRD
, and after distributing10 XRD
as Proposer Rewards (according to point 2. above),90 XRD
is left for Active Validator Set’s Rewards.Let’s say that the Active Validator Set consists of only 2 Validators:
A
with stake4000 XRD
and reliability factor1.0
andB
with stake1000 XRD
and reliability factor0.5
.Then, the effective stake of
A
is4000 XRD
, and the effective stake ofB
is500 XRD
.This means that
A
will receive80 XRD
, whileB
will receive10 XRD
.Any leftovers remaining in the Rewards’ Vault (due to rounding down during calculations) will simply be retrievable on the next Epoch. This will normally be negligibly small amounts.
Selects the new Active Validator Set (for the newly-started Epoch):
This simply requires listing the configured number of top-stake Validators (currently on mainnet:
100
) from theregistered_validators_by_stake
collection.The selected Validators are stored in the
current_validator_set
, together with their stake captured at this moment.Naturally, the
current_proposal_statistic
and the per-Validator counters within thevalidator_rewards
are cleared (i.e. all Validators start the Epoch with a clean slate).
Emits the
EpochChangeEvent
:It contains the started Epoch’s number and the new Active Validator Set.
Additionally, it contains a summarized information about the Protocol Versions for which the new Active Validators signalled their readiness.
Updates its
state
substate:The Epoch number is incremented (always by exactly 1; Epochs cannot be skipped, unlike Rounds).
The Round number is set to 0.
The “actual” Epoch start timestamp is set to the Proposer’s timestamp.
The “effective” Epoch start timestamp is calculated, in a way designed to mitigate a small systematic drift caused by network latencies:
If the ended Epoch’s actual duration is within 10% tolerance from the configured target Epoch duration, then
next.effective_start = previous.effective_start + config.target_duration
(i.e. as if that Epoch had precisely the target duration).Otherwise, the effective start timestamp is set to the Proposer’s timestamp (i.e. the same as actual Epoch start timestamp).
API Reference
Methods
This section focuses only on publicly-available methods. The internal ones (i.e. create
, start
, next_round
) use very restrictive access rules (or even completely custom ones), and are automatically called only by the system itself.
get_current_epoch
Gets the current Epoch number.
This is a simple read-out from the state
field.
Name |
|
Type | Method |
Callable By | Public |
Arguments | None |
Returns |
|
get_current_time
Gets the current Proposer timestamp (i.e. not the one committed to ledger, but the one with which it is actually being proposed), with the requested precision.
Name |
|
Type | Method |
Callable By | Public |
Arguments |
|
Returns |
|
compare_current_time
Checks whether the given condition on the current Proposer timestamp is met.
Name |
|
Type | Method |
Callable By | Public |
Arguments |
|
Returns |
|
create_validator
Creates a new Validator component, allowing the caller to configure a Validator Node and potentially participate in the Consensus.
Name |
|
Type | Method |
Callable By | Public |
Arguments |
|
Returns |
|
Events
The Consensus Manager only emits the following progress-related events:
RoundChangeEvent
{
// The new Round.
round: Round
}
Emitted when the Round has changed - with an important exception of an Epoch change (i.e. this event is not emitted when the Round changes to Round::zero()
on the beginning of an Epoch).
EpochChangeEvent
{
// The new Epoch.
epoch: Epoch,
// The new Epoch's Validator Set.
validator_set: ActiveValidatorSet,
// A mapping of protocol version name to a total stake (within the new Epoch's
// Validator Set) that has signalled the readiness for the given protocol update.
// The mapping only contains entries with associated stake of at least 10% of the
// total stake.
significant_protocol_update_readiness: IndexMap<String, Decimal>
}
Emitted when the Epoch has changed.