Reading Guidelines
The Algorand Specifications consist of normative and non-normative sections.
The normative sections formally define Algorand. The Algorand consensus protocol gates all the components of the normative sections. The scope of these sections is to provide a complete and correct specification of the Algorand protocol, regardless of the implementation. Therefore, the language used in those sections is formal, prescriptive, and succinct.
The non-normative sections provide context and auxiliary information for the Algorand
implementation. The components of the non-normative sections are not enforced through
the Algorand consensus protocol. The scope of these sections is to ease the understanding
of the normative sections and provide readers with a comprehensive view of the Algorand
reference implementation (go-algorand). Therefore, the language used in those
sections is informal, descriptive, and discursive.
The current version of the Algorand Specifications reflects the latest version of
the Algorand consensus protocol in its normative sections and is generally aligned
with the latest stable release of go-algorand in its non-normative sections.
Specifications for previous consensus versions can be found via the link provided
in the block’s current-protocol.upgrade-state field corresponding to the desired
consensus version.
Contents Hierarchy
The node functional diagram above provides an overview of the functional blocks that define the structure of the Algorand Specification.
Contents are organized in four hierarchical levels (see the navigation sidebar on the left):
Part
└── 1. Chapter (Normative / Non-Normative)
└── 1.1. Section
└── 1.1.1. Sub Section
Each Part begins with an Overview, highlighting the covered functional blocks, usually divided into two Chapters: normative and non-normative (always present).
The navigation sidebar can be folded up to the Chapter level by clicking the folding icon (>), next to the level name.
Formatting
Notes like this are non-normative comments in the normative sections.
📎 EXAMPLE
Sections like this are examples aiming to clarify the formal specifications.
⚙️ IMPLEMENTATION
Sections like this contain links to the
go-algorandreference implementation.
The code-blocks may contain pseudocode or real code snippets.
Math Symbols
For a correct rendering of mathematical symbols and formulas, it is recommended to
right-click on the symbol below, and select Math Settings -> Math Renderer -> Common HTML
from the drop-down menu.
$$ \mathcal{C} $$
Once MathJax rendering is correctly set, you should see a calligraphic “C”.
PDF Book
Readers used to classical \( \LaTeX \)-styled books can download the full book here.
Conventions and Notation
This specification defines a player to be a unique participant in this protocol.
This specification describes the operation of a single correct player. A correct player follows this protocol exactly and is distinct from a faulty player. A faulty player may deviate from the protocol in any way, so this specification does not describe the behavior of those players.
Correct players do not follow distinct protocols, so this specification describes correct behavior with respect to a single, implicit player. When the protocol must describe a player distinct from the implicit player (for example, a message that originated from another player), the protocol will use subscripts to distinguish different players. Subscripts are omitted when the player is unambiguous. For instance, a player might be associated with some “address” \(I\); if this player is the \(k\)-th player in the protocol, then this address may also be denoted \(I_k\).
This specification will describe certain objects as opaque. This document does not specify the exact implementation of opaque objects, but it does specify the subset of properties required for any implementation of some opaque object.
Opaque data definitions and semantics may be specified in other documents, which this document will cite when available.
All integers described in this document are unsigned, with a maximum value of \(2^{64}-1\), unless otherwise noted.
Context Tuple
The Algorand protocol’s progress is described using a context tuple \((r, p, s)\), which identifies the current state within the Agreement protocol’s state machine.
-
\(r\) (round): Indicates the current round of the protocol. It increases monotonically and corresponds to the block being committed. It is driven by protocol threshold events.
-
\(p\) (period): Indicates the attempt number for reaching agreement in the current round1. It is typically zero. A non-zero value reflects recovery from a failed commitment attempt. It is driven by protocol threshold events.
-
\(s\) (step): Enumerates the stages of the Agreement protocol within a given period. It is driven by protocol time events.
The protocol defined in the original research papers was based just on a rounds and steps \((r, s)\) state machine and on a Binary Byzantine Agreement, named \(BinaryBA⋆\), that could lead to the proposal of a block or an empty block for a given round. The notion of period (i.e., new consensus attempt for the same round) replaces the outcome of \(BinaryBA⋆\) over an empty block for a round.
Parameters
The Algorand protocol is parameterized by the following constants:
TIME CONSTANTS
These values represent durations of time.
| SYMBOL | DESCRIPTION |
|---|---|
| \(\lambda\) | Time for small message (e.g., a vote) propagation in ideal network conditions |
| \(\lambda_{0min}\) | Minimum amount of time for small message propagation in good network conditions, for \(p = 0\) |
| \(\lambda_{0max}\) | Maximum amount of time for small message propagation in good network conditions, for \(p = 0\) |
| \(\lambda_f\) | Frequency at which the protocol fast recovery steps are repeated |
| \(\Lambda\) | Time for big message (e.g., a block) propagation in ideal network conditions |
| \(\Lambda_0\) | Time for big message propagation in good network conditions, for \(p = 0\) |
ROUNDS CONSTANTS
These are positive integers that represent an amount of protocol rounds.
| SYMBOL | DESCRIPTION |
|---|---|
| \(\delta_s\) | The “seed lookback” |
| \(\delta_r\) | The “seed refresh interval” |
For convenience, we define:
- \(\delta_b = 2\delta_s\delta_r\) (the “balance lookback”).
Timeouts
We define \(FilterTimeout(p)\) on a period \(p\) as follows:
-
If \(p = 0\) and a sufficient history of lowest credential arrival times is available, the \(FilterTimeout(p)\) is calculated dynamically based on the lower 95th percentile of the observed lowest credentials per round arrival time:
- \(10\lambda_{0min} <= FilterTimeout(p) <= 2\lambda_{0max}\)
Refer to the non-normative section for details about the implementation of the dynamic filtering mechanism.
-
If \(p \ne 0\):
- $\FilterTimeout(p) = 4$ seconds (which coincides with $2\lambda$)
This value is currently hardcoded in the reference implementation. However, it should be equal to \(2\lambda\).
We define \(DeadlineTimeout(p)\) on period \(p\) as follows:
-
If \(p = 0\):
- \(DeadlineTimeout(p) = \Lambda_0\)
-
If \(p \ne 0\):
- \(DeadlineTimeout(p) = \Lambda + \lambda\)
Values
| PARAMETER | VALUE | UNIT |
|---|---|---|
| \(\delta_s\) | 2 | rounds |
| \(\delta_r\) | 80 | rounds |
| \(\lambda\) | 2 | seconds |
| \(\lambda_{0min}\) | 0.25 | seconds |
| \(\lambda_{0max}\) | 1.75 | seconds |
| \(\lambda_f\) | 300 | seconds |
| \(\Lambda\) | 15 | seconds |
| \(\Lambda_0\) | 4 | seconds |
The Ledger of Entries
An entry is a pair \(e = (o, Q)\) where \(o\) is some opaque object, and \(Q\) is a 256-bit integer called a seed.
For a detailed definition of this object, see the Algorand Ledger Specification.
The following functions are defined on \(e\):
-
Encoding: \(Encoding(e) = x\) where \(x\) is a variable-length bitstring.
-
Summarizing: \(Digest(e) = h\) where \(h\) is a 256-bit integer. \(h\) should be a cryptographic commitment to the contents of \(e\).
A ledger is a sequence of entries \(L = (e_1, e_2, \ldots, e_n)\).
A round \(r\) is some 64-bit index into this sequence.
The following functions are defined on \(L\):
-
Validating: \(ValidEntry(L, o) = 1\) if and only if \(o\) is valid with respect to \(L\). This validity property is opaque.
-
Seed Lookup: If \(e_r = (o_r, Q_r)\), then \(Seed(L, r) = Q_r\).
-
Record Lookup: \(Record(L, r, I_k) = (pk_{k,r}, B_{k,r}, r_{fv}, r_{lv})\) for some address \(I_k\), some public key \(pk_{k,r}\), and some 64-bit integer \(B_{k,r}\). \(r_{fv}\) and \(r_{lv}\) define the first valid and last valid rounds for this participating account.
-
Digest Lookup: \(DigestLookup(L, r) = Digest(e_r)\).
-
Total Stake Lookup: We use \(\mathcal{K_{r_b,r_v}}\) to represent all players with participation keys at \(r_b\) that are eligible to vote at \(r_v\). Let \(\mathcal{K_{r_b,r_v}}\) be the set of all \(k\) for which \((pk_{k,r_b}, B_{k,r_b}, r_{fv}, r_{lv}) = Record(L, r_b, I_k)\) and \(r_{fv} \leq r_v \leq r_{lv}\) holds. Then \(Stake(L, r_b, r_v) = \sum_{k \in \mathcal{K_{r_b,r_v}}} B_{k,r_b}\).
A ledger may support an opaque entry generation procedure:
\[ o := Entry(L, Q) \]
which produces an object \(o\) for which \(ValidEntry(L, o) = 1\).
For implementation details on this procedure, see the block assembly section in the Algorand Ledger Overview.
Messages
Players communicate with each other by exchanging messages.
A message is an opaque object containing arbitrary data, save for the fields defined below.
For a detailed overview of message composition, whether consensus or other types, see the Algorand Network Overview.
Elementary Data Types
A period \(p\) is a 64-bit integer.
A step \(s\) is an 8-bit integer.
Steps are named for clarity and are defined as follows:
| STEP | ENUMERATIVE |
|---|---|
| \(Propose\) | \(0\) |
| \(Soft\) | \(1\) |
| \(Cert\) | \(2\) |
| \(Late\) | \(253\) |
| \(Redo\) | \(254\) |
| \(Down\) | \(255\) |
| \(Next_s\) | \(s + 3\) |
The following functions are defined on \(s\):
-
\(CommitteeSize(s)\) is a 64-bit integer defined as follows:
\[ CommitteeSize(s) = \begin{cases} 20 & : s = Propose \\\ 2990 & : s = Soft \\\ 1500 & : s = Cert \\\ 500 & : s = Late \\\ 2400 & : s = Redo \\\ 6000 & : s = Down \\\ 5000 & : \text{otherwise} \end{cases} \]
-
\(CommitteeThreshold(s)\) is a 64-bit integer defined as follows:
\[ CommitteeThreshold(s) = \begin{cases} 0 & : s = Propose \\\ 2267 & : s = Soft \\\ 1112 & : s = Cert \\\ 320 & : s = Late \\\ 1768 & : s = Redo \\\ 4560 & : s = Down \\\ 3838 & : \text{otherwise} \end{cases} \]
A proposal-value is a tuple \(v = (I, p, Digest(e), Hash(Encoding(e)))\) where:
-
\(I\) is an address (the “original proposer”),
-
\(p\) is a period (the “original period”),
-
\(Hash\) is some cryptographic hash function.
The special proposal-value where all fields are the zero-string is called the bottom proposal \(\bot\).
Votes
Let
-
\(I\) be an address,
-
\(r\) be a round,
-
\(p\) be a period,
-
\(s\) be a step,
-
\(v\) be a proposal-value.
Let \(x\) be a canonical encoding of the 5-tuple \((I, r, p, s, v)\), and let \(x’\) be a canonical encoding of the 4-tuple \((I, r, p, s)\).
Let \(y\) be an arbitrary bitstring.
Then we say that the tuple
\[ (I, r, p, s, v, y) \]
is a vote from \(I\) for \(v\) at round \(r\), period \(p\), step \(s\) (or a vote from \(I\) for \(v\) at \((r, p, s)\)), denoted
\[ Vote(I, r, p, s, v) \]
⚙️ IMPLEMENTATION
Vote reference implementation.
Moreover, let \(L\) be a ledger where \(|L| \geq \max{\delta_b, \delta_s}\).
Let
- \((sk, pk)\) be a keypair,
- \(B, \bar{B}\) be 64-bit integers,
- \(Q\) be a 256-bit integer,
- \(\tau, \bar{\tau}\) 32-bit integers.
We say that this vote is valid with respect to \(L\) (or simply valid if \(L\) is unambiguous) if the following conditions are true:
⚙️ IMPLEMENTATION
The reference implementation builds an asynchronous vote verifier, which builds a verification pool and under the hood uses two different verifying routines: one for regular unauthenticated votes, and one for unauthenticated equivocation votes.
See the non-normative Algorand ABFT Overview for further details.
-
\(r \leq |L| + 2\)
-
Let \(v = (I_{orig}, p_{orig}, d, h)\).
- If \(s = 0\), then \(p_{orig} \le p\).
- Furthermore, if \(s = 0\) and \(p = p_{orig}\), then \(I = I_{orig}\)$.
-
If \(s \in \{Propose, Soft, Cert, Late, Redo\}\), \(v \neq \bot\). Conversely, if \(s = Down\), \(v = \bot\).
-
Let
- \((pk, B, r_{fv}, r_{lv}) = Record(L, r - \delta_b, I)\),
- \(\bar{B} = Stake(L, r - \delta_b, r)\),
- \(Q = Seed(L, r - \delta_s)\),
- \(\tau = CommitteeThreshold(s)\)
- \(\bar{\tau} = CommitteeSize(s)\).
Then
- \(Verify(y, x, x’, pk, B, \bar{B}, Q, \tau, \bar{\tau}) \neq 0\),
- \(r_{fv} \leq r \leq r_{lv}\).
Observe that valid votes contain outputs of the \(Sign\) procedure; i.e., \(y := Sign(x, x’, sk, B, \bar{B}, Q, \tau, \bar{\tau})\).
📎 EXAMPLE
Informally, these conditions check the following:
The vote is not too far in the future for \(L\) to be able to validate.
“Propose”-step votes can either propose a new proposal-value for this period (\(p_{orig} = p\)) or claim to “re-propose” a value originally proposed in an earlier period (\(p_{orig} < p\)). But they can’t claim to “re-propose” a value from a future period. And if the proposal-value is new (\(p_{orig} = p\)) then the “original proposer” must be the voter.
The \(Propose\), \(Soft\), \(Cert\), \(Late\), and \(Redo\) steps must vote for an actual proposal. The \(Down\) step must only vote for \(\bot\).
The last condition checks that the vote was properly signed by a voter who was selected to serve on the committee for this round, period, and step. The committee selection process uses the voter’s stake and keys as of \(\delta_b\) rounds before the vote and the seed as of \(\delta_s\) rounds before the vote. It also checks if the vote’s round is within the range associated with the voter’s participation key.
An equivocation vote pair or equivocation vote \(Equivocation(I, r, p, s)\) is a pair of votes that differ in their proposal values. In other words,
\[ Equivocation(I, r, p, s) = (Vote(I, r, p, s, v_1), Vote(I, r, p, s, v_2)) \]
for some \(v_1 \neq v_2\).
An equivocation vote pair is valid with respect to \(L\) (or simply valid if \(L\) is unambiguous) if both of its constituent votes are also valid with respect to \(L\).
Bundles
Let \(V\) be any set of votes and equivocation votes.
We say that \(V\) is a bundle for \(v\) in round \(r\), period \(p\), and step \(s\) (or a bundle for \(v\) at \((r, p, s)\)), denoted \(Bundle(r, p, s, v)\).
⚙️ IMPLEMENTATION
Bundle reference implementation.
Moreover, let \(L\) be a ledger where \(|L| \geq \max{\delta_b, \delta_s}\).
We say that this bundle is valid with respect to \(L\) (or simply valid if \(L\) is unambiguous) if the following conditions are true:
⚙️ IMPLEMENTATION
The reference implementation makes use of an asynchronous Bundle verifying function.
See the non-normative Algorand ABFT Overview for further details.
-
\(s \neq 0\) (there are no bundles for the \(Propose\) step).
-
Every element \(a_i \in V\) is valid with respect to \(L\).
-
\(|V| \leq CommitteeThreshold(s)\).
Intuitively, the largest possible bundle is a bundle where every vote’s weight is exactly one.
- For any two elements \(a_i, a_j \in V\), \(I_i \neq I_j\).
- For any element \(a_i \in V\), \(r_i = r, p_i = p, s_i = s\).
-
For any element \(a_i \in V\), either \(a_i\) is a vote and \(v_i = v\), or \(a_i\) is an equivocation vote.
-
Let \(w_i\) be the weight of the signature in \(a_i\). Then \(\sum_i w_i \geq CommitteeThreshold(s)\).
Proposals
Let \(e = (o, Q)\) be an entry and \(y\) be the output of a \(Sign\) procedure.
The pair \((e, y)\) is a proposal or a proposal payload.
Moreover, let
-
\(L\) be a ledger where \(|L| \geq \delta_b\),
-
\(v = (I, p, h, x)\) be some proposal-value.
We say that this proposal is a valid proposal matching \(v\) with respect to \(L\) (or simply that this proposal matches \(v\) if \(L\) is unambiguous) if the following conditions are true:
-
\(ValidEntry(L, e) = 1\),
-
\(h = Digest(e)\),
-
\(x = Hash(Encoding(e))\),
-
The seed \(Q\) and seed proof are valid as specified in the following section,
-
Let \((pk, B, r_{fv}, r_{lv}) = Record(L, r - \delta_b, I)\),
- If \(p = 0\), then \(Verify(y, Q_0, Q_0, pk, 0, 0, 0, 0, 0) \neq 0\),
-
Let \((pk, B, r_{fv}, r_{lv}) = Record(L, r - \delta_b, I)\). Then \(r_{fv} \leq r \leq r_{lv}\).
If \(e\) matches \(v\), we write \(e = Proposal(v)\).
Seed
Informally, the protocol interleaves \(\delta_s\) seeds in an alternating sequence. Each seed is derived from a seed \(\delta_s\) rounds in the past through either a hash function or through a VRF, keyed on the entry proposer. Additionally, every \(\delta_s\delta_r\) rounds, the digest of a previous entry (specifically, from round \(r-\delta_s\delta_r\)) is hashed into the result. The seed proof is the corresponding VRF proof, or 0 if the VRF was not used.
This section makes use of the \(VRF.Prove(.)\), \(VRF.ProofToHash(.)\) and \(VRF.Verify(.)\) cryptographic low-leve functions defined in the reference implementation’s Algorand Sodium Fork. They respectively output a \(VRF\) proof and a \(VRF\) hash for a given input, and perform verification of a \(VRF\) run. For details on the input structure and inner workings, please refer to the Appendix A in the Algorand Crypto Specification.
More formally, suppose \(I\) is a correct proposer in round \(r\) and period \(p\).
Let
-
\((pk, B, r_{fv}, r_{lv}) = Record(L, r - \delta_b, I)\),
-
\(sk\) be the secret key corresponding to \(pk\),
-
\(\alpha\) be a 256-bit integer.
Then \(I\) computes the seed proof \(y\) for a new entry as follows:
-
If \(p = 0\):
- \(y = VRF.Prove(Seed(L, r-\delta_s), sk)\),
- \(\alpha = Hash(I || VRF.ProofToHash(y))\).
-
If \(p \ne 0\):
- \(y = 0\),
- \(\alpha = Hash(Seed(L, r-\delta_s))\).
Now \(I\) computes the seed \(Q\) as follows:
\[ Q = \begin{cases} Hash(\alpha || DigestLookup(L, r-\delta_s\delta_r)) & : (r \bmod \delta_s\delta_r) < \delta_s \\\ Hash(\alpha) & : \text{otherwise} \end{cases} \]
The seed is valid if the following verification procedure succeeds:
-
Let \((pk, B, r_{fv}, r_{lv}) = Record(L, r-\delta_b, I)\); let \(q_0 = Seed(L, r-\delta_s)\).
-
If \(p = 0\), check \(VRF.Verify(y, q_0, pk)\), immediately returning failure if verification fails. Let \(q_1 = Hash(I||VRF.ProofToHash(y))\) and continue to step 4.
-
If \(p \ne 0\), let \(q_1 = Hash(q_0)\). Continue.
-
If \(r \equiv (r \bmod \delta_s) \mod \delta_r\delta_s\), then check \(Q = Hash(q_1||DigestLookup(L, r-\delta_s\delta_r))\). Otherwise, check \(Q = q_1\).
Round \(r\) leader selection and committee selection both use the seed from \(r-\delta_s\) and the balances / public keys from \(r-\delta_b\).
For re-proposals, the period \(p\) used in this section is the original period, not the reproposal period.
For a detailed overview of the seed computation algorithm and some explanatory examples, refer to the non-normative Algorand ABFT Overview.
State Transitions
After receiving message events or a time events, the player may update some components of its state.
New Round
When a player observes that a new round \( (r, 0) \) has begun, the player sets
-
\( \bar{s} := s \),
-
\( \bar{v} := \bot \),
-
\( p := 0 \),
-
\( s := 0 \).
Specifically, if a new round has begun, then
$$ N((r-i, p, s, \bar{s}, V, P, \bar{v}), L, \ldots) = ((r, 0, 0, s, V’, P’, \bot), L’, \ldots) $$
for some \( i > 0 \).
⚙️ IMPLEMENTATION
New round reference implementation.
$$ \newcommand \Soft {\mathit{soft}} \newcommand \Cert {\mathit{cert}} \newcommand \Bundle {\mathrm{Bundle}} $$
New Period
When a player observes that a new period \( (r, p) \) has begun, the player sets
-
\( \bar{s} := s \),
-
\( s := 0 \).
Also, the player sets \( \bar{v} := v \) if the player has observed \( \Bundle(r, p-1, s, v) \) given some values \( s > \Cert \) (or \( s = \Soft \)), \( v \neq \bot \); if none exist, the player sets \( \bar{v} := \sigma(S, r, p-i) \) if it exists, where \( p-i \) was the player’s period immediately before observing the new period; and if none exist, the player does not update \( \bar{v} \).
In other words, if \( \Bundle(r, p-1, s, v) \in V’ \) for some \( v \neq \bot, s > \Cert \) or \( s = \Soft \), then
$$ N((r, p-i, s, \bar{s}, V, P, \bar{v}), L, \ldots) = ((r, p, 0, s, V’, P, v), L’, \ldots); $$
and otherwise, if \( \Bundle(r, p-1, s, \bot) \in V’ \) for some \( s > \Cert \) with \( \sigma(S, r, p-i) \) defined, then
$$ N((r, p-i, s, \bar{s}, V, P, \bar{v}), L, \ldots) = ((r, p, 0, s, V’, P, \sigma(S, r, p-i)), L’, \ldots); $$
and otherwise
$$ N((r, p-i, s, \bar{s}, V, P, \bar{v}), L, \ldots) = ((r, p, 0, s, V’, P, \bar{v}), L’, \ldots); $$
for some \( i > 0 \) (where \( S = (r, p-i, s, \bar{s}, V, P, \bar{v}) \)).
⚙️ IMPLEMENTATION
New period reference implementation.
$$ \newcommand \Vote {\mathrm{Vote}} $$
Garbage Collection
When a player observes that either a new round or a new period \( (r, p) \) has begun, then the player garbage-collects old state.
In other words,
$$ N((r-i, p-i, s, \bar{s}, V, P, \bar{v}), L, \ldots) = ((r, p, \bar{s}, 0, V’ \setminus V^\ast_{r, p}, P’ \setminus P^\ast_{r, p}, \bar{v}), L, \ldots) $$
where
$$ \begin{aligned} V^\ast_{r, p} &= \{\Vote(I, r’, p’, \bar{s}, v) | \Vote \in V, r’ < r\} \\\ &\cup \{\Vote(I, r’, p’, \bar{s}, v) | \Vote \in V, r’ = r, p’ + 1 < p\} \end{aligned} $$
and \( P^\ast_{r, p} \) is defined similarly.
$$ \newcommand \FilterTimeout {\mathrm{FilterTimeout}} \newcommand \DeadlineTimeout {\mathrm{DeadlineTimeout}} \newcommand \Cert {\mathit{cert}} \newcommand \Next {\mathit{next}} $$
New Step
A player may also update its step after receiving a timeout event.
On observing a timeout event of \( \FilterTimeout(p) \) for a period \( p \), the player sets \( s := \Cert \).
On observing a timeout event of \( \DeadlineTimeout(p) \) for a period \( p \), the player sets \( s := \Next_0 \).
On observing a timeout event of \( \DeadlineTimeout(p) + 2^{s_t}\lambda + u \) where \( 0 < s_t \leq 249 \) and \( u \in [0, 2^{s_t}\lambda) \) sampled uniformly at random, the player sets \( s := s_t + 3 = \Next_{s_t} \).
⚙️ IMPLEMENTATION
New step reference implementation.
In other words,
$$
\begin{aligned}
&N((r, p, s, \bar{s}, V, P, \bar{v}), L, t(\FilterTimeout(p), p))
&&= ((r, p, \Cert, \bar{s}, V, P, \bar{v}), L’, \ldots) \\
&N((r, p, s, \bar{s}, V, P, \bar{v}), L, t(\DeadlineTimeout(p), p))
&&= ((r, p, \Next_0, \bar{s}, V, P, \bar{v}), L’, \ldots) \\
&N((r, p, s, \bar{s}, V, P, \bar{v}), L,
t(\DeadlineTimeout(p) + 2^{s_t}\lambda + u, p))
&&= ((r, p, \Next_{s_t}, \bar{s}, V, P, \bar{v}), L’, \ldots).
\end{aligned}
$$
Agreement Protocol Overview
The following section is a non-normative overview of the Algorand Agreement Protocol.
We attempt to ease understanding of the normative section and provide readers with a comprehensive view of its inner workings, while maintaining technical correctness.
We provide pseudocode examples and diagrams for readers and potential implementers to clarify how these processes may be implemented, and how they might interact with one another.
For a normative specification of the Algorand Byzantine Fault Tolerance protocol, refer to the normative ABFT section.
$$ \newcommand \ABFT {\mathrm{ABFT}} \newcommand \Credentials {\mathrm{Credentials}} \newcommand \sk {\mathrm{sk}} \newcommand \VRF {\mathrm{VRF}} $$
General Concepts
The Algorand Agreement Protocol, or Algorand Byzantine Fault Tolerance (\( \ABFT \)), is a consensus mechanism ensuring secure, decentralized agreement on transactions’ ordering and validity in the Algorand blockchain. It tolerates malicious actors as long as less than one-third of the participants are compromised.
The protocol relies on a cryptographic sortition to randomly and verifiably self-select a small, representative group of participants to propose and validate blocks. This randomness ensures security, scalability, and resistance to attacks.
By achieving instant finality, \( \ABFT \) enables Algorand to process transactions efficiently, making it suitable for large-scale, real-time applications.
In each round, a block must be confirmed. In the context of this section, a block is treated as an opaque data packet with two mandatory fields:
- A block hash,
- A randomness seed.
For details on the remaining fields and the structure of a block, please refer to the Ledger’s normative specification and non-normative overview.
The Algorand Agreement Protocol is executed between nodes.
Functionally, an Algorand node “plays” on behalf of every actively participating account whose participation keys are registered for voting.
Each of these accounts can be viewed as an independent player in the protocol, identified by its unique address \( I \) and associated balance.
All the accounts registered on the node share a unified view of the transaction pool and blockchain state, which is maintained by the node through which they participate in the protocol.
Consensus is reached progressively. A round is the primary unit of time in the consensus protocol. Each round aims to agree on a single block to append to the blockchain.
The protocol begins a new round once the previous one has finalized a block.
Credentials
We define a structure \( \Credentials \) for ease of use and readability.
This structure will contain all the necessary fields for identifying a voting player, which includes:
-
Address (\( I \)): A unique identifier for the participating account.
-
Secret key (\( \sk_r \)): A private key associated with the account, used for cryptographic operations such as signing messages1.
-
\( \VRF \) proof (\( y \)): A cryptographic proof generated using the Verifiable Random Function (\( \VRF \))2.
The sets of observed votes \( V \) and proposals \( P \), observed in a given round, are utilized here with the same definition as in the normative specification.
Analogous to these, we define a set of observed bundles \( B \) for a given round, that is built taking subsets of votes in \( V \) according to the rules for a valid bundle specified in the normative specification.
The secret key \( \sk \) is round-dependent because it makes use of a two-level ephemeral key scheme under the hood. In the context of this document, this procedure is replaced by an opaque structure that produces the key needed for the round and abstracts away both a signature and verification procedure.
Since the sortition hash \( \VRF_{out} \) can be derived from a proof \( y \), we assume that \( \VRF_{out} \) is implicitly available whenever \( y \) is present.
Context Tuple
The Algorand Agreement protocol may be modeled as a state machine. The state machine’s progress is driven mainly by two types of events:
-
Time Events: triggered by the node’s local clock. The Agreement Protocol defines the duration of the timeouts for each protocol stage.
-
Threshold Events: triggered by the votes expressed by randomly elected committees and counted by the node. The Agreement Protocol defines the number of votes for each protocol stage.
Analogous to execution coordinates, three integers taken together provide enough context on which stage of the protocol the state machine is currently processing. This triplet is referred to throughout this document as the context tuple.
The context tuple supplies the necessary information to determine the exact phase of the Agreement Protocol being executed, or, in simpler terms, “where we are” in the broader process.
For details on the formal implications of these values in the overall protocol, refer to the Algorand Byzantine Fault Tolerance normative section.
The components of the context tuple are:
-
Round (\( r \)):
- A round defines the top-level cycle of the Algorand Agreement protocol.
- Completing a round adds a block to the ledger (i.e., the blockchain).
- A round is composed of one or more periods.
- Rounds are identified by a monotonically increasing unsigned 64-bit integer representing the ledger’s current size and the committed block’s round number.
- Fundamentally, it serves as a monotonically increasing index into the blockchain, reflecting the progression of confirmed blocks.
- Rounds’ progression is driven by threshold events.
-
Period (\( p \)):
- A period is a cycle within a round.
- One or more periods will be processed until the parent round completes by reaching consensus and producing a block
- A period is composed of multiple steps.
- Within a round, periods are identified by a monotonically increasing unsigned 64-bit integer (starting with 0), indicating the current “run” (consensus attempt) of the same round.
- Under ideal conditions \( p = 0 \), and remains so throughout the entire process from block proposal to block commitment.
- If \( p \neq 0 \), the network is recovering from a failed attempt to commit a block within the current round. In such cases, all or some Agreement stages may be re-executed.
- During recovery, specific routines monitor the network’s ability to reach consensus again. These routines may reuse information from previous failed attempts to speed up the block commitment once normal operation resumes.
- Periods’ progression is driven by threshold events.
-
Step (\( s \)):
- Steps are discrete units of logic that define each Algorand Agreement state machine’s states.
- There are 5 separate steps. Each Step helps move closer to reaching consensus for the next block among the Algorand participants.
- Some steps require a period of time to pass before moving to the next step.
- Steps 4 and 5 will repeat with increasing timeouts until a consensus is reached for the current period.
- Within a period, steps are identified by an unsigned 8-bit integer representing an enumeration of the possible Agreement Protocol stages. See the normative section for a formal definition of this enumeration.
- Steps are bounded (
uint8_max = 255) and follow the protocol’s predefined progression through its stages. - Steps’ progression is driven by time events.
$$ \newcommand \StakeOn {\mathrm{StakeOnline}} \newcommand \VRF {\mathrm{VRF}} $$
Security Model
The parameter selection of Algorand blockchain is based on a combination of assumptions:
-
Majority of online honest stake,
-
Security of cryptographic primitives,
-
Upper bound on message latency.
Honest Majority Assumption
Let’s define \( \StakeOn_{r-322} \) as the total “online” stake at round \( r-322 \).
For every round, at least \( 80\% \) of \( \StakeOn_{r-322} \) is honest in round \( r \). (Larger committee sizes would be required if we assume a smaller honest majority ratio.)
Cryptographic Assumptions
Algorand uses a digital signature scheme for message authentication. It uses a \( \VRF \) and a hash function, modeled as random oracles in the context of the consensus protocol analysis.
We allow an adversary to perform up to \( 2^{128} \) hash operations over the system’s lifetime. This is an extraordinarily large number! With these many hash operations, the adversary can find collisions in SHA-256 function, or mine \( 25 \) billion years’ worth of Bitcoin blocks at today’s hash rate1.
Security Against Dynamic Corruption
In the Algorand protocol, users change their ephemeral participation keys used for every round. That is, after users sign their message for round \( r \), they delete the ephemeral key used for signing, and fresh ephemeral keys will be used in future rounds.
This allows Algorand to be secure against dynamic corruptions, where an adversary may corrupt a user after seeing her propagate a message through the network. (Recall that since users use their \( \VRF \)s to perform cryptographic self-selection, an adversary does not even know whom to corrupt prior to round \( r \)).
Moreover, even if in the future an adversary corrupts all committee members for a round \( r \), as the users holding the supermajority of stakes were honest in round \( r \) and erased their ephemeral keys, no two distinct valid blocks can be produced for the same round.
Network Model
Algorand guarantees liveness assuming a maximum propagation delay on messages sent through the network (see protocol parameters normative section).
Algorand guarantees safety (“no forks”) even in the case of network partitions. When a network partition heals, liveness recovers in linear time against an adversary capable of dynamic corruptions, and in constant time otherwise. Refer to the protocol run non-normative section for network partitioning examples.
\( \sim900 \text{ [EH/s]} \) in May 2025.
$$ \newcommand \VRF {\mathrm{VRF}} \newcommand \Prove {\mathrm{Prove}} \newcommand \ProofToHash {\mathrm{ProofToHash}} \newcommand \Secrets {\mathrm{Secrets}} \newcommand \Rerand {\mathrm{Rerand}} \newcommand \function {\textbf{function }} \newcommand \endfunction {\textbf{end function}} \newcommand \if {\textbf{if }} \newcommand \then {\textbf{ then}} \newcommand \else {\textbf{else}} \newcommand \endif {\textbf{end if}} \newcommand \return {\textbf{return }} $$
Seed Calculation
The cryptographic seed is a source of randomness for many internal operations inside the protocol.
A formal definition of the seed can be found in the normative specification.
This section provides an engineering and implementation-oriented way of conceptualizing the seed computation, to ease its understanding.
The following algorithm makes heavy use of \( \VRF \) specific functions. For more information on their definition and internal work, refer to the Algorand Cryptographic Primitive Specification.
Notation
For the seed calculation algorithm, consider the following notation:
| SYMBOL | DESCRIPTION |
|---|---|
| \( I \) | Player address |
| \( L \) | Ledger (blocks and present sate) |
| \( r \) | Current protocol round |
| \( p \) | Current protocol period |
| \( \delta_s \) | Seed lookback (rounds) |
| \( \delta_r \) | Seed refresh interval (rounds) |
| \( L[r - n] \) | Block \( r - n \) of the ledger |
| \( L[r - n]_Q \) | Seed of the block \( r - n \) of the ledger |
| \( H(x) \) | Hash of \( x \) |
| \( \VRF \) | Verifiable Random Function |
| \( \VRF.\Prove \) | Computes the proof \( y \) of the \( \VRF \) |
| \( \VRF.\ProofToHash \) | Computes the hash of \( y \) |
| \( Q \) | Randomness seed |
Algorithm
For the seed calculation algorithm, consider the following pseudocode:
\( \textbf{Algorithm 1} \text{: Compute Seed and Proof} \)
$$ \begin{aligned} &\text{1: } \function \mathrm{ComputeSeedAndProof}(I) \\ &\text{2: } \quad \if p = 0 \then \\ &\text{3: } \quad \quad y \gets \VRF.\Prove(\Secrets(I)_{\text{VRFkey}}, L[r - \delta_s]_Q) \\ &\text{4: } \quad \quad \alpha \gets H(I || \VRF.\ProofToHash(y)) \\ &\text{5: } \quad \else \\ &\text{6: } \quad \quad y \gets 0 \\ &\text{6: } \quad \quad \alpha \gets H(L[r - \delta_s]_Q) \\ &\text{7: } \quad \endif \\ &\text{9: } \quad \if r \bmod (\delta_s\delta_r) < \delta_s \then \\ &\text{10:} \quad \quad Q \gets H(\alpha || H(L[r - \delta_s \delta_r])) \\ &\text{11:} \quad \else \\ &\text{12:} \quad \quad Q \gets H(\alpha) \\ &\text{13:} \quad \endif \\ &\text{14:} \quad \return (Q, y) \\ &\text{15: } \endfunction \end{aligned} $$
⚙️ IMPLEMENTATION
Seed computation reference implementation.
The function takes as input the address \( I \) of an online player who will be computing the seed \( Q \).
Note that the player needs to have registered participation keys on the node computing the seed, so as for the \( \Secrets(I) \) call (Algorithm 1, line 3) to retrieve available \( \VRF \) secrets generated during that registration process.
For more information on the types of keys a player has to use, refer to the Algorand Participation Key Specification.
The function computes the cryptographic seed appended to the block candidate for round \( r \), which will be used (if said block candidate is committed) as a source of randomness for the \( \VRF \) in a future round.
The seed is computed according to whether the function is called in the first period of the round, \( p = 0 \), or not.
The function also computes the proof \( y \), bundled up with the block inside a proposal structure (for broadcasting), and used by nodes receiving the proposal as part of the proposal validation process.
Example
The following is an example of seed computation in three adjacent blocks, chosen to show both branches of the Algorithm 1 execution, according to \( r \bmod \delta_s\delta_r \) condition, also known as re-randomization.
Noting that:
- \( \delta_s = 2 \),
- \( \delta_r = 80 \).
We define \( \Rerand(r) = r \bmod \delta_s\delta_r \).
When \( \Rerand(r) < \delta_s \) we say we are re-randomizing the seed \( Q \) for the round \( r \).
📎 EXAMPLE
Take the process for a player with address \( I \) at the first consensus attempt of the round (\( p = 0 \)).
- Let’s consider round \( r_a = 48182880 \), as
$$ \Rerand(r_a) = 48182880 \bmod 160 = 0 < \delta_s $$
The computation is:
- Get the seed \( Q \) for round \( r_a - \delta_s = (48182880 - 2) = 48182878 \),
- Construct a \( \VRF \) proof \( y \) with that seed,
- Convert the \( \VRF \) proof \( y \) to a \( \VRF \) proof hash (named \( \VRF_h \)),
- Hash the object \( \{I || \VRF_h\} \) (named \( \alpha \)),
- Lookup the block digest of the old round \( r_a - \delta_s\delta_r = 48182880 - 160 = 48182720 \) (named \( H_\text{old}\)),
- Calculate the final seed by hashing the object \( \{\alpha, H_\text{old} \} \).
- This process will be the same for \( r_b = 48182881 \) as
$$ \Rerand(r_b) = 48182881 \bmod 160 = 1 < \delta_s $$
- For the round \( r_c = 48182882 \), since
$$ Rerand(r_c) = 48182882 \bmod 160 = 2 \ge \delta_s $$
The computation is:
- Get the seed \( Q \) for round \( r_c - \delta_s = (48182882 - 2) = 48182880 \),
- Construct a \( \VRF \) proof \( y \) with that seed,
- Convert the \( \VRF \) proof \( y \) to a \( \VRF \) proof hash (named \( \VRF_h \)),
- Hash the object \( \{I || \VRF_h\} \) (named \( \alpha \)),
- Calculate the final seed by hashing \( \alpha \).
- This process will be the same for rounds \( 48182883, \ldots, 48183039 \) as
$$ \Rerand(48182883, \ldots, 48183039) > \delta_s. $$
If during the execution of consensus for a given round, a period \( p > 0 \) is observed (i.e., the protocol is performing a new consensus attempt for the same round), steps 2-3-4 change calculating \( \alpha \) by hashing the seed of a round \( r - \delta_s \) (instead of the object \( \{I || \VRF_h\}\ \)). This condition occurs when another proposal for the same round has to be created. In this case, to avoid the possibility of seed manipulation by malicious proposers, their input is excluded from the computation (as the process uses a seed that is \( \delta_s \) rounds in the past, outside potential attacker’s influence).
$$ \newcommand \EventHandler {\mathrm{EventHandler}} \newcommand \BlockProposal {\mathrm{BlockProposal}} \newcommand \BlockAssembly {\mathrm{BlockAssembly}} \newcommand \SoftVote {\mathrm{SoftVote}} \newcommand \CertificationVote {\mathrm{CertificationVote}} \newcommand \Commitment {\mathrm{Commitment}} \newcommand \Recovery {\mathrm{Recovery}} \newcommand \FastRecovery {\mathrm{FastRecovery}} \newcommand \DeadlineTimeout {\mathrm{DeadlineTimeout}} \newcommand \Bundle {\mathrm{Bundle}} \newcommand \HandleProposal {\mathrm{HandleProposal}} \newcommand \HandleVote {\mathrm{HandleVote}} \newcommand \HandleBundle {\mathrm{HandleBundle}} \newcommand \Propose {\mathit{propose}} \newcommand \Soft {\mathit{soft}} \newcommand \Cert {\mathit{cert}} \newcommand \Next {\mathit{next}} \newcommand \Late {\mathit{late}} \newcommand \Redo {\mathit{redo}} \newcommand \Down {\mathit{down}} \newcommand \function {\textbf{function }} \newcommand \endfunction {\textbf{end function}} \newcommand \if {\textbf{if }} \newcommand \then {\textbf{ then}} \newcommand \else {\textbf{else}} \newcommand \elseif {\textbf{else if }} \newcommand \endif {\textbf{end if}} \newcommand \ev {\mathit{ev}} \newcommand \t {\mathit{time}} \newcommand \s {\mathit{step}} \newcommand \data {\mathit{msg}_\text{data}} \newcommand \TimeoutEvent {\texttt{TimeoutEvent}} \newcommand \MessageEvent {\texttt{MessageEvent}} \newcommand \DynamicFilterTimeout {\mathrm{DynamicFilterTimeout}} \newcommand \comment {\qquad \small \textsf} $$
Agreement Stages
The Algorand Agreement Protocol can be split into a series of stages.
In the normative section, these stages are univocally associated with infinite subsets of protocol states. These subsets are disjoint and together represent the whole space of possible states for the node state machine to be in.
The stages are, in chronological order within a given round:
- \( \BlockProposal \),
- \( \SoftVote \),
- \( \CertificationVote \), which includes a final \( \Commitment \).
If \( \Commitment \) is not possible because of external reasons (i.e., a network partition), two fallback stages:
- \( \FastRecovery \),
- \( \Recovery \).
By abstracting away some implementation-specific complexity, we propose a model for the Agreement Protocol state machine that captures how and when transitions between different states happen.
Algorithm
We may model the state machine’s main algorithm in the following way:
\( \textbf{Algorithm 2} \text{: Main State Machine} \)
$$ \begin{aligned} &\text{1: } \function \EventHandler(ev) \\ &\text{2: } \qquad \if \ev \text{ is a } \TimeoutEvent \then \\ &\text{3: } \qquad \quad \t \gets \ev_\t \\ &\text{4: } \qquad \quad \if \t = 0 \then \comment{# Last round should have left us with s := propose} \\ &\text{5: } \qquad \quad \quad \BlockProposal() \\ &\text{6: } \qquad \quad \quad \if \text{finished a block} \lor \mathrm{CurrentTime}() = \mathrm{AssemblyDeadline}() \then \\ &\text{7: } \qquad \quad \quad \quad \s \gets \Soft \\ &\text{8: } \qquad \quad \quad \endif \\ &\text{9: } \qquad \quad \elseif time = \DynamicFilterTimeout(p) \then \\ &\text{10:} \qquad \quad \quad \SoftVote() \\ &\text{11:} \qquad \quad \quad \s \gets \Cert \\ &\text{12:} \qquad \quad \elseif \t = \DeadlineTimeout(p) \then \\ &\text{13:} \qquad \quad \quad \s \gets \Next_0 \\ &\text{14:} \qquad \quad \quad \Recovery() \\ &\text{15:} \qquad \quad \elseif \t = \DeadlineTimeout(p) + 2^{s_t - 3}\lambda \text{ for } 4 \le s_t \le 252 \then \\ &\text{16:} \qquad \quad \quad \s \gets \Next_{s_t} \\ &\text{17:} \qquad \quad \quad \Recovery() \\ &\text{18:} \qquad \quad \elseif \t = k\lambda_f + rnd \text{ for } k, rnd \in \mathbb{Z}, k > 0, 0 \le rnd \le \lambda_f \then \\ &\text{19:} \qquad \quad \quad \FastRecovery() \\ &\text{20:} \qquad \quad \endif \\ &\text{21:} \qquad \else \comment{# MessageEvent could trigger a commitment and round advancement} \\ &\text{22:} \qquad \quad msg \gets ev_{msg} \\ &\text{23:} \qquad \quad \if \data \text{ is of type } \texttt{Proposal } pp \then \\ &\text{24:} \qquad \quad \quad \HandleProposal(pp) \\ &\text{25:} \qquad \quad \elseif \data \text{ is of type } \texttt{Vote } v \then \\ &\text{26:} \qquad \quad \quad \HandleVote(v) \\ &\text{27:} \qquad \quad \elseif \data \text{ is of type } \texttt{Bundle } b \then \\ &\text{28:} \qquad \quad \quad \HandleBundle(b) \\ &\text{29:} \qquad \quad \endif \\ &\text{30:} \qquad \endif \\ &\text{31: } \endfunction \end{aligned} $$
The first three steps (\( \Propose, \Soft, \Cert \)) are the fundamental parts, and will be the only steps run in regular “healthy” functioning conditions.
The following steps are recovery procedures if there’s no observable consensus before their trigger times.
Note that in the case of \( \Propose \), if a block is not assembled and finalized in time for the \( \BlockAssembly() \) timeout, this might trigger advancement to the next step.
For more information on this process, refer to the Algorand Ledger non-normative section.
The \( \Next_{s-3} \) with \( s \in [3, 252] \) are recovery steps, while the last three (\( \Late, \Redo, \Down \)) are special fast recovery steps.
A period is an execution of a subset of steps, executed in order until one of them achieves a bundle for a specific value.
A round always starts with a \( \Propose \) step and finishes with a \( \Cert \) step (when a block becomes commitable, it is certified and committed to the Ledger).
However, multiple periods might be executed inside a round until:
-
A certification bundle (\( \Bundle(r,p,s,v) \) where \( s = \Cert \)) is observable by the network, and
-
The corresponding proposal \( Proposal(v) \) has been received and validated, and
-
The proposal payload is available at the moment of commitment.
Events
Events are the only way for the node state machine to transition internally and produce output.
⚙️ IMPLEMENTATION
Events reference implementation.
If an event is not identified as misconstrued or malicious, it will produce a state change. Also, it will almost certainly cause a receiving node to produce and then broadcast or relay an output, consumed by its peers in the network.
There are two main kinds of events:
-
\( \TimeoutEvent \), which are produced once the internal clock of a node reaches a specific time since the start of the current period;
-
\( \MessageEvent \), which are outputs produced by nodes in response to some stimulus (including the receiving node itself).
Internally, we consider the structure of an event to be composed of:
-
A floating point number, representing time (in seconds) from the start of the current period, in which the event has been triggered;
-
An event type, from an enumeration;
-
A data type;
-
Some attached data, plain bytes to be cast and interpreted according to the attached data type, or empty in case of a timeout event.
Time Events
\( \TimeoutEvent \) are triggered when a specific time has elapsed after the start of a new period.
-
\( \Soft \) timeout (a.k.a. Filtering): is run after a timeout of \( \DynamicFilterTimeout(p) \) is observed (where \( p \) is the currently running period). Note that it only depends on the period, whether it’s the first period in the round or a later one. In response to this, the node state machine will perform a filtering action, finding the highest priority proposal observed to produce a soft vote (as detailed in the \( \SoftVote \) algorithm).
-
\( \Next_0 \) timeout: it triggers the first recovery step, only executed if no consensus for a specific value was observed, and no \( \Cert \) bundle is constructible with observed votes. It plays after observing a timeout of \( \DeadlineTimeout(p) \). In this step, the node will next vote a value and attempt to reach a consensus for a \( \Next_0 \) bundle, that would kickstart a new period.
-
\( \Next_s \) timeout: this family of timeouts runs whenever the elapsed time since the start of the current period reaches \( \DeadlineTimeout(p) + 2^{s_t-3}\lambda \) for some \( 4 \le s_t \le 252 \). The algorithm run is the same as in the \( \Next_0 \) step.
⚙️ IMPLEMENTATION
Next vote ranges to reference implementation.
- (\( \Late, \Redo, \Down \)) fast recovery timeouts: on observing a timeout of \( k\lambda_f + rnd \) with \( rnd \) a uniform random sample in \( [0, \lambda_f] \) and \( k \) a positive integer, the fast recovery algorithm is executed. It works very similarly to \( \Next_k \) timeouts, with some subtle differences (besides trigger time).
For a detailed description, refer to its subsection.
Message Events
\( \MessageEvent \) are events triggered after observing a specific message carrying data.
In Algorithm 2, we focused on three kinds of messages:
- \( \texttt{Proposal} \),
- \( \texttt{Vote} \),
- \( \texttt{Bundle} \),
Each carries the corresponding construct (coinciding with their attached data type field).
$$ \newcommand \SoftVote {\mathrm{SoftVote}} \newcommand \CredentialHistory {\mathbf{C}} \newcommand \CredentialHistorySize {|\CredentialHistory|} \newcommand \CredentialIdx {i^\ast} \newcommand \Timeout {T_\SoftVote} \newcommand \TimeoutGracePeriod {T_\epsilon} \newcommand \lambdaMin {\lambda_\text{0min}} \newcommand \lambdaMax {\lambda_\text{0max}} \newcommand \deltaL {\delta_\text{lag}} $$
Dynamic Filter Timeout
An adaptive algorithm computes the dynamic filter timeout (i.e., the timeout to trigger a call to \( \SoftVote \)).
In regular conditions, the filtering timeout \( \Timeout \) tends to the minimum \( \lambdaMin \).
Whenever network conditions force the round advancement to stall, \( \Timeout \) will diverge towards the maximum of \( \lambdaMax \).
See the formal definition of the filtering timeout parameters in the ABFT normative section.
⚙️ IMPLEMENTATION
Dynamic filter timeout to reference implementation.
Algorithm
Let \( \CredentialHistory \) be a circular array of size \( \CredentialHistorySize \), whose elements are rounds’ minimum credential arrival time, defined as the time (elapsed since the start of \( r \)) at which the highest priority proposal vote was observed for that round.
See the formal definition of the lowest credential priority function in the ABFT normative section.
We now define the credential round lag, as:
$$ \deltaL = \min \left\{ \left\lfloor \frac{2\lambda}{\lambdaMin} \right\rfloor, 8 \right\} $$
to be the rounds’ lookback1 for \( \CredentialHistory \).
The node tracks in \( \CredentialHistory \) the minimum credential arrival time for a certain number of rounds before \( r - \deltaL \).
Every time a round \( r \) is “successfully” completed2, the node looks up the arrival time of the relevant credential for the round \( r - \deltaL \), and pushes it into \( \CredentialHistory \). If the circular array is full, the oldest entry is deleted).
It is worth noting that only rounds completed in the first attempt (\( p = 0 \)) are considered and relevant for \( \CredentialHistory \). If the round is completed in later periods (\( p > 0 \)), that round is skipped and \( \CredentialHistory \) remains unchanged.
⚙️ IMPLEMENTATION
Update credential arrival history reference implementation.
When computing the dynamic filter timeout, if a sufficient history of credentials is available (i.e., the node stored \( \CredentialHistorySize \) past credential arrival times), the array holding this history is sorted in ascending order.
Then \( \CredentialIdx \)-th element is selected as the filtering timeout value3.
Finally, a \( \TimeoutGracePeriod \) extra time is added to the selected entry, for the final filter timeout to be returned as
$$ \Timeout = \CredentialHistory[\CredentialIdx] + \TimeoutGracePeriod $$
Note that the filter timeout \( \lambdaMin \leq \Timeout \leq \lambdaMax \) is clamped on the minimum and maximum bounds defined in the ABFT normative section.
⚙️ IMPLEMENTATION
\( \CredentialIdx \)-th element selection reference implementation.
Parameters
| NAME | VALUE | DESCRIPTION |
|---|---|---|
| \( \CredentialHistorySize \) | 40 | Size of the credential arrival time history circular array \( \CredentialHistory \). |
| \( \CredentialIdx \) | 37 | Entry of the (sorted) array \( \CredentialHistory \). Set to represent the 95th percentile (according to \( \CredentialHistorySize \)). |
| \( \TimeoutGracePeriod \) | 50 milliseconds | Filter extra time, atop the one calculated from \( \CredentialHistory \). |
With current values for \( \lambda \) and \( \lambdaMin \), \( \deltaL = 2 \).
A round is “successfully” completed if a certification bundle is observed and the proposal is already available, or if the proposal for an already present certification bundle is received.
With the current parametrization, this corresponds to the 95th percentile of the accumulated arrival times history.
$$ \newcommand \Resync {\mathrm{ResynchronizationAttempt}} \newcommand \BlockProposal {\mathrm{BlockProposal}} \newcommand \BlockAssembly {\mathrm{BlockAssembly}} \newcommand \Sortition {\mathrm{Sortition}} \newcommand \Broadcast {\mathrm{Broadcast}} \newcommand \RetrieveProposal {\mathrm{RetrieveProposal}} \newcommand \function {\textbf{function }} \newcommand \endfunction {\textbf{end function}} \newcommand \if {\textbf{if }} \newcommand \then {\textbf{ then}} \newcommand \else {\textbf{else}} \newcommand \endif {\textbf{end if}} \newcommand \for {\textbf{for }} \newcommand \do {\textbf{ do}} \newcommand \endfor {\textbf{end for}} \newcommand \c {\mathit{credentials}} \newcommand \Proposal {\mathrm{Proposal}} \newcommand \Bundle {\mathrm{Bundle}} \newcommand \Vote {\mathrm{Vote}} \newcommand \prop {\mathit{proposal}} $$
Block Proposal
The following is an abstracted pseudocode of the \( \BlockProposal \) algorithm.
Algorithm
\( \textbf{Algorithm 3} \text{: Block Proposal} \)
$$ \begin{aligned} &\text{1: } \function \BlockProposal() \\ &\text{2: } \quad \if p \ne 0 \then \\ &\text{3: } \quad \quad \Resync() \\ &\text{4: } \quad \endif \\ &\text{5: } \quad \for a \in A \do \\ &\text{6: } \quad \quad \c \gets \Sortition(a_I, r, p, \prop) \\ &\text{7: } \quad \quad \if \c_j > 0 \then \\ &\text{8: } \quad \quad \quad \if p = 0 \lor \exists s’ \text{ such that } \Bundle(r, p-1, s’, \bot) \subset V \then \\ &\text{9: } \quad \quad \quad \quad (e, y) \gets \BlockAssembly(a_I) \\ &\text{10:} \quad \quad \quad \quad \prop \gets \Proposal(e, y, p, a_I) \\ &\text{11:} \quad \quad \quad \quad v \gets \Proposal_\text{value}(\prop) \\ &\text{12:} \quad \quad \quad \quad \Broadcast(\Vote(a_I, r, p, \prop, v, \c)) \\ &\text{13:} \quad \quad \quad \quad \Broadcast(\prop) \\ &\text{14:} \quad \quad \quad \else \\ &\text{15:} \quad \quad \quad \quad \Broadcast(\Vote(a_I, r, p, \prop, \bar{v}, \c)) \\ &\text{16:} \quad \quad \quad \quad \if \RetrieveProposal(\bar{v}) \ne \bot \then \\ &\text{17:} \quad \quad \quad \quad \quad \Broadcast(\RetrieveProposal(\bar{v})) \\ &\text{18:} \quad \quad \quad \quad \endif \\ &\text{19:} \quad \quad \quad \endif \\ &\text{20:} \quad \quad \endif \\ &\text{21:} \quad \endfor \\ &\text{22: } \endfunction \end{aligned} $$
⚙️ IMPLEMENTATION
Block proposal reference implementation.
This algorithm is the first procedure executed when entering a new round, and upon starting any period where a reproposal is not possible.
Starting on Algorithm 3 - Line 2, the node attempts a resynchronization (described in the corresponding section), which has only effect on periods \( p > 0 \).
⚙️ IMPLEMENTATION
The reference implementation executes a resynchronization attempt when entering into a new period. Functionally, the behavior is the same, as resynchronization is performed before starting a new period, save for \( p = 0 \).
The algorithm loops over all the participating accounts (\( a \in A \)) registered on the node. This is a typical pattern in every main algorithm subroutine performing committee voting.
For each participating account, the sortition algorithm runs to check if said account is allowed to participate in the proposal.
If an account \( a \) is selected by sortition (because \( \c_j = \Sortition(a_I, r, p, \prop)_j > 0 \)) there are two options:
-
If this is a proposal step (\( p = 0 \)) or if the node has observed a bundle \( \Bundle(r, p-1, s’, \bot) \) (meaning there is no valid pinned value), then the node:
- Assembles a block (see the Ledger non-normative section for details on this process),
- Computes the proposal value for this block,
- Broadcast a proposal vote by the account \( a \),
- Broadcasts the full block in a \( \texttt{Proposal} \) type message.
-
Otherwise, a value \( \bar{v} \) has been pinned, supported by a bundle observed in period \( p - 1 \), and on Algorithm 3 - Line 15 the node:
- Gets the pinned value,
- Assembles a vote \( \Vote(a_I, r, p, \prop, \bar{v}, \c) \),
- Broadcasts this vote,
- Broadcast the proposal for the pinned vote if it has already been observed.
⚙️ IMPLEMENTATION
The reference implementation assembles a set of transactions and a block header independently of the proposer, in parallel with the proposer loop. This improves timing and guarantees the tight deadline constraints for the block proposal step.
$$ \newcommand \Priority {\mathrm{Priority}} \newcommand \VRF {\mathrm{VRF}} \newcommand \ProofToHash {\mathrm{ProofToHash}} \newcommand \SoftVote {\mathrm{SoftVote}} \newcommand \Hash {\mathrm{Hash}} \newcommand \Sortition {\mathrm{Sortition}} \newcommand \Broadcast {\mathrm{Broadcast}} \newcommand \RetrieveProposal {\mathrm{RetrieveProposal}} \newcommand \DynamicFilterTimeout {\mathrm{DynamicFilterTimeout}} \newcommand \Soft {\mathit{soft}} \newcommand \Prop {\mathit{propose}} \newcommand \Vote {\mathrm{Vote}} \newcommand \function {\textbf{function }} \newcommand \endfunction {\textbf{end function}} \newcommand \if {\textbf{if }} \newcommand \then {\textbf{ then}} \newcommand \endif {\textbf{end if}} \newcommand \for {\textbf{for }} \newcommand \do {\textbf{ do}} \newcommand \endfor {\textbf{end for}} \newcommand \comment {\qquad \small \textsf} \newcommand \loh {\mathit{lowestObservedHash}} \newcommand \vt {\mathit{vote}} \newcommand \ph {\mathit{priorityHash}} \newcommand \c {\mathit{credentials}} $$
Soft Vote
The soft vote stage (also known as “filtering”) filters the proposal-value candidates available for the round, selecting the one with the highest priority to vote for.
Priority Function
Let \( \Priority \) be the function that determines which proposal-value to soft-vote for this round, as defined in the normative section:
$$ \Priority(v) = \min_{i \in [0, w_j)} \left\{ \Hash \left( \VRF.\ProofToHash(y) || I_j || i \right) \right\} $$
Where:
- \( v \) is a proposal value for this round,
- \( I_j \) is the proposer address identified by the subscript \( j \),
- \( w_j \) is the weight of the credentials for \( v \) by proposer \( I_j \),
- \( y \) is the \( \VRF \) proof as computed by proposer \( I_j \) using their \( \VRF \) secret key.
The function selects the minimum among a set of \( w_j \) hash values calculated as \(\Hash \left(\VRF.\ProofToHash(y) || I_j || i \right)\), with \(i \in [0, w_j)\).
The higher the credentials’ weight \( w_j \), the larger the set, the higher the chances for the proposer \( I_j \) to get the lowest value among all the players for this round.
Algorithm
\( \textbf{Algorithm 4} \text{: Soft Vote} \)
$$ \begin{aligned} &\text{1: } \function \SoftVote() \\ &\text{2: } \quad \loh \gets \infty \\ &\text{3: } \quad v \gets \bot \\ &\text{4: } \quad \for \vt_p \in V^\ast \do \comment{# The subset of votes corresponding to proposals} \\ &\text{5: } \quad \quad \ph \gets \Priority(\vt_p) \\ &\text{6: } \quad \quad \if \ph < \loh \then \\ &\text{7: } \quad \quad \quad \loh \gets \ph \\ &\text{8: } \quad \quad \quad v \gets \vt_p \\ &\text{9: } \quad \quad \endif \\ &\text{10:} \quad \endfor \\ &\text{11:} \quad \if \loh < \infty \then \\ &\text{12:} \quad \quad \for a \in A \do \\ &\text{13:} \quad \quad \quad \c \gets \Sortition(a_I, r, p, \Soft) \\ &\text{14:} \quad \quad \quad \if \c_j > 0 \then \\ &\text{15:} \quad \quad \quad \quad \Broadcast(\Vote(a_I, r, p, \Soft, v, \c)) \\ &\text{16:} \quad \quad \quad \quad \if \RetrieveProposal(v) \then \\ &\text{17:} \quad \quad \quad \quad \quad \Broadcast(\RetrieveProposal(v)) \\ &\text{18:} \quad \quad \quad \quad \endif \\ &\text{19:} \quad \quad \quad \endif \\ &\text{20:} \quad \quad \endfor \\ &\text{21:} \quad \endif \\ &\text{22: } \endfunction \end{aligned} $$
⚙️ IMPLEMENTATION
Soft vote filtering reference implementation.
Soft vote issuance reference implementation.
The soft vote stage is run after a timeout of \( \DynamicFilterTimeout(p) \) (where \( p \) is the executing period of the node) is observed by the node (see the dynamic filter timeout section for more details).
Let \( V \) be the set of all observed votes in the currently executing round. For convenience, we define a subset, \( V^\ast \) to be all proposals received; that is \( V^\ast = \{\vt \in V : \vt_s = \Prop\} \).
With the aid of a priority function, this stage performs a filtering action, selecting the highest priority observed proposal to vote for, defined as the one with the lowest hashed value.
The priority function (Algorithm 4 - Lines 4 to 9) should be interpreted as follows.
Consider every proposal value \( \vt_p \) in the subset \( V^\ast \) and the hash of the \( \VRF \) proof \( \ProofToHash(y) \) obtained by its proposer in the sortition.
For each index \( i \) in the interval from \( 0 \) (inclusive) up to the proposer credentials’ weight1 \( w_j \) (exclusive), the node hashes the concatenation of \( \ProofToHash(y) \), the proposer address \( I_j \) and the index \( i \), as \( \Hash(\VRF.\ProofToHash(y) || I_j || i) \) (where \( \Hash \) is the node’s general cryptographic hashing function.
See the cryptography normative section for details on \( VRF \).
See the cryptography normative section for details on the \( \Hash \) function.
Then, the node keeps track of the proposal-value \( v \) that minimizes the concatenation hashing (Algorithm 4 - Lines 6 to 8).
After running the filtering algorithm for all proposal votes observed, and assuming there was at least one proposal in \( V^\ast \), the broadcasting section of the algorithm is executed (Algorithm 4 - Lines 11 to 15).
For every online account (registered on the node), selected to be part of the \( \Soft \) voting committee, a \( \Soft \) vote is broadcast for the previously filtered value \( v \).
If the corresponding full proposal has already been observed and is available in \( P \), it is also broadcast (Algorithm 4 - Lines 16 to 17).
If the previous assumption of non-empty \( V^\ast \) does not hold, no broadcasting is performed, and the node produces no output in its filtering step.
Corresponds to the \( j \) output of \( \Sortition \), stored inside the \( \c \) structure.
$$ \newcommand \ValidateVote {\mathrm{ValidateVote}} \newcommand \VerifyVote {\mathrm{VerifyVote}} \newcommand \SenderPeer {\mathrm{SenderPeer}} \newcommand \DisconnectFromPeer {\mathrm{DisconnectFromPeer}} \newcommand \Equivocation {\mathrm{Equivocation}} \newcommand \IsEquivocation {\mathrm{IsEquivocation}} \newcommand \IsSecondEquivocation {\mathrm{IsSecondEquivocation}} \newcommand \HandleVote {\mathrm{HandleVote}} \newcommand \Relay {\mathrm{Relay}} \newcommand \RetrieveProposal {\mathrm{RetrieveProposal}} \newcommand \Broadcast {\mathrm{Broadcast}} \newcommand \Bundle {\mathrm{Bundle}} \newcommand \Vote {\mathrm{Vote}} \newcommand \Sortition {\mathrm{Sortition}} \newcommand \RequestProposal {\mathrm{RequestProposal}} \newcommand \StartNewPeriod {\mathrm{StartNewPeriod}} \newcommand \GarbageCollect {\mathrm{GarbageCollect}} \newcommand \StartNewRound {\mathrm{StartNewRound}} \newcommand \Commit {\mathrm{Commit}} \newcommand \Prop {\mathit{propose}} \newcommand \Next {\mathit{next}} \newcommand \Soft {\mathit{soft}} \newcommand \Cert {\mathit{cert}} \newcommand \sk {\mathrm{sk}} \newcommand \function {\textbf{function }} \newcommand \return {\textbf{return }} \newcommand \endfunction {\textbf{end function}} \newcommand \not {\textbf{not }} \newcommand \if {\textbf{if }} \newcommand \elseif {\textbf{else if }} \newcommand \then {\textbf{ then}} \newcommand \endif {\textbf{end if}} \newcommand \for {\textbf{for }} \newcommand \do {\textbf{ do}} \newcommand \endfor {\textbf{end for}} \newcommand \comment {\qquad \small \textsf} \newcommand \vt {\mathit{vote}} \newcommand \c {\mathit{credentials}} $$
Vote Handler
The algorithms presented in this section abstract away a series of behaviors as a single vote handler for ease of understanding and to provide an implementation-agnostic engineering overview.
In the reference implementation, the vote verification and vote observation, although dependent on each other, are performed by separate processes.
Note that an equivocation vote is a pair of votes that differ only in their proposal values \( v \). In other words, given a player \( I \) and a node’s context tuple \((r, p, s)\), \( \Equivocation(I, r, p, s) = (\Vote(I, r, p, s, v_1), \Vote(I, r, p, s, v_2)) \) for some \( v_1 \neq v_2 \).
Algorithm
\( \textbf{Algorithm 5} \text{: Handle Vote} \)
$$ \begin{aligned} &\text{1: } \function \ValidateVote(\vt): \\ &\text{2: } \quad \if \not \VerifyVote(\vt) \then \\ &\text{3: } \quad \quad \DisconnectFromPeer(\SenderPeer(\vt)) \\ &\text{4: } \quad \quad \return \comment{# Ignore invalid vote} \\ &\text{5: } \quad \endif \\ &\text{6: } \quad \if \vt_s = 0 \land (\vt \in V \lor \IsEquivocation(\vt)) \then \\ &\text{7: } \quad \quad \return \comment{# Ignore vote, equivocation not allowed in proposal votes} \\ &\text{8: } \quad \endif \\ &\text{9: } \quad \if \vt_s > 0 \land \IsSecondEquivocation(\vt) \then \\ &\text{10:} \quad \quad \return \comment{# Ignore vote if it’s a second equivocation} \\ &\text{11:} \quad \endif \\ &\text{12:} \quad \if \vt_r < r \then \\ &\text{13:} \quad \quad \return \comment{# Ignore vote of past round}\\ &\text{14:} \quad \endif \\ &\text{15:} \quad \if \vt_r = r + 1 \land (\vt_p > 0 \lor \vt_s \in \{\Next_0, \dots, \Next_{249}\}) \then\\ &\text{16:} \quad \quad \return \comment{# Ignore vote of next round if non-zero period or “next_k” step} \\ &\text{17:} \quad \endif \\ &\text{18:} \quad \if \vt_r = r \land (\vt_p \notin \{p-1, p, p+1\} \lor \\ &\text{} \quad \quad \quad \quad \quad \quad (\vt_p = p+1 \land \vt_s \in \{\Next_1, \dots, \Next_{249}\}) \lor \\ &\text{} \quad \quad \quad \quad \quad \quad (\vt_p = p \land \vt_s \in \{\Next_1, \dots, \Next_{249}\} \land \vt_s \notin \{s-1, s, s+1\}) \lor \\ &\text{} \quad \quad \quad \quad \quad \quad (\vt_p = p-1 \land \vt_s \in \{\Next_1, \dots, \Next_{249}\} \land \vt_s \notin \{\bar{s}-1, \bar{s}, \bar{s}+1\})) \then \\ &\text{19:} \quad \quad \return \comment{# Ignore vote} \\ &\text{20:} \quad \endif \\ &\text{21: } \endfunction \\ \\ &\text{22: } \function \HandleVote(\vt): \\ &\text{23:} \quad \ValidateVote(\vt) \comment{# Check the validity of the vote} \\ &\text{24:} \quad V \gets V \cup \vt \comment{# Observe the vote}\\ &\text{25:} \quad \Relay(\vt) \\ &\text{26:} \quad \if \vt_s = \Prop \then \\ &\text{27:} \quad \quad \if \RetrieveProposal(\vt_v) \neq \bot \then \\ &\text{28:} \quad \quad \quad \Broadcast(\RetrieveProposal(\vt_v)) \\ &\text{29:} \quad \quad \endif \\ &\text{30:} \quad \elseif \vt_s = \Soft \then \\ &\text{31:} \quad \quad \if \exists v : \Bundle(\vt_r, \vt_p, \Soft, v) \subset V \then \\ &\text{32:} \quad \quad \quad \for a \in A \do \\ &\text{33:} \quad \quad \quad \quad \c \gets \Sortition(a_{\sk}, r, p, \Cert) \\ &\text{34:} \quad \quad \quad \quad \if \c_j > 0 \then \\ &\text{35:} \quad \quad \quad \quad \quad \Broadcast(\Vote(a_I, r, p, \Cert, v, \c)) \\ &\text{36:} \quad \quad \quad \quad \endif \\ &\text{37:} \quad \quad \quad \endfor \\ &\text{38:} \quad \quad \endif \\ &\text{39:} \quad \elseif \vt_s = \Cert \then \\ &\text{40:} \quad \quad \if \exists v : \Bundle(\vt_r, \vt_p, \Cert, v) \subset V \then \\ &\text{41:} \quad \quad \quad \if \RetrieveProposal(v) = \bot \then \\ &\text{42:} \quad \quad \quad \quad \RequestProposal(v) \\ &\text{43:} \quad \quad \quad \quad \if p < \vt_p \then \\ &\text{44:} \quad \quad \quad \quad \quad p_{old} \gets p \\ &\text{45:} \quad \quad \quad \quad \quad \StartNewPeriod(\vt_p) \\ &\text{46:} \quad \quad \quad \quad \quad \GarbageCollect(r, p_{old}) \\ &\text{47:} \quad \quad \quad \quad \endif \\ &\text{48:} \quad \quad \quad \endif \\ &\text{49:} \quad \quad \quad \Commit(v) \\ &\text{50:} \quad \quad \quad r_{old} \gets r \\ &\text{51:} \quad \quad \quad \StartNewRound(\vt_r + 1) \\ &\text{52:} \quad \quad \quad \GarbageCollect(r_{old}, p) \\ &\text{53:} \quad \quad \endif \\ &\text{54:} \quad \elseif \vt_s > \Cert \then \\ &\text{55:} \quad \quad \if \exists v : \Bundle(\vt_r, \vt_p, \vt_s, v) \subset V \then \\ &\text{56:} \quad \quad \quad p_{old} \gets p \\ &\text{57:} \quad \quad \quad \StartNewPeriod(\vt_p + 1) \\ &\text{58:} \quad \quad \quad \GarbageCollect(r, p_{old}) \\ &\text{59:} \quad \quad \endif \\ &\text{60:} \quad \endif \\ &\text{61: } \endfunction \\ \end{aligned} $$
⚙️ IMPLEMENTATION
Relevant parts of the reference implementation related to vote handling:
The vote handler is triggered when a node receives a message containing a vote for a given proposal value, round, period, or step.
It first performs a series of checks, and if the received vote passes all of them, then it is broadcast by all accounts selected as the appropriate committee members.
Vote Validation
On Line 2, the \( \ValidateVote \) function checks if the vote is valid. If invalid, this is considered adversarial behavior. Therefore, a node may disconnect from the vote sender node (Line 3), retrieving the network ID of the original message sender with the \( SenderPeer \ helper network module function.
For more details on disconnection actions and the definition of a peer, refer to the Algorand Network Layer non-normative section.
Equivocation votes on a proposal step are not allowed, so a check for this condition is performed (Line 6).
Furthermore, second equivocations are never allowed (Line 9).
Any votes for rounds before the current round are discarded (Line 12).
In the special case of receiving a message vote for a round immediately after the current round, the node observes it only if it is related to the first period (\( p = 0 \)), in any of the following steps: proposal, soft, cert, late, down, or redo (ignoring votes for further periods \( p > 0 \) or for \( \Next_k \) steps).
Finally, the node checks that (Line 18) if the vote’s round is for the currently executing round, and one of the following:
-
Vote’s period is not the current node period, the period before, or the next period, or
-
Vote’s period is the next period, and
- Its step is \( \Next_k \) with \( k \geq 1 \), or
-
Vote’s period is the current node period, and
- Its step is \( \Next_k \) with \( k \geq 1 \), and
- Its step is not the current step, the step before, or the next step, or
-
Vote’s period is the period before, and
- Its step is \( \Next_k \) with \( k \geq 1 \), and
- Its step distance is not one or less from the node’s last finished step.
Then the vote is ignored and discarded. Note that the equivocation vote verification uses the same verification functions, but verifies that both constituent votes are valid separately.
Vote Handling
Once finished with the series of validation checks, the vote is observed, relayed, and then processed by the node according to its current context and the vote’s step:
-
If the vote’s step is \( \Prop \), and the proposal corresponding to the proposal-value \( v \) has already been observed, the proposal is broadcast (that is, the node performs a re-proposal payload broadcast).
-
If the vote’s step is \( Soft \), and a \( \Soft \Bundle \) has been observed with the addition of the vote, the \( \Sortition \) sub-procedure is run for every online account managed by the node. Then, a \( Cert \) vote is cast for each account the lottery selects.
-
If the vote’s step is \( Cert \), and observing the vote causes the node to observe a \( Cert \Bundle \) for a proposal-value \( v \), then it checks if the full proposal associated with the critical value has been observed. Simultaneous observation of a \( Cert \Bundle \) for a value \( v \) and of a proposal equal to \( RetrieveProposal(v) \) implies the associated entry is committable. If the full proposal has not yet been observed, the node may stall and request the full proposal from the network. Once the desired proposal can be committed, the node proceeds to commit, start a new round, and garbage collects all transient data from the round it just finished.
-
Finally, if the vote is that of a recovery step (\( s > \Cert \)), and a \( \Bundle \) has been observed for a given proposal-value \( v \), then a new period is started, and the currently executing period-specific data is garbage collected.
$$ \newcommand \HandleProposal {\mathrm{HandleProposal}} \newcommand \VerifyProposal {\mathrm{VerifyProposal}} \newcommand \IsCommittable {\mathrm{IsCommittable}} \newcommand \Relay {\mathrm{Relay}} \newcommand \Broadcast {\mathrm{Broadcast}} \newcommand \Vote {\mathrm{Vote}} \newcommand \Sortition {\mathrm{Sortition}} \newcommand \Proposal {\mathrm{Proposal}} \newcommand \Bundle {\mathrm{Bundle}} \newcommand \Hash {\mathrm{Hash}} \newcommand \Encode {\mathrm{Encode}} \newcommand \bh {\mathrm{bh}} \newcommand \Soft {\mathit{soft}} \newcommand \Cert {\mathit{cert}} \newcommand \Next {\mathit{next}} \newcommand \function {\textbf{function }} \newcommand \return {\textbf{return }} \newcommand \endfunction {\textbf{end function}} \newcommand \if {\textbf{if }} \newcommand \then {\textbf{ then}} \newcommand \endif {\textbf{end if}} \newcommand \for {\textbf{for }} \newcommand \do {\textbf{ do}} \newcommand \endfor {\textbf{end for}} \newcommand \not {\textbf{not }} \newcommand \comment {\qquad \small \textsf} \newcommand \pr {\mathit{proposal}} \newcommand \c {\mathit{credentials}} $$
Proposal Handler
The proposal handler is triggered when a node receives a full proposal message.
A proposal-value and a full proposal are related but separate constructions. This is motivated by a slower gossiping time of a full proposal, compared to a much more succinct and therefore quickly gossiped proposal-value.
A proposal-value contains four fields:
-
The original period in which this block was proposed.
-
The original proposer’s address.
-
The block digest, equal to the block header’s hash (including a domain separator). This field expands to \( \Hash(\texttt{“BH”} || \Encode(\bh)) \), where \( \texttt{“BH”} \) is the domain separator for a “block header”, and the encoding function is the msgpack of the block header (\( \bh \)).
-
A hash of the proposal, \( \Hash(\Proposal) \). This field expands to \( \Hash(\texttt{“PL”} || \Encode(\Proposal)) \), where \( \Proposal \) represents the unauthenticated proposal, \( \texttt{“PL”} \) is the domain separator for a “payload”, and the encoding function is the msgpack of the \( \Proposal \).
⚙️ IMPLEMENTATION
Proposal-value structure.
Domain separators for block header and payload.
On the other hand, an unauthenticated proposal contains a full block and all extra data for the block validation:
-
The original period in which this block was proposed.
-
The original proposer’s address.
-
A full block (header and payset).
-
A seed proof \( \pi_{seed} \).
Note that the original period and proposer’s address are the same as the associated proposal-value. The seed proof \( \pi_{seed} \) is used to verify the seed computation.
⚙️ IMPLEMENTATION
Unauthenticated proposal structure.
⚙️ IMPLEMENTATION
In the reference implementation, the unauthenticated proposal is sent to the network linked to a previously emitted proposal vote. These are sent together in a struct defined as a compound message, which avoids the edge case of receiving proposal and proposal-value messages in an unfavorable order. Consider the following edge case: a node observes a proposal before receiving its supporting proposal-value. It discards the proposal (to avoid DDoS attacks). Right after the node receives the related proposal-value, it goes through all the steps, and certifies this block, but needs to request the previously discarded proposal to be able to commit it and advance a round. If this happens to enough nodes (voting stake), the network might move to a second period. In the new period, the proposal is broadcast and committed fast, since the proposal step is skipped (having carried over the staged and frozen values).
Algorithm
In the following pseudocode the frozen value \( \mu \) is either:
- The highest priority observed proposal-value in the current \((r, p)\) context (i.e., the lowest hashed according to the priority function), or
- \( \bot \) if the node has observed no valid proposal vote.
The staged value \( \sigma \) is either:
- The sole proposal-value for which a \( \Bundle_\Soft \) has been observed in the current \((r, p)\) context (see normative section), or
- \( \bot \) if the node has observed no valid \( \Bundle_\Soft \).
The pinned value \( \bar{v} \) is a proposal-value that was a staged value in a previous period. When available, this value is used to fast-forward the first steps of the protocol when a \( \Next \) vote has been successful.
\( \textbf{Algorithm 6} \text{: Handle Proposal} \)
$$ \begin{aligned} &\text{1: } \function \HandleProposal(\pr) \\ &\text{2: } \quad v \gets \Proposal_v(\pr, \pr_p, \pr_I) \\ &\text{3: } \quad \if \exists \Bundle(r+1, 0, \Soft, v) \in B \then \\ &\text{4: } \quad \quad \Relay(\pr) \\ &\text{5: } \quad \quad \return \comment{# Future round, do not observe (node is behind)} \\ &\text{6: } \quad \endif \\ &\text{7: } \quad \if \not \VerifyProposal(\pr) \lor \pr \in P \then \\ &\text{8: } \quad \quad \return \comment{# Ignore proposal} \\ &\text{9: } \quad \endif \\ &\text{10:} \quad \if v \notin \{\sigma, \bar{v}, \mu\} \then \\ &\text{11:} \quad \quad \return \comment{# Ignore proposal} \\ &\text{12:} \quad \endif \\ &\text{13:} \quad \Relay(\pr) \\ &\text{14:} \quad P \gets P \cup \pr \\ &\text{15:} \quad \if \IsCommittable(v) \land s \le \Cert \then \\ &\text{16:} \quad \quad \for a \in A \do \\ &\text{17:} \quad \quad \quad \c \gets \Sortition(a_I, r, p, \Cert) \\ &\text{18:} \quad \quad \quad \if \c_j > 0 \then \\ &\text{19:} \quad \quad \quad \quad \Broadcast(\Vote(a_I, r, p, \Cert, v, \c)) \\ &\text{20:} \quad \quad \quad \endif \\ &\text{21:} \quad \quad \endfor \\ &\text{22:} \quad \endif \\ &\text{23: } \endfunction \end{aligned} $$
⚙️ IMPLEMENTATION
Proposal handler reference implementation.
The node starts by performing a series of checks, after which it will either:
-
Ignore the received proposal, discarding it and emitting no output, or
-
Relay, observe, and produce an output according to the current context and the characteristics of the proposal.
The node checks if the proposal is from the first period of the next round (Line 3), in which case, the node relays this proposal and then ignores it for the operations of the current round.
Whenever the node catches up (i.e., observes a round change), and only if necessary, it will request this proposal back from the network.
The node checks (Line 7) if the proposal is invalid or has already been observed. Any one of those conditions is enough to discard and ignore the incoming proposal.
Finally, the node checks (Line 10) if the associated proposal value is either a special proposal-value for the current round and period (\( \sigma \), \( \mu \)) or the pinned proposal-value (\( \bar{v} \)). Any full proposal whose proposal-value does not match one of these is ignored.
For formal details on special values, refer to the normative section.
Once the checks have been passed, the node relays and observes the proposal (Lines 13 and 14), by adding it to the observed proposals set \( P \).
Next, only if the proposal-value is committable (meaning the staged value is set for a proposal, and said proposal has already been observed and is available) and the current step is lower than or equal to a \( \Cert \) step (i.e., is not yet in a recovery step), the node plays for each online account (registered on the node), performing a \( \Sortition \)to select the certification committee members.
For each selected account, a \( \Vote_\Cert \) for the current proposal-value is broadcast.
$$ \newcommand \Bundle {\mathrm{Bundle}} \newcommand \HandleBundle {\mathrm{HandleBundle}} \newcommand \VerifyBundle {\mathrm{VerifyBundle}} \newcommand \HandleVote {\mathrm{HandleVote}} \newcommand \SenderPeer {\mathrm{SenderPeer}} \newcommand \DisconnectFromPeer {\mathrm{DisconnectFromPeer}} \newcommand \function {\textbf{function }} \newcommand \return {\textbf{return }} \newcommand \endfunction {\textbf{end function}} \newcommand \if {\textbf{if }} \newcommand \then {\textbf{ then}} \newcommand \endif {\textbf{end if}} \newcommand \for {\textbf{for }} \newcommand \do {\textbf{ do}} \newcommand \endfor {\textbf{end for}} \newcommand \not {\textbf{not }} \newcommand \vt {\mathit{vote}} \newcommand \b {\mathit{bundle}} $$
Bundle Handler
The node runs a bundle handler when receiving a message with a full bundle.
Algorithm
\( \textbf{Algorithm 6} \text{: Handle Bundle} \)
$$ \begin{aligned} &\text{1: } \function \HandleBundle(\b): \\ &\text{2: } \quad \if \not \VerifyBundle(\b) \then \\ &\text{3: } \quad \quad \DisconnectFromPeer(\SenderPeer(\b)) \\ &\text{4: } \quad \quad \return \\ &\text{5: } \quad \endif \\ &\text{6: } \quad \if \b_r = r \land \b_p + 1 \ge p \then \\ &\text{7: } \quad \quad \for \vt \in \b \do \\ &\text{8: } \quad \quad \quad \HandleVote(\vt) \\ &\text{9: } \quad \quad \endfor \\ &\text{10:} \quad \endif \\ &\text{11: } \endfunction \end{aligned} $$
⚙️ IMPLEMENTATION
Bundle verification reference implementation.
Bundle handling in general message handler.
The bundle handler is invoked whenever a bundle message is received.
The received bundle is immediately discarded if it is invalid (Line 2). The node may penalize the malicious sending peer (e.g., disconnecting from or “blacklisting” it).
If the received bundle (Line 6):
- Is for round equal to the node’s current round, and
- Is for at most one period behind the node’s current period.
Then the bundle is processed, calling the vote handler for each vote in the bundle (Lines 7 and 8).
Note that multiple bundles can be processed concurrently. Therefore, while handling votes from a bundle \( b \) for proposal-value \( v \) separately, if another bundle \( \b\prime = \Bundle(\b_r, \b_p, \b_s, v\prime) \) is formed and observed first (with \( v\prime \) not necessarily equal to \( v \)1), votes in \( \b\prime \) are relayed individually, and any output or state changes caused by observing \( \b\prime \) is produced.
All leftover votes in \( b \) are then processed according to the new node state determined by \( \b\prime \) observation (e.g., votes are discarded if the executing step was certification and a new round has started, and so \( b_r < r \)).
If \( \b \) does not pass the previous check (Line 6), then no output is produced, and the bundle is ignored and discarded.
Consider what would happen if equivocation votes contained in \( b \) cause a bundle for \( v\prime \) to reach the required threshold before the player may finish observing every single vote in \( b \).
$$ \newcommand \Commit {\mathrm{Commit}} \newcommand \RetrieveProposal {\mathrm{RetrieveProposal}} \newcommand \ApplyDeltas {\mathrm{ApplyDeltas}} \newcommand \TP {\mathrm{TransactionPool}} \newcommand \Update {\mathrm{Update}} \newcommand \function {\textbf{function }} \newcommand \endfunction {\textbf{end function}} \newcommand \pset {\mathit{payset}} $$
Commitment
The commitment is the final stage that updates the copy of the Ledger on the node, applying all the state deltas (e.g., account balances, application state, etc.) related to the transactions contained in the committed block proposal.
Algorithm
In the following pseudocode \( e_t \) denotes the body (transactions) of the proposal (a.k.a. the payset).
\( \textbf{Algorithm 8} \text{: Commit} \)
$$ \begin{aligned} &\text{1: } \function \Commit(v) \\ &\text{2: } \quad e \gets \RetrieveProposal(v)_e \\ &\text{3: } \quad L \gets L || e \\ &\text{4: } \quad \ApplyDeltas(e) \\ &\text{5: } \quad \TP.\Update(e_t) \\ &\text{6: } \endfunction \end{aligned} $$
⚙️ IMPLEMENTATION
Commit block proposal reference implementation.
The function commits to the Ledger the block corresponding to the received proposal-value.
The proposal-value must be committable, which implies both validity and availability of the full block body and seed.
The node retrieves the full block \( e \) related to the proposal-value (Line 2), and appends it to the Ledger \( L \) (Line 3).
Then, the node updates the Ledger state (and trackers) with all state changes (deltas) produced by the new committed block.
For further details, refer to the Ledger normative section.
The \( \TP \) is then purged of all transactions in the committed block.
For further details on this process, see the Ledger non-normative section.
$$ \newcommand \DeadlineTimeout {\mathrm{DeadlineTimeout}} \newcommand \s {\mathit{step}} \newcommand \Soft {\mathit{soft}} \newcommand \Late {\mathit{late}} \newcommand \Redo {\mathit{redo}} \newcommand \Down {\mathit{down}} \newcommand \Next {\mathit{next}} $$
Recovery Stages
Whenever the threshold for a certification vote is not achieved in the allowed time for the current period, \( DeadlineTimeout(p) \), the protocol enters in recovery mode.
The protocol employs a series of recovery routines to provide a quick response once normal network conditions are reestablished.
In the “best case scenario” the protocol tries to “preserve and carry over” some information from the failed consensus attempt to speed up the recovery process.
In the “worst case scenario” the protocol tries to reach an “agreement to disagree”, that is a bundle of votes to start the next period without any previous assumptions, and goes back to the block assembly and proposal stage.
The following sections present the recovery stages and routines.
Recovery Modes
The Algorand protocol provides two recovery modes, executed in parallel at different time-cadence, driven by the \( \s \) and the local node timer \( t_N \).
The following is a conceptual diagram of the two recovery modes, briefly described below.
Recovery (Exponential Recovery)
\( \s \in [3,252] \): the Recovery (or Exponential Recovery) mode attempts are executed with an exponentially growing time cadence (apart from a finite random variance), and so becoming increasingly sporadic.
When
-
\( t_N = \max{4\lambda,\Lambda} \) (when \( \s = 3 \)) or,
-
\( t_N = \max{4\lambda,\Lambda} + 2^{\s-3} \lambda + r \) (when \( 4 \leq \s \leq 252 \)), where \( r \in [0, 2^{\s-3}\lambda] \) is sampled uniformly at random,
Then
-
If the node has seen a valid block proposal \( B \) and a \( (r, p) \)-\( \Soft \)-quorum for \( H(B) \) has been observed, then the node \( \Next \)-votes \( H(B) \),
-
Otherwise, if \( p > 0 \) and the node has received a \( \Next \)-quorum for \( \bot \) from period \( (r, p-1) \), then the node \( \Next \)-votes \( \bot \),
-
Otherwise, the node has received a \( \Next \)-quorum for \( v = H(B’) \neq \bot \) from period \( (r, p-1) \), and the node \( \Next \)-votes \( v \).
The \( \s \) of the node context tuple \( (r, p, s) \) is incremented every time the local node clock triggers a Recovery trial.
For further details on the Recovery procedure, see the non-normative section.
Fast Recovery (Linear Recovery)
\( \s \in [253,255] \): the Fast Recovery (or Linear Recovery) mode attempts are executed with almost constant time cadence (apart from a finite random variance).
When
- \( t_N = k\lambda_f + t \) for any positive integer \( k \) and \( t \in [0,\lambda_f] \), where \( t \) is sampled uniformly at random,
Then
-
If the node has seen a valid block proposal \( B \) and a \( (r, p ) \)-\( \Soft \)-quorum for \( H(B) \) then the node \( \Late \)-votes \( H(B) \) (\( \s = 253 \)),
-
Otherwise, if \( p > 0 \) and the node has received a \( \Next \)-quorum for \( \bot \) from period \( (r, p-1) \), then the node \( \Down \)-votes \( \bot \) (\( \s = 255 \)),
-
Otherwise, the node has received a \( \Next \)-quorum for \( v = H(B’) \neq \bot \) from period \( (r, p-1) \), and the node \( \Redo \)-votes \( v \) (\( \s = 254 \)).
These three steps are mutually exclusive; therefore, whenever a time event triggers the Fast Recovery procedure, just one of \( \Late, \Redo, \Down \) steps is executed.
In the Fast Recovery procedure, the \( \s \) of the node context tuple \( (r, p, s) \) is not incremented every time the local node clock triggers a Fast Recovery trial. In fact, the node does not wait \( \s \) to be equal to \( 253, 254, 255 \) to execute a Fast Recovery attempt. Fast Recovery attempts are driven just by the local node clock (\( t_N = k\lambda_f + t \)) and just one among the three mutually exclusive \( \Late, \Redo, \Down \) steps is executed.
For further details on the Fast Recovery procedure, see the non-normative section.
$$ \newcommand \Soft {\mathit{soft}} \newcommand \Cert {\mathit{cert}} \newcommand \Next {\mathit{next}} $$
Resynchronization Attempt
The resynchronization is an auxiliary function used throughout the recovery steps.
A partial order relation is defined in the space of all observed bundles. We call this relation freshness.
A resynchronization attempt broadcasts the freshest observed bundle (if any).
Priority-wise, bundles’ freshness is defined as follows:
-
Bundles for a \( \Cert \) step are fresher than all other bundles.
-
Bundles from a later period are fresher than bundles from an older period.
-
Bundles for \( \Next \) step are fresher than bundles for a \( \Soft \) step of the same period.
-
Bundles for \( \Next \) step for the \( \bot \) proposal-value are fresher than bundles for a \( \Next \) step for some other value.
For a formal definition of this property, refer to the ABFT normative section.
⚙️ IMPLEMENTATION
Bundle freshness reference implementation.
In the reference implementation, a resynchronization attempt is handled by the
partitionPolicyfunction, as the network is assumed to be in a “partitioned state” due to the temporary inability to reach consensus. In this case, the function is only invoked when the current step \( s \geq 3 \) or when the current period \( p \geq 3 \) (that is, the player has gone through two full periods without reaching a consensus).
$$ \newcommand \DeadlineTimeout {\mathrm{DeadlineTimeout}} \newcommand \Recovery {\mathrm{Recovery}} \newcommand \ResynchronizationAttempt {\mathrm{ResynchronizationAttempt}} \newcommand \Sortition {\mathrm{Sortition}} \newcommand \Proposal {\mathrm{Proposal}} \newcommand \IsCommittable {\mathrm{IsCommittable}} \newcommand \Broadcast {\mathrm{Broadcast}} \newcommand \Vote {\mathrm{Vote}} \newcommand \Bundle {\mathrm{Bundle}} \newcommand \Cert {\mathit{cert}} \newcommand \Next {\mathit{next}} \newcommand \function {\textbf{function }} \newcommand \endfunction {\textbf{end function}} \newcommand \if {\textbf{if }} \newcommand \elseif {\textbf{else if }} \newcommand \then {\textbf{ then}} \newcommand \else {\textbf{else}} \newcommand \endif {\textbf{end if}} \newcommand \for {\textbf{for }} \newcommand \do {\textbf{ do}} \newcommand \endfor {\textbf{end for}} \newcommand \c {\mathit{credentials}} \newcommand \prop {\mathit{proposal}} \newcommand \s {\mathit{step}} $$
Recovery
The recovery algorithm is executed periodically, whenever a \( \Bundle_\Cert \) has not been observed before \( \DeadlineTimeout(p) \) for a given period \( p \).
Algorithm
\( \textbf{Algorithm 9} \text{: Recovery} \)
$$ \begin{aligned} &\text{1: } \function \Recovery() \\ &\text{2: } \quad \ResynchronizationAttempt() \\ &\text{3: } \quad \for a \in A \do \\ &\text{4: } \quad \quad \c \gets \Sortition(a_I, r, p, s) \\ &\text{5: } \quad \quad \if \c_j > 0 \then \\ &\text{6: } \quad \quad \quad \if \exists v = \Proposal_v(\prop, \prop_p, \prop_I) \\ &\text{ } \quad \quad \quad \quad \quad \text{for some } \prop \in P \mid \IsCommittable(v) \then \\ &\text{7: } \quad \quad \quad \quad \Broadcast(\Vote(a_I, r, p, s, v, \c)) \\ &\text{8: } \quad \quad \quad \elseif \exists s_0 > \Cert \mid \Bundle(r, p - 1, s_0, \bot) \subseteq V \land \\ &\text{ } \quad \quad \quad \quad \quad \quad \quad \exists s_1 > \Cert \mid \Bundle(r, p - 1, s_1, \bar{v}) \subseteq V \then \\ &\text{9: } \quad \quad \quad \quad \Broadcast(\Vote(a_I, r, p, s, \bar{v}, \c)) \\ &\text{10:} \quad \quad \quad \else \\ &\text{11:} \quad \quad \quad \quad \Broadcast(\Vote(a_I, r, p, s, \bot, \c)) \\ &\text{12:} \quad \quad \quad \endif \\ &\text{13:} \quad \quad \endif \\ &\text{14:} \quad \endfor \\ &\text{15:} \quad \s \gets \s + 1 \\ &\text{16: } \endfunction \end{aligned} $$
⚙️ IMPLEMENTATION
Next vote issuance reference implementation.
The node starts by making a resynchronization attempt (Line 2).
Afterward (Lines 3:5), the node plays independently for each online account (registered on the node). This means that for every account available in \( A \), the \( \Sortition \) algorithm is run, and accounts selected in the recovery committee (i.e., the players) for the current step \( \Next_k \) (that is, those whose \( \c_j > 0 \)) will produce one of the following three distinct outputs (Lines 6:14):
-
If a proposal-value \( v \) can be committed in the current context, then the player broadcasts a \( \Next_k \) vote for \( v \).
-
If no proposal-value can be committed, and
- No recovery step \( \Bundle \) for the empty proposal-value (\( \bot \)) was observed in the previous period, and
- A recovery step \( \Bundle \) for the pinned value was observed in the previous period1,
then a \( \Next_k \) vote for \( \bar{v} \) is broadcast by the player.
-
Finally, if none of the above conditions were met, a \( \Next_k \) vote for \( \bot \) is broadcast.
A player is forbidden from equivocating in \( \Next_k \) votes.
Lastly (Line 15), the node’s current \( \s \) is updated.
For a formal definition of this functionality, refer to the ABFT normative section.
This implies \( \bar{v} \neq \bot \).
$$ \newcommand \Recovery {\mathrm{Recovery}} \newcommand \FastRecovery {\mathrm{FastRecovery}} \newcommand \Resync {\mathrm{Resync}} \newcommand \Sortition {\mathrm{Sortition}} \newcommand \Broadcast {\mathrm{Broadcast}} \newcommand \IsCommittable {\mathrm{IsCommittable}} \newcommand \Vote {\mathrm{Vote}} \newcommand \Bundle {\mathrm{Bundle}} \newcommand \Cert {\mathit{cert}} \newcommand \Late {\mathit{late}} \newcommand \Redo {\mathit{redo}} \newcommand \Down {\mathit{down}} \newcommand \Next {\mathit{next}} \newcommand \function {\textbf{function }} \newcommand \endfunction {\textbf{end function}} \newcommand \if {\textbf{if }} \newcommand \elseif {\textbf{else if }} \newcommand \then {\textbf{ then}} \newcommand \else {\textbf{else}} \newcommand \endif {\textbf{end if}} \newcommand \for {\textbf{for }} \newcommand \do {\textbf{ do}} \newcommand \endfor {\textbf{end for}} \newcommand \vt {\mathit{vote}} \newcommand \c {\mathit{credentials}} \newcommand \s {\mathit{step}} $$
Fast Recovery
The fast recovery algorithm is executed periodically every integer multiple of \( \lambda_f \) seconds (plus finite random variance).
This results in an approximately linear execution rate, while the network partitioning continues.
The algorithm uses the last three steps (named \( \Late, \Redo, \Down \) respectively) for \( \s \in [253, 254, 255] \).
These steps are, by nature, mutually exclusive:
-
A \( \Late \)-vote will be attempted if a staged value \( \sigma \) is available (for the current round and period),
-
Otherwise, a \( \Redo \)-vote will be attempted if the current period \( p > 0 \), and the last period was completed with a \( \Next \)-threshold for a proposal-value different from \( \bot \),
-
Finally, as a fallback, a \( \Down \)-vote is attempted if none of the above conditions were met.
A \( \Down \)-vote is always a vote for the \( \bot \) proposal-value, while \( \Late \) and \( \Redo \) must vote for a proposal-value different from \( \bot \).
Algorithm
\( \textbf{Algorithm 10} \text{: Fast Recovery} \)
$$ \begin{aligned} &\text{1: } \function \FastRecovery() \\ &\text{2: } \quad \Resync() \\ &\text{3: } \quad \for a \in A \do \\ &\text{4: } \quad \quad \if \IsCommittable(\bar{v}) \then \\ &\text{5: } \quad \quad \quad \c \gets \Sortition(a_I, r, p, \Late) \\ &\text{6: } \quad \quad \quad \if \c_j > 0 \then \\ &\text{7: } \quad \quad \quad \quad \Broadcast(\Vote(r, p, \Late, \bar{v}, \c)) \\ &\text{8: } \quad \quad \quad \endif \\ &\text{9: } \quad \quad \elseif \nexists s_0 > \Cert \mid \Bundle(r, p - 1, s_0, \bot) \subseteq V \land \\ &\text{ } \quad \quad \quad \quad \quad \quad \exists s_1 > \Cert \mid \Bundle(r, p - 1, s_1, \bar{v}) \subseteq V \then \\ &\text{10:} \quad \quad \quad \c \gets \Sortition(a_I, r, p, \Redo) \\ &\text{11:} \quad \quad \quad \if \c_j > 0 \then \\ &\text{12:} \quad \quad \quad \quad \Broadcast(\Vote(r, p, \Redo, \bar{v}, \c)) \\ &\text{13:} \quad \quad \quad \endif \\ &\text{14:} \quad \quad \else \\ &\text{15:} \quad \quad \quad \c \gets \Sortition(a_I, r, p, \Down) \\ &\text{16:} \quad \quad \quad \if \c_j > 0 \then \\ &\text{17:} \quad \quad \quad \quad \Broadcast(\Vote(r, p, \Down, \bot, \c)) \\ &\text{18:} \quad \quad \quad \endif \\ &\text{19:} \quad \quad \endif \\ &\text{20:} \quad \endfor \\ &\text{21:} \quad \for \vt \in V \text{ such that } \vt_s \geq 253 \do \\ &\text{22:} \quad \quad \Broadcast(\vt) \\ &\text{23:} \quad \endfor \\ &\text{24: } \endfunction \end{aligned} $$
⚙️ IMPLEMENTATION
Fast recovery vote issuance reference implementation.
\( \FastRecovery \) is functionally very close to the regular \( \Recovery \) algorithm (outlined in the previous section), performing the same checks and similar outputs.
The main difference is that it emits votes for any of the three different steps \( \Late, \Redo, \Down \), according to \( \Sortition \) results for every selected account.
Nodes are forbidden to equivocate for \( \Late, \Redo, \Down \) votes.
Finally, the node broadcasts all fast recovery votes observed. That is, all votes \( \vt \in V \) for which \( \vt_s \) is a fast recovery step (\( \Late, \Redo, \Down \)).
For a formal definition of this functionality, refer to the ABFT normative section.
$$ \newcommand \TP {\mathrm{TransactionPool}} \newcommand \Next {\mathit{next}} \newcommand \Late {\mathit{late}} \newcommand \Redo {\mathit{redo}} \newcommand \Down {\mathit{down}} $$
Examples of Protocol Runs
The following section presents three examples of valid protocol runs, going from simple to more complex agreement attempts, by adding partition scenarios that involve recovery stages.
The run examples are named from “best” to “worst” respectively:
-
Vanilla Run: agreement is achieved at the first attempt,
-
Jalapeño Run: agreement is achieved with a \( \Next \) recovery procedure,
-
Habanero Run: agreement is achieved with \( \Late, \Redo, \Down \) fast recovery procedure.
Besides being the simplest, the Vanilla Run is the most common case, as infrastructure failures are extremely rare. However, the partition scenarios in the Jalapeño Run and Habanero Run shed light on the recovery mechanisms.
Initial Context
All three scenarios share the following initial context and are played by the node \( \bar{N} \).
A genesis block was generated. Algorand has been running for a while with a set of nodes and accounts, and several blocks have already been generated.
The network is now at round \( r - 1 \) (with \( r >> 2 \)), meaning that \( r - 1 \) blocks have been generated and confirmed on the blockchain.
Moreover, the node \( \bar{N} \) has:
-
Received some transactions,
-
Verified them to be correctly signed by Algorand accounts,
-
Validated them according to Ledger and node context,
-
Added them to its \( \TP \) (see normative section),
-
Relayed them to other nodes.
For this section, we assume that all players behave according to protocol and are in sync, that is:
-
The context \( (r, p, s) \) for all nodes is the same,
-
Nodes’ internal clocks are synchronized.
$$ \newcommand \BlockProposal {\mathrm{BlockProposal}} \newcommand \BlockAssembly {\mathrm{BlockAssembly}} \newcommand \SoftVote {\mathrm{SoftVote}} \newcommand \DynamicFilterTimeout {\mathrm{DynamicFilterTimeout}} \newcommand \EventHandler {\mathrm{EventHandler}} \newcommand \Proposal {\mathrm{Proposal}} \newcommand \Priority {\mathrm{Priority}} \newcommand \Sortition {\mathrm{Sortition}} \newcommand \Commit {\mathrm{Commit}} \newcommand \HandleProposal {\mathrm{HandleProposal}} \newcommand \HandleVote {\mathrm{HandleVote}} \newcommand \VerifyProposal {\mathrm{VerifyProposal}} \newcommand \RetrieveProposal {\mathrm{RetrieveProposal}} \newcommand \StartNewRound {\mathrm{StartNewRound}} \newcommand \GarbageCollect {\mathrm{GarbageCollect}} \newcommand \CommitteeThreshold {\mathrm{CommitteeThreshold}} \newcommand \VRF {\mathrm{VRF}} \newcommand \ProofToHash {\mathrm{ProofToHash}} \newcommand \TP {\mathrm{TransactionPool}} \newcommand \Vote {\mathrm{Vote}} \newcommand \EventNewRound {\texttt{NewRound}} \newcommand \EventProposal {\texttt{Proposal}} \newcommand \EventVote {\texttt{Vote}} \newcommand \EventTimeout {\texttt{Timeout}} \newcommand \Propose {\mathit{propose}} \newcommand \Soft {\mathit{soft}} \newcommand \prop {\mathit{proposal}} \newcommand \c {\mathit{credentials}} \newcommand \vt {\mathit{vote}} $$
Vanilla Run
For ease of understanding, we present a “vanilla run” of the Algorand consensus algorithm, the simplest scenario in which the agreement protocol produces a valid block and appends it to the Ledger.
The following timeline diagram illustrates the process:
timeline
title Vanilla Run
section (r = i, p = 0, 0 <= s <= 2)
Proposal (s = 0) time = 0 : Emits proposals for i-th round from selected accounts registered on the node
Soft Vote (s = 1) time = DynamicTO(p) : Filters proposals (lowest hash criteria) : Soft vote on the best proposal for round i
Certification (s = 2) time < DeadlineTO(p) : Certification vote of a proposal backed by a Soft Vote Bundle
: Appended i-th block to the Ledger
: State deltas applied
: Transaction pool purged
section (r = i+1, p = 0, 0 <= s <= 2)
Proposal (s = 0) time = 0 : Emits proposals for i+1-th round from selected accounts registered on the node
Soft Vote (s = 1) time = DynamicTO(p) : Filters proposals (lowest hash criteria) : Soft vote on the best proposal for round i+1
Certification (s = 2) time < DeadlineTO(p) : Certification vote of a proposal backed by a Soft Vote Bundle
: Appended i+1-th block to the Ledger
: State deltas applied
: Transaction pool purged
Run
Let us assume the network conditions are those described in the initial context.
Proposal
As the main algorithm starts a round, it is called with a \( \EventNewRound \) event (node’s clock reset \( t = 0 \)) and calls the \( \BlockProposal \) procedure.
The \( \BlockProposal \) algorithm runs a loop in which it iterates over all the accounts registered online in the node. When at least one account gets selected by the \( \Sortition \), the node participates in the proposal voting on behalf of the selected accounts, and starts the \( \BlockAssembly \) procedure.
This procedure will traverse the \( \TP \), calling the Algorand Virtual Machine, and execute one transaction at a time, obtaining a new block \( e \).
The node will:
-
Assemble a \( \prop \) and a \( \vt \) on proposal-value \( v \),
-
Set \( v \) as the proposal-value obtained from block \( e \),
-
Make two separate broadcasts for \( \Vote(a_I, r,p, \prop, v, \c) \) and for \( e \).
Then, the main algorithm enters the \( \Soft \) step setting \( s = 1 \).
Proposal received from other nodes
Assume that some time has passed, now \( 0 < t < \DynamicFilterTimeout(p) \), and that the node receives a block proposal \( e^\prime \) broadcast from another node.
Then, the \( \EventHandler \) runs the proposal handling subroutine \( \HandleProposal(e^\prime) \).
This algorithm receives the proposal \( e^\prime \) and unpacks its contents, including the execution state \( (r^\prime, p^\prime, s^\prime) \).
Given the vanilla context assumptions, both nodes have the same context, therefore \( r = r^\prime \) and \( p = p^\prime = 0 \).
The algorithm checks if the proposal is valid, calling \( \VerifyProposal(v^\prime) \) on \( v^\prime = \Proposal_v(e^\prime) \), and if periods are equal (\( p = p^\prime \)). Both checks pass given the vanilla context assumptions.
Next, if \( e^\prime \in P \), it returns; else the proposal handler re-broadcasts \( e^\prime \), adds \( e^\prime \) to the set \( P \) of stored proposals, and exits.
Vote received from other nodes
Let us now assume that the node received a broadcasted \( \vt \), and that \( 0 < t < \DynamicFilterTimeout(p) \) still holds.
The \( \EventHandler \) for the main algorithm thus calls \( HandleVote(\vt) \). The algorithm exits on failing checks (all passed with the vanilla context assumptions), or if the vote received has already been recorded in the votes set \( V \). If it is a new vote, the node adds it to the votes set \( V \) and broadcasts it to other nodes.
Since nodes are synchronized (by assumption), it holds that \( \vt_s = 0 = \Propose \), so the algorithm checks if \( \RetrieveProposal(\vt_v) \neq \bot \) and broadcasts if it is available, ignore it if not.
Until \( t \ge \DynamicFilterTimeout(p) \) the main algorithm will execute the above steps whenever a vote or a proposal is received.
Filtering (Soft Vote)
Eventually, the node clock reaches \( t = \DynamicFilterTimeout(p) \) (that is, the node observes a \( \EventTimeout \) event for filtering), and the main algorithm calls \( \SoftVote \).
The soft vote procedure selects the highest priority block proposal and votes on it. The node goes through all the votes \( \vt^\prime \in V \) in its votes set which are in the \( \Propose \) step (\( \vt^\prime_s = 0 \)).
Given the \( \c_j \) of player \( I_j \) for the vote \( \vt^\prime_{\c_j} = (w_j, y, \VRF.\ProofToHash(y)) \), the procedure runs a \( \Priority\) function on the vote, as described in the soft vote non-normative section, and keeps track of the one with the highest priority (i.e., the one with the lowest hash).
Next, if there was at least one \( \vt \) in \( V \), for every registered account \( a \in A \) it computes:
$$ (w_j, y, \VRF.\ProofToHash(y)) \gets \c^{\prime\prime} = \Sortition(a, \Soft) $$
and, if \( w_j > 0 \) it broadcasts \( \Vote(r, p, \Soft, v, \c^{\prime\prime}) \).
Moreover, if \( \prop \gets \RetrieveProposal(v) \) is not \( \bot \), it also broadcasts \( \prop \).
Certification
When the node receives a event of type \( \EventProposal \), it runs the \( \HandleProposal \) procedure as before.
Commit
When the node receives a event of type \( \EventVote \), \( \Vote(r, p, \Soft, v, \c) \), it
-
Relays the vote,
-
Adds the vote to the vote set (if new),
-
Checks whether the vote can form a bundle with the votes in \( V \)1.
After a while, the last condition is met, and a bundle can be formed for the \( \Soft \) step.
When a \( \Soft \) bundle is observed for round \( r \), the node adds the accepted \( r \)-th block to the Ledger, updates its state accordingly, garbage collects the information related \( r \)-th round, and sets the round counter to \( r + 1 \).
In other words, the node:
-
Broadcast the proposal if \( \prop \not \in P \),
-
Commits \( v \), calling \( \Commit(v) \),
-
Sets \( r_\text{old} = r \),
-
Calls \( \StartNewRound(r + 1) \),
-
\( \GarbageCollect(r_\text{old}, p) \) ending the round.
Starting a new round will reset context variables as follows:
-
\( \bar{s} = s \),
-
\( \bar{v} = \bot \),
-
\( r = r + 1 \),
-
\( p = 0 \),
-
\( s = \Propose = 0 \).
Calling the garbage collection algorithm will compute:
$$ \begin{aligned} V_{(r, p-1)} & = \{\vt \in V : \vt_r < r \text{ or } (\vt_r = r \text{ and } \vt_p + 1 < p)\} \\ P_{(r, p-1)} & = \{\prop \in P: \prop_r < r \text{ or } (\prop_r = r \text{ and } \prop_p + 1 < p)\} \end{aligned} $$
and then remove these sets from the votes and proposal sets:
-
\( V \gets V \setminus V_{(r, p-1)} \),
-
\( P \gets P \setminus P_{(r, p-1)} \).
The node checks if there is a \( \vt_v \), such that for all the \( \vt \in V \) with \( \vt_r = r, \vt_p = 0, \vt_s = \Soft \), the sum of votes’ weights is bigger than the committee threshold:
$$ \sum_{\vt \in V} \vt_{\c_j} \ge \CommitteeThreshold(\Soft). $$
$$ \newcommand \DeadlineTimeout {\mathrm{DeadlineTimeout}} \newcommand \Sortition {\mathrm{Sortition}} \newcommand \Prop {\mathit{propose}} \newcommand \Soft {\mathit{soft}} \newcommand \Cert {\mathit{cert}} \newcommand \Next {\mathit{next}} \newcommand \TP {\mathrm{TransactionPool}} $$
Jalapeño Run (Recovery)
Let us now assume a scenario similar to the Vanilla run, with the following difference: on round \( r = i \), when \( s = 2 \), before block commitment, the network experiences a network partitioning. The voting stake is fragmented, and no network partition has enough voting power to certify a block.
timeline
title Jalapeño Run
section (r = i, p = 0, 0 <= s <= 1)
Proposal (s = 0) time = 0 : Emits proposals for i-th round from selected accounts registered on the node
Soft Vote (s = 1) time = DynamicTO(p) : Filters proposals (lowest hash criteria) : Soft vote on the best proposal for round i : A Soft Bundle is observed and a pinned value is enstabilished
section (r = i, p = 0, s >= 2) Network partition K begins
Certification (s = 2) time = DeadlineTO(p=0) : Certification vote fails, no Committe Threshold is reached
Recovery (s > 3) time > DeadlineTO(p=0) : Next and Fast Recovery votes triggered
section (r = i, p = 0, s >= 3) Network partition K ends
Recovery (s > 3) time > DeadlineTO(p=0) : Next Bundle observed, new period begins
section (r = i, p = 1, s = 2)
Certification (s = 2) time < DeadlineTO(p=1) : Certification vote of a pinned value
: Appended i-th block to the Ledger
: State deltas applied
: Transaction pool purged
Run
Let us assume the network conditions are those described in the initial context.
Regular Propose and Soft steps
The network starts round \( r \) performing regular \( \Prop \) and \( \Soft \) steps.
Suppose that during the \( \Soft \) step (\( s = 1 \)), a \( \Soft \)-Bundle is observed, therefore a pinned-value \( \bar{v} \) is established on the nodes, and the \( \Cert \) step begins.
Network Partitioning begins: failing Certification step
During the \( \Cert \) step, before the block commitment, the network experiences a partitioning \( K \).
For any two accounts in the certification committee, the nodes playing for them have their connected peers \( K_t \) and \( K_l \), with \( t \neq l \).
Given the proposed network graph, players reach \( \DeadlineTimeout(p = 0) \) without a committable block proposal (that is, no \( \Cert \)-Bundle supporting any proposal-value has been observed).
Recovery
The protocol enters into recovery mode, and several \( \Next \) votes and fast recovery votes sessions happen, without any of them being able to form a Bundle for a value, due to the persisting network partition.
Suppose now that after a given time, connections are restored and \( K \) is solved. For any two accounts in the certification committee, the nodes in which they are registered are part of the same (unique) network. In other words, this time \( K_t = K_l \) for all nodes whose managed accounts are chosen by \( \Sortition \) procedure to vote in a step \( s = \Next_h \), with \( 3 \leq h < 248 \).
Since during \( \Soft \) step (\( s = 1 \)), before the network partitioning occurred, a \( \Soft \)-Bundle had been observed, causing a pinned-value \( \bar{v} \) to be established on the nodes. Therefore, all selected players vote on this pinned-value, and a \( \Next_h \) Bundle is observed for \( \bar{v} \).
Network Partitioning ends: new period
The protocol agrees to move into the next period \( p = 1 \), garbage collects old period (\( p = 0 \)) data, and restarts from \( s = \Prop = 0 \).
Since there is already an agreed-upon value \( \bar{v} \), there are no proposals and the protocol moves quickly into \( \Soft \) and then \( \Cert \) votes.
This time the network is sufficiently connected to observe a \( \Cert \)-Bundle for \( \bar{v} \) and so, before \( \DeadlineTimeout(p = 1) \), the network is able to reach an agreement, and a new block is committed in the same way as in the Vanilla run; advancing the network into the new round.
$$ \newcommand \DynamicFilterTimeout {\mathrm{DynamicFilterTimeout}} \newcommand \DeadlineTimeout {\mathrm{DeadlineTimeout}} \newcommand \Prop {\mathit{propose}} \newcommand \Soft {\mathit{soft}} \newcommand \Cert {\mathit{cert}} \newcommand \Late {\mathit{late}} \newcommand \Redo {\mathit{redo}} \newcommand \Down {\mathit{down}} \newcommand \Next {\mathit{next}} $$
Habanero Run (Fast Recovery)
Let us now assume a scenario identical to the Japlapeño run, up until the attempt at period \( p = 1 \) to form a \( \Cert \)-Bundle for the pinned value.
In this scenario, a consensus-stalling network partition happens again, which lasts more than 5 minutes (i.e., \( \lambda_f \)).
timeline
title Habanero Run
section (r = i, p = 0, 0 <= s <= 1)
Proposal (s = 0) time = 0 : Emits proposals for i-th round from selected accounts registered on the node
Soft Vote (s = 1) time = DynamicTO(p) : Filters proposals (lowest hash criteria) : Soft vote on the best proposal for round i
section (r = i, p = 0, s >= 2) Network partition K begins
Certification (s = 2) time = DeadlineTO(p=0) : Certification vote fails, no Committe Threshold is reached
Recovery (s > 3) time > DeadlineTO(p=0) : Next and Fast Recovery votes triggered : No Next bundle is observed
section (r = i, p = 0, s >= 3) Network partition K ends
Recovery (s > 3) time > DeadlineTO(p=0) : Next Bundle observed, new period begins
section (r = i, p = 1, s = [253, 254, 255])
Fast Recovery (s = 253) time = lambda_f : Late step fails
Fast Recovery (s = 254) : Redo step fails
Fast Recovery (s = 255) : Down Bundle observed, new period begins
section (r = i, p = 2, 0 <= s <= 2)
Proposal (s = 0) time = 0 : Emits proposals for i-th round from selected accounts registered on the node
Soft Vote (s = 1) time = DynamicTO(p=2) : Filters proposals (lowest hash criteria) : Soft vote on the best proposal for round i
Certification (s = 2) time < DeadlineTO(p=2) : Certification vote of a pinned value
: Appended i-th block to the Ledger
: State deltas applied
: Transaction pool purged
Run
Let us assume the network conditions are those described in the initial context.
In addition to the initial context:
-
The network successfully performed \( \Prop \) and \( \Soft \) steps for round \( r \),
-
During the \( \Cert \) step, before the block commitment, the network experiences a partitioning \( K \) (as described in the Jalapeno run),
-
Players reach \( \DeadlineTimeout(p = 0) \) without a committable block proposal (that is, no \( \Cert \)-Bundle supporting any proposal-value has been observed).
Fast Recovery
Under these conditions, a \( \Cert \)-Bundle is not formed and the protocol enters in partition recovery mode.
Now, after connections are reestablished, a \( \Next \)-Bundle for a new period \( p = 1 \) is observed, and a fast recovery is about to take place (when the node’s clock is equal to \( \lambda_f \)).
In this scenario, however, differently from the Jalapeño run, most players have lost the previously observed pinned value, or have since seen \( \Soft \)-Bundle for different values. Therefore, no consensus on a bundle going into the period \( p = 2 \) certification is possible.
In this case, after failing to form \( \Late \) and \( \Redo \) bundles, a \( \Down \)-Bundle is observed by a majority of players (an “agree to disagree” with value \( \bot \)).
Players move into a period \( p = 2 \), this time with no “carry over” information from the previous consensus attempt.
In this scenario, the whole protocol is rerun (a reproposal ensues). The rest of the scenario is similar to the Vanilla run, save for different timeout values for \( DynamicFilterTimeout(p = 2) \) and \( DeadlineTimeout(p = 2) \), the protocol goes over \( \Prop \), \( \Soft \) and \( \Cert \) steps in the same fashion.
However, in this case, the network partition has been solved, so a block commitment ensues before \( \DeadlineTimeout(p = 2) \).
Ledger Overview
This part describes the Algorand Ledger, which records the history and the state of the distributed system, defining its entities (e.g., blocks, transactions, accounts, assets, applications, etc.).
It also covers the processes of ingesting, verifying, prioritizing, and enqueuing transactions in the transaction pool, and finally assembly and appending blocks to commit their state transition.
Algorand Ledger Overview
The following is a non-normative specification of the Algorand Ledger.
The Algorand Ledger consists of
-
The Account Table, which records the current state of the Ledger as an aggregation of individual accounts’ states,
-
The Blockchain, which records the history of the accounts’ states updates since the genesis block up to the current state.
This chapter aids implementors and readers in understanding the Ledger component,
as well as bridging the gap between the normative specification
and the go-algorand reference implementation.
Whenever possible, we illustrate how specific subcomponents may be implemented, providing design patterns from the reference implementation.
Besides the actual Ledger as an ordered sequence of blocks, several subcomponents are defined to look up, commit, validate, and assemble said blocks and their corresponding certificates.
Some constructs are built to optimize specific fields look up in these blocks for a given round, or to get the state implicitly defined by an aggregate of their history up to a certain round. These constructs are called Trackers, and their usage and implementation details are addressed in the corresponding section.
This chapter also includes the Transaction Pool, a queue of transactions that plays a key role in the assembly of a new block, the Transaction Tail used for efficient deduplication, and a dive into the protocol Rewards system.
Blocks
Blocks are data structures that store accounts’ state transitions in the blockchain, global state fields, and information related to the agreement protocol.
A block defines a state transition of the Ledger.
Algorand block size is \( 5 \) MB.
Each block consists of a header and a body.
-
The header contains metadata such as details about the block itself, the current round, various hashes, rewards, etc.,
-
The body holds transaction data and account updates.
For further details on the block header, refer to the Ledger normative specification.
⚙️ IMPLEMENTATION
Block header reference implementation.
The Ledger package in the go-algorand reference implementation includes functions
to effectively manage and interact with blocks.
Blocks are assembled in two steps: first by the MakeBlock function and then by
the WithSeed.
⚙️ IMPLEMENTATION
MakeBlockreference implementation.
WithSeedreference implementation.
The following sections provide a brief explanation and examples for each field in the block structure.
For a formal specification of these fields, refer to the Ledger normative specification.
$$ \newcommand \VRF {\mathrm{VRF}} \newcommand \RewardsRate {\mathrm{RewardsRate}} \newcommand \RewardUnits {\mathrm{RewardUnits}} $$
Block Header
An Algorand Ledger can be minimally defined by a sequence of block headers linked
by the prevHash field, as the header contains a cryptographic commitment to
the contents of the block body (the payset).
The following diagram illustrates the minimal Ledger definition:
Genesis Identifier and Genesis Hash
A string and a 32-byte array, respectively.
They ensure the block belongs to the correct blockchain. These match the genesis information about the chain’s state.
📎 EXAMPLE
For the MainNet:
- Genesis ID:
mainnet-v1.0- Genesis Hash:
wGHE2Pwdvd7S12BL5FaOP20EGYesN73ktiC1qzkkit8=(base64encoding of the 32-byte array).For the TestNet:
- Genesis ID:
testnet-v1.0- Genesis Hash:
SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=(base64encoding of the 32-byte array).
Previous Hash
Cryptographic commitment (hash) of the previous block header, linking blocks into a chain. The genesis block has this field set to \( 0 \).
Round
A 64-bit unsigned integer value that identifies the block’s round. The genesis block has round \( 0 \). For all other cases, it must be equal to the round of the previous block plus one (that is, they must be sequential and monotonically increasing).
Seed
A 32-byte array holding a random value used as a seed for cryptographic processes (e.g., block proposer selection).
The seed calculation algorithm (see ABFT normative specification) defines implicitly a sequence of seeds, whose values alternate according to:
-
The seed lookup constant \( \delta_s \),
-
Some round-specific computation that depends, amongst other things, on the seed refresh interval \( \delta_r \), the period \( p \) during which the block was assembled, and on the \( \VRF \) value obtained by the block proposer.
📎 EXAMPLE
Example a valid seed chain computation.
Timestamp
A 64-bit unsigned integer.
The timestamp is purely informational and states when a block was proposed, expressed in seconds since UNIX Epoch (00:00:00 Thursday, 1 January 1970, at UTC).
The difference between consecutive timestamps cannot be greater than \( t_{\delta} = 25 \) seconds
See the formal definition in the Ledger normative specification.
📎 EXAMPLE
In the reference implementation, checks on the timestamp are performed during block assembly. See the
MakeBlockfunction.
Consensus protocol does not guarantee the accuracy of the timestamp!
Transaction Commitment
Cryptographic commitments (hash) to the block’s transaction sequence. Internally, it uses a Merkle Tree and commits to the tree’s root.
Two different hashes are provided:
⚙️ IMPLEMENTATION
Transactions (
payset) commit reference implementation.
Proposer Payout
The amount in μALGO paid to the proposer is the sum of a fee component and a bonus component. The payout is subject to eligibility criteria and protocol limits.
For further details, refer to the rewards non-normative specification.
-
FeeCollected
Total transaction fees collected in the block expressed in μALGO. -
Bonus
A potential extra reward component of the block proposer payout, in addition to the fee component, expressed in μALGO. Subject to change during upgrades and to decrease every millionth round, according to an exponential decay curve.
Proposer
Address of the account that proposed this block.
Rewards
A structure representing the reward state. It contains the following fields:
FeeSink
A 32-byte array holding a constant address. This address collects transaction fees and pays block rewards.
📎 EXAMPLE
MainNet
FeeSinkaddress:Y76M3MSY6DKBRHBL7C3NNDXGS5IIMQVQVUAB6MP4XEMMGVF2QWNPL226CA.
This legacy rewards distribution mechanism is currently inactive. See the non-normative section for further details on the active reward mechanism.
RewardsPool(legacy)
A 32-byte array holding a constant address. This address pays distribution rewards (legacy system, currently inactive).
📎 EXAMPLE
MainNet
RewardsPooladdress:737777777777777777777777777777777777777777777777777UFEJ2CI.
-
RewardsLevel(legacy)
A 64-bit unsigned integer holding the amount of μALGO distributed to each participant account since the genesis block. -
RewardsRate(legacy)
A 64-bit unsigned integer indicating the amount of μALGO added to the participation stake from theRewardsPoolin the next round (legacy system, currently set to \( 0 \) for every block). -
RewardsResidue(legacy)
A 64-bit unsigned integer holding the leftover amount of μALGO after the distribution of \( \frac{\RewardsRate}{\RewardUnits} \) for every reward unit in the next round. -
RewardsRecalculationRound(legacy)
A 64-bit unsigned integer holding the round at which the \( \RewardsRate \) will be recalculated.
Transaction Counter
A 64-bit unsigned integer counting transactions committed before this block. It
is initialized at \( 0 \) or \( 1000 \) in the genesis block, depending on
the AppForbidLowResources consensus parameter.
Upgrade State
This field tracks the protocol upgrade state machine. It contains a link to the currently active version of this specification.
-
CurrentProtocol
A link to the commit in the Algorand Formal Specification repository of the currently active protocol version. -
NextProtocol
A link to the commit in the Algorand Formal Specification repository of a new protocol version being voted on. -
NextProtocolApprovals
Anuint64integer that represents the vote count for a next protocol upgrade. Set to 0 unless a vote is ongoing. -
NextProtocolSwitchOn
Round number at which the next protocol would be adopted. Set to 0 unless a vote is ongoing. -
NextProtocolVoteBefore
Round number before which a vote for the next protocol version should be issued and computed. Set to 0 unless a vote is ongoing.
Upgrade Vote
This field represents the vote of the block proposer on the new protocol version.
It contains two fields:
-
UpgradeApprove
A boolean flag that indicates an affirmative vote for the new protocol version. Usually set tofalseunless a protocol upgrade vote is ongoing. -
UpgradeDelay
The delay in rounds between the approval of a new protocol version and its execution. Usually set to \( 0 \) unless an upgrade vote is ongoing.
Participation Updates
A structure with two optional fields:
-
Expired Participation Accounts
An optional list of account addresses to be removed from consensus participation, due to expired participation keys (from the end of this round). Limited to \( 32 \) accounts. -
Absent Participation Accounts
An optional list of online account addresses to be removed from consensus participation, due to long-lasting absenteeism in the expected block proposals. Limited to \( 32 \) accounts.
Block Header Examples
With Protocol Upgrade Proposal, Without Payout
{
"genesis-hash":"wGHE2Pwdvd7S12BL5FaOP20EGYesN73ktiC1qzkkit8=",
"genesis-id":"mainnet-v1.0",
"previous-block-hash":"owhv6q/adgf0AvSNw9cC6Va7blSQUeCRaNaVKb0bqY0=",
"round":46000000,
"seed":"6Sv0nt/y7Wltt+nbwMpPZwLADmNURXmFHkrihTpqKYE=",
"timestamp":1736228624,
"transactions-root":"4V2mkvL+vDuQcN2Vq8x4HJzZCtNd/+pChJfFW9YVxNU=",
"transactions-root-sha256":"/nZs/lsL98Lw9NaqdT+3rdlKsFJyLqM8u4AKm42yIzg=",
"rewards":{
"fee-sink":"Y76M3MSY6DKBRHBL7C3NNDXGS5IIMQVQVUAB6MP4XEMMGVF2QWNPL226CA",
"rewards-calculation-round":46500000,
"rewards-level":218288,
"rewards-pool":"737777777777777777777777777777777777777777777777777UFEJ2CI",
"rewards-rate":0,
"rewards-residue":6886250026
},
"state-proof-tracking":[
{
"next-round":45999872,
"online-total-weight":0,
"type":0
}
],
"txn-counter":2667677491,
"upgrade-state":{
"current-protocol":"https://github.com/algorandfoundation/specs/tree/925a46433742afb0b51bb939354bd907fa88bf95",
"next-protocol":"https://github.com/algorandfoundation/specs/tree/236dcc18c9c507d794813ab768e467ea42d1b4d9",
"next-protocol-approvals":2,
"next-protocol-switch-on":46210138,
"next-protocol-vote-before":46002138
},
"upgrade-vote":{
"upgrade-approve":false,
"upgrade-delay":0
}
}
Without Protocol Upgrade Proposal, With Payout
{
"genesis-hash":"wGHE2Pwdvd7S12BL5FaOP20EGYesN73ktiC1qzkkit8=",
"genesis-id":"mainnet-v1.0",
"previous-block-hash":"kl2qk0j7kUVQIfB53HeQMXr0aVjSQpUfLqlD0o9s/rE=",
"round":49920000,
"seed":"dwdIvSTHCglZ17+C/ukJMgj6zRMsh50H+4SlLaa9mO0=",
"timestamp":1747224224,
"transactions-root":"kgdfSAlEK8escojfUfgsiINgrVZcjghYLGBfyfDVL6Y=",
"transactions-root-sha256":"bpefFXuYuJ2SdzUIHk8zZ9WhVd+qNMwriFjIUMNpz2Q=",
"bonus":9702990,
"fees-collected":673000,
"proposer":"2R5FTTVDIAQ55I5SPW5BE6R2SVYY45O5W64XGIVBLHQYWMZARRXTO4VIHQ",
"proposer-payout":10039490,
"rewards":{
"fee-sink":"Y76M3MSY6DKBRHBL7C3NNDXGS5IIMQVQVUAB6MP4XEMMGVF2QWNPL226CA",
"rewards-calculation-round":50000000,
"rewards-level":218288,
"rewards-pool":"737777777777777777777777777777777777777777777777777UFEJ2CI",
"rewards-rate":0,
"rewards-residue":6886250026
},
"state-proof-tracking":[
{
"next-round":49920000,
"online-total-weight":1950063504685967,
"type":0,
"voters-commitment":"Zw1ruItyLxO/Y8L8iFaOZhW6yUOMO1KeCVDOPyK9kPvGxFRF5c/kCxihruqVsiO9ptYcqq6FnRx9X6sZpS1PBw=="
}
],
"txn-counter":2993570351,
"upgrade-state":{
"current-protocol":"https://github.com/algorandfoundation/specs/tree/236dcc18c9c507d794813ab768e467ea42d1b4d9",
"next-protocol-approvals":0,
"next-protocol-switch-on":0,
"next-protocol-vote-before":0
},
"upgrade-vote":{
"upgrade-approve":false,
"upgrade-delay":0
}
}
Genesis Block
This section is directly derived from the
go-algorandreference implementation.
The genesis block defines the Algorand “universe”, which is initialized with:
-
The initial account states (
GenesisAllocation), which includes the ALGO balance and the initial consensus participation state (and keys), -
The initial consensus protocol version (
GenesisProto), -
The special addresses (
FeeSinkandRewardsPool), -
The schema of the Ledger.
⚙️ IMPLEMENTATION
Genesistype definition in the reference implementation.
⚙️ IMPLEMENTATION
GenesisBalancestype definition in the reference implementation. It contains the information needed to generate a new Ledger:
Balances: a map with the account data for each address,FeeSink: address where fees are collected,RewardsPool: address holding distribution rewards (legacy),Timestamp: time when the object is created.
Genesis Block Example
The following is the Algorand MainNet genesis block:
{
"alloc": [
{
"addr": "737777777777777777777777777777777777777777777777777UFEJ2CI",
"comment": "RewardsPool",
"state": {
"algo": 10000000000000,
"onl": 2
}
},
{
"addr": "Y76M3MSY6DKBRHBL7C3NNDXGS5IIMQVQVUAB6MP4XEMMGVF2QWNPL226CA",
"comment": "FeeSink",
"state": {
"algo": 1000000,
"onl": 2
}
},
{
"addr": "ALGORANDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIN5DNAU",
"comment": "A BIT DOES E NOT BUT E STARTS EVERYTHING LIFE A MANY FORTUNE R BUILD SIMPLER BE THE STARTS PERSEVERES FAVORS A ENOUGH RIPROVANDO POSSIBLE JOURNEY VICTORIA HE BOLD U WITHOUT MEN A K OF BORDERS WHO HE E RACES TOMORROW BUT WHO SINGLE PURPOSE GEOGRAPHICAL PROVANDO A KNOW SUFFOCATES NOT SCIENCE STEP MATHEMATICS OF OR A BRIDGES WALLS TECHNOLOGY TODAY AND WITH AS ET MILES OF THOUSAND VITA SIMPLE TOO MUST AS NOT MADE NOT",
"state": {
"algo": 1000000,
"onl": 2
}
},
{
"addr": "XQJEJECPWUOXSKMIC5TCSARPVGHQJIIOKHO7WTKEPPLJMKG3D7VWWID66E",
"comment": "AlgorandCommunityAnnouncement",
"state": {
"algo": 10000000,
"onl": 2
}
},
{
"addr": "VCINCVUX2DBKQ6WP63NOGPEAQAYGHGSGQX7TSH4M5LI5NBPVAGIHJPMIPM",
"comment": "AuctionsMaster",
"state": {
"algo": 1000000000,
"onl": 2
}
},
{
"addr": "OGP6KK5KCMHT4GOEQXJ4LLNJ7D6P6IH7MV5WZ5EX4ZWACHP75ID5PPEE5E",
"comment": "",
"state": {
"algo": 300000000000000,
"onl": 2
}
},
{
"addr": "AYBHAG2DAIOG26QEV35HKUBGWPMPOCCQ44MQEY32UOW3EXEMSZEIS37M2U",
"comment": "",
"state": {
"algo": 300000000000000,
"onl": 2
}
},
{
"addr": "2XKK2L6HOBCYHGIGBS3N365FJKHS733QOX42HIYLSBARUIJHMGQZYAQDRY",
"comment": "",
"state": {
"algo": 300000000000000,
"onl": 2
}
},
{
"addr": "ZBSPQQG7O5TR5MHPG3D5RS2TIFFD5NMOPR77VUKURMN6HV2BSN224ZHKGU",
"comment": "",
"state": {
"algo": 300000000000000,
"onl": 2
}
},
{
"addr": "7NQED6NJ4NZU7B5HGGFU2ZEC2UZQYU2SA5S4QOE2EXBVAR4CNAHIXV2XYY",
"comment": "",
"state": {
"algo": 300000000000000,
"onl": 2
}
},
{
"addr": "RX2ZKVJ43GNYDJNIOB6TIX26U7UEQFUQY46OMHX6CXLMMBHENJIH4YVLUQ",
"comment": "",
"state": {
"algo": 300000000000000,
"onl": 2
}
},
{
"addr": "RHSKYCCZYYQ2BL6Z63626YUETJMLFGVVV47ED5D55EKIK4YFJ5DQT5CV4A",
"comment": "",
"state": {
"algo": 300000000000000,
"onl": 2
}
},
{
"addr": "RJS6FDZ46ZZJIONLMMCKDJHYSJNHHAXNABMAVSGH23ULJSEAHZC6AQ6ALE",
"comment": "",
"state": {
"algo": 300000000000000,
"onl": 2
}
},
{
"addr": "AZ2KKAHF2PJMEEUVN4E2ILMNJCSZLJJYVLBIA7HOY3BQ7AENOVVTXMGN3I",
"comment": "",
"state": {
"algo": 300000000000000,
"onl": 2
}
},
{
"addr": "CGUKRKXNMEEOK7SJKOGXLRWEZESF24ELG3TAW6LUF43XRT2LX4OVQLU4BQ",
"comment": "",
"state": {
"algo": 300000000000000,
"onl": 2
}
},
{
"addr": "VVW6BVYHBG7MZQXKAR3OSPUZVAZ66JMQHOBMIBJG6YSPR7SLMNAPA7UWGY",
"comment": "",
"state": {
"algo": 250000000000000,
"onl": 2
}
},
{
"addr": "N5BGWISAJSYT7MVW2BDTTEHOXFQF4QQH4VKSMKJEOA4PHPYND43D6WWTIU",
"comment": "",
"state": {
"algo": 1740000000000000,
"onl": 2
}
},
{
"addr": "MKT3JAP2CEI5C4IX73U7QKRUF6JR7KPKE2YD6BLURFVPW6N7CYXVBSJPEQ",
"comment": "",
"state": {
"algo": 158000000000000,
"onl": 2
}
},
{
"addr": "GVCPSWDNSL54426YL76DZFVIZI5OIDC7WEYSJLBFFEQYPXM7LTGSDGC4SA",
"comment": "",
"state": {
"algo": 49998988000000,
"onl": 1,
"sel": "lZ9z6g0oSlis/8ZlEyOMiGfX0XDUcObfpJEg5KjU0OA=",
"vote": "Kk+5CcpHWIXSMO9GiAvnfe+eNSeRtpDb2telHb6I1EE=",
"voteKD": 10000,
"voteLst": 3000000
}
},
{
"addr": "M7XKTBQXVQARLS7IVS6NVDHNLJFIAXR2CGGZTUDEKRIHRVLWL5TJFJOL5U",
"comment": "",
"state": {
"algo": 50000000000000,
"onl": 1,
"sel": "Z5gE/m2E/WSuaS5E8aYzO2DugTdSWQdc5W5BroCJdms=",
"vote": "QHHw03LnZQhKvjjIxVj3+qwgohOij2j3TBDMy7V9JMk=",
"voteKD": 10000,
"voteLst": 3000000
}
},
{
"addr": "QFYWTHPNZBKKZ4XG2OWVNEX6ETBISD2VJZTCMODIZKT3QHQ4TIRJVEDVV4",
"comment": "",
"state": {
"algo": 50000000000000,
"onl": 1,
"sel": "NthIIUyiiRVnU/W13ajFFV4EhTvT5EZR/9N6ZRD/Z7U=",
"vote": "3KtiTLYvHJqa+qkGFj2RcZC77bz9yUYKxBZt8B24Z+c=",
"voteKD": 10000,
"voteLst": 3000000
}
},
{
"addr": "DPOZQ6HRYLNNWVQL3I4XV4LMK5UZVROKGJBRIYIRNZUBMVHCU4DZWDBHYE",
"comment": "",
"state": {
"algo": 50000000000000,
"onl": 1,
"sel": "PBZ/agWgmwMdmWgt/W0NvdTN/XSTrVhPvRSMjmP5j90=",
"vote": "FDONnMcq1acmIBjJr3vz4kx4Q8ZRZ8oIH8xXRV5c4L8=",
"voteKD": 10000,
"voteLst": 3000000
}
},
{
"addr": "42GALMKS3HMDB24ZPOR237WQ5QDHL5NIRC3KIA4PCKENJZAD5RP5QPBFO4",
"comment": "",
"state": {
"algo": 50000000000000,
"onl": 1,
"sel": "p7axjoy3Wn/clD7IKoTK2Zahc5ZU+Qkt2POVHKugQU4=",
"vote": "PItHHw+b01XplxRBFmZniqmdm+RyJFYd0fDz+OP4D6o=",
"voteKD": 10000,
"voteLst": 3000000
}
},
{
"addr": "OXWIMTRZA5TVPABJF534EBBERJG367OLAB6VFN4RAW5P6CQEMXEX7VVDV4",
"comment": "",
"state": {
"algo": 50000000000000,
"onl": 1,
"sel": "RSOWYRM6/LD7MYxlZGvvF+WFGmBZg7UUutdkaWql0Xo=",
"vote": "sYSYFRL7AMJ61egushOYD5ABh9p06C4ZRV/OUSx7o3g=",
"voteKD": 10000,
"voteLst": 3000000
}
},
{
"addr": "AICDUO6E46YBJRLM4DFJPVRVZGOFTRNPF7UPQXWEPPYRPVGIMQMLY5HLFM",
"comment": "",
"state": {
"algo": 50000000000000,
"onl": 1,
"sel": "0vxjPZqEreAhUt9PHJU2Eerb7gBhMU+PgyEXYLmbifg=",
"vote": "fuc0z/tpiZXBWARCJa4jPdmDvSmun4ShQLFiAxQkOFI=",
"voteKD": 10000,
"voteLst": 3000000
}
},
{
"addr": "DYATVHCICZA7VVOWZN6OLFFSKUAZ64TZ7WZWCJQBFWL3JL4VBBV6R7Z6IE",
"comment": "",
"state": {
"algo": 50000000000000,
"onl": 1,
"sel": "KO2035CRpp1XmVPOTOF6ICWCw/0I6FgelKxdwPq+gMY=",
"vote": "rlcoayAuud0suR3bvvI0+psi/NzxvAJUFlp+I4ntzkM=",
"voteKD": 10000,
"voteLst": 3000000
}
},
{
"addr": "6XJH2PJMAXWS4RGE6NBYIS3OZFOPU3LOHYC6MADBFUAALSWNFHMPJUWVSE",
"comment": "",
"state": {
"algo": 50000000000000,
"onl": 1,
"sel": "PgW1dncjs9chAVM89SB0FD4lIrygxrf+uqsAeZw8Qts=",
"vote": "pA4NJqjTAtHGGvZWET9kliq24Go5kEW8w7f1BGAWmKY=",
"voteKD": 10000,
"voteLst": 3000000
}
},
{
"addr": "EYOZMULFFZZ5QDDMWQ64HKIMUPPNEL3WJMNGAFD43L52ZXTPESBEVJPEZU",
"comment": "",
"state": {
"algo": 50000000000000,
"onl": 1,
"sel": "sfebD2noAbrn1vblMmeCIeGB3BxLGKQDTG4sKSNibFs=",
"vote": "Cuz3REj26J+JhOpf91u6PO6MV5ov5b1K/ii1U1uPD/g=",
"voteKD": 10000,
"voteLst": 3000000
}
},
{
"addr": "I3345FUQQ2GRBHFZQPLYQQX5HJMMRZMABCHRLWV6RCJYC6OO4MOLEUBEGU",
"comment": "",
"state": {
"algo": 24000000000000,
"onl": 1,
"sel": "MkH9KsdwiFgYtFFWFu48CeejEop1vsyGFG4/kqPIOFg=",
"vote": "RcntidhQqXQIvYjLFtc6HuL335rMnNX92roa2LcC+qQ=",
"voteKD": 10000,
"voteLst": 3000000
}
},
{
"addr": "6LQH42A4QJ3Y27FGKJWERY3MD65SXM4QQCJJR2HRJYNB427IQ73YBI3YFY",
"comment": "",
"state": {
"algo": 24000000000000,
"onl": 1,
"sel": "nF3mu9Bu0Ad5MIrT31NgTxxrsZOXc4u1+WCvaPQTYEQ=",
"vote": "NaqWR/7FzOq/MiHb3adO6+J+kvnQKat8NSqEmoEkVfE=",
"voteKD": 10000,
"voteLst": 3000000
}
},
{
"addr": "3V2MC7WJGAFU2EHWBHEETIMJVFJNAT4KKWVPOMJFJIM6ZPWEJRJ4POTXGI",
"comment": "",
"state": {
"algo": 24000000000000,
"onl": 1,
"sel": "3i4K8zdmnf1kxwgcNmI3x50iIwAxDmLMvoQEhjzhado=",
"vote": "wfJWa0kby76rqX2yvCD/aCfJdNt+qItylDPQiuAWFkQ=",
"voteKD": 10000,
"voteLst": 3000000
}
},
{
"addr": "FTXSKED23VEXNW442T2JKNPPNUC2WKFNRWBVQTFMT7HYX365IVLZXYILAI",
"comment": "",
"state": {
"algo": 24000000000000,
"onl": 1,
"sel": "icuL7ehcGonAcJ02Zy4MIHqcT+Sp1R1UURNCYJQHmo4=",
"vote": "tmFcj3v7X5DDxKI1IDbGdhXh3a5f0Ab1ftltM7TgIDE=",
"voteKD": 10000,
"voteLst": 3000000
}
},
{
"addr": "IAOW7PXLCDGLKMIQF26IXFF4THSQMU662MUU6W5KPOXHIVKHYFLYRWOUT4",
"comment": "",
"state": {
"algo": 24000000000000,
"onl": 1,
"sel": "zTn9rl/8Y2gokMdFyFP/pKg4eP02arkxlrBZIS94vPI=",
"vote": "a0pX68GgY7u8bd2Z3311+Mtc6yDnESZmi9k8zJ0oHzY=",
"voteKD": 10000,
"voteLst": 3000000
}
},
{
"addr": "4NRNE5RIGC2UGOMGMDR6L5YMQUV3Q76TPOR7TDU3WEMJLMC6BSBEKPJ2SY",
"comment": "",
"state": {
"algo": 24000000000000,
"onl": 1,
"sel": "orSV2VHPY8m5ckEHGwK0r+SM9jq4BujAICXegAUAecI=",
"vote": "NJ9tisH+7+S29m/uMymFTD8X02/PKU0JUX1ghnLCzkw=",
"voteKD": 10000,
"voteLst": 3000000
}
},
{
"addr": "E2EIMPLDISONNZLXONGMC33VBYOIBC2R7LVOS4SYIEZYJQK6PYSAPQL7LQ",
"comment": "",
"state": {
"algo": 24000000000000,
"onl": 1,
"sel": "XM2iW9wg9G5TyOfVu9kTS80LDIqcEPkJsgxaZll3SWA=",
"vote": "p/opFfDOsIomj5j7pAYU+G/CNUIwvD2XdEer6dhGquQ=",
"voteKD": 10000,
"voteLst": 3000000
}
},
{
"addr": "APDO5T76FB57LNURPHTLAGLQOHUQZXYHH2ZKR4DPQRKK76FB4IAOBVBXHQ",
"comment": "",
"state": {
"algo": 24000000000000,
"onl": 1,
"sel": "5k2vclbUQBE6zBl45F3kGSv1PYhE2k9wZjxyxoPlnwA=",
"vote": "3dcLRSckm3wd9KB0FBRxub3meIgT6lMZnv5F08GJgEo=",
"voteKD": 10000,
"voteLst": 3000000
}
},
{
"addr": "3KJTYHNHK37G2JDZJPV55IHBADU22TX2FPJZJH43MY64IFWKVNMP2F4JZE",
"comment": "",
"state": {
"algo": 24000000000000,
"onl": 1,
"sel": "o5e9VLqMdmJas5wRovfYFHgQ+Z6sQoATf3a6j0HeIXU=",
"vote": "rG7J8pPAW+Xtu5pqMIJOG9Hxdlyewtf9zPHEKR2Q6OE=",
"voteKD": 10000,
"voteLst": 3000000
}
},
{
"addr": "IVKDCE6MS44YVGMQQFVXCDABW2HKULKIXMLDS2AEOIA6P2OGMVHVJ64MZI",
"comment": "",
"state": {
"algo": 24000000000000,
"onl": 1,
"sel": "XgUrwumD7oin/rG3NKwywBSsTETg/aWg9MjCDG61Ybg=",
"vote": "sBPEGGrEqcQMdT+iq2ududNxCa/1HcluvsosO1SkE/k=",
"voteKD": 10000,
"voteLst": 3000000
}
},
{
"addr": "2WDM5XFF7ONWFANPE5PBMPJLVWOEN2BBRLSKJ37PQYW5WWIHEFT3FV6N5Y",
"comment": "",
"state": {
"algo": 24000000000000,
"onl": 1,
"sel": "Lze5dARJdb1+Gg6ui8ySIi+LAOM3P9dKiHKB9HpMM6A=",
"vote": "ys4FsqUNQiv+N0RFtr0Hh9OnzVcxXS6cRVD/XrLgW84=",
"voteKD": 10000,
"voteLst": 3000000
}
},
{
"addr": "EOZWAIPQEI23ATBWQ5J57FUMRMXADS764XLMBTSOLVKPMK5MK5DBIS3PCY",
"comment": "",
"state": {
"algo": 24000000000000,
"onl": 1,
"sel": "jtmLcJhaAknJtA1cS5JPZil4SQ5SKh8P0w1fUw3X0CE=",
"vote": "pyEtTxJAas/j+zi/N13b/3LB4UoCar1gfcTESl0SI2I=",
"voteKD": 10000,
"voteLst": 3000000
}
},
{
"addr": "REMF542E5ZFKS7SGSNHTYB255AUITEKHLAATWVPK3CY7TAFPT6GNNCHH6M",
"comment": "",
"state": {
"algo": 24000000000000,
"onl": 1,
"sel": "8ggWPvRpSkyrjxoh1SVS9PiSjff2azWtH0HFadwI9Ck=",
"vote": "Ej/dSkWbzRf09RAuWZfC4luRPNuqkLFCSGYXDcOtwic=",
"voteKD": 10000,
"voteLst": 3000000
}
},
{
"addr": "T4UBSEAKK7JHT7RNLXVHDRW72KKFJITITR54J464CAGE5FGAZFI3SQH3TI",
"comment": "",
"state": {
"algo": 24000000000000,
"onl": 1,
"sel": "eIB8MKaG2lyJyM9spk+b/Ap/bkbo9bHfvF9f8T51OQk=",
"vote": "7xuBsE5mJaaRAdm5wnINVwm4SgPqKwJTAS1QBQV3sEc=",
"voteKD": 10000,
"voteLst": 3000000
}
},
{
"addr": "YUDNQMOHAXC4B3BAMRMMQNFDFZ7GYO2HUTBIMNIP7YQ4BL57HZ5VM3AFYU",
"comment": "",
"state": {
"algo": 24000000000000,
"onl": 1,
"sel": "CSTCDvvtsJB0VYUcl3oRXyiJfhm3CtqvRIuFYZ69Z68=",
"vote": "uBK1TH4xKdWfv5nnnHkvYssI0tyhWRFZRLHgVt9TE1k=",
"voteKD": 10000,
"voteLst": 3000000
}
},
{
"addr": "4SZTEUQIURTRT37FCI3TRMHSYT5IKLUPXUI7GWC5DZFXN2DGTATFJY5ABY",
"comment": "",
"state": {
"algo": 24000000000000,
"onl": 1,
"sel": "THGOlrqElX13xMqeLUPy6kooTbXjiyrUoZfVccnHrfI=",
"vote": "k4hde2Q3Zl++sQobo01U8heZd/X0GIX1nyqM8aI/hCY=",
"voteKD": 10000,
"voteLst": 3000000
}
},
{
"addr": "UEDD34QFEMWRGYCBLKZIEHPKSTNBFSRMFBHRJPY3O2JPGKHQCXH4IY6XRI",
"comment": "",
"state": {
"algo": 24000000000000,
"onl": 1,
"sel": "jE+AUFvtp2NJsfNeUZeXdWt0X6I58YOgY+z/HB17GDs=",
"vote": "lmnYTjg1FhRNAR9TwVmOahVr5Z+7H1GO6McmvOZZRTQ=",
"voteKD": 10000,
"voteLst": 3000000
}
},
{
"addr": "HHZQOGQKMQDLBEL3HXMDX7AGTNORYVZ4JFDWVSL5QLWMD3EXOIAHDI5L7M",
"comment": "",
"state": {
"algo": 24000000000000,
"onl": 1,
"sel": "Hajdvzem2rR2GjLmCG+98clHZFY5Etlp0n+x/gQTGj0=",
"vote": "2+Ie4MDWC6o/SfFSqev1A7UAkzvKRESI42b4NKS6Iw8=",
"voteKD": 10000,
"voteLst": 3000000
}
},
{
"addr": "XRTBXPKH3DXDJ5OLQSYXOGX3DJ3U5NR6Y3LIVIWMK7TY33YW4I2NJZOTVE",
"comment": "",
"state": {
"algo": 24000000000000,
"onl": 1,
"sel": "5qe7rVoQfGdIUuDbhP2ABWivCoCstKbUsjdmYY76akA=",
"vote": "3J3O9DyJMWKvACubUK9QvmCiArtZR7yFHWG7k7+apdQ=",
"voteKD": 10000,
"voteLst": 3000000
}
},
{
"addr": "JJFGCPCZPYRLOUYBZVC4F7GRPZ5CLB6BMTVRGNDP7GRGXL6GG4JEN7DL54",
"comment": "",
"state": {
"algo": 24000000000000,
"onl": 1,
"sel": "YoRFAcTiOgJcLudNScYstbaKJ8anrrHwQMZAffWMqYE=",
"vote": "VQFKlDdxRqqqPUQ/mVoF8xZS9BGxUtTnPUjYyKnOVRA=",
"voteKD": 10000,
"voteLst": 3000000
}
},
{
"addr": "4VNSA2GZVUD5ZNO62OVVNP4NEL2LIEE5N3MZEK4BKH62KGKRLVINFZYTZM",
"comment": "",
"state": {
"algo": 100000000000000,
"onl": 2
}
},
{
"addr": "IVCEEIH2Q32DZNRTS5XFVEFFAQGERNZHHQT6S4UPY7ORJMHIQDSTX7YM4E",
"comment": "",
"state": {
"algo": 408400000000000,
"onl": 2
}
},
{
"addr": "PLFHBIRGM3ZWGAMCXTREX2N537TWOMFIQXHFO2ZGQOEPZU473SYBVGVA5M",
"comment": "",
"state": {
"algo": 1011600000000000,
"onl": 2
}
},
{
"addr": "KF7X4ZABZUQU7IFMHSKLDKWCS4F3GZLOLJRDAK5KMEMDAGU32CX36CJQ5M",
"comment": "",
"state": {
"algo": 10000000000000,
"onl": 2
}
},
{
"addr": "BTEESEYQMFLWZKULSKLNDELYJTOOQK6ZT4FBCW3TOZQ55NZYLOO6BRQ5K4",
"comment": "",
"state": {
"algo": 36199095000000,
"onl": 2
}
},
{
"addr": "E36JOZVSZZDXKSERASLAWQE4NU67HC7Q6YDOCG7P7IRRWCPSWXOI245DPA",
"comment": "",
"state": {
"algo": 20000000000000,
"onl": 2
}
},
{
"addr": "I5Q6RRN44OZWYMX6YLWHBGEVPL7S3GBUCMHZCOOLJ245TONH7PERHJXE4A",
"comment": "",
"state": {
"algo": 20000000000000,
"onl": 2
}
},
{
"addr": "2GYS272T3W2AP4N2VX5BFBASVNLWN44CNVZVKLWMMVPZPHVJ52SJPPFQ2I",
"comment": "",
"state": {
"algo": 40000000000000,
"onl": 2
}
},
{
"addr": "D5LSV2UGT4JJNSLJ5XNIF52WP4IHRZN46ZGWH6F4QEF4L2FLDYS6I6R35Y",
"comment": "",
"state": {
"algo": 20000000000000,
"onl": 2
}
},
{
"addr": "UWMSBIP2CGCGR3GYVUIOW3YOMWEN5A2WRTTBH6Y23KE3MOVFRHNXBP6IOE",
"comment": "",
"state": {
"algo": 20000000000000,
"onl": 2
}
},
{
"addr": "OF3MKZZ3L5ZN7AZ46K7AXJUI4UWJI3WBRRVNTDKYVZUHZAOBXPVR3DHINE",
"comment": "",
"state": {
"algo": 40000000000000,
"onl": 2
}
},
{
"addr": "2PPWE36YUMWUVIFTV2A6U4MLZLGROW4GHYIRVHMUCHDH6HCNVPUKPQ53NY",
"comment": "",
"state": {
"algo": 440343426000000,
"onl": 2
}
},
{
"addr": "JRGRGRW4HYBNAAHR7KQLLBAGRSPOYY6TRSINKYB3LI5S4AN247TANH5IQY",
"comment": "",
"state": {
"algo": 362684706000000,
"onl": 2
}
},
{
"addr": "D7YVVQJXJEFOZYUHJLIJBW3ATCAW46ML62VYRJ3SMGLOHMWYH4OS3KNHTU",
"comment": "",
"state": {
"algo": 10000000000000,
"onl": 2
}
},
{
"addr": "PZJKH2ILW2YDZNUIYQVJZ2MANRSMK6LCHAFSAPYT6R3L3ZCWKYRDZXRVY4",
"comment": "",
"state": {
"algo": 10000000000000,
"onl": 2
}
},
{
"addr": "3MODEFJVPGUZH3HDIQ6L2MO3WLJV3FK3XSWKFBHUGZDCHXQMUKD4B7XLMI",
"comment": "",
"state": {
"algo": 130000000000000,
"onl": 2
}
},
{
"addr": "WNSA5P6C5IIH2UJPQWJX6FRNPHXY7XZZHOWLSW5ZWHOEHBUW4AD2H6TZGM",
"comment": "",
"state": {
"algo": 130000000000000,
"onl": 2
}
},
{
"addr": "OO65J5AIFDS6255WL3AESTUGJD5SUV47RTUDOUGYHEIME327GX7K2BGC6U",
"comment": "",
"state": {
"algo": 40000000000000,
"onl": 2
}
},
{
"addr": "DM6A24ZWHRZRM2HWXUHAUDSAACO7VKEZAOC2THWDXH4DX5L7LSO3VF2OPU",
"comment": "",
"state": {
"algo": 20000000000000,
"onl": 2
}
},
{
"addr": "NTJJSFM75RADUOUGOBHZB7IJGO7NLVBWA66EYOOPU67H7LYIXVSPSI7BTA",
"comment": "",
"state": {
"algo": 18099548000000,
"onl": 2
}
},
{
"addr": "DAV2AWBBW4HBGIL2Z6AAAWDWRJPTOQD6BSKU2CFXZQCOBFEVFEJ632I2LY",
"comment": "",
"state": {
"algo": 1000000000000,
"onl": 2
}
},
{
"addr": "M5VIY6QPSMALVVPVG5LVH35NBMH6XJMXNWKWTARGGTEEQNQ3BHPQGYP5XU",
"comment": "",
"state": {
"algo": 20000000000000,
"onl": 2
}
},
{
"addr": "WZZLVKMCXJG3ICVZSVOVAGCCN755VHJKZWVSVQ6JPSRQ2H2OSPOOZKW6DQ",
"comment": "",
"state": {
"algo": 45248869000000,
"onl": 2
}
},
{
"addr": "XEJLJUZRQOLBHHSOJJUE4IWI3EZOM44P646UDKHS4AV2JW7ZWBWNFGY6BU",
"comment": "",
"state": {
"algo": 20000000000000,
"onl": 2
}
},
{
"addr": "OGIPDCRJJPNVZ6X6NBQHMTEVKJVF74QHZIXVLABMGUKZWNMEH7MNXZIJ7Q",
"comment": "",
"state": {
"algo": 40000000000000,
"onl": 2
}
},
{
"addr": "G47R73USFN6FJJQTI3JMYQXO7F6H4LRPBCTTAD5EZWPWY2WCG64AVPCYG4",
"comment": "",
"state": {
"algo": 10000000000000,
"onl": 2
}
},
{
"addr": "PQ5T65QB564NMIY6HXNYZXTFRSTESUEFIF2C26ZZKIZE6Q4R4XFP5UYYWI",
"comment": "",
"state": {
"algo": 5000000000000,
"onl": 2
}
},
{
"addr": "R6S7TRMZCHNQPKP2PGEEJ6WYUKMTURNMM527ZQXABTHFT5GBVMF6AZAL54",
"comment": "",
"state": {
"algo": 1000000000000,
"onl": 2
}
},
{
"addr": "36LZKCBDUR5EHJ74Q6UWWNADLVJOHGCPBBQ5UTUM3ILRTQLA6RYYU4PUWQ",
"comment": "",
"state": {
"algo": 5000000000000,
"onl": 2
}
},
{
"addr": "JRHPFMSJLU42V75NTGFRQIALCK6RHTEK26QKLWCH2AEEAFNAVEXWDTA5AM",
"comment": "",
"state": {
"algo": 40000000000000,
"onl": 2
}
},
{
"addr": "64VZVS2LFZXWA5W3S657W36LWGP34B7XLMDIF4ROXBTPADD7SR5WNUUYJE",
"comment": "",
"state": {
"algo": 171945701000000,
"onl": 2
}
},
{
"addr": "TXDBSEZPFP2UB6BDNFCHCZBTPONIIQVZGABM4UBRHVAAPR5NE24QBL6H2A",
"comment": "",
"state": {
"algo": 60000000000000,
"onl": 2
}
},
{
"addr": "XI5TYT4XPWUHE4AMDDZCCU6M4AP4CAI4VTCMXXUNS46I36O7IYBQ7SL3D4",
"comment": "",
"state": {
"algo": 40000000000000,
"onl": 2
}
},
{
"addr": "Y6ZPKPXF2QHF6ULYQXVHM7NPI3L76SP6QHJHK7XTNPHNXDEUTJPRKUZBNE",
"comment": "",
"state": {
"algo": 40000000000000,
"onl": 2
}
},
{
"addr": "6LY2PGUJLCK4Q75JU4IX5VWVJVU22VGJBWPZOFP3752UEBIUBQRNGJWIEA",
"comment": "",
"state": {
"algo": 40000000000000,
"onl": 2
}
},
{
"addr": "L7AGFNAFJ6Z2FYCX3LXE4ZSERM2VOJF4KPF7OUCMGK6GWFXXDNHZJBEC2E",
"comment": "",
"state": {
"algo": 10000000000000,
"onl": 2
}
},
{
"addr": "RYXX5U2HMWGTPBG2UDLDT6OXDDRCK2YGL7LFAKYNBLRGZGYEJLRMGYLSVU",
"comment": "",
"state": {
"algo": 40000000000000,
"onl": 2
}
},
{
"addr": "S263NYHFQWZYLINTBELLMIRMAJX6J5CUMHTECTGGVZUKUN2XY6ND2QBZVY",
"comment": "",
"state": {
"algo": 21647524000000,
"onl": 2
}
},
{
"addr": "AERTZIYYGK3Q364M6DXPKSRRNSQITWYEDGAHXC6QXFCF4GPSCCSISAGCBY",
"comment": "",
"state": {
"algo": 19306244000000,
"onl": 2
}
},
{
"addr": "34UYPXOJA6WRTWRNH5722LFDLWT23OM2ZZTCFQ62EHQI6MM3AJIAKOWDVQ",
"comment": "",
"state": {
"algo": 10000000000000,
"onl": 2
}
},
{
"addr": "EDVGNQL6APUFTIGFZHASIEWGJRZNWGIKJE64B72V36IQM2SJPOAG2MJNQE",
"comment": "",
"state": {
"algo": 20000000000000,
"onl": 2
}
},
{
"addr": "RKKLUIIGR75DFWGQOMJB5ZESPT7URDPC7QHGYKM4MAJ4OEL2J5WAQF6Z2Q",
"comment": "",
"state": {
"algo": 40000000000000,
"onl": 2
}
},
{
"addr": "M4TNVJLDZZFAOH2M24BE7IU72KUX3P6M2D4JN4WZXW7WXH3C5QSHULJOU4",
"comment": "",
"state": {
"algo": 10000000000000,
"onl": 2
}
},
{
"addr": "WQL6MQS5SPK3CR3XUPYMGOUSCUC5PNW5YQPLGEXGKVRK3KFKSAZ6JK4HXQ",
"comment": "",
"state": {
"algo": 10000000000000,
"onl": 2
}
},
{
"addr": "36JTK4PKUBJGVCWKXZTAG6VLJRXWZXQVPQQSYODSN6WEGVHOWSVK6O54YU",
"comment": "",
"state": {
"algo": 10000000000000,
"onl": 2
}
},
{
"addr": "YFOAYI4SNXJR2DBEZ3O6FJOFSEQHWD7TYROCNDWF6VLBGLNJMRRHDXXZUI",
"comment": "",
"state": {
"algo": 30000000000000,
"onl": 2
}
},
{
"addr": "XASOPHD3KK3NNI5IF2I7S7U55RGF22SG6OEICVRMCTMMGHT3IBOJG7QWBU",
"comment": "",
"state": {
"algo": 40000000000000,
"onl": 2
}
},
{
"addr": "H2AUGBLVQFHHFLFEPJ6GGJ7PBQITEN2GE6T7JZCALBKNU7Q52AVJM5HOYU",
"comment": "",
"state": {
"algo": 10000000000000,
"onl": 2
}
},
{
"addr": "GX3XLHSRMFTADVKJBBQBTZ6BKINW6ZO5JHXWGCWB4CPDNPDQ2PIYN4AVHQ",
"comment": "",
"state": {
"algo": 40000000000000,
"onl": 2
}
},
{
"addr": "VBJBJ4VC3IHUTLVLWMBON36Y5MPAMPV4DNGW5FQ47GRLPT7JR5PQOUST2E",
"comment": "",
"state": {
"algo": 4524887000000,
"onl": 2
}
},
{
"addr": "7AQVTOMB5DJRSUM4LPLVF6PY3Y5EBDF4RZNDIWNW4Z63JYTAQCPQ62IZFE",
"comment": "",
"state": {
"algo": 50000000000000,
"onl": 2
}
},
{
"addr": "B4ZIHKD4VYLA4BAFEP7KUHZD7PNWXW4QLCHCNKWRENJ2LYVEOIYA3ZX6IA",
"comment": "",
"state": {
"algo": 40000000000000,
"onl": 2
}
},
{
"addr": "G5RGT3EENES7UVIQUHXMJ5APMOGSW6W6RBC534JC6U2TZA4JWC7U27RADE",
"comment": "",
"state": {
"algo": 10000000000000,
"onl": 2
}
},
{
"addr": "5AHJFDLAXVINK34IGSI3JA5OVRVMPCWLFEZ6TA4I7XUZ7I6M34Q56DUYIM",
"comment": "",
"state": {
"algo": 20000000000000,
"onl": 2
}
}
],
"fees": "Y76M3MSY6DKBRHBL7C3NNDXGS5IIMQVQVUAB6MP4XEMMGVF2QWNPL226CA",
"id": "v1.0",
"network": "mainnet",
"proto": "https://github.com/algorandfoundation/specs/tree/5615adc36bad610c7f165fa2967f4ecfa75125f0",
"rwd": "737777777777777777777777777777777777777777777777777UFEJ2CI",
"timestamp": 1560211200
}
Block Verification
After the block is assembled or a block is received from the network, a series of checks are performed to verify the integrity of the block.
-
Validate that all transactions are
Alive(can be applied to the Ledger) for the round, -
Validate that the
paysethas valid signatures and the underlying transactions are properly constructed.
Transactions
A transaction defines a state transition of Accounts.
Algorand has \( 8 \) transaction types.
Transactions consist of:
-
A header (common to any type),
-
A body (type-specific).
⚙️ IMPLEMENTATION
Transaction package.
Transaction Levels
Algorand transactions have two execution levels:
-
Top Level: transactions are signed and cannot be duplicated in the Ledger,
-
Inner Level: transactions are not signed and may be duplicated in the Ledger.
Transaction Types
The transaction type is identified with a short string of at most 7 characters:
| TYPE ID | TRANSACTION TYPE |
|---|---|
pay | Payment (ALGO transfer) |
keyreg | Consensus participation keys registration and deregistration |
acfg | Algorand Standard Asset transfer |
axfer | Algorand Strandard Asset creation and reconfiguraiton |
afrz | Algorand Standard Asset freeze (withelisting and blacklisting) |
appl | Application (Smart Contract) call |
stpf | Algorand State Proof |
hb | Consensus heartbeat challange |
For a formal definition of all transaction fields, refer to the normative section.
⚙️ IMPLEMENTATION
The reference implementation also defines the
unknowntransaction type.Transaction types definition.
⚙️ IMPLEMENTATION
Transaction types reference implementation.
Transaction Header
The transaction header, equal for all transaction types, consists of:
-
TransactionTypeIdentifies the transaction type and the related body required fields. -
Sender
That signs the transaction. -
Fee
The amount paid by the sender to execute the transaction. Fees can be delegated (set to \( 0 \)) within a transactionGroup(see group transaction non-normative section). -
FirstValidRound\( \r_F \) andLastValidRound\( \r_L \)\ The difference \( (r_L - r_F) \) cannot be greater than \( 1000 \) rounds. -
Note(Optional)
Contains up to \( 1 \) kB of arbitrary data. -
GenesisHash
Ensures the transaction targets a specific Ledger (e.g.,wGHE2Pwdvd7S12BL5FaOP20EGYesN73ktiC1qzkkit8=for MainNet). -
GenesisID(Optional)
Like the Genesis Hash, it ensures that the transaction targets a specific Ledger (e.g.,mainnet-v1.0for MainNet). -
Group(Optional)
A cryptographic commitment to the group this transaction is part of (see group transaction non-normative section). -
Lease(Optional)
32-byte array that enforces mutual exclusion of transactions. If this field is set, it acquires aLease(Sender,Lease), valid until theLastValidRound\( r_L \) expires. While the transaction maintains theLease, no other transaction with the sameLeasecan be committed.
A typical use case of the
Leaseis a batch of signed transactions, with the sameLease, sent to the network to ensure only one is executed.
RekeyTo
An Algorand address (32-byte). If non-zero, the transaction will set theSenderaccount’s spending key to this address as last transaction effect. Therefore, future transactions sent by theSenderaccount must now be signed with the secret key of the address.
The transaction header verification ensures that a transaction:
-
Is signed adequately by the
Sender(eitherSingleSig,MultiSig, orLogicSig), -
It is submitted to the right Ledger (
GenesisHash); -
Pays the
MinFee(\( 1000 \) μALGO) orPerByteFee(if network is congested);Feeact as an anti-spam mechanism (grows exponentially if the network is congested or decays if there is spare block capacity);Feeprioritization is not enforced by consensus (although a node does that);- Inner Transaction costs always the
MinFee(regardless of network congestion); Feeare pooled inGrouptransactions;Feeis independent of theTransactionTypeor usage (i.e., no local fee market);
-
Round’s validity is not expired (\( 1000 \) rounds at most); -
FirstValidRoundcan be delayed in the future; -
Round’s validity handles transactions’ idempotency, letting Non-Archival nodes participate in consensus; -
It is not leased (combination of
SenderandLease) in its validity range.
Trackers
The Ledger module includes a collection of auxiliary components known as Trackers.
Trackers are state machines that evolve by consuming blocks from the blockchain.
Although logically stateless, meaning Trackers can rebuild their state from scratch by replaying all blocks since the genesis block every time, the reference implementation designs them to persist their state to speed up Ledger reconstruction.
Trackers are widely used in go-algorand to maintain efficient, read-optimized
views over different aspects of Ledger state.
Below is an overview of the main Trackers and their responsibilities:
-
Account Tracker
Maintains account states up to a given round. Key methods include:-
Lookup(round, address): Retrieves the state of the account (address) at the specifiedround. -
AllBalances(round): Returns all account states at the specifiedround. -
Totals(round): Returns aggregate account totals for a givenround, which is useful when querying for total account balance in the cryptographic sortition.
-
-
Recent Transactions Tracker
Uses the Transaction Tail to efficiently check whether a given transaction ID (txid) was included in a recent block. -
State Proof Verification Tracker
Maintains the necessary context to verify State Proofs (see State Proof normative specification. -
Voters Tracker
Tracks the vector commitment representing the most recent set of online accounts participating in the State Proofs. -
Catchpoint Tracker
Monitors the Catch-Up process used during node synchronization (see the non-normative specification).
Trackers API
Each Tracker exposes tracker-specific APIs to access the state it maintains.
⚙️ IMPLEMENTATION
Tracker reference implementation.
Trackers can access the Ledger via a restricted interface called ledgerForTracker,
which grants access to the Ledger’s SQLite database, recent blocks, and other read-only
functionality.
In the other direction, the Ledger communicates with Trackers using the ledgerTracker
interface, which includes the following key methods:
-
loadFromDisk(ledgerForTracker)
Initializes the state of the Tracker. The Tracker can use theledgerForTrackerargument to:- Load persistent state (e.g., for the accounts database),
- Query recent blocks, if its state depends only on recent history (e.g., the one that tracks recently committed transactions).
-
newBlock(rnd, delta)
Notifies the Tracker of a newly added block at roundrnd. The accompanyingdeltaparameter contains the state changes introduced by this block (see block evaluation section). -
committedUpTo(rnd)
Informs the Tracker that all blocks up to and includingrndare written to persistent storage. This call is crucial for stateful trackers, as it ensures correct state recovery and enables responses to queries about older blocks if recent uncommitted ones are lost after a crash. -
close()
Releases any resources or memory held by the Tracker.
The reference implementation ensures that all updates to Trackers are thread-safe using a reader-writer lock.
Protocol Rewards
The Algorand protocol has two reward systems:
-
Distribution Rewards (Legacy)
-
Staking Rewards
Rewards are distributed during the final stage of block assembly (in both systems).
The rewards systems are not mutually exclusive.
Distribution Rewards (Legacy)
The first reward system grants ALGO rewards to all the accounts, unless opted out of rewards, through a passive distribution of ALGO from the Rewards Pool, regardless of their participation in the consensus. The reward amount is proportional to the accounts’ ALGO balance.
Rewards distributed through this system are claimed and added to the account’s balance on each account state change (e.g., sending or receiving a transaction).
This system is disabled on MainNet and is kept for legacy and retro-compatibility reasons.
⚙️ IMPLEMENTATION
Distribution rewards reference implementation.
Staking Rewards
The second reward system grants ALGO rewards to accounts actively participating in the consensus protocol, if they meet eligibility criteria when selected as block proposers (see the following section for further details). The reward amount per block depends on the fee collected from the transactions included in the block, and an exponentially decaying bonus.
Rewards distributed through this system are instantly added to the block proposer balance, in the proposed block.
Staking rewards reference implementation.
Staking Rewards
This section is derived directly from the
go-algoranddocumentation.
Running a validator node on Algorand is a relatively lightweight operation. Therefore, participation in consensus was not compensated. There was an expectation that financially motivated holders of Algos would run nodes in order to help secure their holdings.
Although simple participation is not terribly resource intensive, running any service with high uptime becomes expensive when one considers that it should be monitored for uptime, be somewhat over-provisioned to handle unexpected load spikes, and plans need to be in place to restart in the face of hardware failure (or the accounts should leave consensus properly).
With those burdens in mind, fewer Algo holders chose to run participation nodes than would be preferred to provide security against well-financed bad actors. To alleviate this problem, a mechanism to reward block proposers has been created. With these block payouts in place, Algo holders are incentivized to run participation nodes in order to earn more Algos, increasing security for the entire Algorand network.
With the financial incentive to run participation nodes comes the risk that some nodes may be operated without sufficient care. Therefore, a mechanism to suspend nodes that appear to be performing poorly (or not at all) is required. Appearances can be deceiving, however. Since Algorand is a probabilistic consensus protocol, pure chance might lead to a node appearing to be delinquent. A new transaction type, the heartbeat, allows a node to explicitly indicate that it is online even if it does not propose blocks due to “bad luck”.
Payouts
Payouts are made in every block, if the proposer has opted into receiving them, has an Algo balance
in an appropriate range, and has not been suspended for poor behavior since opting-in. The size of
the payout is indicated in the block header, and comes from the FeeSink. The block payout consists
of two components. First, a portion of the block fees (currently 50%) are paid to the proposer.
This component incentivizes fuller blocks which lead to larger payouts. Second, a bonus payout is
made according to an exponentially decaying formula. This bonus is (intentionally) unsustainable
from protocol fees. It is expected that the Algorand Foundation will seed the FeeSink with
sufficient funds to allow the bonuses to be paid out according to the formula for several years. If
the FeeSink has insufficient funds for the sum of these components, the payout will be as high as
possible while maintaining the FeeSink’s minimum balance. These calculations are performed in
endOfBlock in eval/eval.go.
To opt-in to receive block payouts, an account includes an extra fee in the keyreg
transaction. The amount is controlled by the consensus parameter Payouts.GoOnlineFee. When such a
fee is included, a new account state bit, IncentiveEligible is set to true.
Even when an account is IncentiveEligible there is a proposal-time check of the account’s online
stake. If the account has too much or too little, no payout is performed (though
IncentiveEligible remains true). As explained below, this check occurs in agreement code in
payoutEligible(). The balance check is performed on the online stake, that is the stake from 320
rounds earlier, so a clever proposer can not move Algos in the round it proposes in order to receive
the payout. Finally, in an interesting corner case, a proposing account could be closed at proposal
time, since voting is based on the earlier balance. Such an account receives no payout, even if its
balance was in the proper range 320 rounds ago.
A surprising complication in the implementation of these payouts is that when a block is prepared by
a node, it does not know which account is the proposer. Until now, algod could prepare a single
block which would be used by any of the accounts it was participating for. The block would be
handed off to agreement which would manipulate the block only to add the appropriate block seed
(which depended upon the proposer). That interaction between eval and agreement was widened
(see WithProposer()) to allow agreement to modify the block to include the proper Proposer,
and to zero the ProposerPayout if the account that proposed was not actually eligible to receive a
payout.
Suspensions
Accounts can be suspended for poor behavior. There are two forms of poor behavior that can lead to suspension. First, an account is considered absent if it fails to propose as often as it should. Second, an account can be suspended for failing to respond to a challenge issued by the network at random.
Absenteeism
An account can be expected to propose once every n = TotalOnlineStake/AccountOnlineStake rounds.
For example, a node with 2% of online stake ought to propose once every 50 rounds. Of course the
actual proposer is chosen by random sortition. To make false positive suspensions unlikely, a node
is considered absent if it fails to produce a block over the course of 20n rounds.
The suspension mechanism is implemented in generateKnockOfflineAccountsList in eval/eval.go. It
is closely modeled on the mechanism that knocks accounts offline if their voting keys have expired.
An absent account is added to the AbsentParticipationAccounts list of the block header. When
evaluating a block, accounts in AbsentParticipationAccounts are suspended by changing their
Status to Offline and setting IncentiveEligible to false, but retaining their voting keys.
Keyreg and LastHeartbeat
As described so far, 320 rounds after a keyreg to go online, an account suddenly is expected to
have proposed more recently than 20 times its new expected interval. That would be impossible, since
it was not online until that round. Therefore, when a keyreg is used to go online and become
IncentiveEligible, the account’s LastHeartbeat field is set 320 rounds into the future. In
effect, the account is treated as though it proposed in the first round it is online.
Large Algo increases and LastHeartbeat
A similar problem can occur when an online account receives Algos. 320 rounds after receiving the
new Algos, the account’s expected proposal interval will shrink. If, for example, such an account
increases by a factor of 10, then it is reasonably likely that it will not have proposed recently
enough, and will be suspended immediately. To mitigate this risk, any time an online,
IncentiveEligible account balance doubles from a single Pay, its LastHeartbeat is incremented
to 320 rounds past the current round.
Challenges
The absenteeism checks quickly suspend a high-value account if it becomes inoperative. For example, an account with 2% of stake can be marked absent after 500 rounds (about 24 minutes). After suspension, the effect on consensus is mitigated after 320 more rounds (about 15 minutes). Therefore, the suspension mechanism makes Algorand significantly more robust in the face of operational errors.
However, the absenteeism mechanism is very slow to notice small accounts. An account with 30,000 Algos might represent 1/100,000 or less of total stake. It would only be considered absent after a million or more rounds without a proposal. At current network speeds, this is about a month. With such slow detection, a financially motivated entity might make the decision to run a node even if they lack the wherewithal to run the node with excellent uptime. A worst case scenario might be a node that is turned off daily, overnight. Such a node would generate profit for the runner, would probably never be marked offline by the absenteeism mechanism, yet would impact consensus negatively. Algorand can’t make progress with 1/3 of nodes offline at any given time for a nightly rest.
To combat this scenario, the network generates random challenges periodically. Every
Payouts.ChallengeInterval rounds (currently 1000), a random selected portion (currently 1/32) of
all online accounts are challenged. They must heartbeat within Payouts.ChallengeGracePeriod
rounds (currently 200), or they will be subject to suspension. With the current consensus
parameters, nodes can be expected to be challenged daily. When suspended, accounts must keyreg
with the GoOnlineFee in order to receive block payouts again, so it becomes unprofitable for
these low-stake nodes to operate with poor uptimes.
Heartbeats
The absenteeism mechanism is subject to rare false positives. The challenge mechanism explicitly
requires an affirmative response from nodes to indicate they are operating properly on behalf of a
challenged account. Both of these needs are addressed by a new transaction type — Heartbeat. A
Heartbeat transaction contains a signature (HbProof) of the blockseed (HbSeed) of the
transaction’s FirstValid block under the participation key of the account (HbAddress) in
question. Note that the account being heartbeat for is not the Sender of the transaction, which
can be any address. Signing a recent block seed makes it more difficult to pre-sign heartbeats that
another machine might send on your behalf. Signing the FirstValid’s blockseed (rather than
FirstValid-1) simply enforces a best practice: emit a transaction with FirstValid set to a committed
round, not a future round, avoiding a race. The node you send transactions to might not have
committed your latest round yet.
It is relatively easy for a bad actor to emit Heartbeats for its accounts without actually participating. However, there is no financial incentive to do so. Pretending to be operational when offline does not earn block payouts. Furthermore, running a server to monitor the blockchain to notice challenges and gather the recent blockseed is not significantly cheaper than simply running a functional node. It is already possible for malicious, well-resourced accounts to cause consensus difficulties by putting significant stake online without actually participating. Heartbeats do not mitigate that risk. Heartbeats have rather been designed to avoid motivating such behavior, so that they can accomplish their actual goal of noticing poor behavior stemming from inadvertent operational problems.
Free Heartbeats
Challenges occur frequently, so it important that algod can easily send Heartbeats as
required. How should these transactions be paid for? Many accounts, especially high-value accounts,
would not want to keep their spending keys available for automatic use by algod. Further, creating
(and keeping funded) a low-value side account to pay for Heartbeats would be an annoying operational
overhead. Therefore, when required by challenges, heartbeat transactions do not require a fee.
Therefore, any account, even an unfunded logicsig, can send heartbeats for an account under
challenge.
The conditions for a free Heartbeat are:
- The Heartbeat is not part of a larger group, and has a zero
GroupID. - The
HbAddressis Online and under challenge with the grace period at least half over. - The
HbAddressisIncentiveEligible. - There is no
Note,Lease, orRekeyTo.
Heartbeat Service
The Heartbeat Service (heartbeat/service.go) watches the state of all accounts for which algod
has participation keys. If any of those accounts meets the requirements above, a heartbeat
transaction is sent, starting with the round following half a grace period from the challenge. It
uses the (presumably unfunded) logicsig that does nothing except preclude rekey operations.
The heartbeat service does not heartbeat if an account is unlucky and threatened to be considered absent. We presume such false positives to be so unlikely that, if they occur, the node must be brought back online manually. It would be reasonable to consider in the future:
- Making heartbeats free for accounts that are “nearly absent”.
or
- Allowing for paid heartbeats by the heartbeat service when configured with access to a funded account’s spending key.
$$ \newcommand \TxTail {\mathrm{TxTail}} \newcommand \function {\textbf{function }} \newcommand \return {\textbf{return }} \newcommand \endfunction {\textbf{end function}} \newcommand \if {\textbf{if }} \newcommand \then {\textbf{ then}} \newcommand \endif {\textbf{end if}} \newcommand \for {\textbf{for }} \newcommand \do {\textbf{ do}} \newcommand \endfor {\textbf{end for}} \newcommand \comment {\qquad \small \textsf} \newcommand \CheckDuplicate {\mathrm{CheckDuplicate}} \newcommand \Tx {\mathrm{Tx}} \newcommand \ID {\mathrm{ID}} \newcommand \Lease {\mathrm{Lease}} \newcommand \FirstValid {\mathrm{FirstValid}} \newcommand \LastValid {\mathrm{LastValid}} \newcommand \LowWaterMark {\mathrm{LowWaterMark}} \newcommand \FirstChecked {\mathrm{FirstChecked}} \newcommand \LastChecked {\mathrm{LastChecked}} \newcommand \RecentLeaseMap {\mathrm{RecentLeaseMap}} \newcommand \LastValidMap {\mathrm{LastValidMap}} $$
Transaction Tail
The Transaction Tail \( \TxTail \) is a data structure responsible for deduplication and recent history lookups. It can be considered a rolling window of recent transactions and block headers observed in a reduced history of rounds, optimized for lookup and retrieval.
⚙️ IMPLEMENTATION
Transaction tail reference implementation.
It provides the following fields:
-
recentLeaseMap
A mapping ofround -> (TXLease -> round)that saves the transactionLeaseby observation round, and the mapping usesTXLeaseas keys to store theLeaseexpiring round. -
blockHeaderData
Contains recent block header data. The expected availability range is[Latest - MaxTxnLife, Latest], allowingMaxTxnLife + 1rounds of lookback ( \( 1001 \) with current parameters). -
lastValidMap
A mapping ofround -> (txid -> uint16)that enables the lookup of all transactions expiring in a given round. For each round, the inner map storestxids mapped to 16-bit unsigned integers representing the difference between the transaction’slastValidfield and the round it was confirmed (lastValid > confirmationRoundfor all confirmed transactions). -
lowWaterMark
An unsigned 64-bit integer representing a round number such that for any transactions where thelastValidfield islastValid < lowWaterMark, the node can quickly assert that it is not present in the \( \TxTail \).
Deduplication Check
A duplication check is the core functionality of \( \TxTail \).
\( \textbf{Algorithm 1} \text{: Check Duplicate} \)
$$ \begin{aligned} &\text{1: } \function \CheckDuplicate(\Tx_r, \FirstValid, \LastValid, \Tx_{\ID}, \Tx_{\Lease}) \\ &\text{2: } \quad \if \LastValid < \TxTail.\LowWaterMark \then \\ &\text{3: } \quad \quad \return \Tx_{\ID} \text{ is not in } \TxTail \\ &\text{4: } \quad \endif \\ &\text{5: } \quad \if \Tx_{\Lease} \neq \emptyset \then \\ &\text{6: } \quad \quad \FirstChecked \gets \FirstValid \\ &\text{7: } \quad \quad \LastChecked \gets \LastValid \\ &\text{8: } \quad \quad \for r \in [\FirstChecked, \LastChecked] \do \\ &\text{9: } \quad \quad \quad \if \Tx_{\Lease} \in \RecentLeaseMap(\Tx_r).\Lease \land r \leq \Tx_{\Lease}.\mathrm{Expiration} \then \\ &\text{10:} \quad \quad \quad \quad \return \Lease \text{ is a duplicate} \\ &\text{11:} \quad \quad \quad \endif \\ &\text{12:} \quad \quad \endfor \\ &\text{13:} \quad \endif \\ &\text{14:} \quad \if \Tx_{\ID} \in \TxTail.\LastValidMap(\LastValid).\Tx_{\ID} \then \\ &\text{15:} \quad \quad \return \Tx_{\ID} \text{ is a duplicate transaction} \\ &\text{16:} \quad \endif \\ &\text{17:} \quad \return \\ &\text{18: } \endfunction \end{aligned} $$
The algorithm receives four fields of a transaction:
-
The transaction round \( \Tx_r \),
-
The transaction validity round fields \( \FirstValid \) and \( \LastValid \),
-
The transaction identifier \( \Tx_{\ID} \),
-
The transaction lease \( \Tx_{\Lease} \) (if set).
An early check is performed, where the \( \LowWaterMark \) field is used to quickly discard transactions too far back in history and already purged from the \( \ TxTail \).
In case a \( \Tx_{\Lease} \) is set, the \( \RecentLeaseMap \) field is used to deduplicate by \( \Lease \).
After checking for the \( \Lease \), the \( \LastValidMap \) is used and the transaction is deduplicated through a lookup of \( \Tx_{\ID} \) by its \( \LastValid \) round.
If the transaction is not found on the \( \TxTail \), the node can assume it is not a duplicate, otherwise the validity interval would be too far back in the past for the transaction to be confirmed anyway.
$$ \newcommand \TP {\mathrm{TxPool}} \newcommand \Tx {\mathrm{Tx}} \newcommand \LastValid {\mathrm{LastValid}} \newcommand \BlockEval {\mathrm{BlockEvaluator}} $$
Transaction Pool
The Transaction Pool \( \TP \) is a Ledger component that maintains a queue of transactions received by the node.
This section presents an implementor-oriented definition of \( \TP \) and is based on the reference implementation to clarify how it is constructed and operated by a node.
For a succinct formal definition of the Transaction Pool, refer to the Ledger normative specification.
The \( \TP \) implementation makes use of two distinct queues to aid the processes of pruning already observed transactions and block commitment:
-
The remembered queue \( \TP_{rq} \),
-
The pending queue \( \TP_{pq} \).
The pending queue \( \TP_{pq} \) is the main structure used to supply transactions to the active pending \( \BlockEval \), which evaluates transactions for the next block.
⚙️ IMPLEMENTATION
Pending block evaluator reference implementation.
⚙️ IMPLEMENTATION
Here we provide some implementation details about the remembered queue \( \TP_{rq} \) and the pending queue \( \TP_{pq} \) structures used in the \( \TP \).
Whenever a new block is confirmed and committed to the Ledger, the node triggers
OnNewBlock.This function may rebuild the pending \( \BlockEval \) (except for a future round’s pending \( \BlockEval \)). As part of this process, the pending queue \( \TP_{pq} \) is synchronized with the remembered queue \( \TP_{rq} \) by replacing its contents entirely.
In contrast, when the
Rememberfunction is called, the verified transaction group is first appended to the remembered queue \( \TP_{rq} \). Then the entire \( \TP_{rq} \) is appended to \( \TP_{pq} \) rather than replacing it. This causes the two queues to diverge temporarily until the nextOnNewBlockcall resyncs them.Example of
Rememberfunction used by thetxnHandlerto enqueue a verified transaction group in the reference implementation.For more detail, see the
rememberCommit(bool flush)function, which controls how \( \TP_{pq} \) is updated from \( \TP_{rq} \).If
flush=true, \( \TP_{pq} \) is completely overwritten; ifflush=false, \( \TP_{rq} \) is appended.In summary:
OnNewBlock→ callsrememberCommit(true)→ replaces \( \TP_{pq} \) with \( \TP_{rq} \).
Remember→ appends to \( \TP_{rq} \), then callsrememberCommit(false)→ appends \( \TP_{rq} \) to \( \TP_{pq} \).Temporary queue divergence is expected and resolved at the next block confirmation.
Given a properly signed and well-formed transaction group \( gtx \in \TP_{pq} \), we say that \( gtx \) is remembered when it is pushed into \( \TP_{rq} \) if:
Its aggregated fee is sufficiently high,
Its state changes are consistent with the prior transactions in \( \TP_{rq} \).
Note that a single transaction can be viewed as a group \( gtx \) containing only one transaction.
\( \TP_{rq} \) is structured as a two-dimensional array. Each element in this array holds a list of well-formed, signed transactions.
To improve efficiency, the node also uses a key-value mapping where the keys are transaction IDs and the values are the corresponding signed transactions. This map duplicates the data in the queue, which adds a small computational cost when updating the queue (for insertions and deletions), but it enables fast, constant-time \( \mathcal{O}(1) \) lookup of any enqueued transaction by its ID.
Additionally, \( \TP_{pq} \) serves as another layer of optimization. It stores transaction groups that are prepared in advance for the next block assembly process. In a multithreaded system with strict timing constraints, this setup allows \( \TP_{rq} \) to be pruned as soon as a new block is committed, even while the next block is being assembled concurrently.
Transaction Pool Functions
The following is a list of abstracted minimal functionalities that the \( \TP \) should provide.
Prioritization
An algorithm that decides which transactions should be retained and which ones should
be dropped, especially important when the \( \TP \) becomes congested (i.e., when
transactions are arriving faster than they can be processed, de-enqueued in a block,
or observed in a committed block and pruned). A simple approach could be a “first-come,
first-served” policy. However, the go-algorand reference implementation uses a
more selective method: a threshold-based fee prioritization algorithm, which prioritizes
transactions paying higher fees.
Update
This process is triggered when a new block is observed as committed. At this point, transactions are pruned if they meet either of the following conditions:
- They have already been included in a committed block (as determined by the
OnNewBlockfunction), or - Their
LastValidfield has expired. Specifically, if the current round \( r > \Tx_{\LastValid}\).
In addition to pruning outdated or committed transactions, this step also updates the internal variables used for the prioritization.
Ingestion
This component handles the ingestion of new transaction groups (\( gtx \)) that are to be remembered (enqueued to \( \TP_{rq} \)). Before enqueuing, it verifies that each transaction group is internally valid and consistent in the context of transactions already present in \( \TP_{rq} \). Once transactions pass these checks, they are forwarded to any active Block Evaluator, so they can be considered for inclusion in blocks currently being assembled.
BlockAssembly
This process builds a new block’s payset (the body with block’s
transactions) by selecting valid transaction groups \( gtx \) dequeued from the
\( \TP \), all within a deadline. A (pending) Block Evaluator is responsible
for processing the transactions, while the BlockAssembly function coordinates
with it. The assembly process halts as soon as the time constraints are reached.
$$ \newcommand \TP {\mathrm{TxPool}} \newcommand \AD {\mathrm{assemblyDeadline}} \newcommand \AW {\mathrm{assemblyWait}} \newcommand \NB {\mathrm{newBlock}} \newcommand \FeeMul {\mathrm{feeThresholdMultiplier}} \newcommand \FeeExp {\mathrm{expFeeFactor}} \newcommand \FeePB {\mathrm{feePerByte}} \newcommand \ExpiredHistory {\mathrm{expiredHistory}} $$
In the context of this section, the phrase “giving up” refers to finalizing the current block under assembly; this includes completing the
paysetand calculating all associated metadata that depends on it.
Parameters
This section presents a list of some relevant parameters that govern the behavior of the \( \TP \).
In the case that there are leftover transactions in the \( \TP_{pq} \), the \( \TP \) saves them for the next block assembly.
\( \TP.\mathrm{Size} \)
A consensus parameter that defines the maximum number of transactions the transaction pool queue can hold. When this limit is reached, any new incoming transactions, even if valid, are not enqueued and are instead dropped.
⚙️ IMPLEMENTATION
In the
go-algorandreference implementation, this limit is set to \( 75{,}000 \) transactions.
\( \FeeMul \)
This dynamic parameter is updated each time a new block is observed. Under normal conditions, when the \( \TP \) is not congested, its value is \( 0 \). However, if the queues become congested, this parameter increases. As it grows, it raises the minimum fee threshold required for transactions to be accepted into \( \TP \).
For more details, see the update and fee prioritization sections.
\( \FeeExp \)
A consensus parameter, denoted as
TxPoolExponentialIncreaseFactor, which determines how sharply the required transaction
fee increases based on the number of full blocks pending (an indicator of \( \TP \)
congestion). This factor amplifies the fee threshold in congested conditions. The
default value in go-algorand consensus parameters is currently set to \( 2 \).
\( \FeePB \)
A dynamically computed parameter that defines the current minimum number of μALGO
per byte that a transaction must pay to be accepted into the \( \TP \). When the
\( \TP \) is not heavily congested, this parameter remains well below the minTxnFee,
meaning the base fee requirement still controls the admission threshold. Under normal
conditions (no congestion), this value is set to \( 0 \).
\( \delta_{\AD}\)
A consensus parameter that sets
a strict deadline for the BlockAssembly process to stop constructing a payset.
This ensures block assembly completes within the required time frame.
⚙️ IMPLEMENTATION
In the
go-algorandreference implementation, \( \delta_{\AD} = \mathrm{ProposalAssemblyTime} = 0.5 \) seconds.
\( \epsilon_{\AW} \)
An additional time buffer that the BlockAssembly algorithm waits after the official
deadline before “giving up”. This grace period allows slightly delayed transactions
to be included if possible.
⚙️ IMPLEMENTATION
In the
go-algorandreference implementation, \( \epsilon_{\AW} = 150 \) milliseconds.
\( \ExpiredHistory \)
A multiplier that determines how many rounds of historical data on expired transactions are retained. Specifically, the node keeps \( (\ExpiredHistory \times \mathrm{maxTxnLife}) \) rounds of history. Maintaining this history helps dynamically adjust transaction prioritization based on fee structures, as it provides insight into network congestion (e.g., if many transactions are expiring without being included in a block).
⚙️ IMPLEMENTATION
In the
go-algorandreference implementation,expiredHistoryis set to \( 10 \), therefore, the node keeps \( 10 \times 1000 = 10{,}000 \) rounds of history.
\( \delta_{\NB}\)
A time constant that defines how long the system should wait when processing a new
block that appears to be committed to the Ledger. This timeout is used within the
Ingestion function to ensure timely handling of new blocks.
⚙️ IMPLEMENTATION
In the
go-algorandreference implementation, this limit is set to \( 1 \) second.
Constants
The following two time constants are used to estimate how long it would take to
properly complete a block after the system has “given up” on assembling the payset.
These estimates work together with \( \delta_{\AD} \) and \( \epsilon_{\AW} \) to ensure that block finalization occurs within the required timeframe for proposal generation.
-
generateBlockBaseDuration, currently set to \( 2 \) milliseconds. -
generateBlockTransactionDuration, currently set to \( 2155 \) nanoseconds.
$$ \newcommand \TP {\mathrm{TxPool}} \newcommand \FeePB {\mathrm{feePerByte}} \newcommand \FeeMul {\mathrm{feeThresholdMultiplier}} \newcommand \FeeExp {\mathrm{expFeeFactor}} \newcommand \PendingFB {\mathrm{pendingFullBlocks}} \newcommand \function {\textbf{function }} \newcommand \return {\textbf{return }} \newcommand \endfunction {\textbf{end function}} \newcommand \if {\textbf{if }} \newcommand \then {\textbf{ then}} \newcommand \endif {\textbf{end if}} \newcommand \for {\textbf{for }} \newcommand \do {\textbf{ do}} \newcommand \endfor {\textbf{end for}} \newcommand \ComputeFeePerByte {\mathrm{ComputeFeePerByte}} $$
Prioritization
When the \( \TP \) becomes congested, a fee prioritization algorithm determines which transactions are enqueued into the pool and which are rejected.
The key parameter in this process is \( \FeePB \), which is calculated dynamically based on the number of pending blocks awaiting evaluation.
The \( \PendingFB \) is an unsigned integer that represents the number of uncommitted full blocks present in the \( \TP_{pq} \).
The function computeFeePerByte below demonstrates how this value is computed:
\( \textbf{Algorithm 2} \text{: Compute Fee per Byte} \)
$$ \begin{aligned} &\text{1: } \function \ComputeFeePerByte() \\ &\text{2: } \quad \FeePB \gets \FeeMul \\ &\text{3: } \quad \if \FeePB = 0 \land \TP.\PendingFB > 1 \then \\ &\text{4: } \quad \quad \FeePB \gets 1 \\ &\text{5: } \quad \endif \\ &\text{6: } \quad \for i {\textbf{ from }} 0 \textbf{ to } \TP.\PendingFB \do \\ &\text{7: } \quad \quad \FeePB \gets \FeePB \cdot \TP.\FeeExp \\ &\text{8: } \quad \endfor \\ &\text{9: } \quad \return \FeePB \\ &\text{10: } \endfunction \end{aligned} $$
⚙️ IMPLEMENTATION
Compute fee per byte reference implementation.
The computeFeePerByte function begins by setting \( \FeePB \) equal to the \( \FeeMul \).
When there is no congestion in \( \TP \), this value is \( 0 \).
However, if there are any full blocks currently pending in \( \TP_{pq} \), \( \FeePB \) is initially set to \( 1 \). This setup ensures that the subsequent multiplication step accumulates due to a non-zero base.
Next, for each of these full pending blocks, the \( \FeeExp \) is multiplied by the current \( \FeePB \) value—causing \( \FeePB \) to grow exponentially with the level of congestion.
The resulting \( \FeePB \) is then:
$$ \FeePB = \max\{1, \FeeMul \} \times \FeeExp^{\TP.\PendingFB} $$
$$ \newcommand \TP {\mathrm{TxPool}} \newcommand \NB {\mathrm{newBlock}} \newcommand \SD {\mathrm{stateDelta}} \newcommand \FeeMul {\mathrm{feeThresholdMultiplier}} \newcommand \FeeExp {\mathrm{expFeeFactor}} \newcommand \PendingFB {\mathrm{pendingFullBlocks}} \newcommand \function {\textbf{function }} \newcommand \return {\textbf{return }} \newcommand \endfunction {\textbf{end function}} \newcommand \if {\textbf{if }} \newcommand \then {\textbf{ then}} \newcommand \endif {\textbf{end if}} \newcommand \else {\textbf{else}} \newcommand \switch {\textbf{switch }} \newcommand \case {\textbf{case }} \newcommand \default {\textbf{default}} \newcommand \endswitch {\textbf{end switch}} \newcommand \comment {\qquad \small \textsf} \newcommand \Update {\mathrm{Update}} $$
Update
The Update function is called each time a new block is confirmed.
During this process, the \( \TP \) removes all transactions that have either already
been committed or whose lastValid field has expired.
The adjustment of the fee prioritization mechanism depends on how many full blocks are currently pending in the \( \TP_{pq} \) queue.
The state of the \( \TP \) is then updated as follows:
\( \textbf{Algorithm 3} \text{: Update } \TP \)
$$ \begin{aligned} &\text{1: } \function \Update(\NB\ b, \SD\ sd) \\ &\text{2: } \quad \if \TP_{pq} \text{ is empty or outdated} \then \\ &\text{3: } \quad \quad \switch \TP.\PendingFB \\ &\text{4: } \quad \quad \quad \case\ 0: \\ &\text{5: } \quad \quad \quad \quad \FeeMul \gets \frac{FeeMul}{FeeExp} \\ &\text{6: } \quad \quad \quad \case\ 1: \\ &\text{7: } \quad \quad \quad \quad \comment{# Intentionally left blank to maintain the value of } \FeeMul \\ &\text{8: } \quad \quad \quad \case\ \default: \\ &\text{9: } \quad \quad \quad \quad \if \FeeMul = 0 \then \\ &\text{10:} \quad \quad \quad \quad \quad \FeeMul \gets 1 \\ &\text{11:} \quad \quad \quad \quad \else \\ &\text{12:} \quad \quad \quad \quad \quad \FeeMul \gets \FeeMul \cdot \FeeExp \\ &\text{13:} \quad \quad \quad \quad \endif \\ &\text{14:} \quad \quad \endswitch \\ &\text{15:} \quad \endif \\ &\text{16:} \quad \TP.\mathrm{Prune}(b, sd) \\ &\text{17: } \endfunction \end{aligned} $$
⚙️ IMPLEMENTATION
Update on a new block reference implementation.
The algorithm above updates the \( \FeeMul \) based on the current state of the pending queue. Specifically, it checks whether the queue is either empty (no leftover transactions from the previous block assembly) or outdated (i.e., the remaining transactions were grouped into full blocks from a round \( r_p \) such that \( r \geq r_p \), where \( r \) is the current round).
The adjustment logic works as follows:
-
If there are \( 0 \) pending full blocks:
This suggests that any previous congestion has cleared. The \( \FeeMul \) is reduced by dividing it by the \( \FeeExp \). If this low-congestion state continues, the multiplier quickly diminishes and approaches \( 0 \). -
If there is exactly \( 1 \) pending full block:
The \( \FeeMul \) remains unchanged. -
Otherwise, if there are more than \( 1 \) pending full block (\( \TP.\PendingFB > 1 \)):
-
If the \( \FeeMul \) is currently \( 0 \), it is set to \( 1 \) to reflect a sudden spike in congestion.
-
If it already has a value, it is multiplied by the \( \FeeExp \), causing it to grow in response to continued congestion.
-
After updating the fee prioritization mechanism, the \( \TP \) is pruned by removing:
-
Transactions that were included in the newly committed block \( b \), and
-
Transactions whose
lastValidfield is less than the current round \( r \) (which matches the round of \( b \) that was just observed).
$$ \newcommand \TP {\mathrm{TxPool}} \newcommand \TG {\mathrm{TxnGroup}} \newcommand \NB {\mathrm{newBlock}} \newcommand \BlockEval {\mathrm{BlockEvaluator}} \newcommand \Ledger {\mathrm{Ledger}} \newcommand \CheckSufficientFee {\mathrm{CheckSufficientFee}} \newcommand \now {\mathrm{now}} \newcommand \function {\textbf{function }} \newcommand \return {\textbf{return }} \newcommand \endfunction {\textbf{end function}} \newcommand \if {\textbf{if }} \newcommand \then {\textbf{ then}} \newcommand \endif {\textbf{end if}} \newcommand \while {\textbf{while }} \newcommand \do {\textbf{ do}} \newcommand \endwhile {\textbf{end while}} \newcommand \comment {\qquad \small \textsf} \newcommand \Ingest {\mathrm{Ingest}} $$
Ingestion
This function determines which transaction groups should be passed to the pending Block Evaluator for possible inclusion in the next block, and which ones should be deferred for evaluation in a future round.
When deferring transactions, the node checks that their remaining validity period is sufficient for future inclusion; otherwise, they are marked for removal if they exceed their validity period.
The \( \TP \) also verifies that each transaction group meets the minimum required fee to qualify for execution.
A \( \BlockEval \) is the construct used to ingest \( \TG \).
The following pseudocode snippet illustrates how this ingestion process could be implemented:
\( \textbf{Algorithm 4} \text{: Transaction Ingestion} \)
$$ \begin{aligned} &\text{1: } \function \Ingest(\TG\ gtx) \\ &\text{2: } \quad \dots \\ &\text{3: } \quad \if \lnot \BlockEval \then \\ &\text{4: } \quad \quad \return \comment{# No pending Block Evaluator exists} \\ &\text{5: } \quad \endif \\ &\text{6: } \quad \if \lnot \texttt{recompute} \then \\ &\text{7: } \quad \quad r \gets \Ledger.\mathrm{getLatestRound}() \\ &\text{8: } \quad \quad t^\ast \gets \now() + \delta_{\NB} \\ &\text{9: } \quad \quad \while \BlockEval.\mathrm{round}() \leq r \land \now() < t^\ast \do \\ &\text{10:} \quad \quad \quad \textsf{# Give time to the } \BlockEval \textsf{ to catch up} \\ &\text{11:} \quad \quad \endwhile \\ &\text{12:} \quad \quad \if \lnot \CheckSufficientFee(gtx) \then \\ &\text{13:} \quad \quad \quad \return gtx \comment{# Discarded for insufficient fees} \\ &\text{14:} \quad \quad \endif \\ &\text{15:} \quad \endif \\ &\text{16:} \quad \TP \gets \BlockEval.\mathrm{add}(gtx) \\ &\text{17:} \quad \dots \\ &\text{18: } \endfunction \end{aligned} $$
Transaction ingestion reference implementation.
This algorithm requires a pending \( \BlockEval \) to be already initialized and ready to process transaction groups.
It uses a \( \texttt{recompute} \) flag to verify whether the Update function
has handled the latest block. If not, the algorithm enters a wait phase, controlled
by the \( \delta_{NB} \) parameter (as described in the parameters section).
This waiting period can end early if the pending \( \BlockEval \) catches up to the current round. Once synchronized, the algorithm performs a preliminary check to ensure that the candidate transaction group \( gtx \) is adequately funded, per the fee prioritization rules.
Finally, an attempt is made to add \( gtx \) to the pending \( \BlockEval \). After evaluating \( gtx \) and performing all necessary checks, this step effectively enqueues the transaction group into the \( \TP \).
$$ \newcommand \TP {\mathrm{TxPool}} \newcommand \AD {\mathrm{assemblyDeadline}} \newcommand \AW {\mathrm{assemblyWait}} \newcommand \AssembleBlock {\mathrm{AssembleBlock}} \newcommand \BlockEval {\mathrm{BlockEvaluator}} \newcommand \EB {\mathrm{emptyBlock}} \newcommand \r {\mathrm{round}} \newcommand \function {\textbf{function }} \newcommand \return {\textbf{return }} \newcommand \endfunction {\textbf{end function}} \newcommand \if {\textbf{if }} \newcommand \elseif {\textbf{else if }} \newcommand \then {\textbf{ then}} \newcommand \endif {\textbf{end if}} \newcommand \comment {\qquad \small \textsf} \newcommand \nil {\mathit{nil}} $$
Block Assembly
The \( \TP \) is responsible for populating the payset of a block, a process
referred to as BlockAssembly.
The BlockAssembly is a time-bound algorithm that manages the flow of transactions
into the pending \( \BlockEval \) and stops ingestion once timing constraints are reached.
It also handles possible desynchronizations between the \( \TP.\r \) (the current
round as perceived by the \( \TP \)) and the actual round being assembled by the
pending \( \BlockEval \). This discrepancy arises based on how often the Update
function has been invoked.
The following pseudocode outlines a high-level view of how BlockAssembly operates:
\( \textbf{Algorithm 5} \text{: Block Assembly} \)
$$ \begin{aligned} &\text{1: } \function \AssembleBlock(r) \\ &\text{2: } \quad \if \TP.\r < r - 2 \then \\ &\text{3: } \quad \quad \return \AssembleBlock.\EB(r) \\ &\text{4: } \quad \endif \\ &\text{5: } \quad \if r < \TP.\r \then \\ &\text{6: } \quad \quad \return \nil \\ &\text{7: } \quad \endif \\ &\text{8: } \quad \AD \gets \r.\mathrm{startTime}() + \delta_{\AD} \\ &\text{9: } \quad \text{Wait until } \AD \lor (\TP.\r = r \land \BlockEval \text{ is done}) \\ &\text{10:} \quad \if \lnot \BlockEval.\mathrm{done}() \then \\ &\text{11:} \quad \quad \if \TP.\r > r \then \\ &\text{12:} \quad \quad \quad \return \nil \comment{# r is behind } \TP.\r \\ &\text{13:} \quad \quad \endif \\ &\text{14:} \quad \quad \AD \gets \AD + \epsilon_{\AW} \\ &\text{15:} \quad \quad \text{Wait until } \AD \lor (\TP.\r = r \land \BlockEval \text{ is done}) \\ &\text{16:} \quad \quad \if \lnot \BlockEval.\mathrm{done}() \then \\ &\text{17:} \quad \quad \quad \return \AssembleBlock.\EB(r) \comment{# Ran out of time} \\ &\text{18:} \quad \quad \endif \\ &\text{19:} \quad \quad \if \TP.\r > r \then \\ &\text{20:} \quad \quad \quad \return \nil \comment{# Requested round is behind transaction pool round} \\ &\text{21:} \quad \quad \elseif \TP.\r = r - 1 \then \\ &\text{22:} \quad \quad \quad \return \AssembleBlock.\EB(r) \\ &\text{23:} \quad \quad \elseif \TP.\r < r \then \\ &\text{24:} \quad \quad \quad \return \nil \\ &\text{25:} \quad \quad \endif \\ &\text{26:} \quad \endif \\ &\text{27:} \quad \return \BlockEval.\mathrm{block} \\ &\text{28: } \endfunction \end{aligned} $$
⚙️ IMPLEMENTATION
Block assembly reference implementation.
This algorithm begins by taking a target round \( r \), for which a new block is to be assembled.
It first checks the round currently perceived by the \( \TP \), which matches the round being handled by the pending \( \BlockEval \).
-
If the \( \TP.\r \) is significantly behind \( r \): an empty block is immediately assembled and returned, as there’s no time to catch up.
-
If the \( \TP \) is already ahead of \( r \): no action is needed, as \( \TP \) is simply ahead of the network’s current state.
Next, the algorithm waits for the assembly deadline \( \delta_{\AD} \). During
this time, the pending \( \BlockEval \) is expected to notify the completed block
assembly in the background via the Ingestion function, and that it is caught
up to the round \( r \).
If this doesn’t happen by the deadline, the algorithm performs another round of checks:
-
If the \( \TP.\r \) is now ahead of \( r \): the process is aborted, waiting for the network to catch up. This should rarely happen.
-
Othwewise, if the \( \TP \) is still behind: an additional wait period \( \epsilon_{\AW} \) is introduced.
After this extra wait, similar checks are repeated:
-
If the \( \TP \) is still too far behind: there is no more time to wait, and the algorithm exits.
-
Otherwise: the algorithm proceeds.
If all checks pass and timing constraints are met without returning early (an empty block or a \( \nil \) value), the pending \( \BlockEval \) finally provides the fully assembled block for round \( r \).
$$ \newcommand \TP {\mathrm{TxPool}} $$
Graphic Run Example
This section is a high-level step-by-step graphic example of a \( \TP \) “vanilla run”, which provides an intuition about the typical operations:
- Receiving transactions from the Network and prioritizing them,
- Parallel transactions verification,
- Enqueuing transactions in the \( \TP \),
- Serial transaction (FIFO) queue verification and gossip,
- Re-verification on a new appended block,
- Block assembly.
Step 1: Reception and Prioritization
Step 2: Parallel Verification
Step 3: Enqueuing
Step 4: Serial Verification and Gossip
Step 5: Verification on New Block
Step 6: Block Assembly
$$ \newcommand \TP {\mathrm{TxPool}} \newcommand \BlockEval {\mathrm{BlockEvaluator}} $$
Block Commitment
Block commitment is the process by which a valid block is added to the Ledger.
⚙️ IMPLEMENTATION
Block commitment entry point in the reference implementation.
To support block commitment, verification, and assembly, the node uses a structure called \( \BlockEval \).
A \( \BlockEval \) can:
-
Evaluate transactions in a block body one by one,
-
Add transactions to an in-progress
payset, -
Track state changes caused by each transaction, and
-
Determine if a block is invalid due to a transaction failing validation in the block’s context.
During the block assembly phase, the \( \BlockEval \) can also discard invalid transactions and continue assembling the block with valid ones.
⚙️ IMPLEMENTATION
Block Evaluator reference implementation.
Once a block has been certified, the Ledger is responsible for successfully adding it to the blockchain.
The process of adding a block follows this basic sequence:
sequenceDiagram
ledger.AddBlock ->> + eval.Eval: Calls Eval with Block and Agreement certificate
eval.Eval ->> -ledger.AddBlock: State Delta updates
ledger.AddBlock ->> + ledger.AddValidatedBlock: Adds the validated block with its certificate
Block evaluation takes place after its certificate has been computed. At this point, the Ledger validates the block, applies the resulting State Deltas to its internal state, and adds the block to the blockchain. Once these changes are successfully applied, the block is finalized and officially committed to the Ledger.
flowchart TD
A[**EnsureBlock**] --> C[ledger.**addBlock**]
C --> D[Execute **Eval** to process the Block with its **certificate**]
D --> E[**StartEvaluator**<br>Fetch previous block and protocol params]
E --> G[Initialize Evaluator and Base State]
G --> H[Calculate updates for new Block]
H --> I[Apply updates to Ledger state]
I --> C
C --> J[Finalize and commit Block to the Ledger]
The StartEvaluator function sets up and returns a pending \( \BlockEval \),
which will handle processing the block and updating the Ledger state. As part of
its initialization, the \( \BlockEval \) retrieves the previous block and the
relevant protocol parameters to guarantee that the evaluation is consistent with
the current blockchain state.
⚙️ IMPLEMENTATION
Start Evaluator reference implementation.
The core interface of a \( \BlockEval \) can be broken down into three primary functions:
-
Block Construction:
This begins by callingStartEvaluatorto create a new \( \BlockEval \) instance. It then ingests a sequence of valid transactions from the transaction pool \( \TP_{rq} \), tracking all resulting state changes. The block is finalized at the end of this process with the block proposer setup deferred to this final stage. -
Block Validation:
This function checks the validity of a given block. Internally, it reuses the same logic as the evaluation process to ensure consistency. -
Block Evaluation:
This function processes the block to generate a State Delta, which captures all the changes the block makes to the Ledger and its associated Trackers.
⚙️ IMPLEMENTATION
Validate block reference implementation.
$$ \newcommand \TxTail {\mathrm{TxTail}} $$
State Delta
A State Delta represents the changes made to the Ledger’s state from one round to the next.
It is a compact data structure designed to efficiently update all state Trackers after a block is committed. By recording only the parts of the state that were modified, it also simplifies block assembly and validation.
For a formal definition of this structure, refer to the Algorand Ledger normative specification.
⚙️ IMPLEMENTATION
State Delta reference implementation.
In the go-algorand reference implementation, a State Delta includes the following
fields:
-
AccountStateDeltas:
A set ofAccount State Deltas, collecting changes to accounts affected by the block, detailing how their states were modified. -
KVMods:
A key-value map of modified entries in the Key Value Store, represented as(string → KvValueDelta). -
TxIDs:
A mapping of new transaction IDs to theirLastValidround:(txid → uint). This is used to update the \( \TxTail \) and manage the transaction counter. -
Txleases:
A mapping(TxLease → uint64)of new transaction leases to their expiration rounds, also relevant to the \( \TxTail \) mechanism. -
Creatables:
A mapping of data about newly created or deleted “creatable entities” such as Applications and Assets. -
*Hdr:
A read-only reference to the header of the new block. -
StateProofNext:
Reflects any update to theStateProofNextRoundin the block header. If the block includes a valid State Proof transaction, this is set to the next round for a proof; otherwise, it’s set to \( 0 \). -
PrevTimestamp:
Stores the timestamp of the previous block as an integer. -
AccountTotals:
A snapshot of the updated account totals resulting from the changes in this State Delta.
Appendix A
Copy On Write implementation strategy
___________________
< cow = Copy On Write >
-------------------
\ ^__^
\ (oo)\_______
(__)\ )\/\
||----w |
|| ||
Copy-On-Write (COW) is an optimization strategy designed to manage data structure modifications efficiently. The fundamental principle behind COW is to defer the copying of an object until it is actually modified, allowing multiple processes or components to safely share a single instance of the object for as long as it remains unchanged. A separate copy is created only when a write operation occurs, ensuring both memory efficiency and data consistency.
The COW structure is part of the Block Evaluator.
In the go-algorand reference implementation, the roundCowState structure applies
the Copy-On-Write (COW) strategy to manage the State Delta.
It ensures that state copies are only created when modifications are necessary,
enabling efficient memory usage and optimized performance during state transitions
across blockchain rounds.
⚙️ IMPLEMENTATION
Copy on write reference implementation.
Network Overview
This part describes Algorand Network formation, actors and their attributions, connection management and allowed messages.
$$ \newcommand \WS {\mathrm{WS}} \newcommand \PtoP {\mathrm{P2P}} \newcommand \HYB {\mathrm{HYB}} $$
Non-Normative
This is a non-normative description of the Algorand Network Layer, responsible for handling connections and message transport between nodes.
The purpose of this chapter is to provide a detailed overview of all the necessary network components and their interactions, aiding the reader’s understanding of the infrastructure, and providing implementors with a solid foundation to instrument networking.
The information contained in this chapter is derived from the Algorand reference
implementation (go-algorand).
There are currently two independent network layers on Algorand:
-
Relay Network (\( \WS \)), based on a websockets mesh,
-
Peer-to-Peer Network (\( \PtoP \)), based on the
libp2pnetworking library.
A third option, called Hybrid Network (\( \HYB \)), instantiates the constructs to keep both networking layers running in parallel on the node.
This chapter covers first some general constructs common to \( \WS \) and \( \PtoP \) networks, defining the notation. Then it dives deeper into each network, its components, and interactions.
When sensible, implementation-specific notes are included to hint at possible desirable patterns or optimizations, although the impact of these may vary according to the implementation language of choice.
$$ \newcommand \Peer {\mathrm{Peer}} \newcommand \WS {\mathrm{WS}} \newcommand \PtoP {\mathrm{P2P}} \newcommand \HYB {\mathrm{HYB}} \newcommand \tag {\mathrm{tag}} \newcommand \InMsg {\ast\texttt{M}} \newcommand \OutMsg {\texttt{M}\ast} \newcommand \MessageHandler {\mathrm{MH}} \newcommand \MessageValidatorHandler {\mathrm{MV}_h} \newcommand \ForwardingPolicy {\mathrm{ForwardingPolicy}} $$
Notation and Data Structures
Peer
We define a \( \Peer \) as a generic network actor.
This construct provides a way to refer to nodes indistinctly and keep track of all neighbors with inbound or outbound connections that may relay or broadcast messages.
Each of \( \Peer \) represents a fully operational Algorand node with a working network layer.
A specific \( \Peer_t \), with \( t \in \{\WS, \PtoP, \HYB\} \) is a \( \Peer \) whose network layer implements a specific type of network.
A \( \Peer \) has all the necessary contents to communicate with the node it represents (the HTTP client, the URL representing the node, and extra metadata necessary to maintain an active connection).
⚙️ IMPLEMENTATION
Peer struct reference implementation.
Protocol Tags
A protocol \( tag \) is a short 2-byte string that marks a message type.
A \( tag \) should not contain a comma, as lists of tags are modeled by comma-separated tag handles.
Protocol tags play a key role in routing messages to appropriate handlers and incorporating priority notions.
Conceptually, a \( tag \) determines the purpose of an incoming data packet inside the overarching protocol.
Possible values for the \( tag \) type are:
| TAG | DESCRIPTION |
|---|---|
"AV" | Agreement Vote (a protocol vote, see normative section). |
"MI" | Message of Interest. |
"MS" | Message Digest Skip. A request by a \( \Peer \) to avoid sending messages with a specific hash. |
"NP" | Network Priority Response. |
"NI" | Network ID Verification. |
"PP" | Proposal Payload (see normative section). |
"SP" | State Proof Signature (see normative section). |
"TS" | Topic Message Response. |
"TX" | Transaction (see normative section). |
"UE" | Unicast Catchup Request. Messages used to request blocks by a \( \Peer \) when serving blocks for the catchup service (see Algorand Infrastructure non-normative section. |
"VB" | Vote Bundle (a protocol bundle, see normative section). |
"pi" | Ping1. |
"pj" | Ping Reply1. |
Agreement Vote ("AV") and Proposal Payload ("PP") are the only ones considered
of “high priority”. This means they impact internal ordering in the broadcast
queue, as a priority function discriminates against them.
⚙️ IMPLEMENTATION
High priority tags reference implementation.
Messages tagged with AV or PP get pushed into a separate high-priority queue.
⚙️ IMPLEMENTATION
High priority queue reference implementation.
Every \( tag \) has a corresponding set of handlers, described in detail in the Message Handlers section.
Messages (In and Out)
Algorand nodes communicate inside a network layer exchanging messages.
A message is a data structure with a payload (a set of bytes) and metadata that serves to authenticate, route, and interpret messages received or sent out.
We define a deserializable object incoming message \( \InMsg \), as an object representing a message from some \( \Peer \) in the same network layer.
An incoming message \( \InMsg \) provides the following fields:
-
sender, an identified \( \Peer \) indicating the sending party, -
protocolTag, a tag (see above), used to identify univocally the message type and route it to the correct message handler to produce an outgoing message, -
payload, an array of bytes representing the content of the message. See the parameters section for details on size constraints, -
network, the type of network from which the message originated (Relay Network or P2P Network), -
received, a 64-bit integer representing the reception time of this message (expressed in nanoseconds since theepoch).
When an incoming message \( \InMsg \) is received, and the appropriate message handler has processed it, an outgoing message is produced.
We define a deserializable object outgoing message \( \OutMsg \), as an object representing a message from some \( \Peer \) in the network.
An outgoing message \( \OutMsg \) provides the following fields:
-
protocolTag, a tag (see above). Similarly to incoming messages, it marks how the receiving \( \Peer \) should interpret and handle the produced message, -
payload, an array of bytes representing the content of the message. See the parameters section for details on size constraints, -
topics, a list of key-value pairs (of the formstring -> bytes[]) for topics this message serves, used in certain specific scenarios (mainly for the catch-up service). The possible topic keys are:-
General purpose:
"RequestHash", responding to requests for specific topics,"Error", passing an error message on a specific topic request.
-
Block service:
"roundKey", the block round-number topic-key in the request,"requestDataType", the data-type topic-key in the request (e.g.,block,cert,blockAndCert),"blockData", serving block data,"certData", serving block certificate data,"blockAndCert", requesting block and certificate data,"latest", serving the latest round.
-
-
disconnectReason, only when theActioncalls for aDisconnectas a \( \ForwardingPolicy \) (see below). An enumeration of the reasons to disconnect from a given \( \Peer \) (message sender) may be found right below.
A \( \ForwardingPolicy \) is an enumeration, indicating what action should be taken for a given outgoing message \( \OutMsg \). It may take any of the following values:
-
Ignore, to discard the message (don’t forward), -
Disconnect, to disconnect from the \( \Peer \) that sent the message \( \OutMsg \) which returned this response, -
Broadcast, to forward this message to everyone (except the original sender \( \Peer \)), -
Respond, to reply to the sender \( \Peer \) directly, -
Accept, to accept the message for further processing after successful validation.
When an incoming message \( \InMsg \) is received, a handler function is called
according to its type. The message handler processes the message according to the
protocolTag, and produces an outbound message \( \OutMsg \) with information
on how to proceed further.
Message Handlers
We define a message handler \( \MessageHandler_t(\InMsg) \) as a function that takes an incoming message as input and transforms it into an outgoing message.
$$ \MessageHandler_t(\InMsg) = \OutMsg $$
where \( t \) denotes a \( tag \)-specific handler function (according to the
input inbound message protocolTag).
We define a message validator handler \( \MessageValidatorHandler(\InMsg) \) as a function that performs synchronous validation of a message before processing it with the \( \MessageHandler_t(\InMsg) \) functions.
The prototype of message validator handlers is similar to regular handlers.
⚙️ IMPLEMENTATION
The reference implementation defines a helper function,
Propagate(msg IncomingMessage), representing the prevalent case of a message handler re-propagating an incoming message \( \InMsg \). Internally, it creates an outgoing message \( \OutMsg \), with the same data as the received message and the action toBroadcast.func Propagate(msg IncomingMessage) OutgoingMessage { return OutgoingMessage{Action: Broadcast, Tag: msg.Tag, Payload: msg.Data, Topics: nil} }
Removed in go-algorand 3.2.1., included for completeness.
Parameters
The following tables present the parametrization of the go-algorand network messages
timing and sizing.
Performance Monitoring
| NAME | VALUE (seconds) | DESCRIPTION |
|---|---|---|
pmPresyncTime | \( 10 \) | Performance monitoring |
pmSyncIdleTime | \( 2 \) | Performance monitoring |
pmSyncMaxTime | \( 25 \) | Performance monitoring |
pmAccumulationTime | \( 60 \) | Performance monitoring |
pmAccumulationTimeRange | \( 30 \) | Performance monitoring |
pmAccumulationIdlingTime | \( 2 \) | Performance monitoring |
pmMaxMessageWaitTime | \( 15 \) | Performance monitoring |
pmUndeliveredMessagePenaltyTime | \( 5 \) | Performance monitoring |
pmDesiredMessegeDelayThreshold | \( 0.05 \) | Performance monitoring |
pmMessageBucketDuration | \( 1 \) | Performance monitoring |
Message Sizes
| NAME | VALUE (Bytes) | DESCRIPTION |
|---|---|---|
AgreementVoteTagMaxSize | \( 1228 \) | Maximum size of an AgreementVoteTag message |
MsgOfInterestTagMaxSize | \( 45 \) | Maximum size of a MsgOfInterestTag message |
MsgDigestSkipTagMaxSize | \( 69 \) | Maximum size of a MsgDigestSkipTag message |
NetPrioResponseTagMaxSize | \( 850 \) | Maximum size of a NetPrioResponseTag message |
NetIDVerificationTagMaxSize | \( 215 \) | Maximum size of a NetIDVerificationTag message |
ProposalPayloadTagMaxSize1 | \( 5{,}250{,}313 \) | Maximum size of a ProposalPayloadTag message |
StateProofSigTagMaxSize | \( 6378 \) | Maximum size of a StateProofSigTag message |
TopicMsgRespTagMaxSize | \( 6 \times 1024 \times 1024 \) | Maximum size of a TopicMsgRespTag message |
TxnTagMaxSize | \( 5{,}000{,}000 \) | Maximum size of a TxnTag message |
UniEnsBlockReqTagMaxSize | \( 67 \) | Maximum size of a UniEnsBlockReqTag message |
VoteBundleTagMaxSize | \( 6 \times 1024 \times 1024 \) | Maximum size of a VoteBundleTag message |
MaxMessageLength | \( 6 \times 1024 \times 1024 \) | Maximum length of a message |
averageMessageLength | \( 2 \times 1024 \) | Average length of a message (base allocation) |
This value is dominated by MaxTxnBytesPerBlock, see ledger parameters
normative section.
$$ \newcommand \tag {\mathrm{tag}} \newcommand \MessageHandler {\mathrm{MH}} \newcommand \ForwardingPolicy {\mathrm{ForwardingPolicy}} \newcommand \InMsg {\ast\texttt{M}} \newcommand \OutMsg {\texttt{M}\ast} $$
Message Handlers
Each incoming message \( \InMsg \) is deferred to the correct message handler \( \MessageHandler_t(\InMsg) \) given its protocol \( tag \) (\( t \)).
The message handler then processes the message and decides on a \( \ForwardingPolicy \) (see the definition of this data type for further details).
A message handler \( \MessageHandler_t(\InMsg) \) contains the logic for handling incoming messages.
The following is the list of registered message handlers defined in the reference implementation, ordered by \( tag \):
-
AVfor Agreement Vote: -
MIfor Message of Interest: -
MSfor Message Digest Skip: -
NPfor Net Priority Response: -
NIfor Network ID Verification: -
PPfor Proposal Payload: -
SPfor State Proof Signature: -
TSfor Topic Message Response: -
TXfor Transaction: -
UEfor Unicast Catchup Request: -
VBfor Vote Bundle: -
Unrecognized Tag case (a special tag value to account for transmission errors or adversarial behavior):
In general, after a preliminary validation and a series of computations, the produced output is an outgoing message \( \OutMsg \).
Internally, \( \MessageHandler_t(\InMsg) \) routes data to the corresponding component of the node (e.g., “Agreement” for protocol messages, “Transaction Pool” for transactions, etc.).
Refer to the normative section of each node component to see how these messages are processed and their impact on the node’s overarching state.
⚙️ IMPLEMENTATION
In the reference implementation, a single entry point callback
Notify()is used to monitor an outgoing connection whenever a message is received. This function then sends the message metadata to the appropriate processing stage of thePerformanceMonitor.Usage in Relay Network.
Usage in P2P Network.
$$ \newcommand \WS {\mathrm{WS}} \newcommand \PtoP {\mathrm{P2P}} \newcommand \Peer {\mathrm{Peer}} $$
Addressing
The following section presents how the two Algorand network layers (\( \WS \) and \( \PtoP \)) resolve peer addressing, to univocally identify themselves amongst \( \Peer \)s, establish two-way connections, and effectively route messages regardless of the underlying architecture.
Websocket Addressing Scheme
The Relay Network \( \WS \) relies on an ip:port scheme to let a \( \Peer \)
present itself to and address other peers.
This schema is defined in the NetAddress parameter of the node configuration.
See details in the node configuration non-normative section.
The PublicAddress also can be set in the node configuration to let a \( \Peer \)
differentiate itself from other peers, and to be used in the identity challenges.
⚙️ IMPLEMENTATION
The reference implementation checks the scheme of network addresses against this regex:
^[-a-zA-Z0-9.]+:\\d+$
⚙️ IMPLEMENTATION
Websocket network address reference implementation.
P2P Addressing Scheme
The Peer-to-Peer Network \( \PtoP \) makes use of the underlying libp2p
library primitives for \( \Peer \) addressing, identification and connection.
This section relies on the
libp2pspecifications and developer documentation.
In this addressing scheme, each node participating in the \( \PtoP \) network holds a public and private Ed25519 key pair. The private key is kept secret, and the public key is shared to all participants.
The peer identity (PeerID) is a unique reference to a specific \( \Peer \)
within the \( \PtoP \) network, serving as a unique identifier for each \( \Peer \).
It is linked to the public key of the participant, as it is derived as hash
of said key, encoded in base58.
See
libp2pPeerID specification for details on how these are constructed and encoded.
The PeerID are visible and may be incorporated into multiaddresses
to route messages.
\( \Peer \) private keys are used to sign all messages and are kept as secrets by the node.
⚙️ IMPLEMENTATION
PeerIDare cast-able tostrtype and are used as plain strings in packages where importinglibp2ppackages may not be needed.
⚙️ IMPLEMENTATION
A
GetPrivKeyfunction manages loading and creation of private keys in the \( \PtoP \) network. It prioritizes, in this order:
- User supplied path to
privKey,- The default path to
privKey,- Generating a new
privKey.
⚙️ IMPLEMENTATION
If a new private key is generated, and should be persisted, its default path is
"peerIDPrivKey.key"(inside the root directory). The behavior of this lookup is governed by node configuration valuesP2PPersistPeerIDandP2PPrivateKeyLocation(see the Algorand Infrastructure non-normative section).
Multiaddress
A multiaddress is a convention for encoding multiple layers of addressing information into a single “future-proof” path structure. It allows overlay of protocols and interoperation of many peer addressing layers.
When exchanging addresses, peers send a multiaddress containing both their network
address and PeerID.
Regular NetAddress (as the scheme presented in the previous section)
may be easily converted into a libp2p formatted listen multiaddress.
Given a network address [a]:[b] (where [a] is the IP address and [b] is the
open port), the conversion scheme is /ip4/[a]/tcp/[b].
Refer to the
libp2pspecifications for further detail on this structure.
📎 EXAMPLE
Here are some examples of syntactically valid multiaddresses:
/ip4/127.0.0.1/tcp/8080, for a multiaddress composed only of a network address listening tolocalhoston the port8080.
/ip4/192.168.1.1/tcp/8180/p2p/Qmewz5ZHN1AAGTarRbMupNPbZRfg3p5jUGoJ3JYEatJVVk, for a multiaddress composed of a network address192.168.1.1:8180, joined together with thePeerIDequal toQmewz5ZHN1AAGTarRbMupNPbZRfg3p5jUGoJ3JYEatJVVk.
/ip4/192.255.2.8/tcp/8180/ws, for a multiaddress composed only of a network address192.255.2.8:8180indicating that the connection is through websocketsws.
Hybrid Network Addressing Scheme
The hybrid network maintains a single IdentityTracker entity, shared between both
network definitions (\( \WS \) and \( \PtoP \)).
Note that a PublicAddress must be set for hybrid nodes to operate properly.
For peer identity deduplication, a signing schema involving both the \( \PtoP \) private key and the \( \WS \) identity challenge is put in place. This is to correlate both \( \Peer \) definitions and prevent it from existing in both \( \Peer \) lists.
See the hybrid network identity challenge for further details on this process.
$$ \newcommand \Peer {\mathrm{Peer}} \newcommand \Identity {\mathrm{Identity}} \newcommand \WS {\mathrm{WS}} \newcommand \PtoP {\mathrm{P2P}} \newcommand \HYB {\mathrm{HYB}} \newcommand \IdT {\mathrm{IdentityTracker}} \newcommand \pk {\mathrm{pk}} \newcommand \sk {\mathrm{sk}} $$
Network Identity
To avoid duplicated connections between peers, the node keeps track of the \( \Identity \) of each connected \( \Peer \). The method is different for each network layer.
⚙️ IMPLEMENTATION
Network identity reference implementation. Additional parts are in each network’s implementation.
WebSocket Network Identity Challenge
This method is optional, and is enabled by setting the configuration value
PublicAddressto the node’s public endpoint address stored in other peers’ phonebooks (e.g.,r-aa.algorand-mainnet.network:4160).
The identity is verified in the \( \WS \) network with a 3-way handshake between two peers.
sequenceDiagram
participant Requester
participant Responder
Requester ->> Responder: Step 1: Identity Challenge
Responder ->> Requester: Step 2: Identity Challenge Response
Note over Requester: Requester verifies Responder’s identity
Requester ->> Responder: Step 3: Identity Verification
Note over Responder: Responder verifies Requester’s identity
Note over Requester, Responder: If identity is already in use by another peer,<br/>connection is closed as duplicate
The challenge consists of three steps:
-
Identity Challenge: when a request is made to start a gossip connection, an
identityChallengeSignedmessage is added to HTTP request headers, containing:- A 32-byte random challenge,
- The requester’s \( \Identity(\pk) \),
- The
PublicAddressof the intended recipient, - Signature on the above by the requester’s \( \sk \).
-
Identity Challenge Response: when responding to the gossip connection request, if the identity challenge is valid, an
identityChallengeResponseSignedmessage is added to the HTTP response headers, containing:- The original 32-byte random challenge from Message 1,
- A new “response” 32-byte random challenge,
- The responder’s \( \Identity(\pk) \),
- Signature on the above by the responder’s \( \sk \).
-
Identity Verification: if the
identityChallengeResponseis valid, the requester sends aNetIDVerificationTagmessage over websocket to verify it owns its \( \pk \), with:- Signature on the response challenge from Message 2, using the requester’s \( \sk \).
In steps 2 and 3, the \( \Peer \) that verified the identity tries to add the other one to its \( \IdT \), referencing the \( \Peer \) with their \( \Identity(\pk) \).
⚙️ IMPLEMENTATION
The \( \Identity \) challenge is derived from a random seed.
⚙️ IMPLEMENTATION
Identity challenge reference implementation in:
P2P Network Identity Challenge
When a \( \Peer \) requests to start a gossip connection in the \( \PtoP \) network,
instead of running an \( \Identity \) challenge, the peer’s raw \( \pk \) is
extracted from the libp2p’s PeerID as unique identifier for the \( \Peer \).
In this case, there is no \( \IdT \), as libp2p handles it internally.
⚙️ IMPLEMENTATION
\( \PtoP \) network identity reference implementation.
Hybrid Network Identity Challenge
In the \( \HYB \) network, the tracking of peers works with the \( \Identity \)
challenge as seen in the \( \WS \) Network,
but using the libp2p private key as the \( \Identity \) challenge signer.
In this case, there is an \( \IdT \) as the \( \Peer \) needs to keep track of the identities for the \( \WS \) network.
⚙️ IMPLEMENTATION
\( \HYB \) network identity reference implementation.
$$ \newcommand \WS {\mathrm{WS}} \newcommand \PtoP {\mathrm{P2P}} $$
Peer Management
The node’s peer discovery is based on either:
-
A
Phonebook, for the Relay Network \( \WS \), with initial addresses of relay nodes from the Algorand service record (SRV), -
A
PeerStore, for the Peer-to-Peer Network \( \PtoP \).
📎 EXAMPLE
For the Algorand MainNet
Phonebookbootstrapping, the DNS ismainnet.algorand.networkand the service record isalgobootstrap. The MainNet SRV can be queried with:dig _algobootstrap._tcp.mainnet.algorand.network SRV
Both tracks addressData, which contains:
-
retryAfter, the time to wait before retrying to connect to the address, -
recentConnectionTimes, the log of connection times used to observe the maximum connections to the address in a given time window, -
networkNames, lists the networks to which the given address belongs, -
role, is the role that this address serves (relayorarchival), -
persistent, set totruefor peers whose record should not be removed from the peer container.
The Phonebook tracks rate-limiting info and the addressData for each address.
The PeerStore serves as a unified interface that combines functionality from both
the standard peerstore and the CertifiedAddrBook components in libp2p.
The peerstore aggregates most essential peer management interfaces, including
peer addition and removal, metadata, keys, and metrics. However, it only exposes
the basic addrBook interface for address storage.
The CertifiedAddrBook, on the other hand, extends the capabilities of the addrBook
by supporting self-certified peer records. The peer itself signs these records and
includes a TTL (time-to-live), which defines how long the record is valid before
expiring. This means a peer’s information may exist both in an uncertified (vanilla)
and a certified form, with potentially different expiration semantics.
By merging these two, the PeerStore enables advanced peer address management,
including the ability to store, retrieve, update, and delete certified peer entries,
ensuring that signed peer records are handled with appropriate verification and
lifecycle rules.
⚙️ IMPLEMENTATION
Phonebookreference implementation,PeerStorereference implementation,addressDatareference implementation.
$$ \newcommand \Peer {\mathrm{Peer}} \newcommand \tag {\mathrm{tag}} $$
Network Definitions
Let us define a general GossipNode pseudo interface, which implements a set of
methods that should be available in any network layer to provide all required
functionalities.
-
Start()
Initializes the network by setting up all required data structures, peer connections, and listeners. -
Stop()
Shuts down the network, closing all connections and performing garbage collection. -
Address() -> string
Computes the address of the caller node, inside the specified network structure, according to the network layer addressing (see the addressing section). -
Broadcast(tag protocolTag, data []byte, wait bool, except Peer)
Builds a message and sends a packet of data and protocol \( \tag \) to all connected peers. Conceptually, it only excludes itself, although it could exclude any specific \( \Peer \) using theexceptparameter. Ifwaitis active, the call blocks until the send is complete (allowing for synchronous message sending). -
Relay(tag protocol.Tag, data []byte, wait bool, except Peer)
Similar toBroadcast, but semantically intended for message forwarding. Theexceptparameter explicitly identifies the message’s original sender, ensuring the data is not relayed back to its source. This distinction is useful when implementing relay logic, where the sender is extracted from the raw message metadata, if present. In special cases, when a node simulates the reception of its own message for internal processing, self-exclusion is bypassed, and the node includes itself in the relaying logic. This helps ensure protocol components receive their own outputs when needed.
⚙️ IMPLEMENTATION
Relay reference implementation
-
Disconnect(badnode Peer)
Forces disconnection from the givenbadnode\( \Peer \). -
RequestConnectOutgoing(replace bool)
Initiates outgoing connections to new \( \Peer \), with the option toreplacethe existing managed peer connections. -
OnNetworkAdvance()
Notifies the network layer that the Agreement protocol has made significant progress. While network health monitoring can detect message flow, it cannot ensure protocol-level advancement. A node may reside within a fast, densely connected but isolated partition, receiving messages quickly but missing those from outside. Therefore, this function is triggered whenever the Agreement protocol observes a certification-bundle or at least a proposal-value for a new block, allowing the node to confidently infer that the network is not partitioned.
⚙️ IMPLEMENTATION
Usage of
OnNetworkAdvacein reference implementation
GetGenesisID() -> string
Returns the network-specificgenesisID, a string indicating the kind of network this node is connected to (see the Ledger normative section for further details on this field).
$$ \newcommand \WS {\mathrm{WS}} \newcommand \WSNet {\mathcal{N}_\WS} \newcommand \Peer {\mathrm{Peer}} \newcommand \InMsg {\ast\texttt{M}} \newcommand \OutMsg {\texttt{M}\ast} \newcommand \tag {\mathrm{tag}} \newcommand \MessageHandler {\mathrm{MH}} \newcommand \MessageValidatorHandler {\mathrm{MV}_h} \newcommand \RelayNode {\mathcal{R}} \newcommand \PeerNode {\mathcal{P}} $$
Relay Network Definition
Let’s define \( \WSNet \) as an object that models a working Relay Network \( \WS \).
The Relay Network \( \WS \) uses a websocket mesh for message transmission.
The following diagram is an overview of \( \WSNet \) operations:
graph TD
subgraph Setup
A[Create WS Network] --> B[Create Phonebook]
B --> C[Create IdentityTracker]
end
subgraph Startup
C --> D[Initialize]
D --> E[Listener]
D --> F[Identity Scheme]
D --> G[Priority Handler]
G --> H[Serve & Listen]
F --> H
E --> H
end
subgraph Message Handler thread
H --Message--> I[Message Handler]
I --> L[Check connection to peers]
L --> H
end
A minimal \( \WSNet \) should have:
-
A
GenesisIDidentifying which network it is a part of (see here), -
A
Phonebookto bootstrap the peer discovery, -
A
PeerContainerdata structure to manage and iterate over peer connections (inbound and outbound), -
A
Broadcasterto send messages to the network, -
A
MessageHandlerstructure to route messages into the correct handlers, -
An
IdentityChallengeSchemeto execute the peer identity challenge, -
An
IdentityTracker, for connection deduplication (e.g., to avoid self-gossip), -
Similarly to the identity challenge, a
priorityChallengeSchemeandpriorityTracker, -
A flag indicating if the node wants to receive
TXtagged messages (transactions) or not, -
lastNetworkAdvance, the latest timestamp on which the Agreement protocol made notable progress.
Relay Network Topology
The following sketch represents a typical topology of a Relay Network \( \WSNet \), where:
- \( \RelayNode \) represents a relay node,
- \( \PeerNode \) represents a peer node,
- \( \PeerNode_r \) represents a peer node connected to \( \RelayNode \),
- A \( \PeerNode_r \) is connected on average to \( 4 \RelayNode \),
- A \( \PeerNode_r \) is not connected to other \( \PeerNode \),
- A \( \RelayNode \) is connected to multiple \( \RelayNode \).
Relay Network Peer Definition
Let’s define \( \Peer_{\WS} \) as a data structure that holds all fields necessary for a Relay Network \( \Peer \) to function and collect functioning statistics.
\( \Peer_{\WS} \) must contain:
-
lastPacketTime, an integer that represents the last timestamp at which a successful communication was established with the \( \Peer \) (either inbound or outbound), -
requestNonce, an unsigned 64-bit integer nonce, used to identify requests uniquely (so that identical requests do not get caught in deduplication), -
priorityWeight, an unsigned 64-bit integer that represents the priority of the \( \Peer \) inside thepeersheapstructure. -
Unsigned 64-bit integers to count messages of each type sent by this \( \Peer \) (that is, for each
protocolTag). -
A
readBuffer, used to read incoming messages. -
Incoming and Outgoing message
filters. -
Identity challenges metadata:
- \( \Peer \) public key,
- Challenge value
- A flag indicating whether it has already been verified (see here).
-
Some connection metadata:
- A flag indicating if it is inbound or outbound,
- Timestamp at which the connection was established,
- A map of messages allowed to be sent,
- An average delay time (calculated by the performance monitor).
Connection Management
Connections to peers are constantly monitored. Whenever a \( \Peer \) incurs in some behavior deemed harmful or adversarial (regardless of whether it is coordinated or accidental), the node may choose to disconnect from said \( \Peer \) after processing an incoming message \( \InMsg \) from said peer.
Disconnect reasons are modeled as a finite set of strings and would be part of the generated outbound message \( \OutMsg \) suggesting a disconnection.
The following is a list of disconnection reasons:
| DISCONNECTION REASON | DESCRIPTION |
|---|---|
disconnectBadData | The sender \( \Peer \) is serving wrongly constructed data |
disconnectReadError | Error reading the incoming message from the readBuffer |
disconnectWriteError | Error sending a message to the \( \Peer \) |
disconnectIdleConn | The \( \Peer \) has been idle for a certain amount of time |
disconnectSlowConn | The \( \Peer \) connection is slow |
disconnectLeastPerformingPeer | The \( \Peer \) is the worst performing peer in the peers container |
disconnectCliqueResolve | Node detected it is part of an isolated network partition |
disconnectRequestReceived | Disconnection requested from the \( \Peer \) itself |
disconnectStaleWrite | A write operation has not been successful |
disconnectDuplicateConnection | The \( \Peer \) connection is already present |
disconnectBadIdentityData | The \( \Peer \) address is misconstrued (or the identity challenge has failed) |
disconnectUnexpectedTopicResp | The \( \Peer \) has gossiped a non-normative topic |
disconnectReasonNone | No reason (included for completeness) |
Connection Performance Monitor
The connectionPerformanceMonitor struct monitors connections’ performance by tracking
various metrics such as the message arrival times, delays, and monitoring stages.
The following is a list performance monitor fields in go-algorand:
| FIELD | DESCRIPTION |
|---|---|
monitoredConnections | Maps connections being monitored. Messages from unmonitored connections are ignored |
monitoredMessageTags | Maps message \( \tag \) of interest. Typically, non-broadcast-type messages are monitored |
stage | The current performance monitoring stage |
peerLastMsgTime | Maps the timestamp of the last received message from each \( \Peer \) |
lastIncomingMsgTime | Timestamp of the last received message from any \( \Peer \) |
stageStartTime | Timestamp of the current stage start |
pendingMessagesBuckets | Array of message buckets for messages not received from all peers within pmMaxMessageWaitTime |
connectionDelay | Total delay sustained by each \( \Peer \) during monitoring stages and average delay afterward (in nanoseconds) |
firstMessageCount | Maps peers to their accumulated first message count |
msgCount | Total number of accumulated messages |
accumulationTime | Duration for message accumulation, randomized to prevent cross-node synchronization |
⚙️ IMPLEMENTATION
Connection performance monitor reference implementation.
The Peers Heap and Prioritization
The PeersHeap is a heap of \( \Peer \) entries.
This structure is used in the Relay Network and defines a weighted priority for connection to peers.
When a \( \Peer \) is added, it’s pushed on the PeersHeap with its weight, evicting
the previous one.
⚙️ IMPLEMENTATION
Peers heap reference implementation.
The network priority challenge is a two-way handshake that prioritizes connections resolving the challenge.
⚙️ IMPLEMENTATION
Network priority challenge reference implementation
Multiplexer
A multiplexer is employed to route messages to their respective handlers according to protocol \( \tag \).
A multiplexer contains both message handlers \( \MessageHandler \) and message validator handlers \( \MessageValidatorHandler \) (see network notation).
⚙️ IMPLEMENTATION
Message handlers and message validator handlers are implemented using atomic pointers in
go-algorand, to avoid data races when accessing them. They are loaded upon the network initialization and never modified again until garbage collection or irreversible node stoppage.
Through the use of atomic getters, the multiplexer may atomically retrieve a given message handler from the mappings given a protocol \( \tag \).
⚙️ IMPLEMENTATION
Multiplexer reference implementation.
$$ \newcommand \WS {\mathrm{WS}} \newcommand \PtoP {\mathrm{P2P}} \newcommand \PtoPNet {\mathcal{N}_P} \newcommand \Peer {\mathrm{Peer}} \newcommand \tag {\mathrm{tag}} \newcommand \PeerNode {\mathcal{P}} $$
P2P Network Definition
The Peer-to-Peer Network is currently in experimental mode!
Let’s define \( \PtoPNet \) as an object that models a working Peer-to-Peer Network \( \PtoP \).
A minimal \( \PtoPNet \) should have:
-
A
GenesisIDidentifying which network it is a part of (see here), -
A
PeerStorecontainer to keep peer data and expose relevant connection metadata (see here), -
A
Broadcasterto send messages to the network, -
A
topicTagsmapping of the form (protocolTag -> string), which represents which \( \tag \) to use withGossipSub, mapped to topic names1. -
A set of primitives taken or adapted from the Relay Network \( \WS \) and \( \Peer \), to support \( \WS \) messages:
- The generic
MessageHandlerto route \( \WS \) messages to the appropriate message handler, - \( \Peer \) connectivity monitoring utilities,
- A mapping of
PeerIDto \( \WS \) peers, and a mapping of \( \WS \) peers toPeerID(this is to get \( \mathcal{O}(1) \) lookup in both ways),
- The generic
-
A flag indicating if the node wants to receive
TXtagged messages (transactions) or not, -
A
capabilitiesDiscoverystructure abstracting all functionalities to advertise nd discover peers for specific capabilities (see section below).
A \( \PtoP \) network implements the GossipNode interface to manage peer-to-peer communication.
⚙️ IMPLEMENTATION
\( \PtoP \) network reference implementation.
Currently, transactions are distributed using the
GossipSubprotocol (/meshsub/1.1.0). All other messages are forwarded over a stream/algorand-ws/1.0.0that uses the same message serialization as the existing Relay Network implementation. These two streams are multiplexed over a single connection.
For more information on
GossipSub, refer to thelibp2pspecifications.
P2P Network Topology
The following sketch represents a typical topology of a Peer-to-Peer Network \( \PtoPNet \), where:
- \( \PeerNode \) represents a peer node,
- \( \PeerNode_p \) represents a peer node connected to \( \PeerNode \),
- A \( \PeerNode_p \) is connected on average to \( 4 \PeerNode \).
pubsub for Transaction Dissemination
The publishing/subscribing (pubsub) protocol is a system where peers congregate
around topics they are interested in.
Peers interested in a topic (identified by a simple string) are said to be subscribed to that topic.
Functionally, topics can be thought of as generators of sub-networks: peers subscribed to a topic are discoverable and addressable by other peers capable of serving data about that topic.
For more information on
pubsubprotocol, refer to thelibp2pspecifications.
The topic used to subscribe to transaction gossiping is algotx01.
The naming convention used for the topics is: the word algo, followed by a 2-byte
protocol \( \tag \) (TX in this case) followed by the 2-byte version identifier
(01 in this case).
⚙️ IMPLEMENTATION
Pubsub protocol reference implementation.
The makePubSub(.) function initializes the pubsub protocol with a list of options
that set parameters for peer scoring, topic filters, message validation, and subscription
handling.
-
Peer Scoring: sets peer score parameters, like the
FirstMessageDeliveriesWeight, which scores peers based on the promptness of message delivery, -
Subscription Filters: limits subscriptions to the
algotx01topic, -
Validation Queue Size: configures the validation queue2.
The pubsub protocol makes use of the following functions:
-
Publish(topic string, data []byte): Sends data to a specific topic’s subscribers. After verifying that the topic exists, the data is propagated across the network. -
ListPeersForTopic(topic string) -> []PeerID: Retrieves a list of peers currently subscribed to a given topic, making it accessible to other parts of the network layer. -
Subscribe(topic string): Subscribes to a topic, advertising the network about peer’s intention of sending and receiving data on the topic. -
getOrCreateTopic(topicName string): Attempts to fetch the topic if already mapped, otherwise it creates a local register of it and joins its subscribers.
pubsub Message Validation
The following is an enumeration of pubsub message validation results (where
ValidationResult is an alias for a signed integer):
-
ValidationAccept(0)indicates a valid message that should be accepted, delivered to the application, and forwarded to the network. -
ValidationReject(1)indicates an invalid message that should not be delivered to the application or forwarded to the network. Furthermore, the peer that forwarded the message should be penalized by peer-scoring routers. -
ValidationIgnore(2)andvalidationThrottled(-1)arelibp2pinternals that should not be exposed.
⚙️ IMPLEMENTATION
Pubsub protocol message validation
libp2pextrenal implementation.
Node Capabilities
With direct \( \PtoP \) network support, the notion of node capabilities acquires importance.
The capabilities of a \( \PtoP \) node are:
-
Archival: holds the entire history of the blockchain,
-
Catchpoint Storing: stores catch-point files and provides catch-point labels to node synchronizing with the network using a fast catch-up.
-
Gossip: act as a permissionless relay node, able to perform network-level validation of messages and efficiently route them to peers.
Each node advertises its capabilities and keeps track of peers in a distributed hash table.
⚙️ IMPLEMENTATION
\( \PtoP \) node capabilities reference implementation.
Distributed Hash Table (DHT)
Peer-to-Peer networks, such as IPFS, use distributed hash tables (DHT) to look up files in the network. A DHT is a distributed system for mapping keys to values, storing resource locations throughout the network.
IPFS DHT is based on the Kadmelia algorithm, as a fundamental component for the content routing system, and acts like a cross between a catalog and a navigation system. It maps what users want to the peers storing the matching content.
The Algorand node reference implementation (go-algorand) uses Kadmelia DHT,
to keep track of the peers’ capabilities.
⚙️ IMPLEMENTATION
Currently, TX tagged messages go into the algotx01 topic name.
The current implementation allows up to \( 256 \) messages awaiting validation.
$$ \newcommand \WS {\mathrm{WS}} \newcommand \PtoP {\mathrm{P2P}} \newcommand \HYB {\mathrm{HYB}} \newcommand \WSNet {\mathcal{N}_\WS} \newcommand \PtoPNet {\mathcal{N}_P} \newcommand \HybNet {\mathcal{N}_H} \newcommand \Peer {\mathrm{Peer}} \newcommand \RelayNode {\mathcal{R}} \newcommand \PeerNode {\mathcal{P}} \newcommand \HybridNode {\mathcal{H}} $$
Hybrid Network Definition
The Hybrid Network is currently in experimental mode, as it includes Peer-to-Peer Network functionalities.
Let’s define \( \HybNet \) as an object that models a working Hybrid Network \( \HYB \).
The Hybrid Network \( \HybNet = \WSNet \cup \PtoPNet \) is the network layer that unifies the Relay Network \( \WS \) and the Peer-to-Peer Network \( \PtoP \).
Nodes in the \( \HybNet \) act as gateways, running simultaneously \( \WS \) and \( \PtoP \) for the interoperability of both network layers.
Conceptually, all functions and implementations of \( \HYB \) act as a switch statement to select the appropriate network layer according to the parameters of the sender \( \Peer \) of the incoming message.
A Hybrid Network maintains both the Relay Network definition and the Peer-to-Peer Network definition.
See also the \( \HYB \) identity challenge for details on how peer deduplication works in both subnetworks.
Hybrid Network Topology
The following sketch represents a typical topology of a Hybrid Network \( \HybNet \), where:
- \( \RelayNode \) represents a relay node,
- \( \PeerNode \) represents a peer node,
- \( \HybridNode \) represents a hybrid node,
- \( \PeerNode_r \) represents a peer node connected to \( \RelayNode \),
- \( \PeerNode_p \) represents a peer node connected to \( \PeerNode \),
- \( \HybridNode \) represents a hybrid node, connected both to \( \RelayNode \) and \( \PeerNode \),
- A \( \PeerNode_r \) is connected on average to \( 4 \RelayNode \),
- A \( \PeerNode_r \) is not connected to other \( \PeerNode \),
- A \( \PeerNode_p \) is connected on average to \( 4 \PeerNode \),
- A \( \HybridNode \) is connected on average to \( 4 \RelayNode \),
- A \( \HybridNode \) is connected on average to \( 4 \PeerNode \),
- A \( \RelayNode \) is connected to multiple \( \RelayNode \).
Appendix A - External Network Libraries
gorilla
Algorand uses a fork of gorilla websocket
(v1.4.2), to implement the Relay Network.
Relevant changes:
-
Header size and read limits,
connection.ReadMessage()is marked as unsafe to avoid explosively compressed messages.
-
Server is
TLSServer, -
Message compression,
-
Mutexes for multithreading,
-
Connection closing without flushing, and a thread flusher,
-
Tests and benchmark over additions.
Further details about the fork can be found here.
libp2p
Algorand uses go-libp2p library (building
from the latest release), to implement the Peer-to-Peer network.
See
libp2pspecifications for detailed documentation on this external package.
Appendix B - Packet Examples
The following is a collection of network packet examples with different protocol tags (see definition).
Packets are decoded from msgpakc to JSON, with:
-
Values in
hexformat, -
Algorand addresses decoded according to specification,
-
Transaction notes decoded in
UTF-8.
Original
msgpacknetwork packets can be here. For further details about Algorand canonical encoding, refer to themsgpacknormative section.
Agreement Vote (AV)
{
"cred": {
"pf": "451dbdd6b87db16623551a846964d30e8738dcfb9a99b8e670d834706c070a79d40f7904491c0629ee711904c49c9fb8639f023a6b88ac632ca3cb69e6c16fab8be086efb80ebe279f96473c88209b0a"
},
"r": {
"prop": {
"dig": "5dfa5bf07aee99972b086eeefe65842be1201952d51f3a0f5fdf42b5ebc4d7cc",
"encdig": "3a565c4c6c05d5d3f91f8b5f16685db99c3aeb63c032cd354fac49bf7821d8d9",
"oprop": "985ba4fe9b4f47c47e3a22bf7404ad990d559dae882f0f6029c75156e3a8429d"
},
"rnd": 49767203,
"snd": "3YIIMZRD4UVBXWQKCROQW5KRWGS6KPK6F6C2B6GYGANPMBLGJ5HYOQVP4E",
"step": 1
},
"sig": {
"p": "65e9c36a4e92894e452312d3d9488ea53cd93c7de9dac9f0f21b909937c35283",
"p1s": "94fd68785ee7d746ff9eee67058677f05360a87d31e5ac89190e2077ab3b97ff897c2ecef3f2c58c3b00b4a95816211c1fddf14475f59786b7e72a26b615c90e",
"p2": "36310336389b4e74083dbf9342abdc6cf00d79236951edf89b12b225d41aa3ea",
"p2s": "8f5454e393902dd8b4539aaba992d2387e4b6d56262da78b124f61f2777d6a2e32d877427d3e53d56a68ec5e4986164fda269c24e0884b71ea622f906de8c303",
"ps": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"s": "d8b486afc8b74aa71e1c685fc4084a94e86526a8791c6002e5d87c344fd12f0648de951e1be4b6ce400faa07e65f2496570d80965d777ae31f3d4c12a77ebb0c"
}
}
Message of Interest (MI)
To be added.
Message Digest Skip (MS)
To be added.
Network Priority Response (NP)
To be added.
Network ID Verification (NI)
To be added.
Proposal Payload (PP)
To be added.
State Proof (SP)
{
"a": "EWFQX674JBKUQZWK3P23TKIJ63DFQVUZVZVK5ZNNBUU2VEI5PATZLYDWXM",
"r": 49767168,
"s": {
"idx": 6356,
"prf": {
"hsh": {
"t": 1
},
"pth": [
"c1a359d33a5e28720f7117a296ceb19d5c5828f98c61743d43c5cc7f9b9d8763195029b14f80ee4bacde8c7d082a4b0c8b26605dfc8abfa0613d666c599d7f02",
"4949bab48ef76184212471a517ac04164c120901148caa149f2fcc9515165444cd1f40f717161b33b38a6d699c9ccfb7b84c3267172f7cc2a076fa9872e7192b",
"4a1eb5c73a83276d55d4873b4755da72b9bbea5bc9f2efa85a8fa1ce63b3ce0dae0ea3bd0671f0b5a8b92985079a8aa2ae0589b4747735caaccff33026537159",
"f67899f7909d48cc11957668bd631818652903b450aaa4662bcf4ba204144a0309b2f25a260a2d3e245b7f5dcd4d8bcfe5b1678cfe91590a2ee292905257b228",
"3b6e89b10fd10d7a0f5e60a2318b59f5267b6198aa64d0cb3e45de9b346dc52c120a190e948abeb1e3e9bf0bd0bd2bfcb70843bc0cebbf212e59d4954d76e08b",
"f0d76192c75af3170d069f316920ab7827cc56bb22b7c7164995e7e0687d18e241de81cd9eaaad568525ea259cdf70b325848b76332c71294356df069246fb1c",
"6f841e0f9fef67a3ad76ab88ab730d56dc812dd7af5bfba3b84258bb6f86e43b7306b18aab0a013f124e371faf9f1b69e561d83f9dfca7dfa060d2c060b10244",
"933f0a7805e0978df58cfaa6a232718b1059e45352070b90891e41321dd06247986734a4b18980ebb1f42f3dd87066f51971dfbc813efb02b54dd13a54252664",
"04af30780e93b986f4567a5418c6f1da52d44748be5ae59dacafb457dda68de127660892b1643d0e44552af5a681170fb62060104aeb18bc64a56b93eacbb033",
"570071ee3e256ede272970ac256de00867adc2d13b2ba4aacb212f979c3cac9b597f4f0982503eba1e3bf8c30d34c9196517d2c825e1bbc6b5d293fabcb42847",
"a9a34a2975f81c08bd1cd5ccd4b49d954620dca32acbcb6230c40af20a30f823a9a94c0c4923ce20b797a6b9db7db5f180dfb2d6f995fecc3db0a3f7f8a2dccb",
"686b6cc5609c0dbb415e563472e1e43263165c50fc63bd3a936771bf7b0ae738b28e1646716e015d29bcb52f01bc1a538c57999ac6b7a5e92af8ebd1d263cd2a",
"dbd5eab6f6e60d3eff0db77764c42f3860810b674028bb78b9b07b9bc7026f91e596abf40864c2859ea4d13101812d691905be0b9d33bce7e51dc613400b76c7",
"457aaebecd5e5ac026a7beea387460f3608402ec1cdd7256ebfa170beb41c6b009d43dc9eb0f9172174f4547b360167a9978b4ca7acb409f47ec469622f10f7b"
],
"td": 14
},
"sig": "ba008251f92f1a85224a09c7eb025cf21dfce0c13dcc841a891382613f994cbc1ba09a70e74e29e82c154e0ca6bde69d119c96b987e157f4c7d219aadef621432d57cda73f4e1f591fa8b0dd5252792d7bbe8f374f531bbfdce66cba58fb2ff32dca80b37c22ebb4cd57f58ebbad065b1aac69ca7aa84693c7214334a8bb48b46637bc8e8cee61c350a24622cb0c48718c017a4af22c5f28f443f4489344b363205b4af7e3e7f5912dd842e58b7a2a3989592f28cdf2aba8466476eb255d545fb8bf69b5350dd2271bc5ed74429d0e7f79765120b1f4448bcaec71cb646be9175198be45712a74e4ed720667d2c4df3e79e8c431ebba3bfce8a08118674af66538502927cab0c0c78fce5f24b8c91b243ca6270e9a66e6c1a7b858fbced932982af31e7299fce8c0e392b505013349aa4884421067b214803baccbd55e395598e14f6f59d09beaeebb1f8c54cc367dc618d7e630343e7cd39f85c3b46c51325fdd1c6863bafa12b11245d97aaf5286d8511d39ab0f6c39783ce91d56d5546b9df94f64f9858b20e2d4e5beabdba5c634c42168f7d0cba1b69a7b7d8f5b5bf8e234684f3fb38c642b5a9c1279aebb723ef195c6087399555e2f9b21a411cde9f5265f1f631bd7b951bbd688555bdd1ddfb767bf8c99c7907c0a49ffe7a52f842a089ce30dfaf13389bc6a8f76d7fbb54f699a535910aac2b6e220b13c68451a8825faa4054a8336d1283218c4b10376aee9780f1478d1b3c7bedff39f32f96dfa2b20a0ead2e5cf219221d7d507e176a9a44a32259921cd9eab41d842275a963d184df58f62b6853d379850a7c5a4ef6145f8f55de49b06ef9b4636cdb282c51f678e413ae36058f30dc7bc3bd8f27fdc60885f2fc28aed3306894ab3a939a61d09452b149c1f25a05f2ddb8b3979b6741a17bb06626d4f9cdc90f18d30fca11b7f5503164fc4be5338f1b0c8263e95651d44861133667b5b2bec7ecd0532b2af9945b8f26e895ead8a9a7927da15eb8175e1267ebd8e08565afb33c7346953dc2666dddea297c98ba14f83e1d12a8953423df9cb2e3455b2cc6672988864a24ded1b37847e1684b195904198edc5fe39d0e8c01dfc532b9b95a1928549118cff0d039b82fc41a6a6391be8731333e9406a1b69834b7893ba5c34a7d3d4d2499805574264a2b8fd15963b8f62b04db234fb73cd7ed1248996893eb7408e98c99313ad6257a41b1eb6eae0b1f72d1e63254b7637eba3e6e8f2968cf6873af9c227b9597c61373c93e7561453b9e48709cd565508b1de4663870ff5f183748de217e57c10c9e5cf6b3e4a995de54951ddb418e54f2d34bdb936c8523036121b8e6e24499d2ca1d6928c3c415a775b098a670b482f2d94fa00429163008bad1f092ca13123385b89c43cb15dca1490b70d96cf2e53982bfe2f4dd016884310caa7dd6a39eef745cf638967377c8e2c9239b43e6c2ee6bf65d6b6fa99da539b6873d2e66bd3a168f94e9e20bd584a66b712e5dd6c88568ca9d3a9971ed7d5a0db67885c3f1352dbff6194fde3c2c69fea215a82c48cb5f277a6ca214b448d2e4efb31950ea0a8d1b3d7dad4bded5caf525c648082b07aba8fcb38dfb8ea597372a8291f3d858a4c1d6149be611289de54a858945e575b29eaa6b06c18907fb1cf4302e16f29feef2ab3216630d82215634811290665f1b575a736ee4cd13ec0f6bb339ddb8360597cb6f955d1",
"vkey": {
"k": "0a06b514f1739ad105d97dfbd6e978b3b688c2d41bb43fdd7a2214c735711467288d2d35937064042a838b27094ab33057349239f575e16bf87dfac059e61c23815c112bfd6a7086b95586b82a6344cc075b6699ae31164cb006ed0949be2b17b0a6b587dce20b64d547a904ec071d8dae2b91dc94d0d0ed2bc463d4396d2f3d06930722df0c7df9357952daa5b3206d5af6cbc89ddaa0a733cbbc6db880c9e7d5dd99cd20c470ac3f285cb036e9e95917e8fb816f58258b686b3986ef0e1ccc2dfb0e2323537a4725122aae68ce99bb3252071492e1c77de16c2b31dd75ca922ab1465d94d26f55a41a9b8177528755eeb928e066898d6406e926205880290e69870c8d40a1a1db15904a84e45fa6e96f1a069157140fc16e940c7f2911819f94d006c780ef2392f99828aa04de5326b3d7c691dd311965d8bb575540797292bfca408cb1da4887863b07e1be573c4b513e8d876042df7764848c90708f8c0419a578cf9c4361614ef9a82c4eac46f5aacdeedf01fd2f45eb155f5724272aaa5804b9fc556605c6801440e410cf616e9709aeb66dedba21e8d745ea6fe35edd6f0a12816c4dad811426dd4ea80d6873ac03777f65716270e06f2af500f25c557e370112856f08eaa3c43a5a2d9c0ea0180f30f3aca807a4fe61c6857b20a22c117622ca6302c368ad59620d1eb7690ba84671d0d930acdd256a0566e65dad2a7109c82777a360aad94d2736576e7d8d86e66127d7be69cb71d107efbf2da1fbb4e6df63591f7b1811901fd86a3a61a9ca34417941c515201ced03c8b06a7119809994118d8fb12057835101747e0dd9a905e11c1a012ee5ff403a18a804a9aa9acd0604bae2ec98c24c7d0e129f588c4b41a4ab8d9c0d332e49c5fc82e8a0b9ab400448e796201712fb1f6b0f66e7c4d86bbf09982f3290cbfb93827ae29c0606b286d4f6a653f7811e3e7bea4a616fc5994284671f4586c1ca8edd792fe68418ea5016324ee2e0c996ae0c0699f24611395521c6507b129c8b5834ca82e03e139a8fa3be4bc0a4704ff9bad8743e6651231c6005096e65a077537e1a59d22b7fa68272b9eb361d25b6152dc917e8909d8664612212e99349d837379ed798b7d666d257d0ac09c94b07f297465ced20833da16321b97cab324bde922ada8999dd430ca9b00917d542b014b6173fc5c061752bf4ba8872077c9957726f58a1e9cec2a28e1b2166a640eddaf793b48a5426e3851592c7e815e831be818f5a6fd53f1b49fbb1510ccaae6846b64f0ee800058c28b388279e7c98ab73e4173d88d170f8475f84a09c598cc2434da8622933d1734b39e9173def8463a281eb17e21e9df6d7ea981bf9056c1051c3d9a09c8c2ebb5b46899d85c543d2932a2711d52a21c654925c4758469b9f60b07b2b2f587a8448d647bc02cbb31405f05624f76c9e7349f1822591823f13712bcc6d0002c2a78ce81ea60ce081c1fe2f24b38011825f7454e719d340bb21315382ddcb062fe1a45bf4ca0d1fda74a496ea98468cb89ba0d695305ca724b6680660ea56bb9f609b426195112c42bc16b43f29c89cac312932c310237d91242e24f1ef4e0ecdbd2200ee8a52c944a8addb596ace96878922267846a743b3a5a260ced17149164d37a98ab9956bc839d0661767eb1361dc9e13d9de4a79b6a5e658d71a1b1cd925dd43788eb53f61074f6c739ccd6bb5a82f578c72e41327f55ad5ca6800cf932a453f8b450dafb9f92a529e26554534bb219a4b4bbf2586d442aeecab845eeb5189b75919a9326bc91777706d20b1e1f97106771b04e274b145c584678566a9246f751b29989cf6f6664e13cb1ce9950a79a32d5f92c483ec84825aaac3d5ee9c3bb41d6c86cccaea6bfcd6f87c1248a0ea5a17614dd7792c08a56924c89a18515597a5d81ac2b877d1e37c3fd61d04b10fe7421c4ff9b8d5f1478c6988f712ec8a5ea8f0194698a2bc4ba58de68d5946b105d1895dbfee270503e0207c5a85b5e8af7c0d1cab0b334996b448fea8421bdd662e49963caaec3d3e7397406d1606844c7909e77ba970c7780190e998e6e3c0bc2c2f8ef91db0a985f2b0460fd08de024a751597339451e982e2095ff6506ab49cec1ea804fa80a1039a8cb637f6d61d13d87274c5d5a13d8ad6879dab88345c41d2164241afe225a936cdee90aed5b2db09593fa633bb13442903a47ec214db183634c639d3e7703146dcea60e49899c072906c5cb46adac6b8db98404c169160e99eef277e8f17490697075ce3396bd75d6d88c6abf44c72be5aed76fe9afe90d238c5b1c6cb6f5ef4041551b04ec4e7b026afa82a10a217c368c88e6db3335a9fda9ee666a2afd8a4b838f23b5f8d5b871c3c800a8be2205156c391291407c4d6d090e4c7a84eb4fad74b4720baac951dc15e4d9138f82ba7649ded13b7921c1d37d03b4177c4587145590102edc236484590c15e6bb1f849f8ba0b554bd8e4a65067247facdde82d6d854a5e279244102d0ffa1d100e6918bc84594"
}
}
}
Topic Message Response (TS)
To be added.
Transaction (TX)
Application Call
{
"sig": "82c3a5ec0b0cb9e2cddcbad5b5260af81ea407f6a86d1c03b4719b8bb3708ae346eafabd3e7944eaca3173e4954221219052a81055461c65140bcdf29c42ee00",
"txn": {
"apaa": [
"c80fde67",
"00000000000000000700000000000005c600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
],
"apid": 1776094572,
"fee": 1000,
"fv": 49767204,
"gen": "mainnet-v1.0",
"gh": "c061c4d8fc1dbdded2d7604be4568e3f6d041987ac37bde4b620b5ab39248adf",
"grp": "f3ef5433d168a4d8db92d20e0f9511420e21c752f096647e9a07d1ec2cce82e9",
"lv": 49768204,
"note": "Reach 0.1.13",
"snd": "SDA6DSYRY6P3JIVRA74YD37EXIBMM5FAYCIGXRSWARON6YMWHJSNU3TLDY",
"type": "appl"
}
}
Asset Transfer
{
"sig": "428e835c34b96b2afe42a4e9418a6f5360345d4c52152afd7ccbae016866efb1ad212fe1216a011828133b99e211826e8c21c790c53a0d30bb82c10811728b03",
"txn": {
"aamt": 3000,
"arcv": "GNXE2IUBB2HD5FG44ZCW4YEEHYK6G32WCHCR5WVC44A4CJWAKS2HV3Z6BY",
"fee": 1000,
"fv": 49767201,
"gen": "mainnet-v1.0",
"gh": "c061c4d8fc1dbdded2d7604be4568e3f6d041987ac37bde4b620b5ab39248adf",
"lv": 49768201,
"note": "303536393232353030313734363739333832362d312d3136",
"snd": "TDBXZ37EB36E3GSUN4QM62W3LBRSFCC66YFJZ4OD66UB4XKIOFKM4LUEBQ",
"type": "axfer",
"xaid": 849191641
}
}
Asset Opt-In
{
"sig": "92a66ab561eae804151b4ef2da3c79cf16857d31dc637ae9343911866b6a7eb780c2ef7db23d30c5d9e3ab5e78434e172627ec40cee6b0931ee16ff95e2d3103",
"txn": {
"arcv": "DSOPUQC7P5WO3C32HKZONPW4MMBEQ6FGAN456PNG4A4HTRE322ZMMIK6S4",
"fee": 1000,
"fv": 49767204,
"gen": "mainnet-v1.0",
"gh": "c061c4d8fc1dbdded2d7604be4568e3f6d041987ac37bde4b620b5ab39248adf",
"lv": 49768204,
"note": "Connectivity Check",
"snd": "T3HDEL5I6RC4MMK4DLYVDIM37WBXGLTUQPDI5CS2SGDGSZEJW3VFJK6RHE",
"type": "axfer",
"xaid": 924268058
}
}
Unicast Catch-up Request (UE)
To be added.
Vote Bundle (VB)
To be added.
Contribution Guidelines
The source of the Algorand Specification is released on the official GitHub Algorand Foundation repository.
If you would like to contribute, please consider submitting an issue or opening a pull request.
By clicking on the “Suggest an edit” icon in the top-right corner, while reading this book, you will be redirected to the relevant source code file to be referenced in an issue or edited in a pull request.
Source Code
The Algorand Specifications book is built with mdBook.
The source code is structured as follows:
.github/ -> GitHub actions and CI/CD workflows
.archive/ -> Legacy specification archive
src/ -> mdBook source code
└── .include/ -> Code snippets, templates, TeX-macros, and examples
└── .excalidraw/ -> Excalidraw diagrams source code
└── images/ -> SVG files
└── Part_A/ -> Part A files
└── Part_B/ -> Part B files
└── Part.../ -> ...
└── SUMMARY.md, ... -> mdBook SUMMARY.md, cover, prefix/suffix-chapters, etc.
Markdown
The book is written in CommonMark.
The CI pipeline enforces Markdown linting, formatting, and style checking with
markdownlint.
Numbered Lists
Numbered lists MUST be defined with 1-only style.
📎 EXAMPLE
1. First item 1. Second item 1. Third itemResult:
- First item
- Second item
- Third item
Tables
Table rows MUST use the same column widths.
📎 EXAMPLE
✅ Correct table format
| Month | Savings | |----------|---------| | January | €250 | | February | €80 | | March | €420 |❌ Wrong table format
| Month | Savings | |----------|---------| | January | €250 | | February | €80 | | March | €420 |Result:
Month Savings January €250 February €80 March €420
Consider aligning text in the columns to the left, right, or center by adding a
colon : to the left, right, or on both sides of the dashes --- within the header
row.
📎 EXAMPLE
| Name | Quantity | Size | |:-------|:--------:|-----:| | Item A | 1 | S | | Item B | 5 | M | | Item C | 10 | XL |Result:
Name Quantity Size Item A 1 S Item B 5 M Item C 10 XL
MathJax
Mathematical formulas are defined with MathJax.
mdBook MathJax documentation.
Inline Equations
Inline equations MUST include extra spaces in the MathJax delimiters.
📎 EXAMPLE
Equation: \( \int x dx = \frac{x^2}{2} + C \)
✅ Correct inline delimiter
\\( \int x dx = \frac{x^2}{2} + C \\)❌ Wrong inline delimiter
\\(\int x dx = \frac{x^2}{2} + C\\)
Block Equations
Block equations MUST use the $$ delimiter (instead of \\[ ... \\]).
📎 EXAMPLE
Equation:
$$ \mu = \frac{1}{N} \sum_{i=0} x_i $$
✅ Correct block delimiter
$$ \mu = \frac{1}{N} \sum_{i=0} x_i $$❌ Wrong inline delimiter
\\[ \mu = \frac{1}{N} \sum_{i=0} x_i \\]
TeX-Macros
TeX-macros are defined in the ./src/.include/tex-macros.md file using the mdBook
include feature.
TeX-macros are divided into functional blocks (e.g., pseudocode, operators, constants, etc.).
TeX-macros MUST be imported at the top of the consumer files using the mdBook.
TeX macros can be imported entirely or partially (e.g., just a functional block).
📎 EXAMPLE
Import all TeX-macros:
{{#include ./.include/tex-macros.md:all}}Import just a block of TeX-macros (e.g., pseudocode commands):
{{#include ./.include/tex-macros.md:pseudocode}}
Block Styles
Block styles are defined in the ./src/.include/styles.md file using the mdBook
include feature.
Block styles (e.g., examples, implementation notes, etc.) are “styled quote” blocks included in the book.
📎 EXAMPLE
This example block has been included with the following syntax:
{{#include ./.include/styles.md:example}} > This example block has been included with the following syntax:
GitHub Links
Links to the go-algorand reference implementation or other repositories MUST
be permalinks.
Diagrams
Structured Diagrams
Structured diagrams (e.g., flow charts, sequence diagrams, etc.) are defined with Mermaid “text-to-diagram” tool.
Unstructured Diagrams
Unstructured diagrams and images are drawn with Excalidraw.
Excalidraw images MUST be exported in .svg format and saved in the ./src/images/
folder.
Excalidraw images source code MUST be committed in the ./src/.excalidraw/
folder.
Docker
The Algorand Specifications repository makes use of a Dockerfile.
To run the specs book as a container:
docker compose up
This will serve the specs book on localhost:3000.