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-algorand
reference 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 \( \text{\LaTeX} \)-styled books can download the full book from the latest release artifacts.
Algorand Byzantine Fault Tolerance Protocol Specification
The Algorand Byzantine Fault Tolerance protocol (ABFT) is an interactive protocol which produces a sequence of common information between a set of participants.
$$ \newcommand \BBA {\mathrm{BinaryBA⋆}} $$
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 \( \BBA \), 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 \( \BBA \) over an empty block for a round. ↩
$$ \newcommand \FilterTimeout {\mathrm{FilterTimeout}} \newcommand \DeadlineTimeout {\mathrm{DeadlineTimeout}} $$
Parameters
The Algorand protocol is parameterized by the following constants:
Time Constants
These values represent durations of time.
SYMBOL | VALUE (seconds) | DESCRIPTION |
---|---|---|
\( \lambda \) | \( 2.00 \) | Time for small message (e.g., a vote) propagation in ideal network conditions |
\( \lambda_{0min} \) | \( 0.25 \) | Minimum amount of time for small message propagation in good network conditions, for \( p = 0 \) |
\( \lambda_{0max} \) | \( 1.50 \) | Maximum amount of time for small message propagation in good network conditions, for \( p = 0 \) |
\( \lambda_f \) | \( 300.00 \) | Frequency at which the protocol fast recovery steps are repeated |
\( \Lambda \) | \( 17.00 \) | Time for big message (e.g., a block) propagation in ideal network conditions |
\( \Lambda_0 \) | \( 4.00 \) | Time for big message propagation in good network conditions, for \(p = 0\) |
Round Constants
These are positive integers that represent an amount of protocol rounds.
SYMBOL | VALUE (rounds) | DESCRIPTION |
---|---|---|
\( \delta_s \) | \( 2 \) | The “seed lookback” |
\( \delta_r \) | \( 80 \) | 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 \) the \( \FilterTimeout(p) \) is calculated dynamically based on the lower 95th percentile of the observed lowest credentials per round arrival time:
- \( 2\lambda_{0min} \leq \FilterTimeout(p) \leq 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) = 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 \)
⚙️ IMPLEMENTATION
\( \DeadlineTimeout \) reference implementation.
$$ \newcommand \pk {\mathrm{pk}} \newcommand \sk {\mathrm{sk}} \newcommand \fv {\text{first}} \newcommand \lv {\text{last}} \newcommand \Sign {\mathrm{Sign}} \newcommand \Verify {\mathrm{Verify}} \newcommand \Rand {\mathrm{Rand}} $$
Identity, Authorization, and Authentication
A player is uniquely identified by a 256-bit string \( I \) called an address.
Each player owns exactly one participation keypair. A participation keypair consists of a public key \( \pk \) and a secret key \( \sk \).
A keypair is defined in the specification of participation keys in Algorand. Each participation keypair is valid for a range of protocol rounds \( [r_\fv, r_\lv] \).
Let \( m \), \( m’ \) be arbitrary sequences of bits.
Let \( B_k \), \( \bar{B} \) be 64-bit integers representing balances in μALGO with rewards applied.
Let \( \tau \), \( \bar{\tau} \) be 32-bit integers, and \( Q \) be a 256-bit string.
Let \( (\pk_k, \sk_k) \) be some valid keypair.
A secret key supports a signing procedure
$$ y := \Sign(m, m’, sk_k, B_k, \bar{B}, Q, \tau, \bar{\tau}) $$
Where \( y \) is opaque and cryptographically resistant to tampering, where defined.
Signing is not defined on many inputs: for any given input, signing may fail to produce an output.
The following functions are defined on \( y \):
-
Verifying: \( \Verify(y, m, m’, \pk_k, B_k, \bar{B}, Q, \tau, \bar{\tau}) = w \) where \( w \) is a 64-bit integer called the weight of \( y \). \( w \neq 0 \) if and only if \( y \) was produced by signing by \( \sk_k \) (up to cryptographic security). \( w \) is uniquely determined given fixed values of \( m’, \pk_k, B_k, \bar{B}, Q, \tau, \bar{\tau} \).
-
Comparing: Fixing the inputs \( m’, \bar{B}, Q, \tau, \bar{\tau} \) to a signing operation, there exists a total ordering on the outputs \( y \). In other words, if \( f(\sk, B) = \Sign(m, m’, \sk, B, \bar{B}, Q, \tau, \bar{\tau}) = y \), and \( S = \{(\sk_0, B_0), (\sk_1, B_1), \ldots, (\sk_n, B_n)\} \), then \( \{f(x) | x \in S\} \) is a totally ordered set. We write that \( y_1 < y_2 \) if \( y_1 \) comes before \( y_2 \) in this ordering.
-
Generating Randomness: Let \( y \) be a valid output of a signing operation with \( \sk_k \). Then \( R = \Rand(y, \pk_k) \) is defined to be a pseudorandom 256-bit integer (up to cryptographic security). \( R \) is uniquely determined given fixed values of \( m’, \pk_k, B_k, \bar{B}, Q, \tau, \bar{\tau} \).
The signing procedure is allowed to produce a nondeterministic output, but the functions above must be well-defined with respect to a given input to the signing procedure (e.g., a procedure that implements \( \Verify(\Sign(\ldots)) \) always returns the same value).
$$ \newcommand \pk {\mathrm{pk}} \newcommand \fv {\text{first}} \newcommand \lv {\text{last}} \newcommand \Digest {\mathrm{Digest}} \newcommand \Encoding {\mathrm{Encoding}} \newcommand \Seed {\mathrm{Seed}} \newcommand \Record {\mathrm{Record}} \newcommand \DigestLookup {\mathrm{DigestLookup}} \newcommand \Stake {\mathrm{Stake}} \newcommand \Entry {\mathrm{Entry}} \newcommand \ValidEntry {\mathrm{ValidEntry}} \newcommand \Kcal {\mathcal{K}} $$
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 \( \Kcal_{r_b,r_v} \) to represent all players with participation keys at \( r_b \) that are eligible to vote at \( r_v \). Let \( \Kcal_{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 \Kcal_{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 non-normative specification.
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.
$$ \newcommand \Propose {\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}} \newcommand \CommitteeSize {\mathrm{CommitteeSize}} \newcommand \CommitteeThreshold {\mathrm{CommitteeThreshold}} \newcommand \Proposal {\mathrm{Proposal}} \newcommand \Digest {\mathrm{Digest}} \newcommand \Encoding {\mathrm{Encoding}} \newcommand \Hash {\mathrm{Hash}} $$
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) = \left\{ \begin{array}{rl} 20 & : s = \Propose \\ 2990 & : s = \Soft \\ 1500 & : s = \Cert \\ 500 & : s = \Late \\ 2400 & : s = \Redo \\ 6000 & : s = \Down \\ 5000 & : \text{otherwise} \end{array} \right. $$
- \( \CommitteeThreshold(s) \) is a 64-bit integer defined as follows:
$$ \CommitteeThreshold(s) = \left\{ \begin{array}{rl} 0 & : s = \Propose \\ 2267 & : s = \Soft \\ 1112 & : s = \Cert \\ 320 & : s = \Late \\ 1768 & : s = \Redo \\ 4560 & : s = \Down \\ 3838 & : \text{otherwise} \end{array} \right. $$
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 \).
$$ \newcommand \pk {\mathrm{pk}} \newcommand \sk {\mathrm{sk}} \newcommand \fv {\text{first}} \newcommand \lv {\text{last}} \newcommand \Vote {\mathrm{Vote}} \newcommand \Equivocation {\mathrm{Equivocation}} \newcommand \Propose {\mathit{propose}} \newcommand \Soft {\mathit{soft}} \newcommand \Cert {\mathit{cert}} \newcommand \Late {\mathit{late}} \newcommand \Redo {\mathit{redo}} \newcommand \Down {\mathit{down}} \newcommand \Seed {\mathrm{Seed}} \newcommand \Record {\mathrm{Record}} \newcommand \Stake {\mathrm{Stake}} \newcommand \CommitteeSize {\mathrm{CommitteeSize}} \newcommand \CommitteeThreshold {\mathrm{CommitteeThreshold}} \newcommand \Sign {\mathrm{Sign}} \newcommand \Verify {\mathrm{Verify}} \newcommand \abs[1] {\lvert #1 \rvert} $$
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 \( \abs{L} \geq \delta_b \).
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}) \).
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,
$$ \begin{aligned} \Equivocation(I, r, p, s) = (&\Vote(I, r, p, s, v_1), \\ &\Vote(I, r, p, s, v_2)) \end{aligned} $$
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 \).
$$ \newcommand \Bundle {\mathrm{Bundle}} \newcommand \Propose {\mathit{propose}} \newcommand \CommitteeThreshold {\mathrm{CommitteeThreshold}} \newcommand \abs[1] {\lvert #1 \rvert} $$
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 \( \abs{L} \geq \delta_b \).
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.
-
Every element \( a_i \in V \) is valid with respect to \( L \).
-
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) \).
$$ \newcommand \pk {\mathrm{pk}} \newcommand \fv {\text{first}} \newcommand \lv {\text{last}} \newcommand \Sign {\mathrm{Sign}} \newcommand \ValidEntry {\mathrm{ValidEntry}} \newcommand \Hash {\mathrm{Hash}} \newcommand \Digest {\mathrm{Digest}} \newcommand \Encoding {\mathrm{Encoding}} \newcommand \Record {\mathrm{Record}} \newcommand \Verify {\mathrm{Verify}} \newcommand \Proposal {\mathrm{Proposal}} \newcommand \abs[1] {\lvert #1 \rvert} $$
Proposals
Let \( e = (o, s) \) 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 \( \abs{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 \( s \) 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) \).
$$ \newcommand \VRF {\mathrm{VRF}} \newcommand \Prove {\mathrm{Prove}} \newcommand \ProofToHash {\mathrm{ProofToHash}} \newcommand \Verify {\mathrm{Verify}} \newcommand \pk {\mathrm{pk}} \newcommand \sk {\mathrm{sk}} \newcommand \fv {\text{first}} \newcommand \lv {\text{last}} \newcommand \Record {\mathrm{Record}} \newcommand \Seed {\mathrm{Seed}} \newcommand \Hash {\mathrm{Hash}} \newcommand \DigestLookup {\mathrm{DigestLookup}} $$
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.
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(\VRF.\ProofToHash(y), I) \).
-
If \( p \ne 0 \):
- \( y = 0 \),
- \( \alpha = \Hash(\Seed(L, r-\delta_s)) \).
Now \( I \) computes the seed \( Q \) as follows:
$$ Q = \left\{ \begin{array}{rl} H(\alpha, \DigestLookup(L, r-\delta_s\delta_r)) & : (r \bmod \delta_s\delta_r) < \delta_s \\ H(\alpha) & : \text{otherwise} \end{array} \right. $$
⚙️ IMPLEMENTATION
Seed computation reference implementation.
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(\VRF.\ProofToHash(y), I) \) 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 Algorand ABFT non-normative specification.
$$ \newcommand \Vote {\mathrm{Vote}} $$
State Machine
This specification defines the Algorand agreement protocol as a state machine. The input to the state machine is some serialization of events, which in turn results in some serialization of network transmissions from the state machine.
We can define the operation of the state machine as transitions between different states.
A transition \( N \) maps some initial state \( S_0 \), a ledger \( L_0 \), and an event \( e \) to an output state \( S_1 \), an output ledger \( L_1 \), and a sequence of output network transmissions \( \mathbf{a} = (a_1, a_2, \ldots, a_n) \).
We write this as
$$ N(S_0, L_0, e) = (S_1, L_1, \mathbf{a}) $$
If no transmissions are output, we write that \( \mathbf{a} = \epsilon \).
Events
The state machine receives two types of events as inputs.
-
message events: A message event is received when a vote, a proposal, or a bundle is received. A message event is simply written as the message that is received.
-
timeout events: A timeout event is received when a specific amount of time passes after the beginning of a period. A timeout event \( \lambda \) seconds after a period \( p \) begins is denoted \( t(\lambda, p) \).
For more details on the way these events may be constructed from an implementation point of view, refer to the non-normative Algorand ABFT Overview.
Outputs
The state machine produces a series of network transmissions as output. In each transmission, the player broadcasts a vote, a proposal, or a bundle to the rest of the network.
A player may perform a special broadcast called a relay. In a relay, the data received from another peer is broadcast to all peers except for the sender.
A broadcast action is simply written as the message to be transmitted. A relay action is written as the same message except with an asterisk. For instance, an action to relay a vote is written as \( \Vote^\ast(r, p, s, v) \).
For implementation details on relay and broadcasting actions, refer to the non-normative Algorand Network Overview.
$$ \newcommand \Vote {\mathrm{Vote}} \newcommand \Proposal {\mathrm{Proposal}} \newcommand \Bundle {\mathrm{Bundle}} \newcommand \Soft {\mathit{soft}} \newcommand \Cert {\mathit{cert}} \newcommand \Priority {\mathrm{Priority}} \newcommand \VRF {\mathrm{VRF}} \newcommand \ProofToHash {\mathrm{ProofToHash}} \newcommand \Hash {\mathrm{Hash}} $$
Player State Definition
We define the player state \( S \) to be the following tuple:
$$ S = (r, p, s, \bar{s}, V, P, \bar{v}) $$
where
- \( r \) is the current round,
- \( p \) is the current period,
- \( s \) is the current step,
- \( \bar{s} \) is the last concluding step,
- \( V \) is the set of all votes,
- \( P \) is the set of all proposals, and
- \( \bar{v} \) is the pinned value.
We say that a player has observed
- \( \Proposal(v) \) if \( \Proposal(v) \in P \),
- \( \Vote(r, p, s, v) \) if \( \Vote(r, p, s, v) \in V \),
- \( \Bundle(r, p, s, v) \) if \( \Bundle(r, p, s, v) \subset V \),
- That the round \( r \) (period \( p = 0 \)) has begun if there exists some \( p \) such that \( \Bundle(r-1, p, \Cert, v) \) was also observed for some \( v \),
- That the round \( r \), period \( p > 0 \) has begun if there exists some
\( p \) such that either
- \( \Bundle(r, p-1, s, v) \) was also observed for some \( s > \Cert, v \), or
- \( \Bundle(r, p, \Soft, v) \) was observed for some \( v \).
An event causes a player to observe something if the player has not observed that thing before receiving the event and has observed that thing after receiving the event. For instance, a player may observe a vote \( \Vote \), which adds this vote to \( V \):
$$ N((r, p, s, \bar{s}, V, P, \bar{v}), L_0, \Vote) = ((r’, p’, \ldots, V \cup \{\Vote\}, P, \bar{v}’), L_1, \ldots) $$
We abbreviate the transition above as
$$ N((r, p, s, \bar{s}, V, P, \bar{v}), L_0, \Vote) = ((S \cup \Vote, P, \bar{v}), L_1, \ldots) $$
Note that observing a message is distinct from receiving a message. A message which has been received might not be observed (for instance, the message may be from an old round). Refer to the relay rules for details.
Special Values
We define two functions \( \mu(S, r, p), \sigma(S, r, p) \), which are defined as follows:
The frozen value \( \mu(S, r, p) \) is defined as the proposal-value \( v \) in the proposal vote in round \( r \) and period \( p \) with the minimal credential.
More formally, then, let
$$ V_{r, p, 0} = \{\Vote(I, r, p, 0, v) | \Vote \in V\} $$
where \( V \) is the set of votes in \( S \).
Then if \( \Vote_l(r, p, 0, v_l) \) is the vote with the smallest weight in \( V_{r, p} \), then \( \mu(S, r, p) = v_l \).
If \( V_{r, p} \) is empty, then \( \mu(S, r, p) = \bot \).
The staged value \( \sigma(S, r, p) \) is defined as the sole proposal-value for which there exists a soft-bundle in round \( r \) and period \( p \).
More formally, suppose \( \Bundle(r, p, Soft, v) \subset V \). Then \( \sigma(S, r, p) = v \).
If no such soft-bundle exists, then \( \sigma(S, r, p) = \bot \).
If there exists a proposal-value \( v \) such that \( \Proposal(v) \in P \) and \( \sigma(S, r, p) = v \), we say that \( v \) is committable for round \( r \), period \( p \) (or simply that \( v \) is committable if \( (r, p) \) is unambiguous).
⚙️ IMPLEMENTATION
The current implementation constructs a Proposal Tracker which, amongst other things, is in charge of handling both frozen and staged value tracking.
Relay Rules
Here we describe how players handle message events.
Whenever the player receives a message event, it may decide to relay that or another message. In this case, the player will produce that output before producing any subsequent output (which may result from the player’s observation of that message; see the broadcast rules below).
A player may receive messages from a misbehaving peer. These cases are marked with an asterisk (*) and enable the node to perform a special action (e.g., disconnect from the peer).
For examples of what these special actions may involve, see the non-normative Algorand Network Overview.
We say that a player ignores a message if it produces no outputs on receiving that message.
$$ \newcommand \Vote {\mathrm{Vote}} \newcommand \Late {\mathit{late}} \newcommand \Down {\mathit{down}} \newcommand \Next {\mathit{next}} $$
Votes
On receiving a vote \( \Vote_k(r_k, p_k, s_k, v) \) a player
-
Ignores* it if \( \Vote_k \) is malformed or trivially invalid.
-
Ignores it if \( s = 0 \) and \( \Vote_k \in V \).
-
Ignores it if \( s = 0 \) and \( \Vote_k \) is an equivocation.
-
Ignores it if \( s > 0 \) and \( \Vote_k \) is a second equivocation.
-
Ignores it if
-
\( r_k \notin [r,r+1] \) or
-
\( r_k = r + 1 \) and either
- \( p_k > 0 \) or
- \( s_k \in (\Next_0, \Late) \) or
-
\( r_k = r \) and one of
- \( p_k \notin [p-1,p+1] \) or
- \( p_k = p + 1 \) and \( s_k \in (\Next_0, \Late) \) or
- \( p_k = p \) and \( s_k \in (\Next_0, \Late) \) and \( s_k \notin [s-1,s+1] \) or
- \( p_k = p - 1 \) and \( s_k \in (\Next_0, \Late) \) and \( s_k \notin [\bar{s}-1,\bar{s}+1] \).
-
-
Otherwise, relays \( \Vote_k \), observes it, and then produces any consequent output.
Specifically, if a player ignores the vote, then
$$ N(S, L, \Vote_k(r_k, p_k, s_k, v)) = (S, L, \epsilon) $$
while if a player relays the vote, then
$$ N(S, L, \Vote_k(r_k, p_k, s_k, v)) = (S’ \cup \Vote(I, r_k, p_k, s_k, v), L’, (\Vote_k^\ast(r_k, p_k, s_k, v),\ldots)). $$
$$ \newcommand \Bundle {\mathrm{Bundle}} $$
Bundles
On receiving a bundle \( \Bundle(r_k, p_k, s_k, v) \) a player
-
Ignores* it if \( Bundle(r_k, p_k, s_k, v) \) is malformed or trivially invalid.
-
Ignores it if
- \( r_k \neq r \) or
- \( r_k = r \) and \( p_k + 1 < p \).
-
Otherwise, observes the votes in \( \Bundle(r_k, p_k, s_k, v) \) in sequence. If there exists a vote that causes the player to observe some bundle \( \Bundle(r_k, p_k, s_k, v’) \) for some \( s_k \), then the player relays \( \Bundle(r_k, p_k, s_k, v’) \), and then executes any consequent action; if there does not, the player ignores it.
Specifically, if the player ignores the bundle without observing its votes, then
$$ N(S, L, \Bundle(r_k, p_k, s_k, v)) = (S, L, \epsilon); $$
while if a player ignores the bundle but observes its votes, then
$$ N(S, L, \Bundle(r_k, p_k, s_k, v)) = (S’ \cup \Bundle(r_k, p_k, s_k, v), L, \epsilon); $$
and if a player, on observing the votes in the bundle, observes a bundle for some value (not necessarily distinct from the bundle’s value), then
$$ N(S, L, \Bundle(r_k, p_k, s_k, v)) = (S’ \cup \Bundle(r_k, p_k, s_k, v), L’, (\Bundle^\ast(r_, p_k, s_k, v’), \ldots)). $$
$$ \newcommand \Proposal {\mathrm{Proposal}} $$
Proposals
On receiving a proposal \( \Proposal(v) \) a player
-
Relays \( \Proposal(v) \) if \( \sigma(S, r+1, 0) = v \).
-
Ignores* it if it is malformed or trivially invalid.
-
Ignores it if \( \Proposal(v) \in P \).
-
Relays \( \Proposal(v) \), observes it, and then produces any consequent output, if \( v \in \{\sigma(S, r, p), \bar{v}, \mu(S, r, p)\} \).
-
Otherwise, ignores it.
Specifically, if the player ignores a proposal, then
$$ N(S, L, \Proposal(v)) = (S, L, \epsilon) $$
while if a player relays the proposal after checking if it is valid, then
$$ N(S, L, \Proposal(v)) = (S’ \cup \Proposal(v), L’, (\Proposal^\ast(v), \ldots)). $$
However, in the first condition above, the player relays \( \Proposal(v) \) without checking if it is valid.
Since the proposal has not been seen to be valid, the player cannot observe it yet, so
$$ N(S, L, \Proposal(v)) = (S, L, (\Proposal^\ast(v))). $$
An implementation may buffer a proposal in this case. Specifically, an implementation which relays a proposal without checking that it is valid, may optionally choose to replay this event when it observes that a new round has begun (see State Transition section). In this case, at the conclusion of a new round, this proposal is processed once again as input.
Implementations MAY store and relay fewer proposals than specified here to improve efficiency. However, implementations MUST relay proposals which match the following proposal-values (where \( r \) is the current round and \( p \) is the current period):
-
\( \bar{v} \),
-
\( \sigma(S, r, p), \sigma(S, r, p-1) \),
-
\( \mu(S, r, p) \) if \( \sigma(S, r, p) \) is not set and \( \mu(S, r, p+1) \) if \( \sigma(S, r, p+1) \) is not set.
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 \( u \in [0, 2^{s_t}\lambda) \) sampled uniformly at random, the player sets \( s := 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}
$$
$$ \newcommand \pk {\mathrm{pk}} \newcommand \sk {\mathrm{sk}} \newcommand \Vote {\mathrm{Vote}} \newcommand \fv {\text{first}} \newcommand \Record {\mathrm{Record}} \newcommand \lv {\text{last}} \newcommand \Stake {\mathrm{Stake}} \newcommand \Seed {\mathrm{Seed}} \newcommand \CommitteeThreshold {\mathrm{CommitteeThreshold}} \newcommand \CommitteeSize {\mathrm{CommitteeSize}} \newcommand \Sign {\mathrm{Sign}} $$
Broadcast Rules
Upon observing messages or receiving timeout events, the player state machine emits network outputs, which are externally visible. The player may also append an entry to the ledger.
A correct player emits only valid votes. Suppose the player is identified with the address \( I \) and possesses the secret key \( \sk \), and the agreement is occurring on the ledger \(L\). Then the player constructs a vote \( \Vote(I, r, p, s, v) \) by doing the following:
-
Let
- \(( \pk, B, r_\fv, r_\lv) = \Record(L, r - \delta_b, I) \),
- \( \bar{B} = \Stake(L, r - \delta_b) \),
- \( Q = \Seed(L, r - \delta_s) \),
- \( \tau = \CommitteeThreshold(s) \),
- \( \bar{\tau} = \CommitteeSize(s) \).
-
Encode \( x := (I, r, p, s, v), x’ := (I, r, p, s) \).
-
Try to set \( y := \Sign(x, x’, \sk, B, \bar{B}, Q, \tau, \bar{\tau}) \).
If the signing procedure succeeds, the player broadcasts \( Vote(I, r, p, s, v) = (I, r, p, s, v, y) \). Otherwise, the player does not broadcast anything.
For certain broadcast vote-messages specified here, a node is forbidden to equivocate (i.e., produce a pair of votes which contain the same round, period, and step but which vote for different proposal values). These messages are marked with an asterisk (*) below. To prevent accidental equivocation after a power failure, nodes SHOULD checkpoint their state to crash-safe storage before sending these messages.
For further details on these checkpoint strategies, refer to the non-normative Ledger specification. For an in-depth review of broadcasting functionalities, refer to the non-normative Network specification.
$$ \newcommand \Bundle {\mathrm{Bundle}} \newcommand \Soft {\mathit{soft}} \newcommand \Cert {\mathit{cert}} \newcommand \Proposal {\mathrm{Proposal}} $$
Resynchronization Attempt
Where specified, a player attempts to resynchronize.
A resynchronization attempt involves the following stages.
First, the player broadcasts its freshest bundle, if one exists.
A player’s freshest bundle is a complete bundle defined as follows:
-
\( \Bundle(r, p, \Soft, v) \subset V \) for some \( v \), if it exists, or else
-
\( \Bundle(r, p-1, s, \bot) \subset V \) for some \( s > \Cert \), if it exists, or else
-
\( \Bundle(r, p-1, s, v) \subset V \) for some \( s > \Cert, v \neq \bot \), if it exists.
⚙️ IMPLEMENTATION
Freshness relation reference implementation.
Second, if the player broadcasted a bundle \( \Bundle(r, p, s, v) \), and \( v \neq \bot \), then the player broadcasts \( \Proposal(v) \) if the player has it.
Specifically, a resynchronization attempt:
- Corresponds to no additional outputs if no freshest bundle exists
$$ N(S, L, \ldots) = (S’, L’, \ldots), $$
- Corresponds to a broadcast of the freshest bundle after a relay output and before any subsequent broadcast outputs, if said bundle exists, no matching proposal exists
$$ N(S, L, \ldots) = (S’, L’, (\ldots, \Bundle^\ast(r, p, s, v), \ldots)), $$
- Otherwise corresponds to a broadcast of both a bundle and its associated proposal after a relay output and before any subsequent broadcast outputs
$$ N(S, L, \ldots) = (S’, L’, (\ldots, \Bundle^\ast(r, p, s, v), \Proposal(v), \ldots)). $$
$$ \newcommand \pk {\mathrm{pk}} \newcommand \Bundle {\mathrm{Bundle}} \newcommand \Cert {\mathit{cert}} \newcommand \Proposal {\mathrm{Proposal}} \newcommand \Vote {\mathrm{Vote}} \newcommand \Entry {\mathrm{Entry}} \newcommand \Seed {\mathrm{Seed}} \newcommand \Sign {\mathrm{Sign}} \newcommand \Rand {\mathrm{Rand}} \newcommand \Hash {\mathrm{Hash}} \newcommand \Digest {\mathrm{Digest}} \newcommand \Encoding {\mathrm{Encoding}} $$
Proposals
On observing that \( (r, p) \) has begun, the player attempts to resynchronize, and then
-
if \( p = 0 \) or there exists some \( s > \Cert \) where \( \Bundle(r, p-1, s, \bot) \) was observed, then a player generates a new proposal \( (v’, \Proposal(v’)) \) and then broadcasts \( (\Vote(I, r, p, 0, v’), \Proposal(v’)) \).
-
if \( p > 0 \) and there exists some \( s_0 > \Cert, v \) where \( \Bundle(r, p-1, s_0, v) \) was observed, while there exists no \( s_1 > \Cert \) where \( \Bundle(r, p-1, s_1, \bot) \) was observed, then the player broadcasts \( \Vote(I, r, p, 0, v) \). Moreover, if \( \Proposal(v) \in P \), the player then broadcasts \( \Proposal(v) \).
A player generates a new proposal by executing the entry-generation procedure and by setting the fields of the proposal accordingly. Specifically, the player creates a proposal payload \( ((o, s), y) \) by setting
-
\( o := \Entry(L) \),
-
\( Q := \Seed(L, r-1) \),
-
\( y := \Sign(Q, Q, 0, 0, 0, 0, 0, 0) \),
-
and \( s := \Rand(y, \pk)\) if \( p = 0 \) or \( s := \Hash(\Seed(L, r-1)) \) otherwise.
This consequently defines the matching proposal-value \( v = (I, p, \Digest(e), \Hash(\Encoding(e))) \).
For an in-depth overview of how proposal generation may be implemented, refer to the Algorand Ledger non-normative section.
In other words, if the player generates a new proposal,
$$ N(S, L, \ldots) = (S’, L’, (\ldots, \Vote(I, r, p, 0, v’), \Proposal(v’))), $$
while if the player broadcasts an old proposal,
$$ N(S, L, \ldots) = (S’, L’, (\ldots, \Vote(I, r, p-1, 0, v), \Proposal(v))) $$
if \( \Proposal(v) \in P \) and
$$ N(S, L, \ldots) = (S’, L’, (\ldots, \Vote(I, r, p-1, 0, v))) $$
otherwise.
$$ \newcommand \Vote {\mathrm{Vote}} \newcommand \Proposal {\mathrm{Proposal}} $$
Reproposal Payloads
On observing \( \Vote(I, r, p, 0, v) \), if \( \Proposal(v) \in P \) then the player broadcasts \( \Proposal(v) \).
In other words, if \( \Proposal(v) \in P \),
$$ N(S, L, \Vote(I, r, p, 0, v)) = (S’, L’, (\Proposal(v))). $$
$$ \newcommand \FilterTimeout {\mathrm{FilterTimeout}} \newcommand \Cert {\mathit{cert}} \newcommand \Soft {\mathit{soft}} \newcommand \Vote {\mathrm{Vote}} \newcommand \Bundle {\mathrm{Bundle}} $$
Filtering
On observing a timeout event of \( \FilterTimeout(p) \) (where \( \mu = (H, H’, l, p_\mu) = \mu(S, r, p) \)),
-
if \( \mu \neq \bot \) and if
- \( p_\mu = p \) or
- there exists some \( s > \Cert \) such that \( \Bundle(r, p-1, s, \mu) \) was observed then the player broadcasts \( \Vote(I, r, p, \Soft, \mu) \).
-
if there exists some \( s_0 > \Cert \) such that \( \Bundle(r, p-1, s_0, \bar{v}) \) was observed and there exists no \( s_1 > \Cert \) such that \( \Bundle(r, p-1, s_1, \bot) \) was observed, then the player broadcasts* \( \Vote(I, r, p, \Soft, \bar{v}) \).
-
otherwise, the player does nothing.
For a detailed overview of how the filtering step may be implemented, refer to the Algorand ABFT non-normative section.
In other words, in the first case above,
$$ N(S, L, t(\FilterTimeout(p), p)) = (S, L, \Vote(I, r, p, \Soft, \mu)); $$
while in the second case above,
$$ N(S, L, t(\FilterTimeout(p), p)) = (S, L, \Vote(I, r, p, \Soft, \bar{v})); $$
and if neither case is true,
$$ N(S, L, t(\FilterTimeout(p), p)) = (S, L, \epsilon). $$
$$ \newcommand \Cert {\mathit{cert}} \newcommand \Soft {\mathit{soft}} \newcommand \Vote {\mathrm{Vote}} \newcommand \Bundle {\mathrm{Bundle}} \newcommand \Proposal {\mathrm{Proposal}} $$
Certifying
On observing that some proposal-value \( v \) is committable for its current round \( r \), and some period \( p’ \geq p \) (its current period), if \( s \leq \Cert \), then the player broadcasts* \( \Vote(I, r, p, \Cert, v) \). (It can be shown that this occurs either after a proposal is received or a soft-vote, which can be part of a bundle, is received.)
For a detailed overview of how the certification step may be implemented, refer to the Algorand ABFT non-normative section.
In other words, if observing a soft-vote causes a proposal-value to become committable,
$$ N(S, L, \Vote(I, r, p, \Soft, v)) = (S’, L, (\ldots, \Vote(I, r, p, \Cert, v))); $$
while if observing a bundle causes a proposal-value to become committable,
$$ N(S, L, \Bundle(r, p, \Soft, v)) = (S’, L, (\ldots, \Vote(I, r, p, \Cert, v))); $$
and if observing a proposal causes a proposal-value to become committable,
$$ N(S, L, \Proposal(v)) = (S’, L, (\ldots, \Vote(I, r, p, \Cert, v))); $$
as long as \( s \leq \Cert \).
$$ \newcommand \Cert {\mathit{cert}} \newcommand \Vote {\mathrm{Vote}} \newcommand \Bundle {\mathrm{Bundle}} \newcommand \Proposal {\mathrm{Proposal}} $$
Commitment
On observing \( \Bundle(r, p, \Cert, v) \) for some value \( v \), the player commits the entry \( e \) corresponding to \( \Proposal(v) \); i.e., the player appends \( e \) to the sequence of entries on its ledger \( L \). (Evidently, this occurs either after a vote is received or after a bundle is received.)
For further details on how entry commitment may be implemented, refer to the Algorand Ledger non-normative section.
In other words, if observing a cert-vote causes the player to commit \( e \),
$$ N(S, L, \Vote(I, r, p, \Cert, v)) = (S’, L || e, \ldots)); $$
while if observing a bundle causes the player to commit \( e \),
$$ N(S, L, \Bundle(r, p, \Cert, v)) = (S’, L || e, \ldots)). $$
Occasionally, an implementation may not have \(e\) at the point \(e\) becomes committed. In this case, the implementation may wait until it receives \(e\) somehow (perhaps by requesting peers for \(e\)). Alternatively, the implementation may continue running the protocol until it receives \(e\). However, if the protocol chooses to continue running, it may not transmit any vote for which \(v \neq \bot\) until it has committed \(e\).
$$ \newcommand \Cert {\mathit{cert}} \newcommand \Next {\mathit{next}} \newcommand \DeadlineTimeout {\mathrm{DeadlineTimeout}} \newcommand \Vote {\mathrm{Vote}} \newcommand \Bundle {\mathrm{Bundle}} $$
Recovery
On observing a timeout event of
-
\( T = \DeadlineTimeout(p) \) or
-
\( T = \DeadlineTimeout(p) + 2^{s_t}\lambda + u \) where \( u \in [0, 2^{s_t}\lambda] \) sampled uniformly at random,
the player attempts to resynchronize and then broadcasts* \( \Vote(I, r, p, \Next_h, v) \) where
-
\( v = \sigma(S, r, p) \) if \( v \) is committable in \( (r, p) \),
-
\( v = \bar{v} \) if there does not exist a \( s_0 > \Cert \) such that \( \Bundle(r, p-1, s_0, \bot) \) was observed and there exists an \( s_1 > \Cert \) such that \( \Bundle(r, p-1, s_1, \bar{v} )\) was observed,
-
and \( v = \bot \) otherwise.
⚙️ IMPLEMENTATION
Next vote issuance reference implementation.
Next vote timeout ranges computation reference implementation.
Call to \( \Next_0 \) reference implementation.
Subsequent calls to \( \Next_{st} \) reference implementation.
Step increase in recovery step timeouts reference implementation.
For a detailed overview of how the recovery routine may be implemented, refer to the Algorand ABFT non-normative section.
In other words, if a proposal-value \( v \) is committable in the current period,
$$ N(S, L, t(T, p)) = (S’, L, (\ldots, \Vote(I, r, p, \Next_h, v))); $$
while in the second case,
$$ N(S, L, t(T, p)) = (S’, L, (\ldots, \Vote(I, r, p, \Next_h, \bar{v}))); $$
and otherwise,
$$ N(S, L, t(T, p)) = (S’, L, (\ldots, \Vote(I, r, p, \Next_h, \bot))). $$
$$ \newcommand \Vote {\mathrm{Vote}} \newcommand \Bundle {\mathrm{Bundle}} \newcommand \Cert {\mathit{cert}} \newcommand \Late {\mathit{late}} \newcommand \Redo {\mathit{redo}} \newcommand \Down {\mathit{down}} $$
Fast Recovery
On observing a timeout event of \( T = k\lambda_f + u \) where \( k \) is a positive integer and \( u \in [0, \lambda_f] \) sampled uniformly at random, the player attempts to resynchronize. Then,
-
The player broadcasts* \( \Vote(I, r, p, \Late, v) \) if \( v = \sigma(S, r, p) \) is committable in \( (r, p) \).
-
The player broadcasts* \( \Vote(I, r, p, \Redo, \bar{v}) \) if there does not exist a \( s_0 > \Cert \) such that \( \Bundle(r, p-1, s_0, \bot) \) was observed and there exists an \( s_1 > \Cert \) such that \( \Bundle(r, p-1, s_1, \bar{v}) \) was observed.
-
Otherwise, the player broadcasts* \( \Vote(I, r, p, \Down, \bot \).
Finally, the player broadcasts all \( \Vote(I, r, p, \Late, v) \in V\), all \( \Vote(I, r, p, \Redo, v) \in V\), and all \( \Vote(I, r, p, \Down, \bot) \in V \) that it has observed.
⚙️ IMPLEMENTATION
Fast recovery reference implementation.
For a detailed pseudocode overview of the fast recovery routine, along with protocol recovery run examples, refer to the Algorand ABFT non-normative section.
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 an 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 \PSfunction {\textbf{function }} \newcommand \PSreturn {\textbf{return }} \newcommand \PSendfunction {\textbf{end function}} \newcommand \PSswitch {\textbf{switch }} \newcommand \PScase {\textbf{case }} \newcommand \PSdefault {\textbf{default}} \newcommand \PSendswitch {\textbf{end switch}} \newcommand \PSfor {\textbf{for }} \newcommand \PSendfor {\textbf{end for}} \newcommand \PSwhile {\textbf{while }} \newcommand \PSendwhile {\textbf{end while}} \newcommand \PSdo {\textbf{ do}} \newcommand \PSif {\textbf{if }} \newcommand \PSelseif {\textbf{else if }} \newcommand \PSelse {\textbf{else}} \newcommand \PSthen {\textbf{ then}} \newcommand \PSendif {\textbf{end if}} \newcommand \PSnot {\textbf{not }} \newcommand \PScomment {\qquad \small \textsf} $$
$$ \newcommand \VRF {\mathrm{VRF}} \newcommand \Prove {\mathrm{Prove}} \newcommand \ProofToHash {\mathrm{ProofToHash}} \newcommand \Secrets {\mathrm{Secrets}} \newcommand \Rerand {\mathrm{Rerand}} $$
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: } \PSfunction \mathrm{ComputeSeedAndProof}(I) \\ &\text{2: } \quad \PSif p = 0 \PSthen \\ &\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 \PSelse \\ &\text{6: } \quad \quad y \gets 0 \\ &\text{6: } \quad \quad \alpha \gets H(L[r - \delta_s]_Q) \\ &\text{7: } \quad \PSendif \\ &\text{9: } \quad \PSif r \bmod (\delta_s\delta_r) < \delta_s \PSthen \\ &\text{10:} \quad \quad Q \gets H(\alpha || H(L[r - \delta_s \delta_r])) \\ &\text{11:} \quad \PSelse \\ &\text{12:} \quad \quad Q \gets H(\alpha) \\ &\text{13:} \quad \PSendif \\ &\text{14:} \quad \PSreturn (Q, y) \\ &\text{15: } \PSendfunction \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 \PSfunction {\textbf{function }} \newcommand \PSreturn {\textbf{return }} \newcommand \PSendfunction {\textbf{end function}} \newcommand \PSswitch {\textbf{switch }} \newcommand \PScase {\textbf{case }} \newcommand \PSdefault {\textbf{default}} \newcommand \PSendswitch {\textbf{end switch}} \newcommand \PSfor {\textbf{for }} \newcommand \PSendfor {\textbf{end for}} \newcommand \PSwhile {\textbf{while }} \newcommand \PSendwhile {\textbf{end while}} \newcommand \PSdo {\textbf{ do}} \newcommand \PSif {\textbf{if }} \newcommand \PSelseif {\textbf{else if }} \newcommand \PSelse {\textbf{else}} \newcommand \PSthen {\textbf{ then}} \newcommand \PSendif {\textbf{end if}} \newcommand \PSnot {\textbf{not }} \newcommand \PScomment {\qquad \small \textsf} $$
$$ \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 \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}} $$
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: } \PSfunction \EventHandler(ev) \\ &\text{2: } \qquad \PSif \ev \text{ is a } \TimeoutEvent \PSthen \\ &\text{3: } \qquad \quad \t \gets \ev_\t \\ &\text{4: } \qquad \quad \PSif \t = 0 \PSthen \PScomment{Last round should have left us with s := propose} \\ &\text{5: } \qquad \quad \quad \BlockProposal() \\&\text{6: } \qquad \quad \quad \PSif \text{finished a block} \lor \mathrm{CurrentTime}() = \mathrm{AssemblyDeadline}() \PSthen \\ &\text{7: } \qquad \quad \quad \quad \s \gets \Soft \\ &\text{8: } \qquad \quad \quad \PSendif \\ &\text{9: } \qquad \quad \PSelseif time = \DynamicFilterTimeout(p) \PSthen \\ &\text{10:} \qquad \quad \quad \SoftVote() \\ &\text{11:} \qquad \quad \quad \s \gets \Cert \\ &\text{12:} \qquad \quad \PSelseif \t = \DeadlineTimeout(p) \PSthen \\ &\text{13:} \qquad \quad \quad \s \gets \Next_0 \\ &\text{14:} \qquad \quad \quad \Recovery() \\ &\text{15:} \qquad \quad \PSelseif \t = \DeadlineTimeout(p) + 2^{s_t - 3}\lambda \text{ for } 4 \le s_t \le 252 \PSthen \\ &\text{16:} \qquad \quad \quad \s \gets \Next_{s_t} \\ &\text{17:} \qquad \quad \quad \Recovery() \\ &\text{18:} \qquad \quad \PSelseif \t = k\lambda_f + rnd \text{ for } k, rnd \in \mathbb{Z}, k > 0, 0 \le rnd \le \lambda_f \PSthen \\ &\text{19:} \qquad \quad \quad \FastRecovery() \\ &\text{20:} \qquad \quad \PSendif \\ &\text{21:} \qquad \PSelse \PScomment{MessageEvent could trigger a commitment and round advancement} \\ &\text{22:} \qquad \quad msg \gets ev_{msg} \\ &\text{23:} \qquad \quad \PSif \data \text{ is of type } \texttt{Proposal } pp \PSthen \\ &\text{24:} \qquad \quad \quad \HandleProposal(pp) \\ &\text{25:} \qquad \quad \PSelseif \data \text{ is of type } \texttt{Vote } v \PSthen \\ &\text{26:} \qquad \quad \quad \HandleVote(v) \\ &\text{27:} \qquad \quad \PSelseif \data \text{ is of type } \texttt{Bundle } b \PSthen \\ &\text{28:} \qquad \quad \quad \HandleBundle(b) \\ &\text{29:} \qquad \quad \PSendif \\ &\text{30:} \qquad \PSendif \\ &\text{31: } \PSendfunction \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 (seconds) | 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 \) | \( 0.05 \) | 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 \PSfunction {\textbf{function }} \newcommand \PSreturn {\textbf{return }} \newcommand \PSendfunction {\textbf{end function}} \newcommand \PSswitch {\textbf{switch }} \newcommand \PScase {\textbf{case }} \newcommand \PSdefault {\textbf{default}} \newcommand \PSendswitch {\textbf{end switch}} \newcommand \PSfor {\textbf{for }} \newcommand \PSendfor {\textbf{end for}} \newcommand \PSwhile {\textbf{while }} \newcommand \PSendwhile {\textbf{end while}} \newcommand \PSdo {\textbf{ do}} \newcommand \PSif {\textbf{if }} \newcommand \PSelseif {\textbf{else if }} \newcommand \PSelse {\textbf{else}} \newcommand \PSthen {\textbf{ then}} \newcommand \PSendif {\textbf{end if}} \newcommand \PSnot {\textbf{not }} \newcommand \PScomment {\qquad \small \textsf} $$
$$ \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 \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: } \PSfunction \BlockProposal() \\ &\text{2: } \quad \PSif p \ne 0 \PSthen \\ &\text{3: } \quad \quad \Resync() \\ &\text{4: } \quad \PSendif \\ &\text{5: } \quad \PSfor a \in A \PSdo \\ &\text{6: } \quad \quad \c \gets \Sortition(a_I, r, p, \prop) \\ &\text{7: } \quad \quad \PSif \c_j > 0 \PSthen \\ &\text{8: } \quad \quad \quad \PSif p = 0 \lor \exists s’ \text{ such that } \Bundle(r, p-1, s’, \bot) \subset V \PSthen \\ &\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 \PSelse \\ &\text{15:} \quad \quad \quad \quad \Broadcast(\Vote(a_I, r, p, \prop, \bar{v}, \c)) \\ &\text{16:} \quad \quad \quad \quad \PSif \RetrieveProposal(\bar{v}) \ne \bot \PSthen \\ &\text{17:} \quad \quad \quad \quad \quad \Broadcast(\RetrieveProposal(\bar{v})) \\ &\text{18:} \quad \quad \quad \quad \PSendif \\ &\text{19:} \quad \quad \quad \PSendif \\ &\text{20:} \quad \quad \PSendif \\ &\text{21:} \quad \PSendfor \\ &\text{22: } \PSendfunction \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 \PSfunction {\textbf{function }} \newcommand \PSreturn {\textbf{return }} \newcommand \PSendfunction {\textbf{end function}} \newcommand \PSswitch {\textbf{switch }} \newcommand \PScase {\textbf{case }} \newcommand \PSdefault {\textbf{default}} \newcommand \PSendswitch {\textbf{end switch}} \newcommand \PSfor {\textbf{for }} \newcommand \PSendfor {\textbf{end for}} \newcommand \PSwhile {\textbf{while }} \newcommand \PSendwhile {\textbf{end while}} \newcommand \PSdo {\textbf{ do}} \newcommand \PSif {\textbf{if }} \newcommand \PSelseif {\textbf{else if }} \newcommand \PSelse {\textbf{else}} \newcommand \PSthen {\textbf{ then}} \newcommand \PSendif {\textbf{end if}} \newcommand \PSnot {\textbf{not }} \newcommand \PScomment {\qquad \small \textsf} $$
$$ \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 \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: } \PSfunction \SoftVote() \\ &\text{2: } \quad \loh \gets \infty \\ &\text{3: } \quad v \gets \bot \\ &\text{4: } \quad \PSfor \vt_p \in V^\ast \PSdo \PScomment{The subset of votes corresponding to proposals} \\ &\text{5: } \quad \quad \ph \gets \Priority(\vt_p) \\ &\text{6: } \quad \quad \PSif \ph < \loh \PSthen \\ &\text{7: } \quad \quad \quad \loh \gets \ph \\ &\text{8: } \quad \quad \quad v \gets \vt_p \\ &\text{9: } \quad \quad \PSendif \\ &\text{10:} \quad \PSendfor \\ &\text{11:} \quad \PSif \loh < \infty \PSthen \\ &\text{12:} \quad \quad \PSfor a \in A \PSdo \\ &\text{13:} \quad \quad \quad \c \gets \Sortition(a_I, r, p, \Soft) \\ &\text{14:} \quad \quad \quad \PSif \c_j > 0 \PSthen \\ &\text{15:} \quad \quad \quad \quad \Broadcast(\Vote(a_I, r, p, \Soft, v, \c)) \\ &\text{16:} \quad \quad \quad \quad \PSif \RetrieveProposal(v) \PSthen \\ &\text{17:} \quad \quad \quad \quad \quad \Broadcast(\RetrieveProposal(v)) \\ &\text{18:} \quad \quad \quad \quad \PSendif \\ &\text{19:} \quad \quad \quad \PSendif \\ &\text{20:} \quad \quad \PSendfor \\ &\text{21:} \quad \PSendif \\ &\text{22: } \PSendfunction \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 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 \PSfunction {\textbf{function }} \newcommand \PSreturn {\textbf{return }} \newcommand \PSendfunction {\textbf{end function}} \newcommand \PSswitch {\textbf{switch }} \newcommand \PScase {\textbf{case }} \newcommand \PSdefault {\textbf{default}} \newcommand \PSendswitch {\textbf{end switch}} \newcommand \PSfor {\textbf{for }} \newcommand \PSendfor {\textbf{end for}} \newcommand \PSwhile {\textbf{while }} \newcommand \PSendwhile {\textbf{end while}} \newcommand \PSdo {\textbf{ do}} \newcommand \PSif {\textbf{if }} \newcommand \PSelseif {\textbf{else if }} \newcommand \PSelse {\textbf{else}} \newcommand \PSthen {\textbf{ then}} \newcommand \PSendif {\textbf{end if}} \newcommand \PSnot {\textbf{not }} \newcommand \PScomment {\qquad \small \textsf} $$
$$ \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 \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: } \PSfunction \ValidateVote(\vt): \\ &\text{2: } \quad \PSif \PSnot \VerifyVote(\vt) \PSthen \\ &\text{3: } \quad \quad \DisconnectFromPeer(\SenderPeer(\vt)) \\ &\text{4: } \quad \quad \PSreturn \PScomment{Ignore invalid vote} \\ &\text{5: } \quad \PSendif \\ &\text{6: } \quad \PSif \vt_s = 0 \land (\vt \in V \lor \IsEquivocation(\vt)) \PSthen \\ &\text{7: } \quad \quad \PSreturn \PScomment{Ignore vote, equivocation not allowed in proposal votes} \\ &\text{8: } \quad \PSendif \\ &\text{9: } \quad \PSif \vt_s > 0 \land \IsSecondEquivocation(\vt) \PSthen \\ &\text{10:} \quad \quad \PSreturn \PScomment{Ignore vote if it’s a second equivocation} \\ &\text{11:} \quad \PSendif \\ &\text{12:} \quad \PSif \vt_r < r \PSthen \\ &\text{13:} \quad \quad \PSreturn \PScomment{Ignore vote of past round} \\ &\text{14:} \quad \PSendif \\ &\text{15:} \quad \PSif \vt_r = r + 1 \land (\vt_p > 0 \lor \vt_s \in \{\Next_0, \dots, \Next_{249}\}) \PSthen \\ &\text{16:} \quad \quad \PSreturn \PScomment{Ignore vote of next round if non-zero period or next-k step} \\ &\text{17:} \quad \PSendif \\ &\text{18:} \quad \PSif \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\})) \PSthen \\ &\text{19:} \quad \quad \PSreturn \PScomment{Ignore vote} \\ &\text{20:} \quad \PSendif \\ &\text{21: } \PSendfunction \\ \\ &\text{22: } \PSfunction \HandleVote(\vt): \\ &\text{23:} \quad \ValidateVote(\vt) \PScomment{Check the validity of the vote} \\ &\text{24:} \quad V \gets V \cup \vt \PScomment{Observe the vote} \\ &\text{25:} \quad \Relay(\vt) \\ &\text{26:} \quad \PSif \vt_s = \Prop \PSthen \\ &\text{27:} \quad \quad \PSif \RetrieveProposal(\vt_v) \neq \bot \PSthen \\ &\text{28:} \quad \quad \quad \Broadcast(\RetrieveProposal(\vt_v)) \\ &\text{29:} \quad \quad \PSendif \\ &\text{30:} \quad \PSelseif \vt_s = \Soft \PSthen \\ &\text{31:} \quad \quad \PSif \exists v : \Bundle(\vt_r, \vt_p, \Soft, v) \subset V \PSthen \\ &\text{32:} \quad \quad \quad \PSfor a \in A \PSdo \\ &\text{33:} \quad \quad \quad \quad \c \gets \Sortition(a_{\sk}, r, p, \Cert) \\ &\text{34:} \quad \quad \quad \quad \PSif \c_j > 0 \PSthen \\ &\text{35:} \quad \quad \quad \quad \quad \Broadcast(\Vote(a_I, r, p, \Cert, v, \c)) \\ &\text{36:} \quad \quad \quad \quad \PSendif \\ &\text{37:} \quad \quad \quad \PSendfor \\ &\text{38:} \quad \quad \PSendif \\ &\text{39:} \quad \PSelseif \vt_s = \Cert \PSthen \\ &\text{40:} \quad \quad \PSif \exists v : \Bundle(\vt_r, \vt_p, \Cert, v) \subset V \PSthen \\ &\text{41:} \quad \quad \quad \PSif \RetrieveProposal(v) = \bot \PSthen \\ &\text{42:} \quad \quad \quad \quad \RequestProposal(v) \\ &\text{43:} \quad \quad \quad \quad \PSif p < \vt_p \PSthen \\ &\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 \PSendif \\ &\text{48:} \quad \quad \quad \PSendif \\ &\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 \PSendif \\ &\text{54:} \quad \PSelseif \vt_s > \Cert \PSthen \\ &\text{55:} \quad \quad \PSif \exists v : \Bundle(\vt_r, \vt_p, \vt_s, v) \subset V \PSthen \\ &\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 \PSendif \\ &\text{60:} \quad \PSendif \\ &\text{61: } \PSendfunction \\ \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 \PSfunction {\textbf{function }} \newcommand \PSreturn {\textbf{return }} \newcommand \PSendfunction {\textbf{end function}} \newcommand \PSswitch {\textbf{switch }} \newcommand \PScase {\textbf{case }} \newcommand \PSdefault {\textbf{default}} \newcommand \PSendswitch {\textbf{end switch}} \newcommand \PSfor {\textbf{for }} \newcommand \PSendfor {\textbf{end for}} \newcommand \PSwhile {\textbf{while }} \newcommand \PSendwhile {\textbf{end while}} \newcommand \PSdo {\textbf{ do}} \newcommand \PSif {\textbf{if }} \newcommand \PSelseif {\textbf{else if }} \newcommand \PSelse {\textbf{else}} \newcommand \PSthen {\textbf{ then}} \newcommand \PSendif {\textbf{end if}} \newcommand \PSnot {\textbf{not }} \newcommand \PScomment {\qquad \small \textsf} $$
$$ \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 \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: } \PSfunction \HandleProposal(\pr) \\ &\text{2: } \quad v \gets \Proposal_v(\pr, \pr_p, \pr_I) \\ &\text{3: } \quad \PSif \exists \Bundle(r+1, 0, \Soft, v) \in B \PSthen \\ &\text{4: } \quad \quad \Relay(\pr) \\ &\text{5: } \quad \quad \PSreturn \PScomment{Future round, do not observe (node is behind)} \\ &\text{6: } \quad \PSendif \\ &\text{7: } \quad \PSif \PSnot \VerifyProposal(\pr) \lor \pr \in P \PSthen \\ &\text{8: } \quad \quad \PSreturn \PScomment{Ignore proposal} \\ &\text{9: } \quad \PSendif \\ &\text{10:} \quad \PSif v \notin \{\sigma, \bar{v}, \mu\} \PSthen \\ &\text{11:} \quad \quad \PSreturn \PScomment{Ignore proposal} \\ &\text{12:} \quad \PSendif \\ &\text{13:} \quad \Relay(\pr) \\ &\text{14:} \quad P \gets P \cup \pr \\ &\text{15:} \quad \PSif \IsCommittable(v) \land s \le \Cert \PSthen \\ &\text{16:} \quad \quad \PSfor a \in A \PSdo \\ &\text{17:} \quad \quad \quad \c \gets \Sortition(a_I, r, p, \Cert) \\ &\text{18:} \quad \quad \quad \PSif \c_j > 0 \PSthen \\ &\text{19:} \quad \quad \quad \quad \Broadcast(\Vote(a_I, r, p, \Cert, v, \c)) \\ &\text{20:} \quad \quad \quad \PSendif \\ &\text{21:} \quad \quad \PSendfor \\ &\text{22:} \quad \PSendif \\ &\text{23: } \PSendfunction \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 \PSfunction {\textbf{function }} \newcommand \PSreturn {\textbf{return }} \newcommand \PSendfunction {\textbf{end function}} \newcommand \PSswitch {\textbf{switch }} \newcommand \PScase {\textbf{case }} \newcommand \PSdefault {\textbf{default}} \newcommand \PSendswitch {\textbf{end switch}} \newcommand \PSfor {\textbf{for }} \newcommand \PSendfor {\textbf{end for}} \newcommand \PSwhile {\textbf{while }} \newcommand \PSendwhile {\textbf{end while}} \newcommand \PSdo {\textbf{ do}} \newcommand \PSif {\textbf{if }} \newcommand \PSelseif {\textbf{else if }} \newcommand \PSelse {\textbf{else}} \newcommand \PSthen {\textbf{ then}} \newcommand \PSendif {\textbf{end if}} \newcommand \PSnot {\textbf{not }} \newcommand \PScomment {\qquad \small \textsf} $$
$$ \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 \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: } \PSfunction \HandleBundle(\b): \\ &\text{2: } \quad \PSif \PSnot \VerifyBundle(\b) \PSthen \\ &\text{3: } \quad \quad \DisconnectFromPeer(\SenderPeer(\b)) \\ &\text{4: } \quad \quad \PSreturn \\ &\text{5: } \quad \PSendif \\ &\text{6: } \quad \PSif \b_r = r \land \b_p + 1 \ge p \PSthen \\ &\text{7: } \quad \quad \PSfor \vt \in \b \PSdo \\ &\text{8: } \quad \quad \quad \HandleVote(\vt) \\ &\text{9: } \quad \quad \PSendfor \\ &\text{10:} \quad \PSendif \\ &\text{11: } \PSendfunction \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 \PSfunction {\textbf{function }} \newcommand \PSendfunction {\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: } \PSfunction \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: } \PSendfunction \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
partitionPolicy
function, 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 \PSfunction {\textbf{function }} \newcommand \PSreturn {\textbf{return }} \newcommand \PSendfunction {\textbf{end function}} \newcommand \PSswitch {\textbf{switch }} \newcommand \PScase {\textbf{case }} \newcommand \PSdefault {\textbf{default}} \newcommand \PSendswitch {\textbf{end switch}} \newcommand \PSfor {\textbf{for }} \newcommand \PSendfor {\textbf{end for}} \newcommand \PSwhile {\textbf{while }} \newcommand \PSendwhile {\textbf{end while}} \newcommand \PSdo {\textbf{ do}} \newcommand \PSif {\textbf{if }} \newcommand \PSelseif {\textbf{else if }} \newcommand \PSelse {\textbf{else}} \newcommand \PSthen {\textbf{ then}} \newcommand \PSendif {\textbf{end if}} \newcommand \PSnot {\textbf{not }} \newcommand \PScomment {\qquad \small \textsf} $$
$$ \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 \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: } \PSfunction \Recovery() \\ &\text{2: } \quad \ResynchronizationAttempt() \\ &\text{3: } \quad \PSfor a \in A \PSdo \\ &\text{4: } \quad \quad \c \gets \Sortition(a_I, r, p, s) \\ &\text{5: } \quad \quad \PSif \c_j > 0 \PSthen \\ &\text{6: } \quad \quad \quad \PSif \exists v = \Proposal_v(\prop, \prop_p, \prop_I) \\ &\text{ } \quad \quad \quad \quad \quad \text{for some } \prop \in P \mid \IsCommittable(v) \PSthen \\ &\text{7: } \quad \quad \quad \quad \Broadcast(\Vote(a_I, r, p, s, v, \c)) \\ &\text{8: } \quad \quad \quad \PSelseif \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 \PSthen \\ &\text{9: } \quad \quad \quad \quad \Broadcast(\Vote(a_I, r, p, s, \bar{v}, \c)) \\ &\text{10:} \quad \quad \quad \PSelse \\ &\text{11:} \quad \quad \quad \quad \Broadcast(\Vote(a_I, r, p, s, \bot, \c)) \\ &\text{12:} \quad \quad \quad \PSendif \\ &\text{13:} \quad \quad \PSendif \\ &\text{14:} \quad \PSendfor \\ &\text{15:} \quad \s \gets \s + 1 \\ &\text{16: } \PSendfunction \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 \PSfunction {\textbf{function }} \newcommand \PSreturn {\textbf{return }} \newcommand \PSendfunction {\textbf{end function}} \newcommand \PSswitch {\textbf{switch }} \newcommand \PScase {\textbf{case }} \newcommand \PSdefault {\textbf{default}} \newcommand \PSendswitch {\textbf{end switch}} \newcommand \PSfor {\textbf{for }} \newcommand \PSendfor {\textbf{end for}} \newcommand \PSwhile {\textbf{while }} \newcommand \PSendwhile {\textbf{end while}} \newcommand \PSdo {\textbf{ do}} \newcommand \PSif {\textbf{if }} \newcommand \PSelseif {\textbf{else if }} \newcommand \PSelse {\textbf{else}} \newcommand \PSthen {\textbf{ then}} \newcommand \PSendif {\textbf{end if}} \newcommand \PSnot {\textbf{not }} \newcommand \PScomment {\qquad \small \textsf} $$
$$ \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 \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: } \PSfunction \FastRecovery() \\ &\text{2: } \quad \Resync() \\ &\text{3: } \quad \PSfor a \in A \PSdo \\ &\text{4: } \quad \quad \PSif \IsCommittable(\bar{v}) \PSthen \\ &\text{5: } \quad \quad \quad \c \gets \Sortition(a_I, r, p, \Late) \\ &\text{6: } \quad \quad \quad \PSif \c_j > 0 \PSthen \\ &\text{7: } \quad \quad \quad \quad \Broadcast(\Vote(r, p, \Late, \bar{v}, \c)) \\ &\text{8: } \quad \quad \quad \PSendif \\ &\text{9: } \quad \quad \PSelseif \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 \PSthen \\ &\text{10:} \quad \quad \quad \c \gets \Sortition(a_I, r, p, \Redo) \\ &\text{11:} \quad \quad \quad \PSif \c_j > 0 \PSthen \\ &\text{12:} \quad \quad \quad \quad \Broadcast(\Vote(r, p, \Redo, \bar{v}, \c)) \\ &\text{13:} \quad \quad \quad \PSendif \\ &\text{14:} \quad \quad \PSelse \\ &\text{15:} \quad \quad \quad \c \gets \Sortition(a_I, r, p, \Down) \\ &\text{16:} \quad \quad \quad \PSif \c_j > 0 \PSthen \\ &\text{17:} \quad \quad \quad \quad \Broadcast(\Vote(r, p, \Down, \bot, \c)) \\ &\text{18:} \quad \quad \quad \PSendif \\ &\text{19:} \quad \quad \PSendif \\ &\text{20:} \quad \PSendfor \\ &\text{21:} \quad \PSfor \vt \in V \text{ such that } \vt_s \geq 253 \PSdo \\ &\text{22:} \quad \quad \Broadcast(\vt) \\ &\text{23:} \quad \PSendfor \\ &\text{24: } \PSendfunction \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 \),
-
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 \PSnot \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)} \).
$$ \sum_{\vt \in V} \vt_{\c_j} \ge \CommitteeThreshold(\Soft). $$
-
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: ↩
$$ \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 State Machine Specification
Algorand replicates a state and the state’s history between protocol participants.
This state and its history are called the Algorand Ledger.
$$ \newcommand \BonusBaseAmount {B_{b,\mathrm{base}}} \newcommand \BonusBaseRound {B_{b,\mathrm{start}}} \newcommand \BonusDecayInterval {B_{b,\mathrm{decay}}} \newcommand \MaxTimestampIncrement {\Delta t_{\max}} \newcommand \MaxTxnBytesPerBlock {B_{\max}} \newcommand \MaxVersionStringLen {V_{\max}} \newcommand \Heartbeat {\mathrm{hb}} \newcommand \Fee {\mathrm{fee}} \newcommand \PayoutsChallengeBits {\Heartbeat_\mathrm{bits}} \newcommand \PayoutsChallengeGracePeriod {\Heartbeat_\mathrm{grace}} \newcommand \PayoutsChallengeInterval {\Heartbeat_r} \newcommand \PayoutsGoOnlineFee {B_{p,\Fee}} \newcommand \AccountMinBalance {A_{b,\min}} \newcommand \DefaultUpgradeWaitRounds {\delta_x} \newcommand \MaxUpgradeWaitRounds {\delta_{x_{\max}}} \newcommand \MinUpgradeWaitRounds {\delta_{x_{\min}}} \newcommand \UpgradeThreshold {\tau} \newcommand \UpgradeVoteRounds {\delta_d} \newcommand \RewardUnit {U_r} \newcommand \RewardsRateRefreshInterval {\omega_r} \newcommand \StateProof {\mathrm{SP}} \newcommand \StateProofInterval {\delta_\StateProof} \newcommand \StateProofVotersLookback {\delta_{\StateProof,b}} \newcommand \StateProofTopVoters {N_\StateProof} \newcommand \StateProofStrengthTarget {KQ_\StateProof} \newcommand \StateProofMaxRecoveryIntervals {I_\StateProof} \newcommand \StateProofWeightThreshold {f_\StateProof} \newcommand \MinTxnFee {T_{\Fee,\min}} \newcommand \MaxTxnLife {T_{\Delta r,\max}} \newcommand \MaxTxnNoteBytes {T_{m,\max}} \newcommand \MaxTxGroupSize {GT_{\max}} \newcommand \MaxKeyregValidPeriod {K_{\Delta r,\max}} \newcommand \MaxTxTail {\mathrm{TxTail}_{\max}} $$
$$ \newcommand \MinBalance {b_{\min}} \newcommand \Asset {\mathrm{Asa}} \newcommand \MaxAssetDecimals {\Asset_{d,\max}} \newcommand \MaxAssetNameBytes {\Asset_{n,\max}} \newcommand \MaxAssetUnitNameBytes {\Asset_{u,\max}} \newcommand \MaxAssetURLBytes {\Asset_{r,\max}} \newcommand \LogicSig {\mathrm{LSig}} \newcommand \LogicSigMaxCost {\LogicSig_{c,\max}} \newcommand \LogicSigMaxSize {\LogicSig_{\max}} \newcommand \LogicSigVersion {\LogicSig_{V}} \newcommand \MaxProposedExpiredOnlineAccounts {B_{N_\mathrm{e},\max}} \newcommand \PayoutMaxMarkAbsent {B_{N_\mathrm{a},\max}} \newcommand \PayoutsMaxBalance {A_{r,\max}} \newcommand \PayoutsMinBalance {A_{r,\min}} \newcommand \PayoutsPercent {B_{p,\%}} \newcommand \App {\mathrm{App}} \newcommand \AppFlatOptInMinBalance {\App_{\mathrm{optin},\MinBalance}} \newcommand \AppFlatParamsMinBalance {\App_{\mathrm{create},\MinBalance}} \newcommand \Box {\mathrm{Box}} \newcommand \BoxByteMinBalance {\Box_{\mathrm{byte},\MinBalance}} \newcommand \BoxFlatMinBalance {\Box_{\mathrm{flat},\MinBalance}} \newcommand \BytesPerBoxReference {\Box_{\mathrm{IO}}} \newcommand \MaxBoxSize {\Box_{\max}} \newcommand \MaxAppArgs {\App_{\mathrm{arg},\max}} \newcommand \MaxAppBoxReferences {\App_{\Box,\max}} \newcommand \MaxAppBytesValueLen {\App_{\mathrm{v},\max}} \newcommand \MaxAppKeyLen {\App_{\mathrm{k},\max}} \newcommand \MaxAppProgramCost {\App_{c,\max}} \newcommand \MaxAppProgramLen {\App_{\mathrm{prog},\max}} \newcommand \MaxAppSumKeyValueLens {\App_{\mathrm{kv},\max}} \newcommand \MaxAppTotalArgLen {\App_{\mathrm{ay},\max}} \newcommand \MaxAppTotalProgramLen {\App_{\mathrm{prog},t,\max}} \newcommand \MaxAppTotalTxnReferences {\App_{r,\max}} \newcommand \MaxAppTxnAccounts {\App_{\mathrm{acc},\max}} \newcommand \MaxAppTxnForeignApps {\App_{\mathrm{app},\max}} \newcommand \MaxAppTxnForeignAssets {\App_{\mathrm{asa},\max}} \newcommand \MaxAppAccess {\App_{\mathrm{access},\max}} \newcommand \MaxExtraAppProgramPages {\App_{\mathrm{page},\max}} \newcommand \MaxGlobalSchemaEntries {\App_{\mathrm{GS},\max}} \newcommand \MaxLocalSchemaEntries {\App_{\mathrm{LS},\max}} \newcommand \MaxInnerTransactions {\App_\mathrm{itxn}} \newcommand \SchemaBytesMinBalance {\App_{\mathrm{b},\MinBalance}} \newcommand \SchemaMinBalancePerEntry {\App_{\mathrm{s},\MinBalance}} \newcommand \SchemaUintMinBalance {\App_{\mathrm{u},\MinBalance}} $$
$$ \newcommand \Account {\mathrm{Acc}} \newcommand \MaxAssetsPerAccount {\Account_{\Asset,\max}} \newcommand \MaxAppsCreated {\Account_{\App,\max}} \newcommand \MaxAppsOptedIn {\Account_{\App,optin,\max}} \newcommand \MaximumMinimumBalance {\mathrm{MBR}_{\max}} \newcommand \DefaultKeyDilution {\mathrm{KeyDilution}} $$
Parameters
The Algorand Ledger is parameterized by the values in the following tables.
For each parameter, the tables provide the reference implementation name and the last update version, to facilitate the match of the specifications and the implementation.
Block
Parameter | Current Value | Unit | Description | Reference Implementation Name | Last Update Version |
---|---|---|---|---|---|
\( \MaxTxnBytesPerBlock \) | \( 5{,}242{,}880 \) | bytes | Maximum number of transaction bytes in a block | MaxTxnBytesPerBlock | v33 |
\( \MaxTimestampIncrement \) | \( 25 \) | seconds | Maximum difference between successive timestamps | MaxTimestampIncrement | v7 |
\( \MaxVersionStringLen \) | \( 128 \) | bytes | Maximum length of protocol version strings | MaxVersionStringLen | v12 |
\( \MaxProposedExpiredOnlineAccounts \) | \( 32 \) | Maximum number of expired participation accounts in block header | MaxProposedExpiredOnlineAccounts | v31 | |
\( \PayoutMaxMarkAbsent \) | \( 32 \) | Maximum number of suspended participation accounts in block header | Payout.MaxMarkAbsent | v40 |
Block Rewards
Parameter | Current Value | Unit | Description | Reference Implementation Name | Last Update Version |
---|---|---|---|---|---|
\( \BonusBaseAmount \) | \( 10{,}000{,}000 \) | μALGO | Bonus to be paid when block rewards first applies | Bonus.BaseAmount | v40 |
\( \BonusBaseRound \) | round | Earliest round block rewards can apply | Bonus.BaseRound | v40 | |
\( \BonusDecayInterval \) | \( 1{,}000{,}000 \) | rounds | Time in rounds between 1% decays of the block rewards bonus component | Bonus.DecayInterval | v40 |
\( \PayoutsChallengeBits \) | \( 5 \) | bits | Frequency of online account challenges, about \( \PayoutsChallengeInterval \times 2^{\PayoutsChallengeBits} \) rounds | Payouts.ChallengeBits | v40 |
\( \PayoutsChallengeGracePeriod \) | \( 200 \) | rounds | Active challenge round lookback for the response interval \( [r-2\PayoutsChallengeGracePeriod, r-\PayoutsChallengeGracePeriod] \) | Payouts.ChallengeGracePeriod | v40 |
\( \PayoutsChallengeInterval \) | \( 1{,}000 \) | rounds | Online account challenges interval, it defines the challenges frequency | Payouts.ChallengeInterval | v40 |
\( \PayoutsGoOnlineFee \) | \( 2{,}000{,}000 \) | μALGO | Minimum keyreg transaction fee to be eligible for block rewards | Payouts.GoOnlineFee | v40 |
\( \PayoutsMaxBalance \) | \( 70{,}000{,}000{,}000{,}000 \) | μALGO | Maximum balance an account can have to be eligible for block rewards | Payouts.MaxBalance | v40 |
\( \PayoutsMinBalance \) | \( 30{,}000{,}000{,}000 \) | μALGO | Minimum balance an account must have to be eligible for block rewards | Payouts.MinBalance | v40 |
\( \PayoutsPercent \) | \( 50 \) | % | Percent of fees paid in a block that go to the proposer instead of the FeeSink | Payouts.Percent | v40 |
\( \RewardUnit \) | \( 1{,}000{,}000 \) | μALGO | Size of an earning unit | RewardUnit | v7 |
\( \RewardsRateRefreshInterval \) | \( 500{,}000 \) | rounds | Rate at which the reward rate is refreshed | RewardsRateRefreshInterval | v7 |
Protocol Upgrade
Parameter | Current Value | Unit | Description | Reference Implementation Name | Last Update Version |
---|---|---|---|---|---|
\( \DefaultUpgradeWaitRounds \) | \( 140{,}000 \) | rounds | Default number of rounds needed to prepare for an upgrade | DefaultUpgradeWaitRounds | v20 |
\( \MaxUpgradeWaitRounds \) | \( 250{,}000 \) | rounds | Maximum number of rounds needed to prepare for an upgrade | MaxUpgradeWaitRounds | v39 |
\( \MinUpgradeWaitRounds \) | \( 10{,}000 \) | rounds | Minimum number of rounds needed to prepare for an upgrade | MinUpgradeWaitRounds | v22 |
\( \UpgradeThreshold \) | \( 9{,}000 \) | Number of votes needed to execute an upgrade | UpgradeThreshold | v7 | |
\( \UpgradeVoteRounds \) | \( 10{,}000 \) | rounds | Number of rounds over which an upgrade proposal is open | UpgradeVoteRounds | v7 |
State Proof
Parameter | Current Value | Unit | Description | Reference Implementation Name | Last Update Version |
---|---|---|---|---|---|
\( \StateProofInterval \) | \( 256 \) | rounds | Number of rounds between state proofs | StateProofInterval | v34 |
\( \StateProofMaxRecoveryIntervals \) | \( 10 \) | Number of state proof intervals that the network will try to catch-up with | StateProofMaxRecoveryIntervals | v34 | |
\( \StateProofStrengthTarget \) | \( 256 \) | bits | Security parameter for State Proof1 | StateProofStrengthTarget | v34 |
\( \StateProofTopVoters \) | \( 1{,}024 \) | Maximum number of online accounts included in the vector commitment of state proofs participants | StateProofTopVoters | v34 | |
\( \StateProofVotersLookback \) | \( 16 \) | rounds | Delay in rounds for online participant information committed to in the block header for State Proof | StateProofVotersLookback | v34 |
\( \StateProofWeightThreshold \) | \( 2^{32} \times \frac{30}{100} \) | Fraction of participants proven to have signed by a State Proof | StateProofWeightThreshold | v34 |
Transaction
Parameter | Current Value | Unit | Description | Reference Implementation Name | Last Update Version |
---|---|---|---|---|---|
\( \MaxTxTail \) | \( 1{,}000 \) | Length of the Transaction Tail | |||
\( \MaxKeyregValidPeriod \) | \( 16{,}777{,}215 \) | rounds | Maximum voting range in a keyreg transaction, defined as \( (256 \times 2^{16})-1 \) | MaxKeyregValidPeriod | v31 |
\( \MaxTxGroupSize \) | \( 16 \) | txn | Maximum number of transactions allowed in a group | MaxTxGroupSize | v18 |
\( \MaxTxnLife \) | \( 1,000 \) | rounds | Maximum difference between last valid and first valid round, defines transaction lifespan in the pool | MaxTxnLife | v7 |
\( \MinTxnFee \) | \( 1{,}000 \) | μALGO | Minimum processing fee for any transaction | MinTxnFee | v7 |
\( \MaxTxnNoteBytes \) | \( 1{,}024 \) | bytes | Maximum length of a transaction note field | MaxTxnNoteBytes | v7 |
Account
Parameter | Current Value | Unit | Description | Reference Implementation Name | Last Update Version |
---|---|---|---|---|---|
\( \DefaultKeyDilution \) | \( 10{,}000 \) | rounds | Granularity of top-level ephemeral keys, equal to the number of second-level keys in each batch | DefaultKeyDilution | v7 |
\( \MinBalance \) | \( 100{,}000 \) | μALGO | Minimum balance requirement (MBR) for an account | MinBalance | v9 |
\( \MaxAssetsPerAccount \) | \( 0 \) (unlimited) | Maximum number of assets per account | MaxAssetsPerAccount | v32 | |
\( \MaxAppsCreated \) | \( 0 \) (unlimited) | Maximum number of application created per account | MaxAppsCreated | v32 | |
\( \MaxAppsOptedIn \) | \( 0 \) (unlimited) | Maximum number of application opted-in per account | MaxAppsOptedIn | v32 | |
\( \MaximumMinimumBalance \) | \( 0 \) (unlimited) | μALGO | Maximum MBR of an account | MaximumMinimumBalance | v32 |
Asset
Parameter | Current Value | Unit | Description | Reference Implementation Name | Last Update Version |
---|---|---|---|---|---|
\( \MaxAssetDecimals \) | \( 19 \) | Maximum decimal precision of the asset supply | MaxAssetDecimals | v20 | |
\( \MaxAssetNameBytes \) | \( 32 \) | bytes | Maximum length of asset name | MaxAssetNameBytes | v18 |
\( \MaxAssetUnitNameBytes \) | \( 8 \) | bytes | Maximum length of asset unit (symbol) | MaxAssetUnitNameBytes | v18 |
\( \MaxAssetURLBytes \) | \( 96 \) | bytes | Maximum length of asset URL | MaxAssetURLBytes | v28 |
LogicSig
Parameter | Current Value | Unit | Description | Reference Implementation Name | Last Update Version |
---|---|---|---|---|---|
\( \LogicSigMaxCost \) | \( 20{,}000 \) | opcodes | Maximum opcode cost for LSig | LogicSigMaxCost | v18 |
\( \LogicSigMaxSize \) | \( 1{,}000 \) | bytes | Maximum combined length of LSig program and LSig arguments | LogicSigMaxSize | v18 |
Application
Parameter | Current Value | Unit | Description | Reference Implementation Name | Last Update Version |
---|---|---|---|---|---|
\( \AppFlatOptInMinBalance \) | \( 100{,}000 \) | μALGO | MBR for opting in to a single application | AppFlatOptInMinBalance | v24 |
\( \AppFlatParamsMinBalance \) | \( 100{,}000 \) | μALGO | MBR for creating a single application | AppFlatParamsMinBalance | v24 |
\( \BoxByteMinBalance \) | \( 400 \) | μALGO / byte | MBR per byte of box storage | BoxByteMinBalance | v36 |
\( \BoxFlatMinBalance \) | \( 2{,}500 \) | μALGO | MBR per box created | BoxFlatMinBalance | v36 |
\( \BytesPerBoxReference \) | \( 2{,}048\) | bytes | Box read and write payload per reference | BytesPerBoxReference | v41 |
\( \MaxBoxSize \) | \( 32{,}768 \) | bytes | Maximum size of a box | MaxBoxSize | v36 |
\( \MaxAppArgs \) | \( 16 \) | Maximum number of arguments for an appl transaction | MaxAppArgs | v24 | |
\( \MaxAppBoxReferences \) | \( 8 \) | Maximum number of box references for an appl transaction | MaxAppBoxReferences | v36 | |
\( \MaxAppBytesValueLen \) | \( 128 \) | bytes | Maximum length of a bytes value used in an application’s state | MaxAppBytesValueLen | v28 |
\( \MaxAppKeyLen \) | \( 64 \) | bytes | Maximum length of a key used in an application’s state | MaxAppKeyLen | v24 |
\( \MaxAppProgramCost \) | \( 700 \) | opcodes | Maximum cost of application Approval or ClearState application program | MaxAppProgramCost | v24 |
\( \MaxAppProgramLen \) | \( 2{,}048 \) | bytes | Maximum length of application Approval or ClearState program page | MaxAppProgramLen | v28 |
\( \MaxAppSumKeyValueLens \) | \( 128 \) | bytes | Maximum sum of the lengths of the key and value of one app state entry | MaxAppSumKeyValueLens | v28 |
\( \MaxAppTotalArgLen \) | \( 2{,}048 \) | bytes | Maximum sum of the lengths of argument for an appl transaction | MaxAppTotalArgLen | v24 |
\( \MaxAppTotalProgramLen \) | \( 2{,}048 \) | bytes | Maximum combined length of application Approval and ClearState programs | MaxAppTotalProgramLen | v24 |
\( \MaxAppTotalTxnReferences \) | \( 8 \) | Maximum number of references for an appl transaction | MaxAppTotalTxnReferences | v24 | |
\( \MaxAppTxnAccounts \) | \( 8 \) | Maximum number of account references for an appl transaction | MaxAppTxnAccounts | v41 | |
\( \MaxAppTxnForeignApps \) | \( 8 \) | Maximum number of application references for an appl transaction | MaxAppTxnForeignApps | v28 | |
\( \MaxAppTxnForeignAssets \) | \( 8 \) | Maximum number of asset references for an appl transaction | MaxAppTxnForeignAssets | v28 | |
\( \MaxAppAccess \) | \( 16 \) | Maximum number of resources access list for an appl transaction | MaxAppAccess | v41 | |
\( \MaxExtraAppProgramPages \) | \( 3 \) | Maximum extra length for application program in pages | MaxExtraAppProgramPages | v28 | |
\( \MaxGlobalSchemaEntries \) | \( 64 \) | Maximum number of key/value pairs of application global state | MaxGlobalSchemaEntries | v24 | |
\( \MaxLocalSchemaEntries \) | \( 16 \) | Maximum number of key/value pairs of application local state | MaxLocalSchemaEntries | v24 | |
\( \MaxInnerTransactions \) | \( 16 \) | txn | Maximum number of inner transactions for an appl transaction | MaxInnerTransactions | v30 |
\( \SchemaBytesMinBalance \) | \( 25{,}000 \) | μALGO | Additional MBR for []bytes values in application state | SchemaBytesMinBalance | v24 |
\( \SchemaMinBalancePerEntry \) | \( 25{,}000 \) | μALGO | MBR for key-value pair in application state | SchemaMinBalancePerEntry | v24 |
\( \SchemaUintMinBalance \) | \( 35{,}00 \) | μALGO | Additional MBR for uint64 values in application state | SchemaUintMinBalance | v24 |
-
\( \StateProofStrengthTarget = 256 \) is the value set for State Proofs of type \(0 \). Other types of State Proofs might be added in the future. \( \StateProofStrengthTarget = \mathrm{target_{PQ}} \) is set to achieve post-quantum security for State Proof. For further details, refer to the State Proofs normative specification. ↩
$$ \newcommand \MaxTxTail {\mathrm{TxTail}_{\max}} \newcommand \App {\mathrm{App}} \newcommand \Box {\mathrm{Box}} $$
States
A Ledger (\( L \)) is a sequence of states which comprise the common information established by some instantiation of the Algorand protocol.
A Ledger is identified by a string called the genesis identifier, as well as a genesis hash that cryptographically commits to the starting state of the Ledger.
Each state consists of the following components:
-
The round of the state, which indexes into the Ledger’s sequence of states.
-
The genesis identifier and genesis hash, which identify the Ledger to which the state belongs.
-
The current protocol version and the upgrade state.
-
A timestamp, which is informational and identifies when the state was first proposed.
-
A seed, which is a source of randomness used to establish consensus on the next state.
-
The current reward state, which describes the policy at which incentives are distributed to participants.
-
The current account state, which holds account balances and participation keys for all stakeholding addresses.
- One component of this state is the transaction tail, which caches the transaction sets (see below) in the last \( \MaxTxTail \) blocks.
-
The current box state, which holds mappings from (\( \App_{ID} \), \( \Box_{ID} \)) tuples to box contents of arbitrary bytes.
$$ \newcommand \BonusDecayInterval {B_{b,\mathrm{decay}}} \newcommand \MaxProposedExpiredOnlineAccounts {B_{N_\mathrm{e},\max}} \newcommand \MinBalance {b_{\min}} \newcommand \PayoutsMaxBalance {A_{r,\max}} \newcommand \PayoutsMinBalance {A_{r,\min}} \newcommand \Heartbeat {\mathrm{hb}} \newcommand \PayoutsChallengeBits {\Heartbeat_\mathrm{bits}} \newcommand \PayoutsChallengeGracePeriod {\Heartbeat_\mathrm{grace}} \newcommand \PayoutsChallengeInterval {\Heartbeat_r} \newcommand \PayoutMaxMarkAbsent {B_{N_\mathrm{a},\max}} $$
Blocks
A block is a data structure that specifies the transition between states.
The data in a block is divided between the block header and its block body.
Block Header
The block header contains the following components:
Round
The block’s round, which matches the round of the state it is transitioning
into. (The block with round \( 0 \) is special in that this block specifies not
a transition but rather the entire initial state, which is called the genesis state.
This block is correspondingly called the genesis block).
The round is stored under msgpack key rnd
.
Genesis Identifier
The block’s genesis identifier and genesis hash, which match the genesis identifier
and hash of the states it transitions between (i.e., they stay constant since the
initial state forwards). The genesis identifier is stored under msgpack key gen
,
and the genesis hash is stored under msgpack key gh
.
Upgrade Vote
The block’s upgrade vote, which results in the new upgrade state. The block also duplicates the upgrade state of the state it transitions into. The msgpack representations of the upgrade vote components are described in detail below.
Timestamp
The block’s timestamp, which matches the timestamp of the state it transitions
into. The timestamp is stored under msgpack key ts
.
Seed
The block’s seed, which matches the seed of the
state it transitions into. The seed is stored under msgpack key seed
.
Reward Updates
The block’s reward updates, which results in the new reward state. The block also duplicates the reward state of the state it transitions into. The msgpack representations of the reward updates components are described in detail below.
Transaction Commitments
Cryptographic commitments to the block’s transaction sequence, described below (referred also as payset), using:
-
SHA-512/256 hash function, stored under msgpack key
txn
; -
SHA-256 hash function, stored under msgpack key
txn256
; -
SHA512 hash function, stored under msgpack key
txn512
.
Previous Hash
The block’s previous hash, which is the cryptographic hash of the previous Block Header in the sequence, using:
-
SHA-512/256 hash function, stored under msgpack key
prev
; -
SHA512 hash function, stored under msgpack key
prev512
.
The previous hash of the genesis block is \( 0 \)).
Transaction Counter
The block’s transaction counter, which is the total number of transactions issued
prior to this block. This count starts from the first block with a protocol version
that supports the transaction counter. The counter is stored in msgpack field tc
.
Proposer
The block’s proposer, which is the address of the account that proposed the
block. The proposer is stored in msgpack field prp
.
Fees Collected
The block’s fees collected is the sum of all fees paid by transactions in the
block and is stored in msgpack field fc
.
Bonus
The potential bonus incentive is the amount, in μALGO, that may be paid to the
proposer of this block beyond the amount available from fees. It is stored in msgpack
field bi
. It may be set during a consensus upgrade, or else it must be equal to
the value from the previous block in most rounds, or be \( 99 \% \) of the previous
value (rounded down) if the round of this block is \( 0 \mod \BonusDecayInterval \).
Proposer Payout
The proposer payout is the actual amount that is moved from the \( I_f \) to
the proposer, and is stored in msgpack field pp
. If the proposer is not eligible,
as described below, the proposer payout MUST be \( 0 \). The proposer payout
MUST NOT exceed
- The sum of the bonus incentive and half of the fees collected.
- The fee sink balance minus \( \MinBalance \).
Expired Participation Accounts
The block’s expired participation accounts, which contains an optional list of
account addresses. These accounts’ participation key
expire by the end of the current round, with exact rules below. The list is stored
in msgpack key partupdrmv
.
Suspended Participation Accounts
The block’s suspended participation accounts, which contains an optional list
of account addresses. These accounts have not recently demonstrated that they are
available and participating, with exact rules below. The list is stored in msgpack
key partupdabs
.
A proposer is eligible for bonus payouts if the account’s IncentiveEligible
flag is true and its online balance is between \( \PayoutsMinBalance \) and
\( \PayoutsMaxBalance \).
The expired participation accounts list is valid as long as:
-
The participation keys of all the accounts in the slice are expired by the end of the round;
-
The accounts themselves would have been online at the end of the round if they were not included in the list;
-
The number of elements in the list is less than or equal to \( \MaxProposedExpiredOnlineAccounts \). A block proposer may not include all such accounts in the list and may even omit the list completely.
The suspended participation accounts list is valid if, for each included address, the account is:
- Online;
- Incentive eligible;
- Either absent or failing a challenge as of the current round.
An account is absent if its LastHeartbeat
and LastProposed
rounds are both
more than \( 20n \) rounds before current
, where \( n \) is the reciprocal
of the account’s fraction of online stake.
An account is failing a challenge if:
- The first \( \PayoutsChallengeBits \) bits of the account’s address matches the first \( \PayoutsChallengeBits \) bits of an active challenge round’s block seed;
- The active challenge round is between \( \PayoutsChallengeGracePeriod \) and \( 2\PayoutsChallengeGracePeriod \) rounds before the current round.
An active challenge round is a round that is \( 0 \mod \PayoutsChallengeInterval \).
The length of the list MUST not exceed \( \PayoutMaxMarkAbsent \).
A block proposer MAY NOT include all such accounts in the list and MAY even omit the list completely.
Block Body
The block body is the block’s transaction sequence (also known as payset), which describes the sequence of updates (transactions) to the account state and box state.
Block Validity
A block is valid if each component is also valid. (The genesis block is always valid).
Applying a valid block to a state produces a new state by updating each of its components.
The rest of this document defines block validity and state transitions by describing them for each component.
Round
The round or round number is a 64-bit unsigned integer that indexes into the sequence of states and blocks.
The round \( r \) of each block is one greater than the round of the previous block (\( r_i = r_{i-1} + 1 \)).
Given a Ledger \( L \), the round of a block exclusively identifies it.
The rest of this document describes components of states and blocks with respect to some implicit Ledger. Thus, the round exclusively describes some component, and we denote the round of a component with a subscript. For instance, the timestamp of state/block \( r \) is denoted \( t_r \).
$$ \newcommand \Genesis {\mathrm{Genesis}} \newcommand \GenesisID {\Genesis{\mathrm{ID}}} \newcommand \Hash {\mathrm{Hash}} \newcommand \GenesisHash {\Genesis\Hash} $$
Genesis
Genesis Identifier
The genesis identifier is a short string that identifies an instance of a Ledger \( L \).
The genesis identifier of a valid block is the identifier of the block in the previous round. In other words, \( \GenesisID_{r+1} = \GenesisID_{r} \).
Genesis Hash
The genesis hash is a cryptographic hash of the genesis configuration, used to unambiguously identify an instance of the Ledger \( L \).
The genesis hash is set in the genesis block (or the block at which an upgrade to a protocol supporting \( \GenesisHash \) occurs), and MUST be preserved identically in all subsequent blocks.
$$ \newcommand \Prev {\mathrm{Prev}} \newcommand \Hash {\mathrm{Hash}} $$
Previous Hash
The previous hash is a cryptographic hash of the previous block header in the sequence of blocks.
The sequence of previous hashes in each block header forms an authenticated, linked-list of the reversed sequence.
Let \( B_r \) represent the block header in round \( r \), and let \( \Hash \) be some cryptographic hash function.
Then the previous hash \( \Prev_{r+1} \) in the block for round \( r+1 \) is \( \Prev_{r+1} = \Hash(B_r) \).
In the reference implementation, \( \Hash \) is the SHA512/256 hash function.
$$ \newcommand \MaxVersionStringLen {V_{\max}} \newcommand \DefaultUpgradeWaitRounds {\delta_x} \newcommand \MaxUpgradeWaitRounds {\delta_{x_{\max}}} \newcommand \MinUpgradeWaitRounds {\delta_{x_{\min}}} \newcommand \UpgradeThreshold {\tau} \newcommand \UpgradeVoteRounds {\delta_d} $$
Protocol Upgrade State
A protocol version \( v \) is a string no more than \( \MaxVersionStringLen \) bytes long. It corresponds to parameters used to execute some version of the Algorand protocol.
The upgrade vote in each block consists of:
- A protocol version \( v_r \);
- A 64-bit unsigned integer \( x_r \) which indicates the delay between the acceptance of a protocol version and its execution;
- A single bit \( b \) indicating whether the block proposer supports the given protocol version.
The upgrade state in each block/state consists of:
- The current protocol version \( v_r^{\ast} \);
- The next proposed protocol version \( v_r^{\prime} \);
- A 64-bit round number \( s_r \) counting the number of votes for the next protocol version;
- A 64-bit round number \( d_r \) specifying the deadline for voting on the next protocol version;
- A 64-bit round number \( x_r^{\prime} \) specifying when the next proposed protocol version would take effect, if passed.
An upgrade vote \( (v_r, x_r, b) \) is valid given the upgrade state \( (v_r^{\ast}, v_r^{\prime}, s_r, d_r, x_r^{\prime}) \) if \( v_r \) is the empty string or \( v_r^{\prime} \) is the empty string, \( \MinUpgradeWaitRounds \leq x_r \leq \MaxUpgradeWaitRounds \), and either:
- \( b = 0 \) or
- \( b = 1 \) with \( r < d_r \) and either
- \( v_r^{\prime} \) is not the empty string or
- \( v_r \) is not the empty string.
If the vote is valid, then the new upgrade state is
$$ (v_{r+1}^{\ast}, v_{r+1}^{\prime}, s_{r+1}, d_{r+1}, x_{r+1}) $$
Where
-
\( v_{r+1}^{\ast} \) is \( v_r^{\prime} \) if \( r = x_r^{\prime} \) and \( v_r^{\ast} \) otherwise.
-
\( v^{\prime}_{r+1} \) is
- the empty string if \( r = x_r^{\prime} \) or both \( r = s_r \) and \( s_r + b < \UpgradeThreshold \),
- \( v_r \) if \( v_r^{\prime} \) is the empty string, and
- \( v_r^{\prime} \) otherwise.
-
\( s_{r+1} \) is
- \( 0 \) if \( r = x_r^{\prime} \) or both \( r = s_r \) and \( s_r + b < \UpgradeThreshold \), and
- \( s_r + b \) otherwise
-
\( d_{r+1} \) is
- \( 0 \) if \( r = x_r^{\prime} \) or both \( r = s_r \) and \( s_r + b < \UpgradeThreshold \),
- \( r + \UpgradeVoteRounds \) if \( v_r^{\prime} \) is the empty string and \( v_r \) is not the empty string, and
- \( d_r \) otherwise.
-
\( x_{r+1} \) is
- \( 0 \) if \( r = x_r^{\prime} \) or both \( r = s_r \) and \( s_r + b < \UpgradeThreshold \),
- \( r + \UpgradeVoteRounds + \delta \) if \( v_r^{\prime} \) is the empty string and \( v_r \) is not the empty string (where \( \delta = \DefaultUpgradeWaitRounds \) if \( x_r = 0 \) and \( \delta = x_r \) if \( x_r \neq 0 \)), and
- \( x_r^{\prime} \) otherwise.
$$ \newcommand \MaxTimestampIncrement {\Delta t_{\max}} $$
Timestamp
The timestamp \( t \) is a 64-bit signed integer.
The timestamp is purely informational and states when a block was first proposed, expressed in the number of seconds since the Unix epoch (00:00:00 UTC on Thursday, 1 January 1970).
The timestamp \( t_{r+1} \) of a block in round \( r \) is valid if:
- \( t_{r} = 0 \) or
- \( t_{r+1} > t_{r} \) and \( t_{r+1} < t_{r} + \MaxTimestampIncrement \).
📎 EXAMPLE
Suppose the block production stalls on round \( r \) for a prolonged time. When correct operations resume, a certain number \( n \) of blocks has to be committed until the timestamp catches up to external time references. If \( t^{\ast} \) is the current external time reference, then:
$$ n = \left\lceil \frac{t^{\ast} - t_{r}}{\MaxTimestampIncrement} \right\rceil $$
$$ \newcommand \Seed {\mathrm{Seed}} $$
Cryptographic Seed
The seed is a 256-bit integer.
Seeds are validated and updated according to the specification of the Algorand Byzantine Fault Tolerance protocol.
The \( \Seed \) procedure specified there returns the seed from the desired round.
$$ \newcommand \Tx {\mathrm{Tx}} \newcommand \TxID {\Tx\mathrm{ID}} \newcommand \TxSeq {\Tx\mathrm{Seq}} \newcommand \TxCommit {\Tx\mathrm{Commit}} \newcommand \TxTail {\Tx\mathrm{Tail}} \newcommand \Hash {\mathrm{Hash}} \newcommand \SHATFS {\mathrm{SHA256}} \newcommand \SHAFOT {\mathrm{SHA512}} \newcommand \Sig {\mathrm{Sig}} \newcommand \STIB {\mathrm{STIB}} \newcommand \Genesis {\mathrm{Genesis}} \newcommand \GenesisID {\Genesis{\mathrm{ID}}} \newcommand \GenesisHash {\Genesis\Hash} \newcommand \ApplyData {\mathrm{ApplyData}} \newcommand {\abs}[1] {\lvert #1 \rvert} \newcommand \MaxTxnBytesPerBlock {B_{\max}} \newcommand \MaxTxTail {\mathrm{TxTail}_{\max}} $$
Transaction Sequences, Sets, and Tails
Transaction Sequence
Each block contains a transaction sequence, an ordered sequence of transactions in that block.
The transaction sequence of block \( r \) is denoted \( \TxSeq_r \).
Each valid block contains a transaction commitment \( \TxCommit_r \) which is a Merkle Tree Commitment to this sequence.
The leaves in the Merkle Tree are hashed as:
$$ \Hash(\texttt{TL}, \TxID, \Hash(\STIB)) $$
Where:
-
\( \Hash \) is the cryptographic SHA-512-256 hash function;
-
The \( \TxID \) is the 32-byte transaction identifier;
-
The \( \Hash(\STIB) \) is a 32-byte hash of the signed transaction and ApplyData for the transaction, hashed with the domain-separation prefix
STIB
(signed transaction in block).
Signed transactions in a block \( \STIB \) are encoded in a slightly different way than standalone transactions \( \Tx \), for efficiency:
If a standalone transaction \( \Tx \) contains a \( \GenesisID \) value, then:
-
The transaction’s \( \GenesisID \) MUST match the block’s \( \GenesisID \);
-
The transaction’s \( \GenesisID \) value MUST be omitted from the \( \STIB \) transaction’s msgpack encoding in the block;
-
The \( \STIB \) transaction’s msgpack encoding in the block MUST indicate the \( \GenesisID \) value was omitted by including a key
hgi
with the boolean valueTrue
.
Since transactions MUST include a \( \GenesisHash \) value, the \( \GenesisHash \) value of each transaction in a block MUST match the block’s \( \GenesisHash \), and the \( \GenesisHash \) value is omitted from the \( \STIB \) transaction as encoded in a block.
- Signed transactions in a block are also augmented with the \( \ApplyData \) that reflect how that transaction was applied to the Account State.
The transaction commitment (\( \TxCommit \)) for a block covers the transaction encodings with the changes described above.
Individual transaction signatures cover the original encoding of transactions as standalone transactions (\( \Tx \)).
In addition to the transaction commitment, each block contains SHA-256 and SHA-512 transaction commitments. They allow a verifier not supporting SHA-512/256 function to verify proof of membership for transactions.
To construct these commitments, we use a Vector Commitment.
The leaves in the Vector Commitment tree are hashed respectively as:
$$ \SHATFS(\texttt{TL}, \SHATFS(\TxID), \SHATFS(\STIB)) $$
and
$$ \SHAFOT(\texttt{TL}, \SHAFOT(\TxID), \SHAFOT(\STIB)) $$
Where:
-
\( \SHATFS \) is the cryptographic SHA-256 hash function;
-
\( \SHATFS(\TxID) = \SHATFS(\texttt{TX} || \Tx) \)
-
\( \SHATFS(\STIB) = \SHATFS(\texttt{STIB} || \Sig(\Tx) || \ApplyData) \)
-
\( \SHAFOT \) is the cryptographic SHA-512 hash function;
-
\( \SHAFOT(\TxID) = \SHAFOT(\texttt{TX} || \Tx) \)
-
\( \SHAFOT(\STIB) = \SHAFOT(\texttt{STIB} || \Sig(\Tx) || \ApplyData) \)
These Vector Commitments use SHA-256 and SHA-512 for internal nodes as well.
A valid transaction sequence \( \TxSeq \) contains no duplicates: each transaction in the transaction sequence MUST appear exactly once.
We can call the set of these transactions the transaction set (for convenience, we may also write \( \TxSeq_r \) to refer unambiguously to the set in this block).
For a block to be valid, its transaction sequence \( \TxSeq_r \) MUST be valid (i.e., no duplicate transactions may appear there).
All transactions have a size in bytes. The size of the transaction \( \Tx \) is denoted \( \abs{\Tx} \).
For a block to be valid, the sum of the sizes of each transaction in a transaction sequence MUST NOT exceed \( \MaxTxnBytesPerBlock \); in other words:
$$ \sum_{\Tx \in \TxSeq_r} \abs{\Tx} \leq \MaxTxnBytesPerBlock $$
Transaction Tails
The transaction tail \( \TxTail \) for a given round \( r \) is a set produced from the union of the transaction identifiers \( \TxID \) of each transaction in the last \( \MaxTxTail \) transaction sets and is used to detect duplicate transactions.
In other words,
$$ \TxTail_r = \bigcup_{r-\MaxTxTail \leq s \leq r-1} {\Hash(\Tx) | \Tx \in \TxSeq_s}. $$
As a result, the transaction tail for round \( r+1 \) is computed as follows:
$$ \TxTail_{r+1} = \TxTail_r \setminus {\Hash(\Tx) | \Tx \in \TxSeq_{r-T_{\max}}} \cup {\Hash(\Tx) | \Tx \in \TxSeq_r}. $$
The transaction tail is part of the Ledger state but is distinct from the account state and is not committed to in the block.
$$ \newcommand \Record {\mathrm{Record}} \newcommand \PartKey {\mathrm{PartKey}} \newcommand \Eligibility {\mathrm{A_e}} \newcommand \Stake {\mathrm{Stake}} \newcommand \Units {\mathrm{Units}} $$
Account State
The balances are a set of mappings from addresses, 256-bit integers, to balance records.
A balance record contains the following fields:
- The account raw balance in μALGO,
- The account status,
- The block incentive eligibility flag,
- The account last_proposed round,
- The account last_heartbeat round,
- The account rewards base and total awarded amount in μALGO,
- The account spending key,
- The account participation keys.
In the rest of this section, all references to Reward calculation are with respect to the legacy distribution rewards system. They are kept here for completeness and for backward compatibility.
The account raw balance \( a_I \) is a 64-bit unsigned integer which determines how much μALGO the address has.
The account rewards base \( a^\prime_I \) and total-awarded amount \(a^\ast_I \) are 64-bit unsigned integers.
Combined with the account balance, the reward base and total awarded amount are used to distribute rewards to accounts lazily.
The account stake is a function which maps a given account and round to the account’s balance in that round and is defined as follows:
$$ \Stake(r, I) = a_I + (T_r - a^\ast_I) \left\lfloor \frac{a_I}{A} \right\rfloor $$
unless \( p_I = 2 \) (see below), in which case:
$$ \Stake(r, I) = a_I $$
\( \Units(r) \) is a function that computes the total number of whole earning units present in a system at round \( r \).
A user owns \( \left\lfloor \frac{a_I}{A} \right\rfloor \) whole earning units, so the total number of earning units in the system is:
$$ \Units(r) = \sum_I \left\lfloor \frac{a_I}{A} \right\rfloor $$
for the \( a_I \) corresponding to round \( r \).
In this sum, online and offline accounts are taken into consideration.
The account status \( p_I \) is an 8-bit unsigned integer which is either \( 0, 1, 2 \):
- A status of 0 corresponds to an offline account,
- A status of 1 corresponds to an online account,
- A status of 2 corresponds to a non-participating account.
Combined with the account stake, the account status determines how much voting stake an account has, which is a 64-bit unsigned integer defined as follows:
- The account balance, if the account is online.
- 0 otherwise.
The account’s spending key determines how transactions from this account must be authorized (e.g., what public key to verify transaction signatures against).
Transactions from this account must have this value (or, if this value zero, the account’s address) as their authorization address. This is described in the Authorization and Signatures section.
The account’s participation keys \( \PartKey \) are defined in Algorand’s specification of participation keys.
The account’s eligibility \( \Eligibility \) is a flag that determines whether the account has elected to receive payouts for proposing blocks (assuming it meets balance requirements at the time of block proposal).
An account’s participation keys and voting stake from a recent round is returned by the \( \Record \) procedure in the Byzantine Agreement Protocol.
There exist two special addresses:
-
\( I_\mathrm{pool} \), the address of the incentive pool,
-
\( I_f \), the address of the fee sink.
For both of these accounts, \( p_I = 2 \).
$$ \newcommand \App {\mathrm{App}} \newcommand \MaxAppProgramCost {\App_{c,\max}} \newcommand \ExtraProgramPages {\mathrm{ExtraProgramPages}} \newcommand \MaxGlobalSchemaEntries {\App_{\mathrm{GS},\max}} \newcommand \MaxLocalSchemaEntries {\App_{\mathrm{LS},\max}} \newcommand \MaxAppTotalProgramLen {\App_{\mathrm{prog},t,\max}} \newcommand \MaxExtraAppProgramPages {\App_{\mathrm{page},\max}} \newcommand \MinBalance {b_{\min}} \newcommand \AppFlatOptInMinBalance {\App_{\mathrm{optin},\MinBalance}} \newcommand \AppFlatParamsMinBalance {\App_{\mathrm{create},\MinBalance}} \newcommand \MaxAppKeyLen {\App_{\mathrm{k},\max}} \newcommand \SchemaBytesMinBalance {\App_{\mathrm{b},\MinBalance}} \newcommand \SchemaMinBalancePerEntry {\App_{\mathrm{s},\MinBalance}} \newcommand \SchemaUintMinBalance {\App_{\mathrm{u},\MinBalance}} \newcommand \Box {\mathrm{Box}} \newcommand \MaxBoxSize {\Box_{\max}} \newcommand \BoxByteMinBalance {\Box_{\mathrm{byte},\MinBalance}} \newcommand \BoxFlatMinBalance {\Box_{\mathrm{flat},\MinBalance}} $$
Applications
Each account can create applications, each named by a globally-unique 64-bit unsigned integer (the application ID).
Applications are associated with a set of application parameters, which can be encoded as a msgpack struct:
-
A mutable stateful “Approval” program (
ApprovalProgram
), whose result determines whether anApplicationCall
transaction referring to this application ID is to be allowed. This program executes for allApplicationCall
transactions referring to this application ID except for those whoseOnCompletion == ClearState
, in which case theClearStateProgram
is executed instead. This program may modify the local or global state associated with this application. This field is encoded with msgpack fieldapprov
.-
For AVM Version 3 or lower, the program’s cost as determined by the Stateful
Check
function MUST NOT exceed \( \MaxAppProgramCost \). -
For AVM Version 4 or higher programs, the program’s cost during execution MUST NOT exceed \( \MaxAppProgramCost \).
-
-
A mutable stateful “Clear State” program (
ClearStateProgram
), executed when an opted-in user forcibly removes the local application state associated with this application from their account data. This happens when anApplicationCall
transaction referring to this application ID is executed withOnCompletion == ClearState
. This program, when executed, is not permitted to cause the transaction to fail. This program may modify the local or global state associated with this application. This field is encoded with msgpack fieldclearp
.-
For AVM Version 3 or lower, the program’s cost as determined by the Stateful
Check
function MUST NOT exceed \( \MaxAppProgramCost \). -
For AVM Version 4 or higher programs, the program’s cost during execution MUST NOT exceed \( \MaxAppProgramCost \).
-
-
An immutable “global state schema” (
GlobalStateSchema
), which sets a limit on the size of the global Key/Value Store that may be associated with this application (see State Schemas). This field is encoded with msgpack fieldgsch
.- The maximum number of values that this schema may permit is \( \MaxGlobalSchemaEntries \).
-
An immutable “local state schema” (
LocalStateSchema
), which sets a limit on the size of a Key/Value Store that this application will allocate in the account data of an account that has opted in (see “State Schemas”). This field is encoded with msgpack fieldlsch
.- The maximum number of values that this schema may permit is \( \MaxLocalSchemaEntries \).
-
An immutable “extra pages” value (
ExtraProgramPages
), which limits the total size of the application programs. The sum of the lengths ofApprovalProgram
andClearStateProgram
may not exceed \( \MaxAppTotalProgramLen \times (1+\ExtraProgramPages) \) bytes. This field is encoded with msgpack fieldepp
and may not exceed \( \MaxExtraAppProgramPages \). ThisExtraProgramPages
field is taken into account on application update as well. -
An “application version” (
Version
) value that begins at \( 0 \) when an Application is created or when the protocol version including this field goes into effect whichever is later. This field is encoded with msgpack fieldv
. -
The “global state” (
GlobalState
) associated with this application, stored as a Key/Value Store. This field is encoded with msgpack fieldgs
.
Each application created increases the minimum balance requirement of the creator
by \( \AppFlatParamsMinBalance \times (1+\ExtraProgramPages) \) μALGO, plus the
GlobalStateSchema
minimum balance contribution.
Each application opted in to increases the minimum balance requirements of the opting-in
account by \( \AppFlatOptInMinBalance \) μALGO plus the LocalStateSchema
minimum
balance contribution.
Key/Value Stores
A Key/Value Store, or KV, is an associative array mapping keys of type byte-array to values of type byte-array or 64-bit unsigned integer.
The values in a KV are either:
Bytes
, representing a byte-array,Uint
, representing an unsigned 64-bit integer value.
The maximum length of a key in a KV is \( \MaxAppKeyLen \) bytes.
State Schemas
A state schema represents limits on the number of each value type that may appear in a Key/Value Store.
State schemas control the maximum size of global and local state KVs.
A state schema is composed of two fields:
NumUint
represents the maximum number of integer values that may appear in some KV.NumByteSlice
represents the maximum number of byte-array values that may appear in some KV.
App Minimum Balance Changes
When an account opts in to an application or creates an application, the minimum balance requirements for that account increases. The minimum balance requirement is decreased equivalently when an account closes out or deletes an app.
When opting in to an application, there is a base minimum balance increase of
\( \AppFlatOptInMinBalance \) μALGO. There is an additional minimum balance increase,
in μALGO, based on the LocalStateSchema
for that application, described by the
following formula:
$$ (\SchemaMinBalancePerEntry + \SchemaUintMinBalance) \times \mathrm{NumUint} + (\SchemaMinBalancePerEntry + \SchemaBytesMinBalance) \times \mathrm{NumByteSlice} $$
When creating an application, there is a base minimum balance increase of
\( \AppFlatParamsMinBalance \) μALGO. There is an additional minimum balance increase
of \( \AppFlatParamsMinBalance \times \ExtraProgramPages \) μALGO. Finally,
there is an additional minimum balance increase, in μALGO, based on the GlobalStateSchema
for that application, described by the following formula:
$$ (\SchemaMinBalancePerEntry + \SchemaUintMinBalance) \times \mathrm{NumUint} + (\SchemaMinBalancePerEntry + \SchemaBytesMinBalance) \times \mathrm{NumByteSlice} $$
Boxes
The Box store is an associative array mapping keys of type: (uint64 x []byte)
to
values of type []byte
.
-
The key is a pair in which the first value corresponds to an Application ID, and the second is a box name, \( 1 \) to \( \MaxAppKeyLen \) bytes in length. Unlike Global/Local State keys, an empty array is not a valid Box name. However, empty Box names may appear in transactions to increase the I/O budget or allow creation of Boxes whose Application ID is not known at transaction group construction time (see below).
-
The value is a byte-array of length not greater than \( \MaxBoxSize \).
When an application executes an opcode that creates, resizes, or destroys a box, the minimum balance of the associated application account (whose address is the hash of the application ID) is modified.
When a box with name \( n \) and size \( s \) is created, the minimum balance requirement is raised by \( \BoxFlatMinBalance + \BoxByteMinBalance \times (\mathrm{len}(n) + s) \). The same amount is decremented from the minimum balance when the box is destroyed.
$$ \newcommand \MinBalance {b_{\min}} \newcommand \Asset {\mathrm{Asa}} \newcommand \MaxAssetDecimals {\Asset_{d,\max}} \newcommand \MaxAssetNameBytes {\Asset_{n,\max}} \newcommand \MaxAssetUnitNameBytes {\Asset_{u,\max}} \newcommand \MaxAssetURLBytes {\Asset_{r,\max}} $$
Assets
Each account can create assets, named by a globally-unique 64-bit unsigned integer (the asset ID).
Assets are associated with a set of asset parameters, which can be encoded as a msgpack struct:
-
The total number of units of the asset created, encoded with msgpack field
t
. This value MUST be between \( 0 \) and \( 2^{64}-1 \). -
The number of digits after the decimal place to be used when displaying the asset, encoded with msgpack field
dc
. The divisibility of the asset is given by \( 10^{-\mathrm{dc}} \). Adc
value of \( 0 \) represents an asset that is not divisible, while a value of \( 1 \) represents an asset divisible into tenths, \( 2 \) into hundredths, etc. This value MUST be between \( 0 \) and \( \MaxAssetDecimals \) (inclusive) (because \( 2^{64}-1 \) is \( 20 \) decimal digits integer). -
Whether holdings of that asset are frozen by default, a boolean flag encoded with msgpack field
df
. -
A string representing the unit name of the asset for display to the user, encoded with msgpack field
un
. This field does not uniquely identify an asset; multiple assets can have the same unit name. The maximum length of this field is \( \MaxAssetUnitNameBytes \) bytes. -
A string representing the name of the asset for display to the user, encoded with msgpack field
an
. This does not uniquely identify an asset; multiple assets can have the same name. The maximum length of this field is \( \MaxAssetNameBytes \) bytes. -
A string representing a URL that further describes the asset, encoded with msgpack field
au
. This does not uniquely identify an asset; multiple assets can have the same URL. The maximum length of this field is \( \MaxAssetURLBytes \) bytes. -
A 32-byte hash specifying a commitment to asset-specific metadata, encoded with msgpack field
am
. This does not uniquely identify an asset; multiple assets can have the same commitment. -
An address of a manager account, encoded with msgpack field
m
. The manager address is used to update the addresses in the asset parameters using an asset configuration transaction. The manager can destroy the asset if the creator account holds its total supply entirely. -
An address of a reserve account, encoded with msgpack field
r
. The reserve address is not used in the protocol, and is used purely by client software for user display purposes. -
An address of a freeze account, encoded with msgpack field
f
. The freeze address is used to issue asset freeze transactions. -
An address of a clawback account, encoded with msgpack field
c
. The clawback address is used to issue asset transfer transactions from arbitrary source addresses.
Parameters for assets created by an account are stored alongside the account state, denoted by a pair (address, asset ID).
Accounts can create and hold any number of assets.
An account must hold every asset that it created (even if it holds \( 0 \) units of that asset), until that asset is destroyed.
An account’s asset holding is simply a map from asset IDs to an integer value indicating how many units of that asset are held by the account, and a boolean flag indicating if the holding is frozen or unfrozen.
An account that holds any asset cannot be closed.
Asset Minimum Balance Changes
When an account opts in to an asset or creates an asset, the minimum balance requirements for that account increases. The minimum balance requirement is decreased equivalently when an account closes out or deletes an asset.
When opting in to an asset, there is a base minimum balance increase of \( \MinBalance \) μALGO.
When creating an asset, there is a base minimum balance increase of \( \MinBalance \) μALGO.
Consensus Participation Updates
Participation updates contain two lists of account addresses for which changes are made to their consensus participation status.
The first list contains accounts that have been deemed to be expired. An account is said to be expired when the last valid vote round in its participation key is strictly less than the current round that is being processed. Once included in this list, an account will be marked offline as part of applying the block changes to the Ledger.
The second list contains accounts that have been deemed to be suspended. An account
is said to be suspended according to the rules specified above for suspended participation
accounts list. Once included in this list, an account will be marked offline,
but its voting keys will be retained in the account state, as part of applying
the block changes to the Ledger. The IncentiveEligible
flag of the account will
be set to false.
$$ \newcommand \StateProof {\mathrm{SP}} \newcommand \StateProofInterval {\delta_\StateProof} $$
Light Block Header
A light block header is a structure containing a subset of fields from Algorand’s block header and the commitment of said block header.
Light block header contains the following components:
-
The block’s seed, under msgpack key
0
. -
The block’s hash, under msgpack key
1
. -
The block’s genesis hash, under msgpack key
gh
. -
The block’s round, under msgpack key
r
. -
The block’s SHA-256 transaction commitment, under msgpack key
tc
.
Commitment
The light block header commitment for rounds \( (X \cdot \StateProofInterval, \ldots, (X+1) \cdot \StateProofInterval] \) for some number \( X \), defined as the root of a vector commitment whose leaves are light block headers for rounds \( X \cdot \StateProofInterval, \ldots, (X+1) \cdot \StateProofInterval \) respectively.
The hash function SHA-256 is used to create this vector commitment.
$$ \newcommand \Proven {\mathrm{Proven}} \newcommand \W {\mathrm{Weight}} \newcommand \StateProof {\mathrm{SP}} \newcommand \StateProofInterval {\delta_\StateProof} \newcommand \StateProofVotersLookback {\delta_{\StateProof,b}} \newcommand \StateProofTopVoters {N_\StateProof} \newcommand \StateProofStrengthTarget {KQ_\StateProof} \newcommand \StateProofWeightThreshold {f_\StateProof} \newcommand \RewardUnit {U_r} $$
State Proofs
Message
A State Proof message for rounds \( (X \cdot \StateProofInterval, \ldots, (X+1) \cdot \StateProofInterval] \) for some number \( X \), contains the following components:
-
Light block headers commitment for rounds \( (X \cdot \StateProofInterval, \ldots, (X+1) \cdot \StateProofInterval] \), under msgpack key
b
. -
First attested round which would be equal to \( X \cdot \StateProofInterval + 1 \), under msgpack key
f
. -
Last attested round which would be equal to \( (X+1) \cdot \StateProofInterval \), under msgpack key
l
. -
Participant commitment used to verify state proof for rounds \( ((X+1) \cdot \StateProofInterval, \ldots, (X+2) \cdot \StateProofInterval] \), under msgpack key
v
. -
The value \( \ln(Proven\W) \) with \( 16 \) bits of precision that would be used to verify State Proof for rounds \( ((X+1) \cdot \StateProofInterval, \ldots, (X+2) \cdot \StateProofInterval] \), under msgpack key
P
. This field is calculated based on the total weight of the participants see state-proof-transaction
Tracking
Each block header keeps track of the state needed to construct, validate, and record State Proofs.
This tracking data is stored in a map under the msgpack key spt
in the block header.
The type of the State Proof indexes the map; at the moment, only type \( 0 \)
is supported. In the future, other types of state proofs might be added.
For type \( 0 \):
- \( \StateProofStrengthTarget = 256 \),
- \( \StateProofWeightThreshold = 2^{32} \times \frac{30}{100} \) (as the fraction numerator out of \( 2^{32} \)),
- \( \StateProofTopVoters = 1024 \),
- \( \StateProofInterval = 256 \),
- \( \StateProofVotersLookback = 16 \).
The value of the tracking data is a msgpack map with three elements:
-
Under key
n
, the next expected round of a State Proof should be formed. When upgrading from an earlier consensus protocol to a protocol that supports State Proofs, then
field is set to the lowest value such thatn
is a multiple of \( \StateProofInterval \) and so that then
is at least the first round of the new protocol (supporting State Proofs) plus \( \StateProofVotersLookback + \StateProofInterval \). This field is set in every block. -
Under key
v
, the root of the vector commitment to an array of participants that are eligible to vote in the State Proof at round \( \StateProofInterval \) from the current block. Only blocks whose round number is a multiple of \( \StateProofInterval \) have a non-zerov
field. -
Under key
t
, the total online stake at round \( \StateProofInterval \) (with pending rewards).
The participants committed to by the vector commitment are chosen in a specific fashion:
-
First off, because it takes some time to collect all of the online participants (more than the target assembly time for a block), the set of participants and total online non-expired stake appearing in a commitment in block at round \( r \) are actually based on the account state from round \( r-\StateProofVotersLookback \).
-
The participants are sorted by the number of μALGO they currently hold (including any pending rewards). This enables more compact proofs of pseudorandomly chosen participants weighted by their μALGO holdings. Only accounts in the online state are included in this list of participants.
-
To limit the worst-case size of this vector commitment, the array of participants contains just the top \( \StateProofTopVoters \) participants. Efficiently computing the top \( \StateProofTopVoters \) accounts by their μALGO balance is difficult in the presence of pending rewards. Thus, to make this top-\( \StateProofTopVoters \) calculation more efficient, we choose the top accounts based on a normalized balance, denoted below by \( n_I \).
The normalized balance is a hypothetical balance: consider an account \( I \) with current balance \( a_I \). If an account had a balance \( n_I \) in the genesis block, and did not perform any transactions since then, then its balance by the current round (when rewards are included) will be \( a_I \), except perhaps due to rounding effects.
In more detail, let \( r^\ast_I \) be the last round in which a transaction touched account \( I \) (and therefore all pending rewards were added to it). Consider the following quantities, as defined in the Account State:
-
The raw balance \( a_I \) of the account \( I \) at round \( r^\ast_I \) is its total balance on that round.
-
The rewards base \( a^\prime_I \) is meant to capture the total rewards allocated to all accounts up to round \( r^\ast_I \), expressed as a fraction of the total stake (with limited precision as described below).
Given these two quantities, the normalized balance of an online account \( I \) is \( \frac{a_I}{(1+a^\prime_I)} \).
📎 EXAMPLE
For example, if the total amount of rewards distributed up to round \( r^\ast_I \) is \( 20\% \) of the total stake, then the normalized balance is \( \frac{a_I}{1.2} \).
To limit the required precision in this calculation, the system uses a parameter \( \RewardUnit \) that specifies the rewards-earning unit, namely, accounts only earn rewards for a whole number of \( \RewardUnit \) μALGO. (Currently \( \RewardUnit = 1{,}000{,}000 \), so the rewards-earning unit is \( 1 \) ALGO.)
The parameter \(a^\prime_I \) above is an integer such that \( \frac{a^\prime_I}{\RewardUnit} \) is the desired fraction, rounded down to the precision of \( \frac{1}{\RewardUnit} \).
The normalized balance is computed as:
$$ n_I = \left\lfloor \frac{a_I \cdot \RewardUnit}{(a^\prime_I + \RewardUnit)} \right\rfloor. $$
Parameters
-
To limit the resources allocated for creating State Proofs, State Proof parameters are set to \( \StateProofTopVoters = 1024 \), \( \StateProofInterval = 256 \), and \( \StateProofVotersLookback = 16 \).
-
Setting \( \StateProofStrengthTarget = \mathrm{target_{PQ}} \) to achieve post-quantum security for State Proofs. For further details, refer to the State Proofs [normative
-
specification](../crypto/crypto-state-proofs.md).
-
Algorand assumes that at least \( 70\% \) of the participating stake is honest. Under this assumption, there can’t be a malicious State Proof that the verifier would accept and have a signed weight of more than \( 30\% \) of the total online stake. Hence, we set \( \StateProofWeightThreshold = 2^{32} \times \frac{30}{100} \) (as the numerator of a fraction out of \( 2^{32} \)).
$$ \newcommand \Tx {\mathrm{Tx}} \newcommand \TxSeq {\mathrm{TxSeq}} \newcommand \TxTail {\mathrm{TxTail}} \newcommand \TxType {\mathrm{TxType}} \newcommand \TxCommit {\mathrm{TxCommit}} \newcommand \vpk {\mathrm{vpk}} \newcommand \spk {\mathrm{spk}} \newcommand \sppk {\mathrm{sppk}} \newcommand \vf {\mathrm{vf}} \newcommand \vl {\mathrm{vl}} \newcommand \vkd {\mathrm{vkd}} \newcommand \Hash {\mathrm{Hash}} \newcommand \nonpart {\mathrm{nonpart}} \newcommand \RekeyTo {\mathrm{RekeyTo}} \newcommand \FirstValidRound {r_\mathrm{fv}} \newcommand \LastValidRound {r_\mathrm{lv}} \newcommand \Genesis {\mathrm{Genesis}} \newcommand \GenesisID {\Genesis\mathrm{ID}} \newcommand \GenesisHash {\Genesis\Hash} \newcommand \Group {\Tx\mathrm{G}} \newcommand \RekeyTo {\mathrm{RekeyTo}} \newcommand \MaxTxnNoteBytes {T_{m,\max}} $$
Transactions
Just as a block represents a transition between two Ledger states, a transaction \( \Tx \) represents a transition between two account states.
Algorand transactions have different transaction types.
Transaction fields are divided into a:
-
A header, common to any type,
-
A body, which is type-specific.
Transaction fields are REQUIRED unless specified as OPTIONAL.
The cryptographic hash of the transaction fields, including the transaction specific fields of the body, is called the transaction identifier. This is written as \( \Hash(\Tx) \).
Transaction Header
A transaction header contains the following fields:
FIELD | CODEC | TYPE | REQUIRED |
---|---|---|---|
Transaction Type | type | string | Yes |
Sender | snd | address | Yes |
Fee | fee | uint64 | Yes |
First Valid Round | fv | uint64 | Yes |
Last Valid Round | lv | uint64 | Yes |
Genesis Hash | gh | [32]byte | Yes |
Lease | lx | [32]byte | No |
Genesis Identifier | gen | string | No |
Group | grp | [32]byte | No |
Rekey-to | rekey | address | No |
Note | note | [32]bytes | No |
Transaction Type
The transaction type \( \TxType \) is a short string indicating the type of transaction.
The following transaction types are supported:
CODEC | DESCRIPTION |
---|---|
pay | ALGO transfers (payment) |
keyreg | Consensus keys registration |
acfg | Asset creation and configuration |
axfer | Asset transfer |
afrz | Asset freeze and unfreeze |
appl | Application calls |
stpf | State Proof |
hb | Consensus heartbeat |
Sender
The sender \( I \) identifies the account that authorized the transaction.
Fee
The fee \( f \) specifies the processing fee the sender pays to execute the transaction, expressed in μALGO.
The fee MAY be set to \( 0 \) if the transaction is part of a group.
First and Last Valid Round
The first valid round \( \FirstValidRound \) and last valid round \( \LastValidRound \), define a round interval for which the transaction MAY be executed.
Genesis Hash
The genesis hash \( \GenesisHash \) defines the Ledger for which this transaction is valid.
Genesis Identifier
The genesis identifier \( \GenesisID \) (OPTIONAL) defines the Ledger for which this transaction is valid.
Lease
The lease \( x \) (OPTIONAL) specifies transactions’ mutual exclusion. If \( x \neq 0 \) (i.e., \( x \) is set) and this transaction is confirmed, then this transaction prevents another transaction from the same sender and with the same lease from being confirmed until \( \LastValidRound \) is confirmed.
Group
The group \( \Group \) (OPTIONAL) is a commitment whose meaning is described in the Transaction Groups section.
Rekey-to
The rekey to \( \RekeyTo \) (OPTIONAL) is an address. If nonzero, the transaction will set the sender account’s authorization address field to this value. If the \( \RekeyTo \) address matches the sender address, then the authorization address is instead set to zero, and the original spending keys are re-established.
The rekey functionally works as if the account replaces its private spending keys, while its address remains the same. The account is now controlled by the authorization address (i.e., transaction signatures are checked against this address).
Note
The note \( N \) (OPTIONAL) contains arbitrary data appended to the transaction.
- The note byte length MUST NOT exceed \( \MaxTxnNoteBytes \).
Semantic
TODO
Validation
TODO
Payment Transaction
Fields
A payment transaction additionally has the following fields:
FIELD | CODEC | TYPE | REQUIRED |
---|---|---|---|
Amount | amt | uint64 | Yes |
Receiver | rcv | address | Yes |
Close-to | close | address | No |
Amount
The amount \( a \) indicates the number of μALGO being transferred by the payment transaction.
If the amount is omitted (\( a = 0 \)), the number of μALGO transferred is zero.
Receiver
The receiver \( I^\prime \) specifies the receiver of the amount in the payment transaction.
If the receiver is omitted (\( I^\prime = 0 \)), the amount is transferred to the zero address.
Close-to
The close to address \( I_0 \) (OPTIONAL) collects all remaining μALGO in the sender account after the payment transfer.
If the close to address is omitted (\( I_0 = 0 \)), the field has no effect.
Semantic
TODO
Validation
TODO
$$ \newcommand \PartKey {\mathrm{PartKey}} \newcommand \VoteKey {\mathrm{vpk}} \newcommand \SelectionKey {\mathrm{spk}} \newcommand \StateProofKey {\mathrm{sppk}} \newcommand \VoteFirstValid {v_\mathrm{fv}} \newcommand \VoteLastValid {v_\mathrm{lv}} \newcommand \KeyDilution {\mathrm{KeyDilution}} \newcommand \NonPart {\mathrm{nonpart}} \newcommand \LastValidRound {r_\mathrm{lv}} \newcommand \MaxKeyregValidPeriod {K_{\Delta r,\max}} $$
Key Registration Transaction
Fields
A key registration transaction additionally has the following fields:
FIELD | CODEC | TYPE | REQUIRED |
---|---|---|---|
Vote Public Key | votekey | [32]byte | Yes (for Online) |
Selection Public Key | selkey | [32]byte | Yes (for Online) |
State Proof Public Key | sprfkey | [64]byte | Yes (for Online) |
Vote First | votefst | uint64 | Yes (for Online) |
Vote Last | votelst | uint64 | Yes (for Online) |
Vote Key Dilution | votekd | uint64 | Yes (for Online) |
Non-Participation | nonpart | bool | No |
Vote Public Key
The vote public key \( \VoteKey \) is the (root) Ed25519 public authentication key of an account’s participation keys (\( \PartKey \)).
Selection Public Key
The selection public key \( \SelectionKey \) is the public VRF key of an account’s participation keys (\( \PartKey \)).
State Proof Public Key
The state proof public key \( \StateProofKey \) is the public commitment to the account’s State Proof keys \( \StateProofKey \).
If \( \VoteKey \), \( \SelectionKey \), and \( \StateProofKey \) are all omitted, the transaction deregisters the account’s participation key set, and as a result marks the account as offline (that is, non-participating in the agreement).
Vote First
The vote first \( \VoteFirstValid \) indicates first valid round (inclusive) of an account’s participation key sets.
Vote Last
The vote last \( \VoteLastValid \) indicates last valid round (inclusive) of an account’s participation key sets.
Vote Key Dilution
The vote key dilution defines the number of rounds for generating a new (second-level) ephemeral participation key. The higher the number, the more “dilution” is added to the authentication key’s security.
For further details on the two-level ephemeral key scheme used for consensus participation authentication, refer to the Algorand Participation Key Specification.
Non-Participation
The non-participation \( \NonPart \) (OPTIONAL) is flag which, when deregistering
keys, specifies whether to mark the account just as offline (if \( \NonPart \)
is false
) or as non-participatory (if \( \NonPart \) is true
).
The non-participatory status is set to true the account is irreversibly excluded from consensus participation (i.e., can no longer be marked as online) and from the legacy distribution rewards mechanism.
Semantic
TODO
Validation
For a key registration transaction to be valid, the following conditions MUST apply:
- The elements of the set \( (\VoteKey, \SelectionKey, \StateProofKey, \KeyDilution) \) are REQUIRED to be all present, or all omitted (clear).
Providing the default value or the empty value, for any of the set members, would be interpreted as if these values were omitted.
-
\( \VoteFirstValid \leq \VoteLastValid \).
-
If the set \( (\VoteKey, \SelectionKey, \StateProofKey, \KeyDilution) \) is clear, then \( \VoteFirstValid \) and \( \VoteLastValid \) MUST be clear as well.
-
If the set \( (\VoteKey, \SelectionKey, \StateProofKey, \KeyDilution) \) is not clear, the following MUST apply:
-
\( \VoteFirstValid \leq r + 1 \) and \( \VoteLastValid > r \), where \( r \) is the current network round (the round of the last block committed).
-
\( \VoteFirstValid \leq \LastValidRound + 1 \).
-
\( \VoteLastValid - \VoteFirstValid < \MaxKeyregValidPeriod \).
It is RECOMMENDED that \( \VoteLastValid - \VoteFirstValid \leq 3{,}000{,}000 \) rounds for security reasons, to ensure safe rotation of participation keys.
$$ \newcommand \Asset {\mathrm{Asa}} \newcommand \MaxAssetDecimals {\Asset_{d,\max}} \newcommand \MaxAssetNameBytes {\Asset_{n,\max}} \newcommand \MaxAssetUnitNameBytes {\Asset_{u,\max}} \newcommand \MaxAssetURLBytes {\Asset_{r,\max}} $$
Asset Configuration Transaction
Fields
An asset configuration transaction additionally has the following fields:
FIELD | CODEC | TYPE | REQUIRED |
---|---|---|---|
Asset ID | caid | uint64 | Yes (except for Creation) |
Asset Parameters | apar | struct | Yes (for Creation) |
Asset ID
The asset ID \( \Asset_\mathrm{cfg,ID} \) identifies the asset being configured.
If the asset ID is omitted (zero), this transaction is creating an asset.
Asset Parameters
The asset parameters are the parameters for configuring the asset.
These asset parameters is structure containing:
FIELD | CODEC | TYPE | DESCRIPTION |
---|---|---|---|
Total | t | uint64 | Total amount of units of the asset |
Decimals | dc | uint32 | Number of digits after the decimal place |
Default Frozen | df | bool | Flag that specifies if the asset requires whitelisting (yes if true ) |
Unit Name | un | string | Asset unit symbol (or asset short-name) |
Asset Name | an | string | Asset name |
URL | au | string | URL to retrieve additional asset information |
Metadata Hash | am | [32]byte | Commitment to asset metadata |
Manager | m | address | Account allowed to set the asset role-based access control and destroy the asset |
Reserve | r | address | Account whose asset holdings should be interpreted as “not mined” (this is purely a label) |
Freeze | f | address | Account allowed to change the account’s frozen state for the asset holdings |
Clawback | c | address | Account allowed to transfer units of the asset from any account |
-
The decimals MUST NOT exceed \( \MaxAssetDecimals \).
-
The unit name byte length MUTS NOT exceed \( \MaxAssetUnitNameBytes \).
-
The asset name byte length MUST NOT exceed \( \MaxAssetNameBytes \).
-
The URL byte length MUST NOT exceed \( \MaxAssetURLBytes \).
If the asset parameters are omitted (struct of zero values), this transaction is deleting the asset.
Semantic
TODO
Validation
TODO
$$ \newcommand \Asset {\mathrm{Asa}} $$
Asset Transfer Transaction
Fields
An asset transfer transaction additionally has the following fields:
FIELD | CODEC | TYPE | REQUIRED |
---|---|---|---|
Asset ID | xaid | uint64 | Yes |
Asset Amount | aamt | uint64 | No |
Asset Sender | asnd | address | No |
Asset Receiver | arcv | address | Yes |
Asset Close-to | aclose | address | No |
Asset ID
The asset ID \( \Asset_\mathrm{xfer,ID} \) identifies the asset being transferred.
Asset Amount
The asset amount \( \Asset_a \) (OPTIONAL) indicates the number of asset units being transferred.
If the asset amount is omitted (\( \Asset_a = 0 \)), the number of asset units transferred is zero.
Asset Sender
The asset sender \( \Asset_I \) (OPTIONAL) identifies the source address for the asset transfer (non-zero if the transaction is a clawback).
If the asset sender is omitted (\( \Asset_I = 0 \)), the source address of the asset transfer is the sender of the transaction.
Asset Receiver
The asset receiver \( \Asset_{I^\prime} \) identifies the destination address for the asset transfer.
Asset Close-to Address
The asset close to address \( \Asset_{I_0}\) (OPTIONAL) collects all remaining asset units in the sender account after the asset transfer.
If the asset close to address is omitted (\( \Asset_{I_0}\)), the field has no effect.
Semantic
TODO
Validation
TODO
$$ \newcommand \Asset {\mathrm{Asa}} $$
Asset Freeze Transaction
Fields
An asset freeze transaction additionally has the following fields:
FIELD | CODEC | TYPE | REQUIRED |
---|---|---|---|
Asset ID | faid | uint64 | Yes |
Freeze Address | fadd | address | Yes |
Frozen Status | afrz | bool | Yes |
Asset ID
The asset ID \( \Asset_\mathrm{frz,ID} \) identifies the asset being frozen or unfrozen.
Freeze Address
The freeze address \( I_\mathrm{frz} \) identifies the account whose holdings of the asset ID should be frozen or unfrozen.
Frozen Status
The frozen status \( \Asset_f \) is a flag setting of whether the freeze address
holdings of asset ID should be frozen (true
) or unfrozen (false
).
Semantic
TODO
Validation
TODO
$$ \newcommand \App {\mathrm{App}} \newcommand \MaxAppTotalTxnReferences {\App_{r,\max}} \newcommand \MaxExtraAppProgramPages {\App_{\mathrm{page},\max}} \newcommand \MaxAppProgramLen {\App_{\mathrm{prog},\max}} \newcommand \MaxGlobalSchemaEntries {\App_{\mathrm{GS},\max}} \newcommand \MaxLocalSchemaEntries {\App_{\mathrm{LS},\max}} \newcommand \MaxAppArgs {\App_{\mathrm{arg},\max}} \newcommand \MaxAppTotalArgLen {\App_{\mathrm{ay},\max}} \newcommand \MaxAppTxnAccounts {\App_{\mathrm{acc},\max}} \newcommand \MaxAppTxnForeignApps {\App_{\mathrm{app},\max}} \newcommand \MaxAppTxnForeignAssets {\App_{\mathrm{asa},\max}} \newcommand \MaxAppAccess {\App_{\mathrm{access},\max}} \newcommand \Box {\mathrm{Box}} \newcommand \MaxAppBoxReferences {\App_{\Box,\max}} \newcommand \MaxAppKeyLen {\App_{\mathrm{k},\max}} $$
Application Call Transaction
Fields
An application call transaction additionally has the following fields:
FIELD | CODEC | TYPE | REQUIRED |
---|---|---|---|
Application ID | apid | uint64 | Yes (except for Creation) |
On Completion Action | apan | uint64 | Yes |
Approval Program | apap | []byte | No |
Clear State Program | apsu | []byte | No |
Reject Version | aprv | uint64 | No |
Extra Program Pages | apep | uint64 | No |
Global State Schema | apgs | struct | No |
Local State Schema | apls | struct | No |
Arguments | apaa | [][]byte | No |
Foreign Accounts | apat | []address | No |
Foreign Applications | apfa | []uint64 | No |
Foreign Assets | apas | []uint64 | No |
Box References | apbx | []struct | No |
Access List | al | []struct | No |
The sum of the elements in foreign accounts (apat
), foreign applications (apfa
),
foreign assets (apas
), and box references (apbx
) MUST NOT exceed \( \MaxAppTotalTxnReferences \).
Application ID
The application ID identifies the application being called.
If the application ID is omitted (zero), this transaction is creating an application.
On Completion Action
The on completion action specifies an additional effect of this transaction on the application’s state in the sender or application creator’s account data.
The apan
field values are enumerated as follows:
ACTION | VALUE | EFFECT |
---|---|---|
NoOpOC | 0 | Only execute the Approval Program associated with this application ID, with no additional effects. |
OptInOC | 1 | Before executing the Approval Program, allocate local state for this application ID into the sender’s account data. |
CloseOutOC | 2 | After executing the Approval Program, clear any local state for this application ID out of the sender’s account data. |
ClearStateOC | 3 | Do not execute the Approval Program, and instead execute the Clear State Program (which may not reject this transaction). Additionally, clear any local state for this application ID out of the sender’s account data (as in CloseOutOC ). |
UpdateApplicationOC | 4 | After executing the Approval Program, replace the Approval Program and Clear State Program associated with this application ID with the programs specified in this transaction. |
DeleteApplicationOC | 5 | After executing the Approval Program, delete the parameters of with this application ID from the account data of the application’s creator. |
Approval Program
The approval program (OPTIONAL) contains the approval program bytecode.
This field is used for application creation and updates, and sets the corresponding application’s Approval Program.
Clear State Program
The clear state program (OPTIONAL) contains the clear state program bytecode.
This field is used for application creation and updates, and sets the corresponding application’s Clear State Program.
- The Approval Program and the Clear State Program MUST have the same version number if either is \( 6 \) or higher.
Reject Version
The reject version (OPTIONAL), if set to a positive number, specifies that the application call MUST fail unless the reject version value exceeds the Application Version.
For further details on Application Versions, refer to the Applications Specifications.
Extra Program Pages
A program page (OPTIONAL) is a chunk of application program bytecode. The extra program pages define the number of program pages besides the first one.
This field is only used during application creation, and requests an increased maximum size for the Approval Program or Clear State Program.
-
There MUST NOT be more than \( \MaxExtraAppProgramPages \) extra program pages,
-
The program page byte length MUST NOT exceed \( \MaxAppProgramLen \).
Global State Schema
The global state schema (OPTIONAL) defines the global state allocated into the creator account that of the application ID.
The global state schema is a structure containing:
FIELD | CODEC | TYPE | DESCRIPTION |
---|---|---|---|
Number of Uints | nui | uint64 | The number of global 64-bit unsigned integer variables for the application. |
Number of Bytes | nbs | uint64 | The number of global byte-array variables for the application. |
This field is only used during application creation, and sets bounds on the size of the global state associated with this application.
- There MUST NOT be more than \( \MaxGlobalSchemaEntries \) global variables for the application.
TODO: Restrictions on key-value lengths
Local State Schema
The local state schema (OPTIONAL) defines the local state allocated into the accounts that opt-in the application ID.
The local state schema is a structure containing:
FIELD | CODEC | TYPE | DESCRIPTION |
---|---|---|---|
Number of Uints | nui | uint64 | The number of local 64-bit unsigned integer variables for the application. |
Number of Bytes | nbs | uint64 | The number of local byte-array variables for the application. |
This field is only used during application creation, and sets bounds on the size of the local state for accounts that opt in to this application.
- There MUST NOT be more than \( \MaxLocalSchemaEntries \) local variables for the application.
TODO: Restrictions on key-value lengths
Arguments
The application arguments (OPTIONAL) list provides byte-array arguments to the application being called.
-
There MUST NOT be more than \( \MaxAppArgs \) entries in this list,
-
The sum of their byte lengths MUST NOT exceed \( \MaxAppTotalArgLen \).
Foreign Accounts
The foreign accounts (OPTIONAL) list specifies the accounts, besides the sender, whose local states MAY be referred to by executing the Approval Program or the Clear State Program. These accounts are referred to by their 32-byte addresses.
- There MUST NOT be more than \( \MaxAppTxnAccounts \) entries in this list.
Foreign Applications
The foreign applications (OPTIONAL) list specifies the application IDs, besides the application whose Approval Program or Clear State Program is executing, that the executing program MAY read global state from. These applications are referred to by their 64-bit unsigned integer IDs.
- There MUST NOT be more than \( \MaxAppTxnForeignApps \) entries in this list.
Foreign Assets
The foreign assets (OPTIONAL) list specifies the asset IDs from which the executing Approval Program or Clear State Program MAY read asset parameters. These assets are referred to by their 64-bit unsigned integer IDs.
- There MUST NOT be more than \( \MaxAppTxnForeignAssets \) entries in this list.
Box References
The box references (OPTIONAL) list specifies the boxes that the executing Approval Program (with AVM Version 9 or later), or any other Approval Program in the same group, MAY access for reading or writing when the box reference matches the running program application IDs.
For further details on AVM resource availability, refer to the AVM Specifications.
Each element of the box reference encodes a structure containing:
FIELD | CODEC | TYPE | DESCRIPTION |
---|---|---|---|
Index | i | uint64 | A \( 1 \)-based index in the foreign applications (apfa ) list. |
Name | n | []byte | The box identifier. |
An omitted index (i
) is interpreted as the application ID of this transaction
(apid
, or the ID allocated for the created application when apid
is \( 0 \)).
-
There MUST NOT be more than \( \MaxAppBoxReferences \) entries in this list.
-
The box name byte length MUST NOT exceed \( \MaxAppKeyLen \).
Access List
The access list (OPTIONAL) specifies resources (accounts, application IDs, asset IDs, and box references) that the executing Approval Program (with AVM Version 9 or later), or any other Approval Program in the same group, MAY access for reading or writing.
For further details on AVM resource availability, refer to the AVM Specifications.
Each element of the access list encodes one of the following resources:
FIELD | CODEC | TYPE | DESCRIPTION |
---|---|---|---|
Address | d | []byte | An Account |
Asset | s | uint64 | An Asset ID |
Application | p | uint64 | An Application ID |
Holding | h | struct | A Holding contains subfields d and s that refer to the Address and Asset of the Holding, respectively. |
Locals | l | struct | A Local contains subfields d and p that refer to the Address and Application of the Local State, respectively. |
Box Reference | b | struct | A Box Reference contains subfields i and n , that refer to the Application and Box Name, respectively. |
The subfields of h
, l
, b
refer to Accounts, Assets, and Applications by using
an \( 1 \)-based index into the Access List.
An omitted d
indicates the sender of the transaction.
An omitted app (p
or i
) indicates the called Application.
The access list MUST be empty if any of the foreign arrays (apat
, apfa
,
apas
), or box reference list (apbx
) fields are populated.
- There MUST NOT be more than \( \MaxAppAccess \) entries in this list.
Validation
TODO
Semantic
TODO
$$ \newcommand \Proven {\mathrm{Proven}} \newcommand \Total {\mathrm{Total}} \newcommand \W {\mathrm{Weight}} \newcommand \StateProof {\mathrm{SP}} \newcommand \StateProofInterval {\delta_\StateProof} \newcommand \StateProofWeightThreshold {f_\StateProof} \newcommand \Offset {\mathrm{Offset}} $$
State Proof Transaction
The state proof is a special transaction used to disseminate and store State Proofs.
Fields
A state proof transaction additionally has the following fields:
FIELD | CODEC | TYPE | REQUIRED |
---|---|---|---|
State Proof Type | sptype | uint64 | Yes |
State Proof | sp | struct | Yes |
Message | spmsg | struct | Yes |
State Proof Last Round | sprnd | uint64 | Yes |
State Proof Type
The state proof type identifies the type of the State Proof.
Currently, always \( 0 \).
State Proof
The state proof structure as defined in the State Proof specification.
Message
The message is a structure that composes the State Proof message, whose hash is being attested to by the State Proof.
The message structure is defined in the State Proof message section.
Validation
In order for a state proof transaction to be valid, the following conditions MUST be meet:
-
The transaction type MUST be
stpf
. -
The sender MUST be equal to a special address, which is the hash of the domain-separation prefix
SpecialAddr
(see the corresponding section in the Algorand Cryptographic Primitive Specification) with the string constantStateProofSender
. -
The fee MUST be \( 0 \).
-
The lease MUST be omitted.
-
The group MUST be omitted.
-
The rekey to MUST be omitted.
-
The note MUST be omitted.
-
The transaction MUST NOT have any signature.
-
The state proof round (defined in the message structure) MUST be exactly equal to the next expected State Proof round in the block header, as described in the State Proof tracking section.
-
The state proof verification code MUST return
true
(see State Proof validity), given the State Proof message and the State Proof transaction fields.
In addition, the verifier should also be given a trusted commitment to the participant array and \( \Proven\W \) value. The trusted data SHOULD be taken from the Ledger at the relevant round.
To encourage the formation of shorter State Proof, the rule for validity of state proof transactions is dependent on the first valid round in the transaction.
In particular, the signed weight of a State Proof MUST be:
-
Equal to the total online stake, \( \Total\W \), if the first valid round on the transaction is no greater than the state proof round (defined in the message structure) plus \( \frac{\StateProofInterval}{2} \).
-
At least \( \Proven\W + (\Total\W - \Proven\W) \times \frac{\Offset}{\frac{\StateProofInterval}{2}} \), if the first valid round on the transaction is the state proof round (defined in the message structure) plus \( \frac{\StateProofInterval}{2} + \Offset \).
-
At least the minimum weight being proven by the proof, \( \Proven\W \), if the first valid round on the transaction is no less than state proof round (defined in the message structure) plus \( \StateProofInterval \).
Where \( \Proven\W = \frac{\Total\W \times \StateProofWeightThreshold}{2^{32}} \)
When a state proof transaction is applied to the state, the next expected State Proof round for that type of State Proof is incremented by \( \StateProofInterval \).
A node should be able to verify a state proof transaction at any time, even if the transaction first valid round is greater than the next expected State Proof round in the block header.
Semantic
TODO
Heartbeat Transaction
The heartbeat is a special transaction used to challenge the liveness of an online account.
Fields
A heartbeat transaction additionally has the following fields:
FIELD | CODEC | TYPE | REQUIRED |
---|---|---|---|
Heartbeat Address | a | address | Yes |
Heartbeat Seed | sd | [32]bytes | Yes |
Heartbeat Vote ID | vid | [32]bytes | Yes |
Heartbeat Key Dilution | kd | uint64 | Yes |
Heartbeat Proof | prf | struct | Yes |
Heartbeat Address
The heartbeat address is an account address that this heartbeat transaction proves liveness for.
Heartbeat Seed
The heartbeat seed is a block seed.
- It MUST be the block seed found in the first valid round of the transaction.
Heartbeat Vote ID
The heartbeat vote id is a public key.
- It MUST be the current public key of the root voting key of the heartbeat address’s account state.
Heartbeat Key Dilution
The heartbeat key dilution is a key dilution parameter (see Algorand Participation Keys specification).
- It MUST be the current key dilution of the heartbeat address’s account state.
Heartbeat Proof
The heartbeat proof MUST contain a valid signing of the heartbeat seed using the heartbeat vote id and the heartbeat key dilution using the voting signature scheme defined in the Algorand Participation Keys specification.
Validation
TODO
Semantic
TODO
$$ \newcommand \Hash {\mathrm{Hash}} \newcommand \MaxTxGroupSize {GT_{\max}} \newcommand \MinTxnFee {T_{\Fee,\min}} \newcommand \BytesPerBoxReference {\Box_{\mathrm{IO}}} \newcommand \LogicSigMaxSize {\LogicSig_{\max}} $$
Transaction Groups
A transaction MAY include a group field (msgpack codec grp
), a 32-byte hash
that specifies what transaction group the transaction belongs to.
Informally, a transaction group is an ordered list of transactions that MUST all be confirmed together, in the specified order, and included in the same block. If any transaction in the group fails to be confirmed, none of them will be.
The group field, in each transaction part of the same group, is set to the hash representing a commitment to the entire group. This group hash is computed by creating an ordered list of the hashes of all transactions in the group. When computing each transaction’s hash for this purpose, its own group field is omitted to avoid circular dependency.
📎 EXAMPLE
A user wants to require transaction \( A \) to confirm if and only if transactions \( B \) and \( C \) confirm in a certain order. The user performs the following procedure:
Specifies transactions’ order: \( [A, B, C] \);
Computes the hashes of each transaction in the list (without their group field set): \( [\Hash(A), \Hash(B), \Hash(C)] \);
Hash them together in the specified order, getting the group hash: \( G = \Hash([\Hash(A), \Hash(B), \Hash(C)]) \);
Set the group field of all the transactions to the group hash (\( G \)) before signing them.
A group MAY contain no more than \( \MaxTxGroupSize \) transactions.
More formally, when evaluating a block, the \( i \)-th and \( i+1 \)-th transactions in the payset are considered to belong to the same transaction group if the group fields of the two transactions are equal and non-zero.
The block may now be viewed as an ordered sequence of transaction groups, where each transaction group is a contiguous sublist of the payset consisting of one or more transactions with equal group field.
For each transaction group where the transactions have non-zero group, compute the group hash as follows:
-
Take the hash of each transaction in the group, but with its group field omitted.
-
Hash this ordered list of hashes. More precisely, hash the canonical msgpack encoding of a structure with a field
txlist
containing the list of hashes, usingTG
as domain separation prefix.
If the group hash of any transaction group in a block does not match the group field of the transactions in that group (and that group field is non-zero), then the block is invalid.
Additionally, if a block contains a transaction group of more than \( \MaxTxGroupSize \) transactions, the block is invalid.
If the sum of the fees paid by the \( n \) transactions in a transaction group is less than \( n \times \MinTxnFee )\, then the block is invalid. There are two exceptions to this fee requirement:
-
State Proof transactions require no fee;
-
Heartbeat transactions require no fee if they have a zero group field, and the heartbeat address was challenged between \( 100 \) and \( 200 \) rounds ago, and has not proposed or heartbeat since that challenge.
Further explanation of this rule is found in Heartbeat transaction semantics section.
If the sum of the lengths of the boxes denoted by the box references in a transaction group exceeds \( \BytesPerBoxReference \) times the total number of box references in the transaction group, then the block is invalid. Call this limit the I/O Budget for the group. Box references with an empty name are counted toward the total number of references, but add nothing to the sum of lengths.
If the sum of the lengths of the boxes modified (by creation or modification) in a transaction group exceeds the I/O Budget of the group at any time during evaluation (see Application Call transaction semantics), then the block is invalid.
If the sum of the lengths of all the logic signatures and their arguments in a transaction group exceeds the number of transactions in the group times \( \LogicSigMaxSize \), then the block in invalid.
Beyond the group field, group minimum fee, group I/O Budget, and group logic sig size checks, each transaction in a group is evaluated separately and MUST be valid on its own, as described in the Validity and State Changes section.
📎 EXAMPLE
An account with balance \( 50 \) ALGO could not spend \( 100 \) ALGO in transaction \( A \) and afterward receive \( 500 \) in transaction \( B \), even if transactions \( A \) and \( B \) are in the same group, because transaction \( A \) would overspend the account’s balance.
$$ \newcommand \Hash {\mathrm{Hash}} \newcommand \pk {\mathrm{pk}} \newcommand \MSigPrefix {\texttt{MultisigAddr}} $$
Authorization and Signatures
Transactions are not valid unless they are somehow authorized by the sender account (for example, with a signature).
The authorization information is not considered part of the transaction and does
not affect the transaction ID (TXID
).
Rather, when serializing a transaction for submitting to a node or including in
a block, the transaction and its authorization appear together in a structure called
a SignedTxn
.
The SignedTxn
struct contains:
-
The transaction (in msgpack field
txn
); -
An OPTIONAL authorizer address (field
sgnr
); -
Exactly one of a signature (field
sig
), multisignature (fieldmsig
), or logic signature (fieldlsig
).
The authorizer address, a 32-byte address, determines against what to verify the
sig
/ msig
/ lsig
, as described below.
If the sgnr
field is omitted (or zero), then the authorizer address defaults
to the transaction sender address.
At the time the transaction is applied to the Ledger, the authorizer address MUST match the transaction sender account’s spending key (or the sender address, if the account’s spending key is zero). If it does not match, then the transaction was improperly authorized and is invalid.
Signatures
-
A valid signature (
sig
) is a (64-byte) valid Ed25519 signature of the transaction (encoded in canonical msgpack and with domain separation prefixTX
) where the public key is the authorizer address (interpreted as an Ed25519 public key). -
A valid multisignature (
msig
) is an object containing the following fields and which hashes to the authorizer address as described in the Multisignature section:-
The
subsig
array of subsignatures, each consisting of a signer address and a 64-byte signature of the transaction. Note that transaction signed by a multisignature account MUST contain all signer’s addresses in thesubsig
array even if the transaction has not been signed by that address. -
The threshold
thr
that is a minimum number of REQUIRED signatures. -
The multisignature version
v
(current value is \( 1 \)).
-
-
A valid logic signature (
lsig
) is an object containing the following fields:-
The logic
l
which is versioned bytecode (see AVM specifications). -
An OPTIONAL single signature
sig
of 64-bytes valid from the authorizer address of the transaction which has signed the bytes inl
. -
An OPTIONAL multisignature
lmsig
from the authorizer address of the transaction over the bytes of the authorizer address and the bytes inl
. -
An OPTIONAL array of byte strings
arg
which are arguments supplied to the program inl
(arg
bytes are not covered bysig
ormsig
).
-
The logic signature is valid if exactly one of sig
or msig
is a valid signature
of the program by the authorizer address of the transaction, or if neither sig
nor msig
is set and the hash of the program is equal to the authorizer address.
Also the program MUST execute and finish with a single non-zero value on the AVM stack (see AVM specifications for details on program execution semantics).
Multisignature
Multisignature term describes a special multisignature address, signing and validation procedures.
In contrast with a single signature address that may be understood as a public key, multisignature address is a hash of a constant string identifier for:
-
The \( \MSigPrefix \) prefix,
-
A version \( v \),
-
The multisignature authorization threshold \( t \),
-
All \( n \) addresses (\( \pk \)) used for multisignature address creation.
$$ \mathrm{MSig} = \Hash(\MSigPrefix, v, t, \pk_1, \ldots \pk_n) $$
One address MAY be specified multiple times in multisignature address creation. In this case, every occurrence is counted independently in validation.
The repetition of the same address in the multisignature defines the “weight” of the address.
The multisignature validation process checks that:
-
All non-empty signatures are valid;
-
The valid signatures count is not less than the threshold.
Validation fails if any of the signatures is invalid, even if the count of all remaining correct signatures is greater or equals than the threshold.
$$ \newcommand \ApplyData {\mathrm{ApplyData}} \newcommand \ClosingAmount {\mathrm{ClosingAmount}} \newcommand \AssetClosingAmount {\mathrm{AssetClosingAmount}} $$
ApplyData
Each transaction is associated with some information about how it is applied to the
Account State. This information is called \( \ApplyData \),
and contains the fields based on the transaction type (type
).
Payment Transaction
- The closing amount, \( \ClosingAmount \), encoded as msgpack field
ca
, specifies how many μALGO were transferred to the close-to address (close
).
Asset Transfer Transaction
- The asset closing amount \( \AssetClosingAmount \), encoded as msgpack field
aca
, specifies how many of the asset units were transferred to the asset close-to address (aclose
).
Application Call Transaction
-
The eval delta, encoded as msgpack field
dt
, is associated with the successful execution of the corresponding application’s Approval Program or Clear State Program. It contains the following fields:-
A global delta, encoded as msgpack field
gd
, encoding the changes to the global state of the called application. This field is a State Delta; -
Zero or more local deltas, encoded as msgpack field
ld
, encoding changes to some local states associated with the called application. The local deltald
maps an “account offset” to a State Delta. Account offset \( 0 \) is the transaction’s sender. Account offsets \( 1 \) and greater refer to the account specified at that offset minus one in the transaction’s foreign accounts (apat
). An account would have ald
entry as long as there is at least a single change in that set. -
Zero or more logs, encoded as msgpack array
lg
, recording the arguments to each call of thelog
opcode in the called application. The order of the entries follows the execution order of thelog
opcode invocations. The total number oflog
calls MUST NOT exceed \( 32 \), and the total size of all logged bytes is MUST NOT exceed \( 1024 \). No logs are included if a Clear State Program fails. -
Zero or more inner transactions, encoded as msgpack array
itx
. Each element ofitx
records a successful inner transaction. Each element will contain the transaction fields, encoded under the msgpack fieldtxn
, in the same way that the regular (top-level) transaction is encoded, recursively, including \( ApplyData \) that applies to the inner transaction.- The recursive depth of inner transactions is MUST NOT exceed \( 8 \);
- In Program Version 5, the inner transactions MUST NOT exceed \( 16 \). From Program Version 6, the count of all inner transactions across the transaction group MUST NOT exceed \( 256 \).
- Before Program Version 6, the inner transaction types (
type
) are limited topay
,axfer
,acfg
, andafrz
. From Program Version 6,keyreg
andappl
are allowed. - A Clear State Program execution MUST NOT have any inner transaction.
-
Distribution Rewards
The following \( ApplyData \) item refers to the legacy Distribution Reward system.
-
The amount of rewards distributed to each of the accounts touched by this transaction, encoded as three msgpack fields
rs
,rr
, andrc
, representing amount of reward distributed respectively to:- The sender address,
- The receiver address,
- The cose-to addresses.
The fields have integer values representing μALGO.
If any of the addresses are the same (e.g., the sender and receiver are the same),
then that account received the sum of the respective reward distributions (i.e.,
rs
plus rr
).
⚙️ IMPLEMENTATION
In the reference implementation, one of these two fields will be zero in that case.
Inner Transactions
TODO (Similarities and differences with respect to regular transactions)
State Deltas
A state delta represents an update to a Key/Value Store (KV).
It is constructed as an associative array mapping a byte-array key to a single value
delta. It represents a series of actions that, when applied to the previous state
of the Key/Value store, will yield the new state. These state deltas are included
in the Application Call \( ApplyData \) structure under the field eval delta
(dt
).
A _value delta_is composed of three fields:
-
An action, encoded as msgpack field
at
, which specifies how the value for this key has changed. It has three possible values:SetUintAction
(value =1
), indicating that the value for this key should be set to the value delta’sUint
field.SetBytesAction
(value =2
), indicating that the value for this key should be set to the value delta’sBytes
field.DeleteAction
(value =3
), indicating that the value for this key should be deleted.
-
Bytes
, encoded as msgpack fieldbs
, which specifies a byte slice value to set. -
Uint
, encoded as msgpack fieldui
, which specifies a 64-bit unsigned integer value to set.
$$ \newcommand \MinBalance {b_{\min}} $$
Asset Transaction Semantics
Asset Configuration
An asset configuration transaction has the following semantics:
- If the asset ID is zero, create a new asset with an ID corresponding to one plus this transaction’s unique counter value. The transaction’s counter value is the transaction counter field from the block header plus the positional index of this transaction in the block (starting from \( 0 \)).
On asset creation, the asset parameters for the created asset are stored in the creator’s account under the newly allocated asset ID. The creating account also allocates space to hold asset units of the newly allocated asset. All units of the newly created asset (i.e., the total specified in the parameters) are held by the creator. When the creator holding is initialized, it ignores the _default frozen_flag and is always initialized to unfrozen.
-
If the asset ID is non-zero, the transaction MUST be issued by the asset manager (based on the asset’s current parameters). A zero manager address means no such transaction can be issued.
-
If the asset parameters are omitted (the zero value), the asset is destroyed. This is allowed only if the asset creator holds all of the units of that asset (i.e., equal to the total in the parameters).
-
If the asset parameters are not omitted, any non-zero key in the asset’s current parameters (as stored in the asset creator’s account) is updated to the key specified in the asset parameters. This applies to the manager, reserve, freeze, and clawback keys. Once a key is set to zero, it cannot be updated. Other parameters are immutable.
-
Asset Transfer
An asset transfer transaction has the following semantics:
-
If the asset sender field is non-zero, the transaction MUST be issued by the asset’s clawback address, and this transaction can neither allocate nor close out holdings of an asset (i.e., the asset close-to field MUST NOT be specified, and the source account MUST already have allocated space to store holdings of the asset in question). In this clawback case, freezes MUST be bypassed on both the source and destination of this transfer.
-
If the asset sender field is zero, the asset sender is assumed to be the transaction’s sender, and freezes MUST NOT be bypassed.
-
If the asset amount is \( 0 \), the transaction allocates space in the sender’s account to store the asset ID. The holdings are initialized with a zero number of units of that asset, and the default frozen flag from the asset’s parameters. Space cannot be allocated for asset IDs that have never been created, or that have been destroyed, at the time of space allocation. Space can remain allocated, however, after the asset is destroyed.
-
The transaction moves the specified number of units of the asset from the asset sender to the asset receiver.
-
If either account is frozen, and freezes are not bypassed, the transaction fails to execute.
-
If either account has no space allocated to hold units of this asset, the transaction fails to execute.
-
If the asset sender has fewer than the specified number of units of that asset, the transaction fails to execute.
-
-
If the asset close-to field is specified, the transaction transfers all remaining units of the asset to the close-to address.
-
If the close-to address is not the creator address, then neither the asset sender’s holdings of this asset nor the close-to address’s holdings can be frozen; otherwise, the transaction fails to execute.
-
Closing to the asset creator is always allowed, even if the source and/or creator account’s holdings are frozen.
-
If the asset sender or close-to address does not have allocated space for the asset in question, the transaction fails to execute.
-
After transferring all outstanding units of the asset, space for the asset is deallocated from the sender account.
-
Asset Freeze
An asset freeze transaction has the following semantics:
-
If the transaction is not issued by the freeze address in the specified asset’s parameters, the transaction fails to execute.
-
If the specified asset ID does not exist in the specified account, the transaction fails to execute.
-
The freeze flag of the specified asset in the specified account is updated to the frozen status flag value from the freeze transaction.
Asset Allocation
When an asset transaction allocates space in an account for an asset, whether by creation or opt-in, the sender’s minimum balance requirement is incremented by \( \MinBalance \).
When the space is deallocated, whether by asset destruction or asset close-to, the minimum balance requirement of the sender is decremented by \( \MinBalance \).
$$ \newcommand \Box {\mathrm{Box}} \newcommand \BytesPerBoxReference {\Box_{\mathrm{IO}}} $$
Application Call Transaction Semantics
When an application call transaction is evaluated, it is processed according to the following procedure.
The transaction effects MUST NOT be visible to other transactions until the points marked SUCCEED below.
FAIL indicates that any modifications to state up to that point MUST be discarded and the entire transaction rejected.
Procedure
Step 1
-
If the application ID specified by the transaction is zero, create a new application with ID equal to one plus the system transaction counter (this is the same ID selection algorithm as used by Assets).
When creating an application, the application parameters specified by the transaction (Approval Program, Clear State Program, Global State Schema, Local State Schema, and Extra Program Pages) are allocated into the sender’s account data, keyed by the new application ID.
Continue to Step 2.
-
If the application ID specified by the transaction is nonzero, continue to Step 2.
Step 2
-
If the on completion action is equal to
ClearStateOC
, then:-
Check if the transaction’s sender is opted in to this application ID. If not, FAIL.
-
Check if the application parameters still exist in the creator’s account data.
-
If the application does not exist, delete the sender’s local state for this application (marking them as no longer opted in), and SUCCEED.
-
If the application does exist, continue to Step 3.
-
-
-
If the on completion action is not equal to
ClearStateOC
, continue to Step 4.
Step 3
-
Execute the Clear State Program.
-
If the program execution returns
PASS == true
, apply the local/global key/value store deltas generated by the program’s execution. -
If the program execution returns
PASS == false
, do not apply any local/global key/value store deltas generated by the program’s execution. -
Delete the sender’s local state for this application (marking them as no longer opted in). SUCCEED.
-
Step 4
-
If on completion action is equal to
OptInOC
, then at this point during execution we will allocate a local key/value store for the sender for this application ID, marking the sender as opted in.Continue to Step 5.
Step 5
-
Execute the Approval Program.
-
If the program execution returns
PASS == true
, apply any local/global key/value store deltas generated by the program’s execution. Continue to Step 6. -
If the program execution returns
PASS == false
, FAIL.
-
Step 6
-
If on completion action is equal to
NoOpOC
- SUCCEED.
-
If on completion action is equal to
OptInOC
- This was handled above. SUCCEED.
-
If on completion action is equal to
CloseOutOC
-
Check if the transaction’s sender is opted in to this application ID. If not, FAIL.
-
Delete the sender’s local state for this application (marking them as no longer opted in). SUCCEED.
-
-
If on completion action is equal to
ClearStateOC
- This was handled above (unreachable).
-
If on completion action is equal to
DeleteApplicationOC
- Delete the application’s parameters from the creator’s account data. (Note: this does not affect any local state). SUCCEED.
-
If on completion action is equal to
UpdateApplicationOC
-
If an existing program is version 4 or higher, and the supplied program is a downgrade from the existing version. FAIL.
-
Update the Approval Program and Clear State Program for this application according to the programs specified in this application call transaction and increment the Application Version. The new programs are not executed in this transaction. SUCCEED.
-
Application Stateful Execution Semantics
-
Before the execution of the first application call transaction in a group, the combined size of all boxes referred to in the box references of all transactions in the group MUST NOT exceed the I/O budget (i.e., \( \BytesPerBoxReference \) times the total number of box references in the group), or else the group fails.
-
During the execution of an Approval Program or Clear State Program, the application’s Local State Schema and Global State Schema SHALL never be violated. The program’s execution will fail on the first instruction that would cause the relevant schema to be violated. Writing a
Bytes
value to a local or global Key/Value Store such that the sum of the lengths of the key and value in bytes exceeds \( \MaxAppBytesValueLen \), or writing any value to a key longer than \( \MaxAppKeyLen \) bytes, will likewise cause the program to fail on the offending instruction. -
During the execution of an Approval Program, the total size of all boxes that are created or modified in the group MUST NOT exceed the I/O budget. The program’s execution will fail on the first instruction that would cause the constraint to be violated. If a box is deleted after creation or modification, its size is not considered in this sum.
-
An attempt to access resources that are not available will cause the program execution to fail.
-
Boxes MAY NOT be accessed by an application’s Clear State Program.
-
In addition to the group’s named Boxes, transactions MAY also access the Boxes of Applications that were previously created in the same group. Across the execution of the entire group, the group can access as many of these unnamed boxes as the group has empty box references.
For further details on AVM Resource Availability, refer to the AVM Specifications.
$$ \newcommand \Fee {\mathrm{fee}} \newcommand \MinTxnFee {T_{\Fee,\min}} \newcommand \RekeyTo {\mathrm{RekeyTo}} \newcommand \Heartbeat {\mathrm{hb}} \newcommand \PayoutsChallengeBits {\Heartbeat_\mathrm{bits}} $$
Heartbeat Transaction Semantics
If a heartbeat transaction’s group is empty, and the fee is less than \( \MinTxnFee \), the transaction FAILS to execute unless:
-
The note \( N \) is empty;
-
The lease \( x \) is empty;
-
The rekey to address \( \RekeyTo \) is empty;
-
The heartbeat_address, \( a \), is
Online
; -
The heartbeat_address, \( a \), eligibility flag (
ie
) isTrue
; -
The heartbeat_address, \( a \), is at risk of suspension.
An account is at risk of suspension if the current round (\( r \)) is
$$ 100\mod1000 \leq r \leq 200\mod1000, $$
and the block seed of the most recent round that is \( 0 \mod 1000 \) matches \( a \) in the first \( \PayoutsChallengeBits \) bits.
If successful, the LastHeartbeat
of the specified heartbeat address \( a \)
is updated to the current round.
$$ \newcommand \Genesis {\mathrm{Genesis}} \newcommand \GenesisID {\Genesis\mathrm{ID}} \newcommand \pk {\mathrm{pk}} \newcommand \spk {\mathrm{spk}} \newcommand \Tx {\mathrm{Tx}} \newcommand \TxType {\mathrm{TxType}} \newcommand \TxTail {\Tx\mathrm{Tail}} \newcommand \Hash {\mathrm{Hash}} \newcommand \FirstValidRound {r_\mathrm{fv}} \newcommand \LastValidRound {r_\mathrm{lv}} \newcommand \sppk {\mathrm{sppk}} \newcommand \nonpart {\mathrm{nonpart}} \newcommand \MaxTxTail {\mathrm{TxTail}_{\max}} \newcommand \abs[1] {\lvert #1 \rvert} \newcommand \floor[1] {\left \lfloor #1 \right \rfloor } $$
$$ \newcommand \MaxTxnNoteBytes {T_{m,\max}} \newcommand \Stake {\mathrm{Stake}} \newcommand \Fee {\mathrm{fee}} \newcommand \MinTxnFee {T_{\Fee,\min}} \newcommand \PayoutsGoOnlineFee {B_{p,\Fee}} \newcommand \Eligibility {\mathrm{A_e}} \newcommand \Units {\mathrm{Units}} \newcommand \MinBalance {b_{\min}} $$
Validity and State Changes
The new Ledger’s state that results from applying a valid block is the account state that results from applying each transaction in that block, in sequence.
For a block to be valid, each transaction in its transaction sequence MUST be valid at the block’s round \( r \) and for the block’s genesis identifier \( \GenesisID_B \).
For a transaction
$$ \Tx = (\GenesisID, \TxType, \FirstValidRound, \LastValidRound, I, I^\prime, I_0, f, a, x, N, \pk, \sppk, \nonpart, \ldots) $$
(where \( \ldots \) represents fields specific to transaction types
besides pay
and keyreg
) to be valid at the intermediate state \( \rho \) in
round \( r \) for the genesis identifier \( \GenesisID_B \), the following conditions
MUST all hold:
-
It MUST represent a transition between two valid account states.
-
Either \( \GenesisID = \GenesisID_B \) or \( \GenesisID \) is the empty string.
-
\( \TxType \) is either
pay
,keyreg
,acfg
,axfer
,afrz
,appl
,stpf
, orhb
. -
There are no extra fields that do not correspond to \( \TxType \).
-
\( 0 \leq \LastValidRound - \FirstValidRound \leq \MaxTxTail \).
-
\( \FirstValidRound \leq r \leq \LastValidRound \).
-
\( \abs{N} \leq \MaxTxnNoteBytes \).
-
\( I \neq I_\mathrm{pool} \), \( I \neq I_f \), and \( I \neq 0 \).
-
\( \Stake(r+1, I) \geq f \geq \MinTxnFee \).
-
The transaction is properly authorized as described in the Authorization and Signatures section.
-
\( \Hash(\Tx) \notin \TxTail_r \).
-
If \( x \neq 0 \), there exists no \( \Tx^\prime \in \TxTail \) with sender \( I^\prime \), lease value \( x^\prime \), and last valid round \( \LastValidRound^\prime \) such that \( I^\prime = I \), \( x^\prime = x \), and \( \LastValidRound^\prime \geq r \).
-
If \( \TxType \) is
pay
,-
\( I \neq I_k \) or both \( I^\prime \neq I_\mathrm{pool} \) and \( I_0 \neq 0 \).
-
\( \Stake(r+1, I) - f > a \) if \( I^\prime \neq I \) and \( I^\prime \neq 0 \).
-
If \( I_0 \neq 0 \), then \( I_0 \neq I \).
-
If \( I_0 \neq 0 \), \( I \) cannot hold any assets.
-
-
If \( \TxType \) is
keyreg
,-
\( p_{\rho, I} \ne 2 \) (i.e., nonparticipatory accounts may not issue
keyreg
transactions) -
If \( \nonpart \) is
True
then \( \spk = 0 \), \( \pk = 0 \) and \( \sppk = 0 \)
-
Given that a transaction is valid, it produces the following updated account state for intermediate state \( \rho+1 \):
-
For \( I \):
-
If \( I_0 \neq 0 \) then \( a_{\rho+1, I} = a^\prime_{\rho+1, I} = a^\ast_{\rho+1, I} = p_{\rho+1, I} = \pk_{\rho+1, I} = 0 \);
-
otherwise,
- \( a_{\rho+1, I} = \Stake(\rho+1, I) - a - f \) if \( I^\prime \neq I \) and \( a_{\rho+1, I} = \Stake(\rho+1, I) - f \) otherwise.
- \( a^\prime_{\rho+1, I} = T_{r+1} \).
- \( a^\ast_{\rho+1, I} = a^\ast_{\rho, I} + (T_{r+1} - a^\prime_{\rho, I}) \floor{\frac{a_{\rho, I}}{A}} \).
- If \( \TxType \) is
pay
, then \( \pk_{\rho+1, I} = \pk_{\rho, I} \) and \( p_{\rho+1, I} = p_{\rho, I} \) - Otherwise (i.e., if \( \TxType \) is
keyreg
),- \( \pk_{\rho+1, I} = \pk \)
- \( p_{\rho+1, I} = 0 \) if \( \pk = 0 \) and \( \nonpart = \texttt{False} \)
- \( p_{\rho+1, I} = 2 \) if \( \pk = 0 \) and \( \nonpart = \texttt{True} \)
- \( p_{\rho+1, I} = 1 \) if \( \pk \ne 0 \)
- If \( f > \PayoutsGoOnlineFee \), then \( \Eligibility{\rho+1, I} = \texttt{True} \)
-
-
For \( I^\prime \) if \( I \neq I^\prime \) and either \( I^\prime \neq 0 \) or \( a \neq 0 \):
-
\( a_{\rho+1, I^\prime} = \Stake(\rho+1, I^\prime) + a \).
-
\( a^\prime_{\rho+1, I^\prime} = T_{r+1} \).
-
\( a^\ast_{\rho+1, I^\prime} = a^\ast_{\rho, I^\prime} + (T_{r+1} - a^\prime_{\rho, I^\prime}) \floor{\frac{a_{\rho, I^\prime}}{A}} \).
-
-
For \( I_0 \) if \( I_0 \neq 0 \):
-
\( a_{\rho+1, I_0} = \Stake(\rho+1, I_0) + \Stake(\rho+1, I) - a - f \).
-
\( a^\prime_{\rho+1, I_0} = T_{r+1} \).
-
\( a^\ast_{\rho+1, I_0} = a^\ast_{\rho, I_0} + (T_{r+1} - a^\prime_{\rho, I_0}) \floor{\frac{a_{\rho, I_0}}{A}} \).
-
-
For all other \( I^\ast \neq I \), the account state is identical to that in view \( \rho \).
For transaction types other than pay
and keyreg
, account state is updated based
on the reference logic described in the Transaction section.
Additionally, for all types of transactions, if the rekey to address of the transaction is nonzero and does not match the transaction sender address, then the transaction sender account’s spending key is set to the rekey to address. If the rekey to address of the transaction does match the transaction sender address, then the transaction sender account’s spending key is set to zero.
The rest of this section describes the legacy Distribution Rewards system. If the \( R_r \) rewards rate parameter is \( 0 \), all computations keep values constant and no legacy reward distribution is carried out.
The final intermediate account \( \rho_k \) state changes the balance of the incentive pool as follows:
$$ a_{\rho_k, I_\mathrm{pool}} = a_{\rho_{k-1}, I_\mathrm{pool}} - R_r(\Units(r)) $$
An account state in the intermediate state \( \rho+1 \) and at round \( r \) is valid if all following conditions hold:
-
For all addresses \( I \notin \{I_\mathrm{pool}, I_f\} \), either \( \Stake(\rho+1, I) = 0 \) or \( \Stake(\rho+1, I) \geq \MinBalance \times (1 + N_A) \), where \( N_A \) is the number of assets held by that account.
-
\( \sum_I \Stake(\rho+1, I) = \sum_I \Stake(\rho, I) \).
$$ \newcommand \Stake {\mathrm{Stake}} \newcommand \Units {\mathrm{Units}} \newcommand \floor [1]{\left \lfloor #1 \right \rfloor } \newcommand \MinBalance {b_{\min}} $$
Reward State
The reward state consists of three 64-bit unsigned integers:
-
The total amount of money distributed to each earning unit since the genesis state \( T_r \),
-
The amount of money to be distributed to each earning unit at the next round \( R_r\),
-
The amount of money left over after distribution \( B^\ast_r \).
The reward state depends on:
-
The address of the incentive pool \( I_\mathrm{pool} \),
-
The functions \( \Stake(r, I_\mathrm{pool}) \)
-
\( \Units(r) \).
These are defined as part of the Account State.
Informally, every \( \omega_r \) rounds, the rate \( R_r \) is updated such that rewards given over the next \( \omega_r \) rounds will drain the incentive pool, leaving it with the minimum balance \( \MinBalance \).
The rewards residue \( B^\ast_r \) is the amount of leftover rewards that should have been given in the previous round but could not be evenly divided among all reward units. The residue carries over into the rewards to be given in the next round.
The actual draining of the incentive pool account is described in the Validity and State Changes section.
More formally, let \( Z = \Units(r) \).
Given a reward state \( (T_r, R_r, B^\ast_r) \), the new reward state is \( (T_{r+1}, R_{r+1}, B^\ast_{r+1}) \), where:
-
\( R_{r+1} = \floor{\frac{\Stake(r, I_{pool}) - B^\ast_r - \MinBalance}{\omega_r}} \) if \(R_r \equiv 0 \bmod \omega_r \) or \( R_{r+1} = R_r \) otherwise, and
-
\( T_{r+1} = T_r + \floor{\frac{R_r}{Z}} \) if \( Z \neq 0 \) or \( T_{r+1} = T_r \) otherwise, and
-
\( B^\ast_{r+1} = (B^\ast_r + R_r) \bmod Z \) if \(Z \neq 0\) or \( B^\ast_{r+1} = B^\ast_r \) otherwise.
A valid block’s reward state matches the expected reward state.
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
MakeBlock
reference implementation.
WithSeed
reference 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=
(base64
encoding of the 32-byte array).For the TestNet:
- Genesis ID:
testnet-v1.0
- Genesis Hash:
SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=
(base64
encoding 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
MakeBlock
function.
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
FeeSink
address: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
RewardsPool
address: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 theRewardsPool
in 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
Anuint64
integer 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 tofalse
unless 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-algorand
reference 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 (
FeeSink
andRewardsPool
), -
The schema of the Ledger.
⚙️ IMPLEMENTATION
Genesis
type definition in the reference implementation.
⚙️ IMPLEMENTATION
GenesisBalances
type 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
payset
has 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 Standard Asset creation and reconfiguration |
afrz | Algorand Standard Asset freeze (whitelisting and blacklisting) |
appl | Application (Smart Contract) call |
stpf | Algorand State Proof |
hb | Consensus heartbeat challenge |
For a formal definition of all transaction fields, refer to the normative section.
⚙️ IMPLEMENTATION
The reference implementation also defines the
unknown
transaction type.Transaction types definition.
⚙️ IMPLEMENTATION
Transaction types reference implementation.
Transaction Header
The transaction header, equal for all transaction types, consists of:
-
TransactionType
Identifies 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
. -
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.0
for MainNet). -
Group
(Optional)
A cryptographic commitment to the group this transaction is part of. -
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 sameLease
can be committed.
A typical use case of the
Lease
is 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 theSender
account’s spending key to this address as last transaction effect. Therefore, future transactions sent by theSender
account 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);Fee
act as an anti-spam mechanism (grows exponentially if the network is congested or decays if there is spare block capacity);Fee
prioritization is not enforced by consensus (although a node does that);- Inner Transaction costs always the
MinFee
(regardless of network congestion); Fee
are pooled inGroup
transactions;Fee
is independent of theTransactionType
or usage (i.e., no local fee market);
-
Round
’s validity is not expired (\( 1000 \) rounds at most); -
FirstValidRound
can be delayed in the future; -
Round
’s validity handles transactions’ idempotency, letting Non-Archival nodes participate in consensus; -
It is not leased (combination of
Sender
andLease
) 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 theledgerForTracker
argument 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 accompanyingdelta
parameter contains the state changes introduced by this block (see block evaluation section). -
committedUpTo(rnd)
Informs the Tracker that all blocks up to and includingrnd
are 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-algorand
documentation.
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
HbAddress
is Online and under challenge with the grace period at least half over. - The
HbAddress
isIncentiveEligible
. - 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 \PSfunction {\textbf{function }} \newcommand \PSreturn {\textbf{return }} \newcommand \PSendfunction {\textbf{end function}} \newcommand \PSswitch {\textbf{switch }} \newcommand \PScase {\textbf{case }} \newcommand \PSdefault {\textbf{default}} \newcommand \PSendswitch {\textbf{end switch}} \newcommand \PSfor {\textbf{for }} \newcommand \PSendfor {\textbf{end for}} \newcommand \PSwhile {\textbf{while }} \newcommand \PSendwhile {\textbf{end while}} \newcommand \PSdo {\textbf{ do}} \newcommand \PSif {\textbf{if }} \newcommand \PSelseif {\textbf{else if }} \newcommand \PSelse {\textbf{else}} \newcommand \PSthen {\textbf{ then}} \newcommand \PSendif {\textbf{end if}} \newcommand \PSnot {\textbf{not }} \newcommand \PScomment {\qquad \small \textsf} $$
$$ \newcommand \TxTail {\mathrm{TxTail}} \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 transactionLease
by observation round, and the mapping usesTXLease
as keys to store theLease
expiring round. -
blockHeaderData
Contains recent block header data. The expected availability range is[Latest - MaxTxnLife, Latest]
, allowingMaxTxnLife + 1
rounds 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 storestxid
s mapped to 16-bit unsigned integers representing the difference between the transaction’slastValid
field and the round it was confirmed (lastValid > confirmationRound
for all confirmed transactions). -
lowWaterMark
An unsigned 64-bit integer representing a round number such that for any transactions where thelastValid
field 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: } \PSfunction \CheckDuplicate(\Tx_r, \FirstValid, \LastValid, \Tx_{\ID}, \Tx_{\Lease}) \\ &\text{2: } \quad \PSif \LastValid < \TxTail.\LowWaterMark \PSthen \\ &\text{3: } \quad \quad \PSreturn \Tx_{\ID} \text{ is not in } \TxTail \\ &\text{4: } \quad \PSendif \\ &\text{5: } \quad \PSif \Tx_{\Lease} \neq \emptyset \PSthen \\ &\text{6: } \quad \quad \FirstChecked \gets \FirstValid \\ &\text{7: } \quad \quad \LastChecked \gets \LastValid \\ &\text{8: } \quad \quad \PSfor r \in [\FirstChecked, \LastChecked] \PSdo \\ &\text{9: } \quad \quad \quad \PSif \Tx_{\Lease} \in \RecentLeaseMap(\Tx_r).\Lease \land r \leq \Tx_{\Lease}.\mathrm{Expiration} \PSthen \\ &\text{10:} \quad \quad \quad \quad \PSreturn \Lease \text{ is a duplicate} \\ &\text{11:} \quad \quad \quad \PSendif \\ &\text{12:} \quad \quad \PSendfor \\ &\text{13:} \quad \PSendif \\ &\text{14:} \quad \PSif \Tx_{\ID} \in \TxTail.\LastValidMap(\LastValid).\Tx_{\ID} \PSthen \\ &\text{15:} \quad \quad \PSreturn \Tx_{\ID} \text{ is a duplicate transaction} \\ &\text{16:} \quad \PSendif \\ &\text{17:} \quad \PSreturn \\ &\text{18: } \PSendfunction \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.
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
Remember
function 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 nextOnNewBlock
call resyncs them.Example of
Remember
function used by thetxnHandler
to 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
OnNewBlock
function), or - Their
LastValid
field 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
payset
and 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-algorand
reference 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-algorand
reference 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-algorand
reference 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-algorand
reference implementation,expiredHistory
is 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-algorand
reference 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 \PSfunction {\textbf{function }} \newcommand \PSreturn {\textbf{return }} \newcommand \PSendfunction {\textbf{end function}} \newcommand \PSswitch {\textbf{switch }} \newcommand \PScase {\textbf{case }} \newcommand \PSdefault {\textbf{default}} \newcommand \PSendswitch {\textbf{end switch}} \newcommand \PSfor {\textbf{for }} \newcommand \PSendfor {\textbf{end for}} \newcommand \PSwhile {\textbf{while }} \newcommand \PSendwhile {\textbf{end while}} \newcommand \PSdo {\textbf{ do}} \newcommand \PSif {\textbf{if }} \newcommand \PSelseif {\textbf{else if }} \newcommand \PSelse {\textbf{else}} \newcommand \PSthen {\textbf{ then}} \newcommand \PSendif {\textbf{end if}} \newcommand \PSnot {\textbf{not }} \newcommand \PScomment {\qquad \small \textsf} $$
$$ \newcommand \TP {\mathrm{TxPool}} \newcommand \FeePB {\mathrm{feePerByte}} \newcommand \FeeMul {\mathrm{feeThresholdMultiplier}} \newcommand \FeeExp {\mathrm{expFeeFactor}} \newcommand \PendingFB {\mathrm{pendingFullBlocks}} \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: } \PSfunction \ComputeFeePerByte() \\ &\text{2: } \quad \FeePB \gets \FeeMul \\ &\text{3: } \quad \PSif \FeePB = 0 \land \TP.\PendingFB > 1 \PSthen \\ &\text{4: } \quad \quad \FeePB \gets 1 \\ &\text{5: } \quad \PSendif \\ &\text{6: } \quad \PSfor i {\textbf{ from }} 0 \textbf{ to } \TP.\PendingFB \PSdo \\ &\text{7: } \quad \quad \FeePB \gets \FeePB \cdot \TP.\FeeExp \\ &\text{8: } \quad \PSendfor \\ &\text{9: } \quad \PSreturn \FeePB \\ &\text{10: } \PSendfunction \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 \PSfunction {\textbf{function }} \newcommand \PSreturn {\textbf{return }} \newcommand \PSendfunction {\textbf{end function}} \newcommand \PSswitch {\textbf{switch }} \newcommand \PScase {\textbf{case }} \newcommand \PSdefault {\textbf{default}} \newcommand \PSendswitch {\textbf{end switch}} \newcommand \PSfor {\textbf{for }} \newcommand \PSendfor {\textbf{end for}} \newcommand \PSwhile {\textbf{while }} \newcommand \PSendwhile {\textbf{end while}} \newcommand \PSdo {\textbf{ do}} \newcommand \PSif {\textbf{if }} \newcommand \PSelseif {\textbf{else if }} \newcommand \PSelse {\textbf{else}} \newcommand \PSthen {\textbf{ then}} \newcommand \PSendif {\textbf{end if}} \newcommand \PSnot {\textbf{not }} \newcommand \PScomment {\qquad \small \textsf} $$
$$ \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 \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: } \PSfunction \Update(\NB\ b, \SD\ sd) \\ &\text{2: } \quad \PSif \TP_{pq} \text{ is empty or outdated} \PSthen \\ &\text{3: } \quad \quad \PSswitch \TP.\PendingFB \\ &\text{4: } \quad \quad \quad \PScase\ 0: \\ &\text{5: } \quad \quad \quad \quad \FeeMul \gets \frac{FeeMul}{FeeExp} \\ &\text{6: } \quad \quad \quad \PScase\ 1: \\ &\text{7: } \quad \quad \quad \quad \PScomment{Intentionally left blank to maintain the value of } \FeeMul \\ &\text{8: } \quad \quad \quad \PScase\ \PSdefault: \\ &\text{9: } \quad \quad \quad \quad \PSif \FeeMul = 0 \PSthen \\ &\text{10:} \quad \quad \quad \quad \quad \FeeMul \gets 1 \\ &\text{11:} \quad \quad \quad \quad \PSelse \\ &\text{12:} \quad \quad \quad \quad \quad \FeeMul \gets \FeeMul \cdot \FeeExp \\ &\text{13:} \quad \quad \quad \quad \PSendif \\ &\text{14:} \quad \quad \PSendswitch \\ &\text{15:} \quad \PSendif \\ &\text{16:} \quad \TP.\mathrm{Prune}(b, sd) \\ &\text{17: } \PSendfunction \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
lastValid
field is less than the current round \( r \) (which matches the round of \( b \) that was just observed).
$$ \newcommand \PSfunction {\textbf{function }} \newcommand \PSreturn {\textbf{return }} \newcommand \PSendfunction {\textbf{end function}} \newcommand \PSswitch {\textbf{switch }} \newcommand \PScase {\textbf{case }} \newcommand \PSdefault {\textbf{default}} \newcommand \PSendswitch {\textbf{end switch}} \newcommand \PSfor {\textbf{for }} \newcommand \PSendfor {\textbf{end for}} \newcommand \PSwhile {\textbf{while }} \newcommand \PSendwhile {\textbf{end while}} \newcommand \PSdo {\textbf{ do}} \newcommand \PSif {\textbf{if }} \newcommand \PSelseif {\textbf{else if }} \newcommand \PSelse {\textbf{else}} \newcommand \PSthen {\textbf{ then}} \newcommand \PSendif {\textbf{end if}} \newcommand \PSnot {\textbf{not }} \newcommand \PScomment {\qquad \small \textsf} $$
$$ \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 \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: } \PSfunction \Ingest(\TG\ gtx) \\ &\text{2: } \quad \dots \\ &\text{3: } \quad \PSif \lnot \BlockEval \PSthen \\ &\text{4: } \quad \quad \PSreturn \PScomment{No pending Block Evaluator exists} \\ &\text{5: } \quad \PSendif \\ &\text{6: } \quad \PSif \lnot \texttt{recompute} \PSthen \\ &\text{7: } \quad \quad r \gets \Ledger.\mathrm{getLatestRound}() \\ &\text{8: } \quad \quad t^\ast \gets \now() + \delta_{\NB} \\ &\text{9: } \quad \quad \PSwhile \BlockEval.\mathrm{round}() \leq r \land \now() < t^\ast \PSdo \\ &\text{10:} \quad \quad \quad \textsf{Give time to the } \BlockEval \textsf{ to catch up} \\ &\text{11:} \quad \quad \PSendwhile \\ &\text{12:} \quad \quad \PSif \lnot \CheckSufficientFee(gtx) \PSthen \\ &\text{13:} \quad \quad \quad \PSreturn gtx \PScomment{Discarded for insufficient fees} \\ &\text{14:} \quad \quad \PSendif \\ &\text{15:} \quad \PSendif \\ &\text{16:} \quad \TP \gets \BlockEval.\mathrm{add}(gtx) \\ &\text{17:} \quad \dots \\ &\text{18: } \PSendfunction \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 \PSfunction {\textbf{function }} \newcommand \PSreturn {\textbf{return }} \newcommand \PSendfunction {\textbf{end function}} \newcommand \PSswitch {\textbf{switch }} \newcommand \PScase {\textbf{case }} \newcommand \PSdefault {\textbf{default}} \newcommand \PSendswitch {\textbf{end switch}} \newcommand \PSfor {\textbf{for }} \newcommand \PSendfor {\textbf{end for}} \newcommand \PSwhile {\textbf{while }} \newcommand \PSendwhile {\textbf{end while}} \newcommand \PSdo {\textbf{ do}} \newcommand \PSif {\textbf{if }} \newcommand \PSelseif {\textbf{else if }} \newcommand \PSelse {\textbf{else}} \newcommand \PSthen {\textbf{ then}} \newcommand \PSendif {\textbf{end if}} \newcommand \PSnot {\textbf{not }} \newcommand \PScomment {\qquad \small \textsf} $$
$$ \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 \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: } \PSfunction \AssembleBlock(r) \\ &\text{2: } \quad \PSif \TP.\r < r - 2 \PSthen \\ &\text{3: } \quad \quad \PSreturn \AssembleBlock.\EB(r) \\ &\text{4: } \quad \PSendif \\ &\text{5: } \quad \PSif r < \TP.\r \PSthen \\ &\text{6: } \quad \quad \PSreturn \nil \\ &\text{7: } \quad \PSendif \\ &\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 \PSif \lnot \BlockEval.\mathrm{done}() \PSthen \\ &\text{11:} \quad \quad \PSif \TP.\r > r \PSthen \\ &\text{12:} \quad \quad \quad \PSreturn \nil \PScomment{r is behind } \TP.\r \\ &\text{13:} \quad \quad \PSendif \\ &\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 \PSif \lnot \BlockEval.\mathrm{done}() \PSthen \\ &\text{17:} \quad \quad \quad \PSreturn \AssembleBlock.\EB(r) \PScomment{Ran out of time} \\ &\text{18:} \quad \quad \PSendif \\ &\text{19:} \quad \quad \PSif \TP.\r > r \PSthen \\ &\text{20:} \quad \quad \quad \PSreturn \nil \PScomment{Requested round is behind transaction pool round} \\ &\text{21:} \quad \quad \PSelseif \TP.\r = r - 1 \PSthen \\ &\text{22:} \quad \quad \quad \PSreturn \AssembleBlock.\EB(r) \\ &\text{23:} \quad \quad \PSelseif \TP.\r < r \PSthen \\ &\text{24:} \quad \quad \quad \PSreturn \nil \\ &\text{25:} \quad \quad \PSendif \\ &\text{26:} \quad \PSendif \\ &\text{27:} \quad \PSreturn \BlockEval.\mathrm{block} \\ &\text{28: } \PSendfunction \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 callingStartEvaluator
to 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 theirLastValid
round:(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 theStateProofNextRound
in 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.
Algorand Virtual Machine Overview
The Algorand Virtual Machine (AVM) is a bytecode-based Turing-complete stack interpreter that executes programs associated with Algorand transactions.
TEAL is an assembly language syntax for specifying a program that is ultimately converted to AVM bytecode.
The AVM approves or rejects transactions’ effects on the Ledger state.
The Algorand Virtual Machine (AVM) and TEAL
The AVM is a bytecode-based stack interpreter that executes programs associated with Algorand transactions.
The Algorand Transaction Execution Approval Language (TEAL) is an assembly language syntax for specifying a program that is ultimately converted to AVM bytecode.
These programs can be used to check the parameters of the transaction and approve the transaction as if by a signature. This use is called a Logic Signature. Starting with AVM Version 2, these programs may also execute as Smart Contracts, which are often called Applications. Contract executions are invoked with explicit application call transactions.
Logic Signatures have read-only access to the transaction they are attached to, the other transactions in their transaction group, and a few global values. In addition, Applications have access to limited state that is global to the application, per-account local state for each account that has opted-in to the application, and additional per-application arbitrary state in named boxes.
For both types of program, approval is signaled by finishing with the stack containing
a single non-zero uint64
value, though return
can be used to signal an early
approval which approves based only upon the top stack value being a non-zero uint64
value.
The Stack
The AVM Stack is the core structure for the interpreter’s execution. It is constructed and used as a regular stack data structure.
⚙️ IMPLEMENTATION
Stack reference implementation.
On a new AVM program execution, the Stack starts empty. After opcode execution the Stack can contain arbitrary values of either:
-
64-bit unsigned integer type (referred to as
uint64
), or -
Byte-array type (referred to as
bytes
or[]byte
).
Byte-arrays length MUST NOT exceed \( 4096 \) bytes.
A value that may be of either one of these types (not simultaneously) is generically
referred to as a StackValue
.
Most operations act on the stack, popping arguments from it and pushing results to it. Some operations have immediate arguments encoded directly into the instruction, rather than coming from the Stack.
The maximum Stack depth is \( 1000 \). If the Stack depth is exceeded or if a byte-array element exceeds \( 4096 \) bytes, the program fails.
If an opcode is documented to access a position in the Stack that does not exist, the operation fails.
📎 EXAMPLE
This is often an attempt to access an element below the Stack. A simple example is an operation like
concat
that expects two arguments on the Stack: if the Stack has fewer than two elements, the operation fails.
Some operations (like frame_dig
and proto
) could fail because of an attempt to
access above the current Stack.
Stack Types
While every element of the Stack is restricted to the types uint64
and bytes
,
the values of these types may be known to be bounded.
The most common bounded types are named to provide more semantic information in the documentation and as type checking during assembly time to provide more informative error messages.
Definitions
Name | Bound | AVM Type |
---|---|---|
[]byte | \( len(x) \leq 4096 \) | []byte |
[32]byte | \( len(x) = 32 \) | []byte |
[64]byte | \( len(x) = 64 \) | []byte |
[80]byte | \( len(x) = 80 \) | []byte |
address | \( len(x) = 32 \) | []byte |
bigint | \( len(x) \leq 64 \) | []byte |
boxName | \( 1 \leq len(x) \leq 64 \) | []byte |
method | \( len(x) = 4 \) | []byte |
stateKey | \( len(x) \leq 64 \) | []byte |
bool | \( x \leq 1 \) | uint64 |
uint64 | \( x \leq 18446744073709551615 \) | uint64 |
any | any | |
none | none |
The Scratch Space (Heap)
In addition to the Stack, the AVM has a Scratch Space (or heap) of \( 256 \) positions.
Like Stack values, scratch locations may hold stackValues
(of either uint64
or bytes
types) and are initialized as zeroed-out uint64
values.
The Scratch Space is an additional area of volatile memory used at runtime. It’s useful for storing values needed multiple times during program execution, or that stay the same for long periods. It provides a convenient place to keep such persistent or reusable data during the program execution.
⚙️ IMPLEMENTATION
Scratch Space reference implementation.
Indexing
Scratch Space locations are mapped to \( 0 \)-based integer index.
Access
The Scratch Space is accessed by the load(s)
and store(s)
opcodes which move
data respectively:
-
From the Scratch Sspace to the Stack;
-
From the Stack to the Scratch Space.
Persistency
Application calls MAY inspect the final Scratch Space of earlier application
call transactions in the same group using gload(s')(s)
, where:
-
s
is the integer index of a Scratch Sspace location, -
s'
is the integer index of an earlier application call transaction in the group.
$$ \newcommand \LogicSig {\mathrm{LSig}} \newcommand \LogicSigMaxSize {\LogicSig_{\max}} \newcommand \LogicSigMaxCost {\LogicSig_{c,\max}} \newcommand \App {\mathrm{App}} \newcommand \MaxAppTotalProgramLen {\App_{\mathrm{prog},t,\max}} \newcommand \MaxExtraAppProgramPages {\App_{\mathrm{page},\max}} \newcommand \MaxAppProgramCost {\App_{c,\max}} $$
Execution Modes
Starting from Version 2, the AVM can run programs in two modes:
-
Logic Signature (or stateless) mode, used to execute Logic Signatures;
-
Application (or stateful) mode, used to execute Smart Contracts.
Differences between modes include:
-
The maximum allowed program size, defined by the following Ledger parameters:
- \( \LogicSigMaxSize \)
- \( \MaxAppTotalProgramLen \)
- \( \MaxExtraAppProgramPages \)
-
The maximum allowed program cost, defined by the following Ledger parameters:
- \( \LogicSigMaxCost \)
- \( \MaxAppProgramCost \)
-
Opcode availability (refer to the Opcodes Specifications for details).
-
Some Global Fields are only available in Application mode.
-
Only Applications can observe transaction effects, such as Logs or IDs allocated to ASAs or new Applications.
$$ \newcommand \LogicSig {\mathrm{LSig}} \newcommand \LogicSigMaxSize {\LogicSig_{\max}} \newcommand \LogicSigMaxCost {\LogicSig_{c,\max}} $$
Execution Environment for Logic Signatures
Logic Signatures execute as part of testing a proposed transaction to see if it is
valid and authorized to be committed into a block. If an authorized program executes
and finishes with a single non-zero uint64
value on the Stack, then that program
has validated the transaction it is attached to.
The program has access to data from the transaction it is attached to (txn
opcode),
any transactions in a transaction group it is part of (gtxn
opcode), and a few
global values like consensus parameters (global
opcode).
Some arguments may be attached to a transaction being validated by a program. Arguments are an array of byte strings. A common pattern would be to have the key to unlock some contract as an argument.
Be aware that Logic Signature arguments are recorded on the blockchain and publicly
visible when the transaction is submitted to the network, even before the transaction
has been included in a block. These arguments are not part of the transaction ID
nor of the transaction group hash (grp
). They
also cannot be read from other programs in the group of transactions.
Bytecode Size
The size of a Logic Signature is defined as the length of its bytecode plus the length of all its arguments. The sum of the sizes of all Logic Signatures in a group MUST NOT exceed \( \LogicSigMaxSize \) bytes times the number of transactions in the group1.
Opcode Budget
Each opcode has an associated cost, usually \( 1 \), but a few slow operations have higher costs.
Before Version 4, the program’s cost was estimated as the static sum of all the opcode costs in the program (whether they were actually executed or not).
Beginning with Version 4, the program’s cost is tracked dynamically while being evaluated. If the program exceeds its opcode budget, it fails.
The total program cost of all Logic Signatures in a group MUST NOT exceed \( \LogicSigMaxCost \) times the number of transactions in the group1.
⚙️ IMPLEMENTATION
Opcode budget tracker reference implementation.
Modes
A program can either authorize some delegated action on a normal signature-based or multisignature-based account or be wholly in charge of a contract account.
Delegated Signature Mode
If the account has signed the program (by providing a valid Ed25519
signature or valid multisignature for the authorizer address on the string Program
concatenated with the program bytecode) then, if the program returns True
, the
transaction is authorized as if the account had signed it. This allows an account
to hand out a signed program so that other users can carry out delegated actions
that are approved by the program. Note that Logic Signature arguments are not
signed.
Contract Account Mode
If the SHA-512/256 hash of the program (prefixed
by Program
) is equal to the authorizer address of the transaction sender, then
this is a contract account wholly controlled by the program. No other signature is
necessary or possible. The only way to execute a transaction against the contract
account is for the program to approve it.
-
See the Ledger parameters section. ↩ ↩2
$$ \newcommand \App {\mathrm{App}} \newcommand \MaxAppProgramCost {\App_{c,\max}} \newcommand \MaxTxGroupSize {GT_{\max}} \newcommand \MaxInnerTransactions {\App_\mathrm{itxn}} \newcommand \MaxAppTotalProgramLen {\App_{\mathrm{prog},t,\max}} \newcommand \MaxExtraAppProgramPages {\App_{\mathrm{page},\max}} $$
Execution Environment for Applications (Smart Contracts)
Applications (Smart Contracts) are executed in Application Call transactions.
Like Logic Signatures, Applications indicate success by leaving a single non-zero
uint64
value on the Stack.
A failed Application Call to an Approval Program is not a valid transaction, thus not written to the blockchain.
An Application Call with On Complete
set to ClearStateOC
invokes the Clear State Program, rather than the usual Approval
Program. If the Clear State Program fails, application state changes are rolled back,
but the transaction still succeeds, and the sender’s Local State for the called application
is removed.
Applications have access to everything a Logic Signature may access (see section), as well as the ability to examine blockchain state, such as balances and application state (their own state and the state of other applications).
Applications also have access to some Global Fields that are not visible to Logic Signatures because their values change over time.
Since Applications access changing state, nodes have to rerun their code to determine if the Application Call transactions in their Transaction Pool would still succeed each time a block is added to the blockchain.
Bytecode Size
The size of an Application is defined as the length of its approval program bytecode plus its clearstate program bytecode. The sum of these two programs MUST NOT exceed \( \MaxAppTotalProgramLen \times \MaxExtraAppProgramPages \).
Opcode Budget
Applications have limits on their execution cost.
Before Version 4, this was a static limit on the cost of all the instructions in the program.
Starting in Version 4, the cost is tracked dynamically during execution and MUST NOT exceed \( \MaxAppProgramCost \).
Beginning with Version 5, programs costs are pooled and tracked dynamically across Application executions in a group. If \( n \) application invocations appear in a group, then the total execution cost of all such calls MUST NOT exceed \( n \times \MaxAppProgramCost \).
In Version 6, inner Application Calls become possible, and each such call increases
the pooled opcode budget by \( \MaxAppProgramCost \) at the time the inner group
is submitted (with the itxn_submit
opcode), allowing a maximum opcode budget of:
$$ \MaxTxGroupSize \times (1 + \MaxInnerTransactions) \times \MaxAppProgramCost = 190{,}400 $$
Clear Program Execution
Executions of the Clear State Program are more stringent to ensure that Applications may be closed out (by accounts) and have a chance to clean up their internal state.
At the beginning of a Clear State Program execution, the pooled budget available MUST be \( \MaxAppProgramCost \) or higher. If it is not, the containing transaction group fails without clearing the Application’s state.
During the Clear State Program execution, no more than \( \MaxAppProgramCost \) may be drawn. If further execution is attempted, the Clear State Program fails, and the Application’s state is cleared.
Resource Availability
Applications have limits on the amount of blockchain state they may examine.
These limits are enforced by failing any opcode that attempts to access a resource unless the resource is available. These resources are:
-
Accounts, which MUST be available to access their balance, or other Account parameters such as voting details.
-
Assets, which MUST be available to access global asset parameters, such as the asset’s URL, name, or privileged addresses.
-
Holdings, which MUST be available to access a particular Account’s balance or frozen status for a particular asset.
-
Applications, which MUST be available to read an Application’s programs, parameters, or Global State.
-
Locals, which MUST be available to read a particular Account’s Local State for a particular Application.
-
Boxes, which MUST be available to read or write a box, designated by an Application and name for the Box.
Resources are available based on the contents of the executing transaction and, in later versions, the contents of other transactions in the same group.
-
A resource in the foreign array fields of the Application Call transaction (foreign accounts, foreign assets, and foreign applications) is available.
-
The transaction sender (
snd
) is available. -
The Global Fields
CurrentApplicationID
, andCurrentApplicationAddress
are available. -
In pre-Version 4 programs, all Holdings are available to the
asset_holding_get
opcode, and all Locals are available to theapp_local_get_ex
opcode if the Account of the resource is available. -
In Version 6 (and later) programs, any Asset or Application created earlier in the same transaction group (whether by a top-level or inner transaction) is available. In addition, any Account that is the associated Account of an Application that was created earlier in the group is available.
-
In Version 7 (and later) programs, the Account associated with any Application present in the foreign applications field is available.
-
In Version 4 (and later) programs, Holdings and Locals are available if both components of the resource are available according to the above rules.
-
In Version 9 (and later) programs, there is group resource sharing. Any resource that is available in some top-level transaction in a group is available in all Version 9 or later Application calls in the group, whether those Application calls are top-level or inner.
-
Version 9 (and later) programs MAY use the transaction access list instead of the foreign arrays. When using the transaction access list, each resource MUST be listed explicitly, since the automatic availability of the foreign arrays is no longer provided, in particular:
-
Holdings and Locals are no longer automatically available because their components are.
-
Application Accounts are no longer automatically available because of the availability of their corresponding Applications.
-
However, the transaction access list allows for the listing of more resources than the foreign arrays. Listed resources become available to other (post-Version 8 programs) Applications through group resource sharing.
- When considering whether a Holding or Local is available by group resource sharing, the Holding or Local MUST be available in a top-level transaction based on pre-Version 9 rules.
📎 EXAMPLE
If account
A
is made available in one transaction, and assetX
is made available in another, group resource sharing does not makeA
’sX
Holding available.
-
Top-level transactions that are not Application Calls also make resources available to group-level resource sharing. The following resources are made available by other transaction types:
-
pay
: sender (snd
), receiver (rcv
), and close-to (close
) (if set). -
keyreg
: sender (snd
). -
acfg
: sender (snd
), configured asset (caid
), and the configured asset holding of the sender. -
axfer
: sender (snd
), asset receiver (arcv
), asset sender (asnd
) (if set), asset close-to (aclose
) (if set), transferred asset (xaid
), and the transferred asset holding of each of those accounts. -
afrz
: sender (snd
), freeze account (fadd
), freeze asset (faid
), and the freeze asset holding of the freeze account. The freeze asset holding of the sender is not made available.
-
-
A Box is available to an Approval Program if any transaction in the same group contains a box reference (in transaction’s box references
apbx
or access listal
) that denotes the Box. A box reference contains an indexi
, and namen
. The index refers to thei
-th Application in the transaction’s foreign applications or access list array (only one of which can be used), with the usual convention that0
indicates the Application ID of the Application called by that transaction. No Box is ever available to a Clear State Program.
Regardless of availability, any attempt to access an Asset or Application with an ID less than \( 256 \) from within an Application will fail immediately. This avoids any ambiguity in opcodes that interpret their integer arguments as resource IDs or indexes into the foreign assets or foreign applications arrays.
It is RECOMMENDED that Application authors avoid supplying array indexes to these opcodes, and always use explicit resource IDs. By using explicit IDs, contracts will better take advantage of group resource sharing.
The array indexing interpretation MAY be deprecated in a future program version.
Constants
Constants can be pushed onto the Stack in two different ways:
-
Constants can be pushed directly with
pushint
orpushbytes
opcodes. This method is more efficient for constants that are only used once. -
Constants can be loaded into storage separate from the Stack and Scratch Space, with
intcblock
orbytecblock
opcodes. Then, constants from this storage can be pushed onto the Stack by referring to the type and index usingintc
,intc_[0123]
,bytec
, andbytec_[0123]
. This method is more efficient for constants that are used multiple times.
The opcodes intcblock
and bytecblock
use proto-buf style variable length unsigned int,
reproduced in the varuint
section.
The intcblock
opcode is followed by a varuint
specifying the number of integer
constants, and then that number of varuint
.
The bytecblock
opcode is followed by a varuint
specifying the number of byte
constants, and then that number of pairs of (varuint, bytes)
length prefixed byte
strings.
Assembly
The Assembler will hide most of this, for example, allowing
simple use of int 1234
and byte 0xcafed00d
. Constants introduced via int
and
byte
will be assembled into appropriate uses of pushint|pushbytes
and {int|byte}c, {int|byte}c_[0123]
to minimize program bytecode size.
Named Integer Constants
On Complete Action Enum Constants
An application call transaction MUST indicate the action to be taken following the execution of its Approval Program or Clear State Program.
The constants below describe the available actions.
ACTION | VALUE | DESCRIPTION |
---|---|---|
NoOpOC | 0 | Only execute the Approval Program associated with the application ID, with no additional effects. |
OptInOC | 1 | Before executing the Approval Program, allocate local state for the application ID into the sender’s account data. |
CloseOutOC | 2 | After executing the Approval Program, clear any local state for the application ID out of the sender’s account data. |
ClearStateOC | 3 | Do not execute the Approval Program, and instead execute the Clear State Program (which may not reject this transaction). Additionally, clear any local state for the application ID out of the sender’s account data (as in CloseOutOC ). |
UpdateApplicationOC | 4 | After executing the Approval Program, replace the Approval Program and Clear State Program associated with the application ID with the programs specified in this transaction. |
DeleteApplicationOC | 5 | After executing the Approval Program, delete the parameters of with the application ID from the account data of the application’s creator. |
Transaction Type Enum Constants
TYPE | VALUE | DESCRIPTION |
---|---|---|
unknown | 0 | Unknown type, invalid transaction |
pay | 1 | ALGO transfers (payment) |
keyreg | 2 | Consensus keys registration |
acfg | 3 | Asset creation and configuration |
axfer | 4 | Asset transfer |
afrz | 5 | Asset freeze and unfreeze |
appl | 6 | Application calls |
stpf | 7 | State Proof |
hb | 8 | Consensus heartbeat |
Operations
Most AVM operations work with only one type of argument, uint64
or bytes
, and
fail if the wrong type value is on the Stack.
Many instructions accept values to designate Accounts, Assets, or Applications.
Beginning with Version 4, these values may be given as an offset in the corresponding
transaction fields (foreign accounts,
foreign assets, foreign
applications) or
as the value itself (bytes
address for foreign accounts, or a uint64
ID for
foreign assets and applications). The values, however, MUST still be present
in the transaction fields.
Before Version 4, most opcodes required using an offset, except for reading account local values of assets or applications, which accepted the IDs directly and did not require the ID to be present in the corresponding foreign array.
Beginning with Version 4, those IDs are required to be present in their corresponding foreign array. See individual opcodes for details.
In the case of account offsets or application offsets, \( 0 \) is specially defined to the transaction sender or the ID of the current application, respectively.
This section provides a summary of the AVM opcodes, divided by categories. A short description and a link to the reference implementation are provided for each opcode. This summary is supplemented by more detail in the opcodes specification.
Some operations immediately fail the program. A transaction checked by a program that fails is not valid.
In the documentation for each opcode, the popped Stack arguments are referred to
alphabetically, beginning with the deepest argument as A
. These arguments are
shown in the opcode description, and if the opcode must be of a specific type, it
is noted there.
All opcodes fail if a specified type is incorrect.
If an opcode pushes multiple results, the values are named for ease of exposition and clarity concerning their Stack positions.
When an opcode manipulates the Stack in such a way that a value changes position but is otherwise unchanged, the name of the output on the return Stack matches the name of the input value.
Arithmetic and Logic Operations
OPCODE | DESCRIPTION |
---|---|
+ | A plus B. Fail on overflow. |
- | A minus B. Fail if B > A. |
/ | A divided by B (truncated division). Fail if B == 0. |
* | A times B. Fail on overflow. |
< | A less than B => {0 or 1} |
> | A greater than B => {0 or 1} |
<= | A less than or equal to B => {0 or 1} |
>= | A greater than or equal to B => {0 or 1} |
&& | A is not zero and B is not zero => {0 or 1} |
|| | A is not zero or B is not zero => {0 or 1} |
shl | A times 2^B, modulo 2^64 |
shr | A divided by 2^B |
sqrt | The largest integer I such that I^2 <= A |
bitlen | The highest set bit in A. If A is a byte-array, it is interpreted as a big-endian unsigned integer. bitlen of 0 is 0, bitlen of 8 is 4 |
exp | A raised to the Bth power. Fail if A == B == 0 and on overflow |
== | A is equal to B => {0 or 1} |
!= | A is not equal to B => {0 or 1} |
! | A == 0 yields 1; else 0 |
itob | converts uint64 A to big-endian byte array, always of length 8 |
btoi | converts big-endian byte array A to uint64. Fails if len(A) > 8. Padded by leading 0s if len(A) < 8. |
% | A modulo B. Fail if B == 0. |
| | A bitwise-or B |
& | A bitwise-and B |
^ | A bitwise-xor B |
~ | bitwise invert value A |
mulw | A times B as a 128-bit result in two uint64s. X is the high 64 bits, Y is the low |
addw | A plus B as a 128-bit result. X is the carry-bit, Y is the low-order 64 bits. |
divw | A,B / C. Fail if C == 0 or if result overflows. |
divmodw | W,X = (A,B / C,D); Y,Z = (A,B modulo C,D) |
expw | A raised to the Bth power as a 128-bit result in two uint64s. X is the high 64 bits, Y is the low. Fail if A == B == 0 or if the results exceeds 2^128-1 |
Byte Array Manipulation
Byte Manipulation
OPCODE | DESCRIPTION |
---|---|
getbit | Bth bit of (byte-array or integer) A. If B is greater than or equal to the bit length of the value (8*byte length), the program fails |
setbit | Copy of (byte-array or integer) A, with the Bth bit set to (0 or 1) C. If B is greater than or equal to the bit length of the value (8*byte length), the program fails |
getbyte | Bth byte of A, as an integer. If B is greater than or equal to the array length, the program fails |
setbyte | Copy of A with the Bth byte set to small integer (between 0..255) C. If B is greater than or equal to the array length, the program fails |
concat | Join A and B |
len | Yields length of byte value A |
substring s e | A range of bytes from A starting at S up to but not including E. If E < S, or either is larger than the array length, the program fails |
substring3 | A range of bytes from A starting at B up to but not including C. If C < B, or either is larger than the array length, the program fails |
extract s l | A range of bytes from A starting at S up to but not including S+L. If L is 0, then extract to the end of the string. If S or S+L is larger than the array length, the program fails |
extract3 | A range of bytes from A starting at B up to but not including B+C. If B+C is larger than the array length, the program fails extract3 can be called using extract with no immediates. |
extract_uint16 | A uint16 formed from a range of big-endian bytes from A starting at B up to but not including B+2. If B+2 is larger than the array length, the program fails |
extract_uint32 | A uint32 formed from a range of big-endian bytes from A starting at B up to but not including B+4. If B+4 is larger than the array length, the program fails |
extract_uint64 | A uint64 formed from a range of big-endian bytes from A starting at B up to but not including B+8. If B+8 is larger than the array length, the program fails |
replace2 s | Copy of A with the bytes starting at S replaced by the bytes of B. Fails if S+len(B) exceeds len(A) replace2 can be called using replace with 1 immediate. |
replace3 | Copy of A with the bytes starting at B replaced by the bytes of C. Fails if B+len(C) exceeds len(A) replace3 can be called using replace with no immediates. |
base64_decode e | decode A which was base64-encoded using encoding E. Fail if A is not base64 encoded with encoding E |
json_ref r | key B’s value, of type R, from a valid UTF-8 encoded json object A |
Byte Arithmetic
The following opcodes take byte-array values that are interpreted as big-endian unsigned integers. For mathematical operators, the returned values are the shortest byte-array that can represent the returned value. For example, the zero value is the empty byte-array. For comparison operators, the returned value is a uint64.
Input lengths are limited to a maximum length of 64 bytes,
representing a 512 bit unsigned integer. Output lengths are not
explicitly restricted, though only b*
and b+
can produce a larger
output than their inputs, so there is an implicit length limit of 128
bytes on outputs.
OPCODE | DESCRIPTION |
---|---|
b+ | A plus B. A and B are interpreted as big-endian unsigned integers |
b- | A minus B. A and B are interpreted as big-endian unsigned integers. Fail on underflow. |
b/ | A divided by B (truncated division). A and B are interpreted as big-endian unsigned integers. Fail if B is zero. |
b* | A times B. A and B are interpreted as big-endian unsigned integers. |
b< | 1 if A is less than B, else 0. A and B are interpreted as big-endian unsigned integers |
b> | 1 if A is greater than B, else 0. A and B are interpreted as big-endian unsigned integers |
b<= | 1 if A is less than or equal to B, else 0. A and B are interpreted as big-endian unsigned integers |
b>= | 1 if A is greater than or equal to B, else 0. A and B are interpreted as big-endian unsigned integers |
b== | 1 if A is equal to B, else 0. A and B are interpreted as big-endian unsigned integers |
b!= | 0 if A is equal to B, else 1. A and B are interpreted as big-endian unsigned integers |
b% | A modulo B. A and B are interpreted as big-endian unsigned integers. Fail if B is zero. |
bsqrt | The largest integer I such that I^2 <= A. A and I are interpreted as big-endian unsigned integers |
Bitwise Operations
These opcodes operate on the bits of byte-array values. The shorter input array is interpreted as though left padded with zeros until it is the same length as the other input. The returned values are the same length as the longer input. Therefore, unlike array arithmetic, these results may contain leading zero bytes.
OPCODE | DESCRIPTION |
---|---|
b| | A bitwise-or B. A and B are zero-left extended to the greater of their lengths |
b& | A bitwise-and B. A and B are zero-left extended to the greater of their lengths |
b^ | A bitwise-xor B. A and B are zero-left extended to the greater of their lengths |
b~ | A with all bits inverted |
Cryptographic Operations
OPCODE | DESCRIPTION |
---|---|
sha256 | SHA256 hash of value A, yields [32]byte |
keccak256 | Keccak256 hash of value A, yields [32]byte |
sha512_256 | SHA512_256 hash of value A, yields [32]byte |
sha3_256 | SHA3_256 hash of value A, yields [32]byte |
falcon_verify | for (data A, compressed-format signature B, pubkey C) verify the signature of data against the pubkey => {0 or 1} |
ed25519verify | for (data A, signature B, pubkey C) verify the signature of (“ProgData” || program_hash || data) against the pubkey => {0 or 1} |
ed25519verify_bare | for (data A, signature B, pubkey C) verify the signature of the data against the pubkey => {0 or 1} |
ecdsa_verify v | for (data A, signature B, C and pubkey D, E) verify the signature of the data against the pubkey => {0 or 1} |
ecdsa_pk_recover v | for (data A, recovery id B, signature C, D) recover a public key |
ecdsa_pk_decompress v | decompress pubkey A into components X, Y |
vrf_verify s | Verify the proof B of message A against pubkey C. Returns vrf output and verification flag. |
ec_add g | for curve points A and B, return the curve point A + B |
ec_scalar_mul g | for curve point A and scalar B, return the curve point BA, the point A multiplied by the scalar B. |
ec_pairing_check g | 1 if the product of the pairing of each point in A with its respective point in B is equal to the identity element of the target group Gt, else 0 |
ec_multi_scalar_mul g | for curve points A and scalars B, return curve point B0A0 + B1A1 + B2A2 + … + BnAn |
ec_subgroup_check g | 1 if A is in the main prime-order subgroup of G (including the point at infinity) else 0. Program fails if A is not in G at all. |
ec_map_to g | maps field element A to group G |
mimc c | MiMC hash of scalars A, using curve and parameters specified by configuration C |
Loading Values
Opcodes for getting data onto the stack.
Some of these have immediate data in the byte or bytes after the opcode.
OPCODE | DESCRIPTION |
---|---|
intcblock uint ... | prepare block of uint64 constants for use by intc |
intc i | Ith constant from intcblock |
intc_0 | constant 0 from intcblock |
intc_1 | constant 1 from intcblock |
intc_2 | constant 2 from intcblock |
intc_3 | constant 3 from intcblock |
pushint uint | immediate UINT |
pushints uint ... | push sequence of immediate uints to stack in the order they appear (first uint being deepest) |
bytecblock bytes ... | prepare block of byte-array constants for use by bytec |
bytec i | Ith constant from bytecblock |
bytec_0 | constant 0 from bytecblock |
bytec_1 | constant 1 from bytecblock |
bytec_2 | constant 2 from bytecblock |
bytec_3 | constant 3 from bytecblock |
pushbytes bytes | immediate BYTES |
pushbytess bytes ... | push sequences of immediate byte arrays to stack (first byte array being deepest) |
bzero | zero filled byte-array of length A |
arg n | Nth LogicSig argument |
arg_0 | LogicSig argument 0 |
arg_1 | LogicSig argument 1 |
arg_2 | LogicSig argument 2 |
arg_3 | LogicSig argument 3 |
args | Ath LogicSig argument |
txn f | field F of current transaction |
gtxn t f | field F of the Tth transaction in the current group |
txna f i | Ith value of the array field F of the current transaction txna can be called using txn with 2 immediates. |
txnas f | Ath value of the array field F of the current transaction |
gtxna t f i | Ith value of the array field F from the Tth transaction in the current group gtxna can be called using gtxn with 3 immediates. |
gtxnas t f | Ath value of the array field F from the Tth transaction in the current group |
gtxns f | field F of the Ath transaction in the current group |
gtxnsa f i | Ith value of the array field F from the Ath transaction in the current group gtxnsa can be called using gtxns with 2 immediates. |
gtxnsas f | Bth value of the array field F from the Ath transaction in the current group |
global f | global field F |
load i | Ith scratch space value. All scratch spaces are 0 at program start. |
loads | Ath scratch space value. All scratch spaces are 0 at program start. |
store i | store A to the Ith scratch space |
stores | store B to the Ath scratch space |
gload t i | Ith scratch space value of the Tth transaction in the current group |
gloads i | Ith scratch space value of the Ath transaction in the current group |
gloadss | Bth scratch space value of the Ath transaction in the current group |
gaid t | ID of the asset or application created in the Tth transaction of the current group |
gaids | ID of the asset or application created in the Ath transaction of the current group |
Fields
Transaction Fields
For further details on transaction fields, refer to the
txn
opcode specification.
Scalar Fields
INDEX | NAME | TYPE | IN | DESCRIPTION |
---|---|---|---|---|
0 | Sender | address | 32 byte address | |
1 | Fee | uint64 | microalgos | |
2 | FirstValid | uint64 | round number | |
3 | FirstValidTime | uint64 | v7 | UNIX timestamp of block before txn.FirstValid. Fails if negative |
4 | LastValid | uint64 | round number | |
5 | Note | []byte | Any data up to 1024 bytes | |
6 | Lease | [32]byte | 32 byte lease value | |
7 | Receiver | address | 32 byte address | |
8 | Amount | uint64 | microalgos | |
9 | CloseRemainderTo | address | 32 byte address | |
10 | VotePK | [32]byte | 32 byte address | |
11 | SelectionPK | [32]byte | 32 byte address | |
12 | VoteFirst | uint64 | The first round that the participation key is valid. | |
13 | VoteLast | uint64 | The last round that the participation key is valid. | |
14 | VoteKeyDilution | uint64 | Dilution for the 2-level participation key | |
15 | Type | []byte | Transaction type as bytes | |
16 | TypeEnum | uint64 | Transaction type as integer | |
17 | XferAsset | uint64 | Asset ID | |
18 | AssetAmount | uint64 | value in Asset’s units | |
19 | AssetSender | address | 32 byte address. Source of assets if Sender is the Asset’s Clawback address. | |
20 | AssetReceiver | address | 32 byte address | |
21 | AssetCloseTo | address | 32 byte address | |
22 | GroupIndex | uint64 | Position of this transaction within an atomic transaction group. A stand-alone transaction is implicitly element 0 in a group of 1 | |
23 | TxID | [32]byte | The computed ID for this transaction. 32 bytes. | |
24 | ApplicationID | uint64 | v2 | ApplicationID from ApplicationCall transaction |
25 | OnCompletion | uint64 | v2 | ApplicationCall transaction on completion action |
27 | NumAppArgs | uint64 | v2 | Number of ApplicationArgs |
29 | NumAccounts | uint64 | v2 | Number of Accounts |
30 | ApprovalProgram | []byte | v2 | Approval program |
31 | ClearStateProgram | []byte | v2 | Clear state program |
32 | RekeyTo | address | v2 | 32 byte Sender’s new AuthAddr |
33 | ConfigAsset | uint64 | v2 | Asset ID in asset config transaction |
34 | ConfigAssetTotal | uint64 | v2 | Total number of units of this asset created |
35 | ConfigAssetDecimals | uint64 | v2 | Number of digits to display after the decimal place when displaying the asset |
36 | ConfigAssetDefaultFrozen | bool | v2 | Whether the asset’s slots are frozen by default or not, 0 or 1 |
37 | ConfigAssetUnitName | []byte | v2 | Unit name of the asset |
38 | ConfigAssetName | []byte | v2 | The asset name |
39 | ConfigAssetURL | []byte | v2 | URL |
40 | ConfigAssetMetadataHash | [32]byte | v2 | 32 byte commitment to unspecified asset metadata |
41 | ConfigAssetManager | address | v2 | 32 byte address |
42 | ConfigAssetReserve | address | v2 | 32 byte address |
43 | ConfigAssetFreeze | address | v2 | 32 byte address |
44 | ConfigAssetClawback | address | v2 | 32 byte address |
45 | FreezeAsset | uint64 | v2 | Asset ID being frozen or un-frozen |
46 | FreezeAssetAccount | address | v2 | 32 byte address of the account whose asset slot is being frozen or un-frozen |
47 | FreezeAssetFrozen | bool | v2 | The new frozen value, 0 or 1 |
49 | NumAssets | uint64 | v3 | Number of Assets |
51 | NumApplications | uint64 | v3 | Number of Applications |
52 | GlobalNumUint | uint64 | v3 | Number of global state integers in ApplicationCall |
53 | GlobalNumByteSlice | uint64 | v3 | Number of global state byteslices in ApplicationCall |
54 | LocalNumUint | uint64 | v3 | Number of local state integers in ApplicationCall |
55 | LocalNumByteSlice | uint64 | v3 | Number of local state byteslices in ApplicationCall |
56 | ExtraProgramPages | uint64 | v4 | Number of additional pages for each of the application’s approval and clear state programs. An ExtraProgramPages of 1 means 2048 more total bytes, or 1024 for each program. |
57 | Nonparticipation | bool | v5 | Marks an account nonparticipating for rewards |
59 | NumLogs | uint64 | v5 | Number of Logs (only with itxn in v5). Application mode only |
60 | CreatedAssetID | uint64 | v5 | Asset ID allocated by the creation of an ASA (only with itxn in v5). Application mode only |
61 | CreatedApplicationID | uint64 | v5 | ApplicationID allocated by the creation of an application (only with itxn in v5). Application mode only |
62 | LastLog | []byte | v6 | The last message emitted. Empty bytes if none were emitted. Application mode only |
63 | StateProofPK | []byte | v6 | 64 byte state proof public key |
65 | NumApprovalProgramPages | uint64 | v7 | Number of Approval Program pages |
67 | NumClearStateProgramPages | uint64 | v7 | Number of ClearState Program pages |
68 | RejectVersion | uint64 | v12 | Application version for which the txn must reject |
Array Fields
INDEX | NAME | TYPE | IN | DESCRIPTION |
---|---|---|---|---|
26 | ApplicationArgs | []byte | v2 | Arguments passed to the application in the ApplicationCall transaction |
28 | Accounts | address | v2 | Accounts listed in the ApplicationCall transaction |
48 | Assets | uint64 | v3 | Foreign Assets listed in the ApplicationCall transaction |
50 | Applications | uint64 | v3 | Foreign Apps listed in the ApplicationCall transaction |
58 | Logs | []byte | v5 | Log messages emitted by an application call (only with itxn in v5). Application mode only |
64 | ApprovalProgramPages | []byte | v7 | Approval Program as an array of pages |
66 | ClearStateProgramPages | []byte | v7 | ClearState Program as an array of pages |
Global Fields
Global fields are fields that are common to all the transactions in the group. In particular, it includes consensus parameters.
INDEX | NAME | TYPE | IN | DESCRIPTION |
---|---|---|---|---|
0 | MinTxnFee | uint64 | microalgos | |
1 | MinBalance | uint64 | microalgos | |
2 | MaxTxnLife | uint64 | rounds | |
3 | ZeroAddress | address | 32 byte address of all zero bytes | |
4 | GroupSize | uint64 | Number of transactions in this atomic transaction group. At least 1 | |
5 | LogicSigVersion | uint64 | v2 | Maximum supported version |
6 | Round | uint64 | v2 | Current round number. Application mode only. |
7 | LatestTimestamp | uint64 | v2 | Last confirmed block UNIX timestamp. Fails if negative. Application mode only. |
8 | CurrentApplicationID | uint64 | v2 | ID of current application executing. Application mode only. |
9 | CreatorAddress | address | v3 | Address of the creator of the current application. Application mode only. |
10 | CurrentApplicationAddress | address | v5 | Address that the current application controls. Application mode only. |
11 | GroupID | [32]byte | v5 | ID of the transaction group. 32 zero bytes if the transaction is not part of a group. |
12 | OpcodeBudget | uint64 | v6 | The remaining cost that can be spent by opcodes in this program. |
13 | CallerApplicationID | uint64 | v6 | The application ID of the application that called this application. 0 if this application is at the top-level. Application mode only. |
14 | CallerApplicationAddress | address | v6 | The application address of the application that called this application. ZeroAddress if this application is at the top-level. Application mode only. |
15 | AssetCreateMinBalance | uint64 | v10 | The additional minimum balance required to create (and opt-in to) an asset. |
16 | AssetOptInMinBalance | uint64 | v10 | The additional minimum balance required to opt-in to an asset. |
17 | GenesisHash | [32]byte | v10 | The Genesis Hash for the network. |
18 | PayoutsEnabled | bool | v11 | Whether block proposal payouts are enabled. |
19 | PayoutsGoOnlineFee | uint64 | v11 | The fee required in a keyreg transaction to make an account incentive eligible. |
20 | PayoutsPercent | uint64 | v11 | The percentage of transaction fees in a block that can be paid to the block proposer. |
21 | PayoutsMinBalance | uint64 | v11 | The minimum balance an account must have in the agreement round to receive block payouts in the proposal round. |
22 | PayoutsMaxBalance | uint64 | v11 | The maximum balance an account can have in the agreement round to receive block payouts in the proposal round. |
Asset Fields
Asset fields include AssetHolding
and AssetParam
fields that are used in the
asset_holding_get
and asset_params_get
opcodes.
Asset Holding
INDEX | NAME | TYPE | DESCRIPTION |
---|---|---|---|
0 | AssetBalance | uint64 | Amount of the asset unit held by this account |
1 | AssetFrozen | bool | Is the asset frozen or not |
Asset Parameters
INDEX | NAME | TYPE | IN | DESCRIPTION |
---|---|---|---|---|
0 | AssetTotal | uint64 | Total number of units of this asset | |
1 | AssetDecimals | uint64 | See AssetParams.Decimals | |
2 | AssetDefaultFrozen | bool | Frozen by default or not | |
3 | AssetUnitName | []byte | Asset unit name | |
4 | AssetName | []byte | Asset name | |
5 | AssetURL | []byte | URL with additional info about the asset | |
6 | AssetMetadataHash | [32]byte | Arbitrary commitment | |
7 | AssetManager | address | Manager address | |
8 | AssetReserve | address | Reserve address | |
9 | AssetFreeze | address | Freeze address | |
10 | AssetClawback | address | Clawback address | |
11 | AssetCreator | address | v5 | Creator address |
Application Fields
Application fields used in the app_params_get
opcode.
INDEX | NAME | TYPE | IN | DESCRIPTION |
---|---|---|---|---|
0 | AppApprovalProgram | []byte | Bytecode of Approval Program | |
1 | AppClearStateProgram | []byte | Bytecode of Clear State Program | |
2 | AppGlobalNumUint | uint64 | Number of uint64 values allowed in Global State | |
3 | AppGlobalNumByteSlice | uint64 | Number of byte array values allowed in Global State | |
4 | AppLocalNumUint | uint64 | Number of uint64 values allowed in Local State | |
5 | AppLocalNumByteSlice | uint64 | Number of byte array values allowed in Local State | |
6 | AppExtraProgramPages | uint64 | Number of Extra Program Pages of code space | |
7 | AppCreator | address | Creator address | |
8 | AppAddress | address | Address for which this application has authority | |
9 | AppVersion | uint64 | v12 | Version of the app, incremented each time the approval or clear program changes |
Account Fields
Account fields used in the acct_params_get
opcode.
INDEX | NAME | TYPE | IN | DESCRIPTION |
---|---|---|---|---|
0 | AcctBalance | uint64 | Account balance in microalgos | |
1 | AcctMinBalance | uint64 | Minimum required balance for account, in microalgos | |
2 | AcctAuthAddr | address | Address the account is rekeyed to. | |
3 | AcctTotalNumUint | uint64 | v8 | The total number of uint64 values allocated by this account in Global and Local States. |
4 | AcctTotalNumByteSlice | uint64 | v8 | The total number of byte array values allocated by this account in Global and Local States. |
5 | AcctTotalExtraAppPages | uint64 | v8 | The number of extra app code pages used by this account. |
6 | AcctTotalAppsCreated | uint64 | v8 | The number of existing apps created by this account. |
7 | AcctTotalAppsOptedIn | uint64 | v8 | The number of apps this account is opted into. |
8 | AcctTotalAssetsCreated | uint64 | v8 | The number of existing ASAs created by this account. |
9 | AcctTotalAssets | uint64 | v8 | The numbers of ASAs held by this account (including ASAs this account created). |
10 | AcctTotalBoxes | uint64 | v8 | The number of existing boxes created by this account’s app. |
11 | AcctTotalBoxBytes | uint64 | v8 | The total number of bytes used by this account’s app’s box keys and values. |
12 | AcctIncentiveEligible | bool | v11 | Has this account opted into block payouts |
13 | AcctLastProposed | uint64 | v11 | The round number of the last block this account proposed. |
14 | AcctLastHeartbeat | uint64 | v11 | The round number of the last block this account sent a heartbeat. |
Flow Control
OPCODE | DESCRIPTION |
---|---|
err | Fail immediately. |
bnz target | branch to TARGET if value A is not zero |
bz target | branch to TARGET if value A is zero |
b target | branch unconditionally to TARGET |
return | use A as success value; end |
pop | discard A |
popn n | remove N values from the top of the stack |
dup | duplicate A |
dup2 | duplicate A and B |
dupn n | duplicate A, N times |
dig n | Nth value from the top of the stack. dig 0 is equivalent to dup |
bury n | replace the Nth value from the top of the stack with A. bury 0 fails. |
cover n | remove top of stack, and place it deeper in the stack such that N elements are above it. Fails if stack depth <= N. |
uncover n | remove the value at depth N in the stack and shift above items down so the Nth deep value is on top of the stack. Fails if stack depth <= N. |
frame_dig i | Nth (signed) value from the frame pointer. |
frame_bury i | replace the Nth (signed) value from the frame pointer in the stack with A |
swap | swaps A and B on stack |
select | selects one of two values based on top-of-stack: B if C != 0, else A |
assert | immediately fail unless A is a non-zero number |
callsub target | branch unconditionally to TARGET, saving the next instruction on the call stack |
proto a r | Prepare top call frame for a retsub that will assume A args and R return values. |
retsub | pop the top instruction from the call stack and branch to it |
switch target ... | branch to the Ath label. Continue at following instruction if index A exceeds the number of labels. |
match target ... | given match cases from A[1] to A[N], branch to the Ith label where A[I] = B. Continue to the following instruction if no matches are found. |
State Access
Block Access
OPCODE | DESCRIPTION |
---|---|
online_stake | the total online stake in the agreement round |
log | write A to log state of the current application |
block f | field F of block A. Fail unless A falls between txn.LastValid-1002 and txn.FirstValid (exclusive) |
Account Access
OPCODE | DESCRIPTION |
---|---|
balance | balance for account A, in microalgos. The balance is observed after the effects of previous transactions in the group, and after the fee for the current transaction is deducted. Changes caused by inner transactions are observable immediately following itxn_submit |
min_balance | minimum required balance for account A, in microalgos. Required balance is affected by ASA, App, and Box usage. When creating or opting into an app, the minimum balance grows before the app code runs, therefore the increase is visible there. When deleting or closing out, the minimum balance decreases after the app executes. Changes caused by inner transactions or box usage are observable immediately following the opcode effecting the change. |
acct_params_get f | X is field F from account A. Y is 1 if A owns positive algos, else 0 |
voter_params_get f | X is field F from online account A as of the balance round: 320 rounds before the current round. Y is 1 if A had positive algos online in the agreement round, else Y is 0 and X is a type specific zero-value |
Asset Access
OPCODE | DESCRIPTION |
---|---|
asset_holding_get f | X is field F from account A’s holding of asset B. Y is 1 if A is opted into B, else 0 |
asset_params_get f | X is field F from asset A. Y is 1 if A exists, else 0 |
Application Access
OPCODE | DESCRIPTION |
---|---|
app_opted_in | 1 if account A is opted in to application B, else 0 |
app_local_get | local state of the key B in the current application in account A |
app_local_get_ex | X is the local state of application B, key C in account A. Y is 1 if key existed, else 0 |
app_global_get | global state of the key A in the current application |
app_global_get_ex | X is the global state of application A, key B. Y is 1 if key existed, else 0 |
app_local_put | write C to key B in account A’s local state of the current application |
app_global_put | write B to key A in the global state of the current application |
app_local_del | delete key B from account A’s local state of the current application |
app_global_del | delete key A from the global state of the current application |
app_params_get f | X is field F from app A. Y is 1 if A exists, else 0 |
Box Access
Box opcodes that create, delete, or resize boxes affect the minimum
balance requirement of the calling application’s account. The change
is immediate, and can be observed after exection by using
min_balance
. If the account does not possess the new minimum
balance, the opcode fails.
All box related opcodes fail immediately if used in a ClearStateProgram. This behavior is meant to discourage Smart Contract authors from depending upon the availability of boxes in a ClearState transaction, as accounts using ClearState are under no requirement to furnish appropriate Box References. Authors would do well to keep the same issue in mind with respect to the availability of Accounts, Assets, and Apps though State Access opcodes are allowed in ClearState programs because the current application and sender account are sure to be available.
OPCODE | DESCRIPTION |
---|---|
box_create | create a box named A, of length B. Fail if the name A is empty or B exceeds 32,768. Returns 0 if A already existed, else 1 |
box_extract | read C bytes from box A, starting at offset B. Fail if A does not exist, or the byte range is outside A’s size. |
box_replace | write byte-array C into box A, starting at offset B. Fail if A does not exist, or the byte range is outside A’s size. |
box_splice | set box A to contain its previous bytes up to index B, followed by D, followed by the original bytes of A that began at index B+C. |
box_del | delete box named A if it exists. Return 1 if A existed, 0 otherwise |
box_len | X is the length of box A if A exists, else 0. Y is 1 if A exists, else 0. |
box_get | X is the contents of box A if A exists, else ‘’. Y is 1 if A exists, else 0. |
box_put | replaces the contents of box A with byte-array B. Fails if A exists and len(B) != len(box A). Creates A if it does not exist |
box_resize | change the size of box named A to be of length B, adding zero bytes to end or removing bytes from the end, as needed. Fail if the name A is empty, A is not an existing box, or B exceeds 32,768. |
Inner Transactions
The following opcodes allow for inner transactions.
Inner transactions allow Applications to have many of the effects of a true top-level transaction, programmatically. However, they are different in significant ways. The most important differences are that:
-
They are not signed;
-
Duplicates are not rejected;
-
They do not appear in the block in the usual way.
Instead, their effects are noted in metadata associated with their top-level application call transaction.
An inner transaction’s sender MUST be the SHA512/256 hash
of the Application ID (prefixed by appID
), or an account that has been rekeyed
to that hash.
In Version 5, inner transactions may perform pay
, axfer
, acfg
, and afrz
effects.
After executing an inner transaction with itxn_submit
, the effects of the transaction
are visible beginning with the next instruction with, for example, balance
and
min_balance
checks.
In Version 6, inner transactions may also perform keyreg
and appl
effects. Inner
appl
calls fail if they attempt to invoke a program with a Version less than Version
4, or if they attempt to opt-in to an app with a Clear State Program less than Version 4.
In Version 5, only a subset of the transaction’s header fields may be set: Type
/
TypeEnum
, Sender
, and Fee
.
In Version 6, header fields Note
and RekeyTo
may also be set.
For the specific (non-header) fields of each transaction type, any field may be set.
This allows, for example, clawback transactions, asset opt-ins, and asset creates
in addition to the more common uses of axfer
and acfg
.
All fields default to the zero value, except those described under itxn_begin
.
Fields may be set multiple times, but may not be read. The most recent setting is
used when itxn_submit
executes. For this purpose Type
and TypeEnum
are considered
to be the same field. When using itxn_field
to set an array field (ApplicationArgs
,
Accounts
, Assets
, or Applications
) each use adds an element to the end of
the array, rather than setting the entire array at once.
itxn_field
fails immediately for unsupported fields, unsupported transaction types,
or improperly typed values for a particular field. itxn_field
makes acceptance
decisions entirely from the field and value provided, never considering previously
set fields.
Illegal interactions between fields, such as setting fields that belong to two different
transaction types, are rejected by itxn_submit
.
OPCODE | DESCRIPTION |
---|---|
itxn_begin | begin preparation of a new inner transaction in a new transaction group |
itxn_next | begin preparation of a new inner transaction in the same transaction group |
itxn_field f | set field F of the current inner transaction to A |
itxn_submit | execute the current inner transaction group. Fail if executing this group would exceed the inner transaction limit, or if any transaction in the group fails. |
itxn f | field F of the last inner transaction |
itxna f i | Ith value of the array field F of the last inner transaction |
itxnas f | Ath value of the array field F of the last inner transaction |
gitxn t f | field F of the Tth transaction in the last inner group submitted |
gitxna t f i | Ith value of the array field F from the Tth transaction in the last inner group submitted |
gitxnas t f | Ath value of the array field F from the Tth transaction in the last inner group submitted |
Assembler
The Assembler parses TEAL programs line by line:
-
Opcodes that only take Stack arguments appear on a line by themselves.
-
Opcodes that take immediate arguments are followed by their arguments on the same line, separated by whitespace.
Directives
The #
character at the beginning of a line identifies preprocessor directives.
Pragma
The first line of a TEAL program may contain a special version directive #pragma version X
(where X
is an unsigned integer), which directs the Assembler to generate
bytecode targeting a certain AVM Version.
📎 EXAMPLE
For instance,
#pragma version 2
produces bytecode targeting Version 2. By default, the Assembler targets Version 1.
It is syntactically invalid to have a
#pragma
definition after the first program opcode.
Subsequent lines may contain other pragma declarations (i.e., #pragma <some-specification>
),
pertaining to checks the Assembler should perform before agreeing to emit the program
bytecode, specific optimizations, etc. Those declarations are OPTIONAL and cannot
alter the semantics as described in this document.
⚙️ IMPLEMENTATION
Pragma directive reference implementation.
Macro
A #define M M_def
directive (where M
is an identifier and M_def
is a valid
TEAL expression) is used to define a macro. This is syntactically equivalent to
replacing each occurence of the M
identifier for the corresponding definition M_def
.
⚙️ IMPLEMENTATION
Macro directive reference implementation.
Comments
//
prefixes a line comment.
Separators
Both ;
and /n
may be used interchangeably to separate statements.
Constants and Pseudo-Ops
A few pseudo-opcodes simplify writing code. The following pseudo-opcodes:
addr
method
int
byte
Followed by a constant record the constant to a intcblock
or bytecblock
at the
beginning of code and insert an intc
or bytec
reference where the instruction
appears to load that value.
addr
addr
parses an Algorand account address base32
and converts it to a regular bytes
constant.
method
method
is passed a method signature and takes the first four bytes of the hash
to convert it to the standard method selector defined in ARC4.
byte
byte
constants are:
byte base64 AAAA...
byte b64 AAAA...
byte base64(AAAA...)
byte b64(AAAA...)
byte base32 AAAA...
byte b32 AAAA...
byte base32(AAAA...)
byte b32(AAAA...)
byte 0x0123456789abcdef...
byte "\x01\x02"
byte "string literal"
int
int
constants may be:
0x
prefixed for hex,0o
or0
prefixed for octal,0b
prefixed for binary,- Or decimal numbers.
intcblock
and bytecblock
intcblock
may be explicitly assembled. It will conflict with the assembler gathering
int
pseudo-ops into a intcblock
program prefix, but may be used if code only
has explicit intc
references. intcblock
should be followed by space separated
int
constants all on one line.
bytecblock
may be explicitly assembled. It will conflict with the assembler if
there are any byte
pseudo-ops but may be used if only explicit bytec
references
are used. bytecblock
should be followed with byte
constants all on one line,
either “encoding value” pairs (b64 AAA...
) or 0x
prefix or function-style values
(base64(...)
) or string literal values.
Labels and Branches
A label is defined by any string (that:
-
Is a valid identifier string,
-
Is not some other opcode or keyword,
-
Ends with in
:
.
A label without the trailing :
can be an argument to a branching instruction.
📎 EXAMPLE
Here is an example of TEAL program that uses a
bnz
opcode with thesafe
label as an argument. Since a1
is pushed into the Stack, the execution jumps to thepop
instruction after thesafe:
label:int 1 bnz safe err safe: pop
Versioning and Encoding
Versioning
To preserve existing semantics for previously written programs, AVM code is versioned.
When new opcodes are added or existing behavior is changed, a new Version is introduced. Programs carrying old versions are executed with their original semantics.
In the AVM bytecode, the Version is an incrementing integer, denoted in the program
as vX
(where X
is the version number, see Pragma directive section).
The AVM current Version is: \( 12 \).
For further details about the available opcodes per version, refer to the AVM Opcodes Specification.
A compiled program starts with a varuint declaring the Version of the compiled code.
Any addition, removal, or change of opcode behavior increments the Version.
Existing opcode behavior SHOULD NOT change. Opcode additions will be infrequent, and opcode removals SHOULD be very rare.
For Version \( 1 \), subsequent bytes after the varuint are program opcode bytes.
Future versions MAY put other metadata following the version identifier.
Newly introduced transaction types and fields MUST NOT break assumptions made by programs written before they existed.
If one of the transactions in a group executes a program whose Version predates a transaction type or field that can violate expectations, that transaction type or field MUST NOT be used anywhere in the transaction group.
📎 EXAMPLE
A Version \( 1 \) program included in a transaction group that includes an application call transaction or a non-zero rekey-to field will fail regardless of the program itself.
This requirement is enforced as follows:
-
For every transaction in the group, compute the earliest Version that supports all the fields and values in this transaction.
-
Compute
maxVerNo
, the largest Version number across all the transactions in a group (of size 1 or more). -
If any transaction in this group has a program with a version smaller than
maxVerNo
, then that program will fail.
In addition, Applications MUST be Version 4 or greater to be called in an inner transaction.
Encoding
Trailing versioning information on each program, as well as intcblock
and bytecblock
instructions, are handled using proto-buf style variable length unsigned ints.
These are encoded with packets of \( 7 \) data bits per byte. For every packet, the high data bit is a \( 1 \) if there is a byte after the current one, and \( 0 \) for the last byte of the sequence. The lowest order \( 7 \) bits are in the first byte, followed by successively higher groups of 7 bits.
Version 12 Opcodes
Opcodes have a cost of 1 unless otherwise specified.
err
- Bytecode: 0x00
- Stack: … → exits
- Fail immediately.
sha256
- Bytecode: 0x01
- Stack: …, A: []byte → …, [32]byte
- SHA256 hash of value A, yields [32]byte
- Cost: 35
keccak256
- Bytecode: 0x02
- Stack: …, A: []byte → …, [32]byte
- Keccak256 hash of value A, yields [32]byte
- Cost: 130
sha512_256
- Bytecode: 0x03
- Stack: …, A: []byte → …, [32]byte
- SHA512_256 hash of value A, yields [32]byte
- Cost: 45
ed25519verify
- Bytecode: 0x04
- Stack: …, A: []byte, B: [64]byte, C: [32]byte → …, bool
- for (data A, signature B, pubkey C) verify the signature of (“ProgData” || program_hash || data) against the pubkey => {0 or 1}
- Cost: 1900
The 32 byte public key is the last element on the stack, preceded by the 64 byte signature at the second-to-last element on the stack, preceded by the data which was signed at the third-to-last element on the stack.
ecdsa_verify
- Syntax:
ecdsa_verify V
where V: ECDSA - Bytecode: 0x05 {uint8}
- Stack: …, A: [32]byte, B: [32]byte, C: [32]byte, D: [32]byte, E: [32]byte → …, bool
- for (data A, signature B, C and pubkey D, E) verify the signature of the data against the pubkey => {0 or 1}
- Cost: Secp256k1=1700; Secp256r1=2500
- Availability: v5
Field ECDSA
Curves
Index | Name | In | Notes |
---|---|---|---|
0 | Secp256k1 | secp256k1 curve, used in Bitcoin | |
1 | Secp256r1 | v7 | secp256r1 curve, NIST standard |
The 32 byte Y-component of a public key is the last element on the stack, preceded by X-component of a pubkey, preceded by S and R components of a signature, preceded by the data that is fifth element on the stack. All values are big-endian encoded. The signed data must be 32 bytes long, and signatures in lower-S form are only accepted.
ecdsa_pk_decompress
- Syntax:
ecdsa_pk_decompress V
where V: ECDSA - Bytecode: 0x06 {uint8}
- Stack: …, A: [33]byte → …, X: [32]byte, Y: [32]byte
- decompress pubkey A into components X, Y
- Cost: Secp256k1=650; Secp256r1=2400
- Availability: v5
The 33 byte public key in a compressed form to be decompressed into X and Y (top) components. All values are big-endian encoded.
ecdsa_pk_recover
- Syntax:
ecdsa_pk_recover V
where V: ECDSA - Bytecode: 0x07 {uint8}
- Stack: …, A: [32]byte, B: uint64, C: [32]byte, D: [32]byte → …, X: [32]byte, Y: [32]byte
- for (data A, recovery id B, signature C, D) recover a public key
- Cost: 2000
- Availability: v5
S (top) and R elements of a signature, recovery id and data (bottom) are expected on the stack and used to deriver a public key. All values are big-endian encoded. The signed data must be 32 bytes long.
+
- Bytecode: 0x08
- Stack: …, A: uint64, B: uint64 → …, uint64
- A plus B. Fail on overflow.
Overflow is an error condition which halts execution and fails the transaction. Full precision is available from addw
.
-
- Bytecode: 0x09
- Stack: …, A: uint64, B: uint64 → …, uint64
- A minus B. Fail if B > A.
/
- Bytecode: 0x0a
- Stack: …, A: uint64, B: uint64 → …, uint64
- A divided by B (truncated division). Fail if B == 0.
divmodw
is available to divide the two-element values produced by mulw
and addw
.
*
- Bytecode: 0x0b
- Stack: …, A: uint64, B: uint64 → …, uint64
- A times B. Fail on overflow.
Overflow is an error condition which halts execution and fails the transaction. Full precision is available from mulw
.
<
- Bytecode: 0x0c
- Stack: …, A: uint64, B: uint64 → …, bool
- A less than B => {0 or 1}
>
- Bytecode: 0x0d
- Stack: …, A: uint64, B: uint64 → …, bool
- A greater than B => {0 or 1}
<=
- Bytecode: 0x0e
- Stack: …, A: uint64, B: uint64 → …, bool
- A less than or equal to B => {0 or 1}
>=
- Bytecode: 0x0f
- Stack: …, A: uint64, B: uint64 → …, bool
- A greater than or equal to B => {0 or 1}
&&
- Bytecode: 0x10
- Stack: …, A: uint64, B: uint64 → …, bool
- A is not zero and B is not zero => {0 or 1}
||
- Bytecode: 0x11
- Stack: …, A: uint64, B: uint64 → …, bool
- A is not zero or B is not zero => {0 or 1}
==
- Bytecode: 0x12
- Stack: …, A, B → …, bool
- A is equal to B => {0 or 1}
!=
- Bytecode: 0x13
- Stack: …, A, B → …, bool
- A is not equal to B => {0 or 1}
- Bytecode: 0x14
- Stack: …, A: uint64 → …, uint64
- A == 0 yields 1; else 0
len
- Bytecode: 0x15
- Stack: …, A: []byte → …, uint64
- yields length of byte value A
itob
- Bytecode: 0x16
- Stack: …, A: uint64 → …, [8]byte
- converts uint64 A to big-endian byte array, always of length 8
btoi
- Bytecode: 0x17
- Stack: …, A: []byte → …, uint64
- converts big-endian byte array A to uint64. Fails if len(A) > 8. Padded by leading 0s if len(A) < 8.
btoi
fails if the input is longer than 8 bytes.
%
- Bytecode: 0x18
- Stack: …, A: uint64, B: uint64 → …, uint64
- A modulo B. Fail if B == 0.
|
- Bytecode: 0x19
- Stack: …, A: uint64, B: uint64 → …, uint64
- A bitwise-or B
&
- Bytecode: 0x1a
- Stack: …, A: uint64, B: uint64 → …, uint64
- A bitwise-and B
^
- Bytecode: 0x1b
- Stack: …, A: uint64, B: uint64 → …, uint64
- A bitwise-xor B
~
- Bytecode: 0x1c
- Stack: …, A: uint64 → …, uint64
- bitwise invert value A
mulw
- Bytecode: 0x1d
- Stack: …, A: uint64, B: uint64 → …, X: uint64, Y: uint64
- A times B as a 128-bit result in two uint64s. X is the high 64 bits, Y is the low
addw
- Bytecode: 0x1e
- Stack: …, A: uint64, B: uint64 → …, X: uint64, Y: uint64
- A plus B as a 128-bit result. X is the carry-bit, Y is the low-order 64 bits.
- Availability: v2
divmodw
- Bytecode: 0x1f
- Stack: …, A: uint64, B: uint64, C: uint64, D: uint64 → …, W: uint64, X: uint64, Y: uint64, Z: uint64
- W,X = (A,B / C,D); Y,Z = (A,B modulo C,D)
- Cost: 20
- Availability: v4
The notation J,K indicates that two uint64 values J and K are interpreted as a uint128 value, with J as the high uint64 and K the low.
intcblock
- Syntax:
intcblock UINT ...
where UINT …: a block of int constant values - Bytecode: 0x20 {varuint count, [varuint …]}
- Stack: … → …
- prepare block of uint64 constants for use by intc
intcblock
loads following program bytes into an array of integer constants in the evaluator. These integer constants can be referred to by intc
and intc_*
which will push the value onto the stack. Subsequent calls to intcblock
reset and replace the integer constants available to the script.
intc
- Syntax:
intc I
where I: an index in the intcblock - Bytecode: 0x21 {uint8}
- Stack: … → …, uint64
- Ith constant from intcblock
intc_0
- Bytecode: 0x22
- Stack: … → …, uint64
- constant 0 from intcblock
intc_1
- Bytecode: 0x23
- Stack: … → …, uint64
- constant 1 from intcblock
intc_2
- Bytecode: 0x24
- Stack: … → …, uint64
- constant 2 from intcblock
intc_3
- Bytecode: 0x25
- Stack: … → …, uint64
- constant 3 from intcblock
bytecblock
- Syntax:
bytecblock BYTES ...
where BYTES …: a block of byte constant values - Bytecode: 0x26 {varuint count, [varuint length, bytes …]}
- Stack: … → …
- prepare block of byte-array constants for use by bytec
bytecblock
loads the following program bytes into an array of byte-array constants in the evaluator. These constants can be referred to by bytec
and bytec_*
which will push the value onto the stack. Subsequent calls to bytecblock
reset and replace the bytes constants available to the script.
bytec
- Syntax:
bytec I
where I: an index in the bytecblock - Bytecode: 0x27 {uint8}
- Stack: … → …, []byte
- Ith constant from bytecblock
bytec_0
- Bytecode: 0x28
- Stack: … → …, []byte
- constant 0 from bytecblock
bytec_1
- Bytecode: 0x29
- Stack: … → …, []byte
- constant 1 from bytecblock
bytec_2
- Bytecode: 0x2a
- Stack: … → …, []byte
- constant 2 from bytecblock
bytec_3
- Bytecode: 0x2b
- Stack: … → …, []byte
- constant 3 from bytecblock
arg
- Syntax:
arg N
where N: an arg index - Bytecode: 0x2c {uint8}
- Stack: … → …, []byte
- Nth LogicSig argument
- Mode: Signature
arg_0
- Bytecode: 0x2d
- Stack: … → …, []byte
- LogicSig argument 0
- Mode: Signature
arg_1
- Bytecode: 0x2e
- Stack: … → …, []byte
- LogicSig argument 1
- Mode: Signature
arg_2
- Bytecode: 0x2f
- Stack: … → …, []byte
- LogicSig argument 2
- Mode: Signature
arg_3
- Bytecode: 0x30
- Stack: … → …, []byte
- LogicSig argument 3
- Mode: Signature
txn
- Syntax:
txn F
where F: txn - Bytecode: 0x31 {uint8}
- Stack: … → …, any
- field F of current transaction
Field txn
Fields (see transaction reference)
Index | Name | Type | In | Notes |
---|---|---|---|---|
0 | Sender | address | 32 byte address | |
1 | Fee | uint64 | microalgos | |
2 | FirstValid | uint64 | round number | |
3 | FirstValidTime | uint64 | v7 | UNIX timestamp of block before txn.FirstValid. Fails if negative |
4 | LastValid | uint64 | round number | |
5 | Note | []byte | Any data up to 1024 bytes | |
6 | Lease | [32]byte | 32 byte lease value | |
7 | Receiver | address | 32 byte address | |
8 | Amount | uint64 | microalgos | |
9 | CloseRemainderTo | address | 32 byte address | |
10 | VotePK | [32]byte | 32 byte address | |
11 | SelectionPK | [32]byte | 32 byte address | |
12 | VoteFirst | uint64 | The first round that the participation key is valid. | |
13 | VoteLast | uint64 | The last round that the participation key is valid. | |
14 | VoteKeyDilution | uint64 | Dilution for the 2-level participation key | |
15 | Type | []byte | Transaction type as bytes | |
16 | TypeEnum | uint64 | Transaction type as integer | |
17 | XferAsset | uint64 | Asset ID | |
18 | AssetAmount | uint64 | value in Asset’s units | |
19 | AssetSender | address | 32 byte address. Source of assets if Sender is the Asset’s Clawback address. | |
20 | AssetReceiver | address | 32 byte address | |
21 | AssetCloseTo | address | 32 byte address | |
22 | GroupIndex | uint64 | Position of this transaction within an atomic transaction group. A stand-alone transaction is implicitly element 0 in a group of 1 | |
23 | TxID | [32]byte | The computed ID for this transaction. 32 bytes. | |
24 | ApplicationID | uint64 | v2 | ApplicationID from ApplicationCall transaction |
25 | OnCompletion | uint64 | v2 | ApplicationCall transaction on completion action |
27 | NumAppArgs | uint64 | v2 | Number of ApplicationArgs |
29 | NumAccounts | uint64 | v2 | Number of Accounts |
30 | ApprovalProgram | []byte | v2 | Approval program |
31 | ClearStateProgram | []byte | v2 | Clear state program |
32 | RekeyTo | address | v2 | 32 byte Sender’s new AuthAddr |
33 | ConfigAsset | uint64 | v2 | Asset ID in asset config transaction |
34 | ConfigAssetTotal | uint64 | v2 | Total number of units of this asset created |
35 | ConfigAssetDecimals | uint64 | v2 | Number of digits to display after the decimal place when displaying the asset |
36 | ConfigAssetDefaultFrozen | bool | v2 | Whether the asset’s slots are frozen by default or not, 0 or 1 |
37 | ConfigAssetUnitName | []byte | v2 | Unit name of the asset |
38 | ConfigAssetName | []byte | v2 | The asset name |
39 | ConfigAssetURL | []byte | v2 | URL |
40 | ConfigAssetMetadataHash | [32]byte | v2 | 32 byte commitment to unspecified asset metadata |
41 | ConfigAssetManager | address | v2 | 32 byte address |
42 | ConfigAssetReserve | address | v2 | 32 byte address |
43 | ConfigAssetFreeze | address | v2 | 32 byte address |
44 | ConfigAssetClawback | address | v2 | 32 byte address |
45 | FreezeAsset | uint64 | v2 | Asset ID being frozen or un-frozen |
46 | FreezeAssetAccount | address | v2 | 32 byte address of the account whose asset slot is being frozen or un-frozen |
47 | FreezeAssetFrozen | bool | v2 | The new frozen value, 0 or 1 |
49 | NumAssets | uint64 | v3 | Number of Assets |
51 | NumApplications | uint64 | v3 | Number of Applications |
52 | GlobalNumUint | uint64 | v3 | Number of global state integers in ApplicationCall |
53 | GlobalNumByteSlice | uint64 | v3 | Number of global state byteslices in ApplicationCall |
54 | LocalNumUint | uint64 | v3 | Number of local state integers in ApplicationCall |
55 | LocalNumByteSlice | uint64 | v3 | Number of local state byteslices in ApplicationCall |
56 | ExtraProgramPages | uint64 | v4 | Number of additional pages for each of the application’s approval and clear state programs. An ExtraProgramPages of 1 means 2048 more total bytes, or 1024 for each program. |
57 | Nonparticipation | bool | v5 | Marks an account nonparticipating for rewards |
59 | NumLogs | uint64 | v5 | Number of Logs (only with itxn in v5). Application mode only |
60 | CreatedAssetID | uint64 | v5 | Asset ID allocated by the creation of an ASA (only with itxn in v5). Application mode only |
61 | CreatedApplicationID | uint64 | v5 | ApplicationID allocated by the creation of an application (only with itxn in v5). Application mode only |
62 | LastLog | []byte | v6 | The last message emitted. Empty bytes if none were emitted. Application mode only |
63 | StateProofPK | [64]byte | v6 | State proof public key |
65 | NumApprovalProgramPages | uint64 | v7 | Number of Approval Program pages |
67 | NumClearStateProgramPages | uint64 | v7 | Number of ClearState Program pages |
68 | RejectVersion | uint64 | v12 | Application version for which the txn must reject |
global
- Syntax:
global F
where F: global - Bytecode: 0x32 {uint8}
- Stack: … → …, any
- global field F
Field global
Fields
Index | Name | Type | In | Notes |
---|---|---|---|---|
0 | MinTxnFee | uint64 | microalgos | |
1 | MinBalance | uint64 | microalgos | |
2 | MaxTxnLife | uint64 | rounds | |
3 | ZeroAddress | address | 32 byte address of all zero bytes | |
4 | GroupSize | uint64 | Number of transactions in this atomic transaction group. At least 1 | |
5 | LogicSigVersion | uint64 | v2 | Maximum supported version |
6 | Round | uint64 | v2 | Current round number. Application mode only. |
7 | LatestTimestamp | uint64 | v2 | Last confirmed block UNIX timestamp. Fails if negative. Application mode only. |
8 | CurrentApplicationID | uint64 | v2 | ID of current application executing. Application mode only. |
9 | CreatorAddress | address | v3 | Address of the creator of the current application. Application mode only. |
10 | CurrentApplicationAddress | address | v5 | Address that the current application controls. Application mode only. |
11 | GroupID | [32]byte | v5 | ID of the transaction group. 32 zero bytes if the transaction is not part of a group. |
12 | OpcodeBudget | uint64 | v6 | The remaining cost that can be spent by opcodes in this program. |
13 | CallerApplicationID | uint64 | v6 | The application ID of the application that called this application. 0 if this application is at the top-level. Application mode only. |
14 | CallerApplicationAddress | address | v6 | The application address of the application that called this application. ZeroAddress if this application is at the top-level. Application mode only. |
15 | AssetCreateMinBalance | uint64 | v10 | The additional minimum balance required to create (and opt-in to) an asset. |
16 | AssetOptInMinBalance | uint64 | v10 | The additional minimum balance required to opt-in to an asset. |
17 | GenesisHash | [32]byte | v10 | The Genesis Hash for the network. |
18 | PayoutsEnabled | bool | v11 | Whether block proposal payouts are enabled. |
19 | PayoutsGoOnlineFee | uint64 | v11 | The fee required in a keyreg transaction to make an account incentive eligible. |
20 | PayoutsPercent | uint64 | v11 | The percentage of transaction fees in a block that can be paid to the block proposer. |
21 | PayoutsMinBalance | uint64 | v11 | The minimum balance an account must have in the agreement round to receive block payouts in the proposal round. |
22 | PayoutsMaxBalance | uint64 | v11 | The maximum balance an account can have in the agreement round to receive block payouts in the proposal round. |
gtxn
- Syntax:
gtxn T F
where T: transaction group index, F: txn - Bytecode: 0x33 {uint8}, {uint8}
- Stack: … → …, any
- field F of the Tth transaction in the current group
for notes on transaction fields available, see txn
. If this transaction is i in the group, gtxn i field
is equivalent to txn field
.
load
- Syntax:
load I
where I: position in scratch space to load from - Bytecode: 0x34 {uint8}
- Stack: … → …, any
- Ith scratch space value. All scratch spaces are 0 at program start.
store
- Syntax:
store I
where I: position in scratch space to store to - Bytecode: 0x35 {uint8}
- Stack: …, A → …
- store A to the Ith scratch space
txna
- Syntax:
txna F I
where F: txna, I: transaction field array index - Bytecode: 0x36 {uint8}, {uint8}
- Stack: … → …, any
- Ith value of the array field F of the current transaction
txna
can be called usingtxn
with 2 immediates. - Availability: v2
Field txna
Fields (see transaction reference)
Index | Name | Type | In | Notes |
---|---|---|---|---|
26 | ApplicationArgs | []byte | v2 | Arguments passed to the application in the ApplicationCall transaction |
28 | Accounts | address | v2 | Accounts listed in the ApplicationCall transaction |
48 | Assets | uint64 | v3 | Foreign Assets listed in the ApplicationCall transaction |
50 | Applications | uint64 | v3 | Foreign Apps listed in the ApplicationCall transaction |
58 | Logs | []byte | v5 | Log messages emitted by an application call (only with itxn in v5). Application mode only |
64 | ApprovalProgramPages | []byte | v7 | Approval Program as an array of pages |
66 | ClearStateProgramPages | []byte | v7 | ClearState Program as an array of pages |
gtxna
- Syntax:
gtxna T F I
where T: transaction group index, F: txna, I: transaction field array index - Bytecode: 0x37 {uint8}, {uint8}, {uint8}
- Stack: … → …, any
- Ith value of the array field F from the Tth transaction in the current group
gtxna
can be called usinggtxn
with 3 immediates. - Availability: v2
gtxns
- Syntax:
gtxns F
where F: txn - Bytecode: 0x38 {uint8}
- Stack: …, A: uint64 → …, any
- field F of the Ath transaction in the current group
- Availability: v3
for notes on transaction fields available, see txn
. If top of stack is i, gtxns field
is equivalent to gtxn _i_ field
. gtxns exists so that i can be calculated, often based on the index of the current transaction.
gtxnsa
- Syntax:
gtxnsa F I
where F: txna, I: transaction field array index - Bytecode: 0x39 {uint8}, {uint8}
- Stack: …, A: uint64 → …, any
- Ith value of the array field F from the Ath transaction in the current group
gtxnsa
can be called usinggtxns
with 2 immediates. - Availability: v3
gload
- Syntax:
gload T I
where T: transaction group index, I: position in scratch space to load from - Bytecode: 0x3a {uint8}, {uint8}
- Stack: … → …, any
- Ith scratch space value of the Tth transaction in the current group
- Availability: v4
- Mode: Application
gload
fails unless the requested transaction is an ApplicationCall and T < GroupIndex.
gloads
- Syntax:
gloads I
where I: position in scratch space to load from - Bytecode: 0x3b {uint8}
- Stack: …, A: uint64 → …, any
- Ith scratch space value of the Ath transaction in the current group
- Availability: v4
- Mode: Application
gloads
fails unless the requested transaction is an ApplicationCall and A < GroupIndex.
gaid
- Syntax:
gaid T
where T: transaction group index - Bytecode: 0x3c {uint8}
- Stack: … → …, uint64
- ID of the asset or application created in the Tth transaction of the current group
- Availability: v4
- Mode: Application
gaid
fails unless the requested transaction created an asset or application and T < GroupIndex.
gaids
- Bytecode: 0x3d
- Stack: …, A: uint64 → …, uint64
- ID of the asset or application created in the Ath transaction of the current group
- Availability: v4
- Mode: Application
gaids
fails unless the requested transaction created an asset or application and A < GroupIndex.
loads
- Bytecode: 0x3e
- Stack: …, A: uint64 → …, any
- Ath scratch space value. All scratch spaces are 0 at program start.
- Availability: v5
stores
- Bytecode: 0x3f
- Stack: …, A: uint64, B → …
- store B to the Ath scratch space
- Availability: v5
bnz
- Syntax:
bnz TARGET
where TARGET: branch offset - Bytecode: 0x40 {int16 (big-endian)}
- Stack: …, A: uint64 → …
- branch to TARGET if value A is not zero
The bnz
instruction opcode 0x40 is followed by two immediate data bytes which are a high byte first and low byte second which together form a 16 bit offset which the instruction may branch to. For a bnz instruction at pc
, if the last element of the stack is not zero then branch to instruction at pc + 3 + N
, else proceed to next instruction at pc + 3
. Branch targets must be aligned instructions. (e.g. Branching to the second byte of a 2 byte op will be rejected.) Starting at v4, the offset is treated as a signed 16 bit integer allowing for backward branches and looping. In prior version (v1 to v3), branch offsets are limited to forward branches only, 0-0x7fff.
At v2 it became allowed to branch to the end of the program exactly after the last instruction: bnz to byte N (with 0-indexing) was illegal for a TEAL program with N bytes before v2, and is legal after it. This change eliminates the need for a last instruction of no-op as a branch target at the end. (Branching beyond the end–in other words, to a byte larger than N–is still illegal and will cause the program to fail.)
bz
- Syntax:
bz TARGET
where TARGET: branch offset - Bytecode: 0x41 {int16 (big-endian)}
- Stack: …, A: uint64 → …
- branch to TARGET if value A is zero
- Availability: v2
See bnz
for details on how branches work. bz
inverts the behavior of bnz
.
b
- Syntax:
b TARGET
where TARGET: branch offset - Bytecode: 0x42 {int16 (big-endian)}
- Stack: … → …
- branch unconditionally to TARGET
- Availability: v2
See bnz
for details on how branches work. b
always jumps to the offset.
return
- Bytecode: 0x43
- Stack: …, A: uint64 → exits
- use A as success value; end
- Availability: v2
assert
- Bytecode: 0x44
- Stack: …, A: uint64 → …
- immediately fail unless A is a non-zero number
- Availability: v3
bury
- Syntax:
bury N
where N: depth - Bytecode: 0x45 {uint8}
- Stack: …, A → …
- replace the Nth value from the top of the stack with A. bury 0 fails.
- Availability: v8
popn
- Syntax:
popn N
where N: stack depth - Bytecode: 0x46 {uint8}
- Stack: …, [N items] → …
- remove N values from the top of the stack
- Availability: v8
dupn
- Syntax:
dupn N
where N: copy count - Bytecode: 0x47 {uint8}
- Stack: …, A → …, A, [N copies of A]
- duplicate A, N times
- Availability: v8
pop
- Bytecode: 0x48
- Stack: …, A → …
- discard A
dup
- Bytecode: 0x49
- Stack: …, A → …, A, A
- duplicate A
dup2
- Bytecode: 0x4a
- Stack: …, A, B → …, A, B, A, B
- duplicate A and B
- Availability: v2
dig
- Syntax:
dig N
where N: depth - Bytecode: 0x4b {uint8}
- Stack: …, A, [N items] → …, A, [N items], A
- Nth value from the top of the stack. dig 0 is equivalent to dup
- Availability: v3
swap
- Bytecode: 0x4c
- Stack: …, A, B → …, B, A
- swaps A and B on stack
- Availability: v3
select
- Bytecode: 0x4d
- Stack: …, A, B, C: uint64 → …, A or B
- selects one of two values based on top-of-stack: B if C != 0, else A
- Availability: v3
cover
- Syntax:
cover N
where N: depth - Bytecode: 0x4e {uint8}
- Stack: …, [N items], A → …, A, [N items]
- remove top of stack, and place it deeper in the stack such that N elements are above it. Fails if stack depth <= N.
- Availability: v5
uncover
- Syntax:
uncover N
where N: depth - Bytecode: 0x4f {uint8}
- Stack: …, A, [N items] → …, [N items], A
- remove the value at depth N in the stack and shift above items down so the Nth deep value is on top of the stack. Fails if stack depth <= N.
- Availability: v5
concat
- Bytecode: 0x50
- Stack: …, A: []byte, B: []byte → …, []byte
- join A and B
- Availability: v2
concat
fails if the result would be greater than 4096 bytes.
substring
- Syntax:
substring S E
where S: start position, E: end position - Bytecode: 0x51 {uint8}, {uint8}
- Stack: …, A: []byte → …, []byte
- A range of bytes from A starting at S up to but not including E. If E < S, or either is larger than the array length, the program fails
- Availability: v2
substring3
- Bytecode: 0x52
- Stack: …, A: []byte, B: uint64, C: uint64 → …, []byte
- A range of bytes from A starting at B up to but not including C. If C < B, or either is larger than the array length, the program fails
- Availability: v2
getbit
- Bytecode: 0x53
- Stack: …, A, B: uint64 → …, uint64
- Bth bit of (byte-array or integer) A. If B is greater than or equal to the bit length of the value (8*byte length), the program fails
- Availability: v3
see explanation of bit ordering in setbit
setbit
- Bytecode: 0x54
- Stack: …, A, B: uint64, C: uint64 → …, any
- Copy of (byte-array or integer) A, with the Bth bit set to (0 or 1) C. If B is greater than or equal to the bit length of the value (8*byte length), the program fails
- Availability: v3
When A is a uint64, index 0 is the least significant bit. Setting bit 3 to 1 on the integer 0 yields 8, or 2^3. When A is a byte array, index 0 is the leftmost bit of the leftmost byte. Setting bits 0 through 11 to 1 in a 4-byte-array of 0s yields the byte array 0xfff00000. Setting bit 3 to 1 on the 1-byte-array 0x00 yields the byte array 0x10.
getbyte
- Bytecode: 0x55
- Stack: …, A: []byte, B: uint64 → …, uint64
- Bth byte of A, as an integer. If B is greater than or equal to the array length, the program fails
- Availability: v3
setbyte
- Bytecode: 0x56
- Stack: …, A: []byte, B: uint64, C: uint64 → …, []byte
- Copy of A with the Bth byte set to small integer (between 0..255) C. If B is greater than or equal to the array length, the program fails
- Availability: v3
extract
- Syntax:
extract S L
where S: start position, L: length - Bytecode: 0x57 {uint8}, {uint8}
- Stack: …, A: []byte → …, []byte
- A range of bytes from A starting at S up to but not including S+L. If L is 0, then extract to the end of the string. If S or S+L is larger than the array length, the program fails
- Availability: v5
extract3
- Bytecode: 0x58
- Stack: …, A: []byte, B: uint64, C: uint64 → …, []byte
- A range of bytes from A starting at B up to but not including B+C. If B+C is larger than the array length, the program fails
extract3
can be called usingextract
with no immediates. - Availability: v5
extract_uint16
- Bytecode: 0x59
- Stack: …, A: []byte, B: uint64 → …, uint64
- A uint16 formed from a range of big-endian bytes from A starting at B up to but not including B+2. If B+2 is larger than the array length, the program fails
- Availability: v5
extract_uint32
- Bytecode: 0x5a
- Stack: …, A: []byte, B: uint64 → …, uint64
- A uint32 formed from a range of big-endian bytes from A starting at B up to but not including B+4. If B+4 is larger than the array length, the program fails
- Availability: v5
extract_uint64
- Bytecode: 0x5b
- Stack: …, A: []byte, B: uint64 → …, uint64
- A uint64 formed from a range of big-endian bytes from A starting at B up to but not including B+8. If B+8 is larger than the array length, the program fails
- Availability: v5
replace2
- Syntax:
replace2 S
where S: start position - Bytecode: 0x5c {uint8}
- Stack: …, A: []byte, B: []byte → …, []byte
- Copy of A with the bytes starting at S replaced by the bytes of B. Fails if S+len(B) exceeds len(A)
replace2
can be called usingreplace
with 1 immediate. - Availability: v7
replace3
- Bytecode: 0x5d
- Stack: …, A: []byte, B: uint64, C: []byte → …, []byte
- Copy of A with the bytes starting at B replaced by the bytes of C. Fails if B+len(C) exceeds len(A)
replace3
can be called usingreplace
with no immediates. - Availability: v7
base64_decode
- Syntax:
base64_decode E
where E: base64 - Bytecode: 0x5e {uint8}
- Stack: …, A: []byte → …, []byte
- decode A which was base64-encoded using encoding E. Fail if A is not base64 encoded with encoding E
- Cost: 1 + 1 per 16 bytes of A
- Availability: v7
Field base64
Encodings
Index | Name | Notes |
---|---|---|
0 | URLEncoding | |
1 | StdEncoding |
Warning: Usage should be restricted to very rare use cases. In almost all cases, smart contracts should directly handle non-encoded byte-strings. This opcode should only be used in cases where base64 is the only available option, e.g. interoperability with a third-party that only signs base64 strings.
Decodes A using the base64 encoding E. Specify the encoding with an immediate arg either as URL and Filename Safe (URLEncoding
) or Standard (StdEncoding
). See RFC 4648 sections 4 and 5. It is assumed that the encoding ends with the exact number of =
padding characters as required by the RFC. When padding occurs, any unused pad bits in the encoding must be set to zero or the decoding will fail. The special cases of \n
and \r
are allowed but completely ignored. An error will result when attempting to decode a string with a character that is not in the encoding alphabet or not one of =
, \r
, or \n
.
json_ref
- Syntax:
json_ref R
where R: json_ref - Bytecode: 0x5f {uint8}
- Stack: …, A: []byte, B: []byte → …, any
- key B’s value, of type R, from a valid UTF-8 encoded JSON object A
- Cost: 25 + 2 per 7 bytes of A
- Availability: v7
Field json_ref
Types
Index | Name | Type | Notes |
---|---|---|---|
0 | JSONString | []byte | |
1 | JSONUint64 | uint64 | |
2 | JSONObject | []byte |
Warning: Usage should be restricted to very rare use cases, as JSON decoding is expensive and quite limited. In addition, JSON objects are large and not optimized for size.
Almost all smart contracts should use simpler and smaller methods (such as the ABI. This opcode should only be used in cases where JSON is only available option, e.g. when a third-party only signs JSON.
balance
- Bytecode: 0x60
- Stack: …, A → …, uint64
- balance for account A, in microalgos. The balance is observed after the effects of previous transactions in the group, and after the fee for the current transaction is deducted. Changes caused by inner transactions are observable immediately following
itxn_submit
- Availability: v2
- Mode: Application
params: Txn.Accounts offset (or, since v4, an available account address), available application id (or, since v4, a Txn.ForeignApps offset). Return: value.
app_opted_in
- Bytecode: 0x61
- Stack: …, A, B: uint64 → …, bool
- 1 if account A is opted in to application B, else 0
- Availability: v2
- Mode: Application
params: Txn.Accounts offset (or, since v4, an available account address), available application id (or, since v4, a Txn.ForeignApps offset). Return: 1 if opted in and 0 otherwise.
app_local_get
- Bytecode: 0x62
- Stack: …, A, B: stateKey → …, any
- local state of the key B in the current application in account A
- Availability: v2
- Mode: Application
params: Txn.Accounts offset (or, since v4, an available account address), state key. Return: value. The value is zero (of type uint64) if the key does not exist.
app_local_get_ex
- Bytecode: 0x63
- Stack: …, A, B: uint64, C: stateKey → …, X: any, Y: bool
- X is the local state of application B, key C in account A. Y is 1 if key existed, else 0
- Availability: v2
- Mode: Application
params: Txn.Accounts offset (or, since v4, an available account address), available application id (or, since v4, a Txn.ForeignApps offset), state key. Return: did_exist flag (top of the stack, 1 if the application and key existed and 0 otherwise), value. The value is zero (of type uint64) if the key does not exist.
app_global_get
- Bytecode: 0x64
- Stack: …, A: stateKey → …, any
- global state of the key A in the current application
- Availability: v2
- Mode: Application
params: state key. Return: value. The value is zero (of type uint64) if the key does not exist.
app_global_get_ex
- Bytecode: 0x65
- Stack: …, A: uint64, B: stateKey → …, X: any, Y: bool
- X is the global state of application A, key B. Y is 1 if key existed, else 0
- Availability: v2
- Mode: Application
params: Txn.ForeignApps offset (or, since v4, an available application id), state key. Return: did_exist flag (top of the stack, 1 if the application and key existed and 0 otherwise), value. The value is zero (of type uint64) if the key does not exist.
app_local_put
- Bytecode: 0x66
- Stack: …, A, B: stateKey, C → …
- write C to key B in account A’s local state of the current application
- Availability: v2
- Mode: Application
params: Txn.Accounts offset (or, since v4, an available account address), state key, value.
app_global_put
- Bytecode: 0x67
- Stack: …, A: stateKey, B → …
- write B to key A in the global state of the current application
- Availability: v2
- Mode: Application
app_local_del
- Bytecode: 0x68
- Stack: …, A, B: stateKey → …
- delete key B from account A’s local state of the current application
- Availability: v2
- Mode: Application
params: Txn.Accounts offset (or, since v4, an available account address), state key.
Deleting a key which is already absent has no effect on the application local state. (In particular, it does not cause the program to fail.)
app_global_del
- Bytecode: 0x69
- Stack: …, A: stateKey → …
- delete key A from the global state of the current application
- Availability: v2
- Mode: Application
params: state key.
Deleting a key which is already absent has no effect on the application global state. (In particular, it does not cause the program to fail.)
asset_holding_get
- Syntax:
asset_holding_get F
where F: asset_holding - Bytecode: 0x70 {uint8}
- Stack: …, A, B: uint64 → …, X: any, Y: bool
- X is field F from account A’s holding of asset B. Y is 1 if A is opted into B, else 0
- Availability: v2
- Mode: Application
Field asset_holding
Fields
Index | Name | Type | Notes |
---|---|---|---|
0 | AssetBalance | uint64 | Amount of the asset unit held by this account |
1 | AssetFrozen | bool | Is the asset frozen or not |
params: Txn.Accounts offset (or, since v4, an available address), asset id (or, since v4, a Txn.ForeignAssets offset). Return: did_exist flag (1 if the asset existed and 0 otherwise), value.
asset_params_get
- Syntax:
asset_params_get F
where F: asset_params - Bytecode: 0x71 {uint8}
- Stack: …, A: uint64 → …, X: any, Y: bool
- X is field F from asset A. Y is 1 if A exists, else 0
- Availability: v2
- Mode: Application
Field asset_params
Fields
Index | Name | Type | In | Notes |
---|---|---|---|---|
0 | AssetTotal | uint64 | Total number of units of this asset | |
1 | AssetDecimals | uint64 | See AssetParams.Decimals | |
2 | AssetDefaultFrozen | bool | Frozen by default or not | |
3 | AssetUnitName | []byte | Asset unit name | |
4 | AssetName | []byte | Asset name | |
5 | AssetURL | []byte | URL with additional info about the asset | |
6 | AssetMetadataHash | [32]byte | Arbitrary commitment | |
7 | AssetManager | address | Manager address | |
8 | AssetReserve | address | Reserve address | |
9 | AssetFreeze | address | Freeze address | |
10 | AssetClawback | address | Clawback address | |
11 | AssetCreator | address | v5 | Creator address |
params: Txn.ForeignAssets offset (or, since v4, an available asset id. Return: did_exist flag (1 if the asset existed and 0 otherwise), value.
app_params_get
- Syntax:
app_params_get F
where F: app_params - Bytecode: 0x72 {uint8}
- Stack: …, A: uint64 → …, X: any, Y: bool
- X is field F from app A. Y is 1 if A exists, else 0
- Availability: v5
- Mode: Application
Field app_params
Fields
Index | Name | Type | In | Notes |
---|---|---|---|---|
0 | AppApprovalProgram | []byte | Bytecode of Approval Program | |
1 | AppClearStateProgram | []byte | Bytecode of Clear State Program | |
2 | AppGlobalNumUint | uint64 | Number of uint64 values allowed in Global State | |
3 | AppGlobalNumByteSlice | uint64 | Number of byte array values allowed in Global State | |
4 | AppLocalNumUint | uint64 | Number of uint64 values allowed in Local State | |
5 | AppLocalNumByteSlice | uint64 | Number of byte array values allowed in Local State | |
6 | AppExtraProgramPages | uint64 | Number of Extra Program Pages of code space | |
7 | AppCreator | address | Creator address | |
8 | AppAddress | address | Address for which this application has authority | |
9 | AppVersion | uint64 | v12 | Version of the app, incremented each time the approval or clear program changes |
params: Txn.ForeignApps offset or an available app id. Return: did_exist flag (1 if the application existed and 0 otherwise), value.
acct_params_get
- Syntax:
acct_params_get F
where F: acct_params - Bytecode: 0x73 {uint8}
- Stack: …, A → …, X: any, Y: bool
- X is field F from account A. Y is 1 if A owns positive algos, else 0
- Availability: v6
- Mode: Application
Field acct_params
Fields
Index | Name | Type | In | Notes |
---|---|---|---|---|
0 | AcctBalance | uint64 | Account balance in microalgos | |
1 | AcctMinBalance | uint64 | Minimum required balance for account, in microalgos | |
2 | AcctAuthAddr | address | Address the account is rekeyed to. | |
3 | AcctTotalNumUint | uint64 | v8 | The total number of uint64 values allocated by this account in Global and Local States. |
4 | AcctTotalNumByteSlice | uint64 | v8 | The total number of byte array values allocated by this account in Global and Local States. |
5 | AcctTotalExtraAppPages | uint64 | v8 | The number of extra app code pages used by this account. |
6 | AcctTotalAppsCreated | uint64 | v8 | The number of existing apps created by this account. |
7 | AcctTotalAppsOptedIn | uint64 | v8 | The number of apps this account is opted into. |
8 | AcctTotalAssetsCreated | uint64 | v8 | The number of existing ASAs created by this account. |
9 | AcctTotalAssets | uint64 | v8 | The numbers of ASAs held by this account (including ASAs this account created). |
10 | AcctTotalBoxes | uint64 | v8 | The number of existing boxes created by this account’s app. |
11 | AcctTotalBoxBytes | uint64 | v8 | The total number of bytes used by this account’s app’s box keys and values. |
12 | AcctIncentiveEligible | bool | v11 | Has this account opted into block payouts |
13 | AcctLastProposed | uint64 | v11 | The round number of the last block this account proposed. |
14 | AcctLastHeartbeat | uint64 | v11 | The round number of the last block this account sent a heartbeat. |
voter_params_get
- Syntax:
voter_params_get F
where F: voter_params - Bytecode: 0x74 {uint8}
- Stack: …, A → …, X: any, Y: bool
- X is field F from online account A as of the balance round: 320 rounds before the current round. Y is 1 if A had positive algos online in the agreement round, else Y is 0 and X is a type specific zero-value
- Availability: v11
- Mode: Application
Field voter_params
Fields
Index | Name | Type | Notes |
---|---|---|---|
0 | VoterBalance | uint64 | Online stake in microalgos |
1 | VoterIncentiveEligible | bool | Had this account opted into block payouts |
online_stake
- Bytecode: 0x75
- Stack: … → …, uint64
- the total online stake in the agreement round
- Availability: v11
- Mode: Application
min_balance
- Bytecode: 0x78
- Stack: …, A → …, uint64
- minimum required balance for account A, in microalgos. Required balance is affected by ASA, App, and Box usage. When creating or opting into an app, the minimum balance grows before the app code runs, therefore the increase is visible there. When deleting or closing out, the minimum balance decreases after the app executes. Changes caused by inner transactions or box usage are observable immediately following the opcode effecting the change.
- Availability: v3
- Mode: Application
params: Txn.Accounts offset (or, since v4, an available account address), available application id (or, since v4, a Txn.ForeignApps offset). Return: value.
pushbytes
- Syntax:
pushbytes BYTES
where BYTES: a byte constant - Bytecode: 0x80 {varuint length, bytes}
- Stack: … → …, []byte
- immediate BYTES
- Availability: v3
pushbytes args are not added to the bytecblock during assembly processes
pushint
- Syntax:
pushint UINT
where UINT: an int constant - Bytecode: 0x81 {varuint}
- Stack: … → …, uint64
- immediate UINT
- Availability: v3
pushint args are not added to the intcblock during assembly processes
pushbytess
- Syntax:
pushbytess BYTES ...
where BYTES …: a list of byte constants - Bytecode: 0x82 {varuint count, [varuint length, bytes …]}
- Stack: … → …, [N items]
- push sequences of immediate byte arrays to stack (first byte array being deepest)
- Availability: v8
pushbytess args are not added to the bytecblock during assembly processes
pushints
- Syntax:
pushints UINT ...
where UINT …: a list of int constants - Bytecode: 0x83 {varuint count, [varuint …]}
- Stack: … → …, [N items]
- push sequence of immediate uints to stack in the order they appear (first uint being deepest)
- Availability: v8
pushints args are not added to the intcblock during assembly processes
ed25519verify_bare
- Bytecode: 0x84
- Stack: …, A: []byte, B: [64]byte, C: [32]byte → …, bool
- for (data A, signature B, pubkey C) verify the signature of the data against the pubkey => {0 or 1}
- Cost: 1900
- Availability: v7
falcon_verify
- Bytecode: 0x85
- Stack: …, A: []byte, B: [1232]byte, C: [1793]byte → …, bool
- for (data A, compressed-format signature B, pubkey C) verify the signature of data against the pubkey => {0 or 1}
- Cost: 1700
- Availability: v12
callsub
- Syntax:
callsub TARGET
where TARGET: branch offset - Bytecode: 0x88 {int16 (big-endian)}
- Stack: … → …
- branch unconditionally to TARGET, saving the next instruction on the call stack
- Availability: v4
The call stack is separate from the data stack. Only callsub
, retsub
, and proto
manipulate it.
retsub
- Bytecode: 0x89
- Stack: … → …
- pop the top instruction from the call stack and branch to it
- Availability: v4
If the current frame was prepared by proto A R
, retsub
will remove the ‘A’ arguments from the stack, move the R
return values down, and pop any stack locations above the relocated return values.
proto
- Syntax:
proto A R
where A: number of arguments, R: number of return values - Bytecode: 0x8a {uint8}, {uint8}
- Stack: … → …
- Prepare top call frame for a retsub that will assume A args and R return values.
- Availability: v8
Fails unless the last instruction executed was a callsub
.
frame_dig
- Syntax:
frame_dig I
where I: frame slot - Bytecode: 0x8b {int8}
- Stack: … → …, any
- Nth (signed) value from the frame pointer.
- Availability: v8
frame_bury
- Syntax:
frame_bury I
where I: frame slot - Bytecode: 0x8c {int8}
- Stack: …, A → …
- replace the Nth (signed) value from the frame pointer in the stack with A
- Availability: v8
switch
- Syntax:
switch TARGET ...
where TARGET …: list of labels - Bytecode: 0x8d {varuint count, [int16 (big-endian) …]}
- Stack: …, A: uint64 → …
- branch to the Ath label. Continue at following instruction if index A exceeds the number of labels.
- Availability: v8
match
- Syntax:
match TARGET ...
where TARGET …: list of labels - Bytecode: 0x8e {varuint count, [int16 (big-endian) …]}
- Stack: …, [A1, A2, …, AN], B → …
- given match cases from A[1] to A[N], branch to the Ith label where A[I] = B. Continue to the following instruction if no matches are found.
- Availability: v8
match
consumes N+1 values from the stack. Let the top stack value be B. The following N values represent an ordered list of match cases/constants (A), where the first value (A[0]) is the deepest in the stack. The immediate arguments are an ordered list of N labels (T). match
will branch to target T[I], where A[I] = B. If there are no matches then execution continues on to the next instruction.
shl
- Bytecode: 0x90
- Stack: …, A: uint64, B: uint64 → …, uint64
- A times 2^B, modulo 2^64
- Availability: v4
shr
- Bytecode: 0x91
- Stack: …, A: uint64, B: uint64 → …, uint64
- A divided by 2^B
- Availability: v4
sqrt
- Bytecode: 0x92
- Stack: …, A: uint64 → …, uint64
- The largest integer I such that I^2 <= A
- Cost: 4
- Availability: v4
bitlen
- Bytecode: 0x93
- Stack: …, A → …, uint64
- The highest set bit in A. If A is a byte-array, it is interpreted as a big-endian unsigned integer. bitlen of 0 is 0, bitlen of 8 is 4
- Availability: v4
bitlen interprets arrays as big-endian integers, unlike setbit/getbit
exp
- Bytecode: 0x94
- Stack: …, A: uint64, B: uint64 → …, uint64
- A raised to the Bth power. Fail if A == B == 0 and on overflow
- Availability: v4
expw
- Bytecode: 0x95
- Stack: …, A: uint64, B: uint64 → …, X: uint64, Y: uint64
- A raised to the Bth power as a 128-bit result in two uint64s. X is the high 64 bits, Y is the low. Fail if A == B == 0 or if the results exceeds 2^128-1
- Cost: 10
- Availability: v4
bsqrt
- Bytecode: 0x96
- Stack: …, A: bigint → …, bigint
- The largest integer I such that I^2 <= A. A and I are interpreted as big-endian unsigned integers
- Cost: 40
- Availability: v6
divw
- Bytecode: 0x97
- Stack: …, A: uint64, B: uint64, C: uint64 → …, uint64
- A,B / C. Fail if C == 0 or if result overflows.
- Availability: v6
The notation A,B indicates that A and B are interpreted as a uint128 value, with A as the high uint64 and B the low.
sha3_256
- Bytecode: 0x98
- Stack: …, A: []byte → …, [32]byte
- SHA3_256 hash of value A, yields [32]byte
- Cost: 130
- Availability: v7
b+
- Bytecode: 0xa0
- Stack: …, A: bigint, B: bigint → …, []byte
- A plus B. A and B are interpreted as big-endian unsigned integers
- Cost: 10
- Availability: v4
b-
- Bytecode: 0xa1
- Stack: …, A: bigint, B: bigint → …, bigint
- A minus B. A and B are interpreted as big-endian unsigned integers. Fail on underflow.
- Cost: 10
- Availability: v4
b/
- Bytecode: 0xa2
- Stack: …, A: bigint, B: bigint → …, bigint
- A divided by B (truncated division). A and B are interpreted as big-endian unsigned integers. Fail if B is zero.
- Cost: 20
- Availability: v4
b*
- Bytecode: 0xa3
- Stack: …, A: bigint, B: bigint → …, []byte
- A times B. A and B are interpreted as big-endian unsigned integers.
- Cost: 20
- Availability: v4
b<
- Bytecode: 0xa4
- Stack: …, A: bigint, B: bigint → …, bool
- 1 if A is less than B, else 0. A and B are interpreted as big-endian unsigned integers
- Availability: v4
b>
- Bytecode: 0xa5
- Stack: …, A: bigint, B: bigint → …, bool
- 1 if A is greater than B, else 0. A and B are interpreted as big-endian unsigned integers
- Availability: v4
b<=
- Bytecode: 0xa6
- Stack: …, A: bigint, B: bigint → …, bool
- 1 if A is less than or equal to B, else 0. A and B are interpreted as big-endian unsigned integers
- Availability: v4
b>=
- Bytecode: 0xa7
- Stack: …, A: bigint, B: bigint → …, bool
- 1 if A is greater than or equal to B, else 0. A and B are interpreted as big-endian unsigned integers
- Availability: v4
b==
- Bytecode: 0xa8
- Stack: …, A: bigint, B: bigint → …, bool
- 1 if A is equal to B, else 0. A and B are interpreted as big-endian unsigned integers
- Availability: v4
b!=
- Bytecode: 0xa9
- Stack: …, A: bigint, B: bigint → …, bool
- 0 if A is equal to B, else 1. A and B are interpreted as big-endian unsigned integers
- Availability: v4
b%
- Bytecode: 0xaa
- Stack: …, A: bigint, B: bigint → …, bigint
- A modulo B. A and B are interpreted as big-endian unsigned integers. Fail if B is zero.
- Cost: 20
- Availability: v4
b|
- Bytecode: 0xab
- Stack: …, A: []byte, B: []byte → …, []byte
- A bitwise-or B. A and B are zero-left extended to the greater of their lengths
- Cost: 6
- Availability: v4
b&
- Bytecode: 0xac
- Stack: …, A: []byte, B: []byte → …, []byte
- A bitwise-and B. A and B are zero-left extended to the greater of their lengths
- Cost: 6
- Availability: v4
b^
- Bytecode: 0xad
- Stack: …, A: []byte, B: []byte → …, []byte
- A bitwise-xor B. A and B are zero-left extended to the greater of their lengths
- Cost: 6
- Availability: v4
b~
- Bytecode: 0xae
- Stack: …, A: []byte → …, []byte
- A with all bits inverted
- Cost: 4
- Availability: v4
bzero
- Bytecode: 0xaf
- Stack: …, A: uint64 → …, []byte
- zero filled byte-array of length A
- Availability: v4
log
- Bytecode: 0xb0
- Stack: …, A: []byte → …
- write A to log state of the current application
- Availability: v5
- Mode: Application
log
fails if called more than MaxLogCalls times in a program, or if the sum of logged bytes exceeds 1024 bytes.
itxn_begin
- Bytecode: 0xb1
- Stack: … → …
- begin preparation of a new inner transaction in a new transaction group
- Availability: v5
- Mode: Application
itxn_begin
initializes Sender to the application address; Fee to the minimum allowable, taking into account MinTxnFee and credit from overpaying in earlier transactions; FirstValid/LastValid to the values in the invoking transaction, and all other fields to zero or empty values.
itxn_field
- Syntax:
itxn_field F
where F: txn - Bytecode: 0xb2 {uint8}
- Stack: …, A → …
- set field F of the current inner transaction to A
- Availability: v5
- Mode: Application
itxn_field
fails if A is of the wrong type for F, including a byte array of the wrong size for use as an address when F is an address field. itxn_field
also fails if A is an account, asset, or app that is not available, or an attempt is made extend an array field beyond the limit imposed by consensus parameters. (Addresses set into asset params of acfg transactions need not be available.)
itxn_submit
- Bytecode: 0xb3
- Stack: … → …
- execute the current inner transaction group. Fail if executing this group would exceed the inner transaction limit, or if any transaction in the group fails.
- Availability: v5
- Mode: Application
itxn_submit
resets the current transaction so that it can not be resubmitted. A new itxn_begin
is required to prepare another inner transaction.
itxn
- Syntax:
itxn F
where F: txn - Bytecode: 0xb4 {uint8}
- Stack: … → …, any
- field F of the last inner transaction
- Availability: v5
- Mode: Application
itxna
- Syntax:
itxna F I
where F: txna, I: a transaction field array index - Bytecode: 0xb5 {uint8}, {uint8}
- Stack: … → …, any
- Ith value of the array field F of the last inner transaction
- Availability: v5
- Mode: Application
itxn_next
- Bytecode: 0xb6
- Stack: … → …
- begin preparation of a new inner transaction in the same transaction group
- Availability: v6
- Mode: Application
itxn_next
initializes the transaction exactly as itxn_begin
does
gitxn
- Syntax:
gitxn T F
where T: transaction group index, F: txn - Bytecode: 0xb7 {uint8}, {uint8}
- Stack: … → …, any
- field F of the Tth transaction in the last inner group submitted
- Availability: v6
- Mode: Application
gitxna
- Syntax:
gitxna T F I
where T: transaction group index, F: txna, I: transaction field array index - Bytecode: 0xb8 {uint8}, {uint8}, {uint8}
- Stack: … → …, any
- Ith value of the array field F from the Tth transaction in the last inner group submitted
- Availability: v6
- Mode: Application
box_create
- Bytecode: 0xb9
- Stack: …, A: boxName, B: uint64 → …, bool
- create a box named A, of length B. Fail if the name A is empty or B exceeds 32,768. Returns 0 if A already existed, else 1
- Availability: v8
- Mode: Application
Newly created boxes are filled with 0 bytes. box_create
will fail if the referenced box already exists with a different size. Otherwise, existing boxes are unchanged by box_create
.
box_extract
- Bytecode: 0xba
- Stack: …, A: boxName, B: uint64, C: uint64 → …, []byte
- read C bytes from box A, starting at offset B. Fail if A does not exist, or the byte range is outside A’s size.
- Availability: v8
- Mode: Application
box_replace
- Bytecode: 0xbb
- Stack: …, A: boxName, B: uint64, C: []byte → …
- write byte-array C into box A, starting at offset B. Fail if A does not exist, or the byte range is outside A’s size.
- Availability: v8
- Mode: Application
box_del
- Bytecode: 0xbc
- Stack: …, A: boxName → …, bool
- delete box named A if it exists. Return 1 if A existed, 0 otherwise
- Availability: v8
- Mode: Application
box_len
- Bytecode: 0xbd
- Stack: …, A: boxName → …, X: uint64, Y: bool
- X is the length of box A if A exists, else 0. Y is 1 if A exists, else 0.
- Availability: v8
- Mode: Application
box_get
- Bytecode: 0xbe
- Stack: …, A: boxName → …, X: []byte, Y: bool
- X is the contents of box A if A exists, else ‘’. Y is 1 if A exists, else 0.
- Availability: v8
- Mode: Application
For boxes that exceed 4,096 bytes, consider box_create
, box_extract
, and box_replace
box_put
- Bytecode: 0xbf
- Stack: …, A: boxName, B: []byte → …
- replaces the contents of box A with byte-array B. Fails if A exists and len(B) != len(box A). Creates A if it does not exist
- Availability: v8
- Mode: Application
For boxes that exceed 4,096 bytes, consider box_create
, box_extract
, and box_replace
txnas
- Syntax:
txnas F
where F: txna - Bytecode: 0xc0 {uint8}
- Stack: …, A: uint64 → …, any
- Ath value of the array field F of the current transaction
- Availability: v5
gtxnas
- Syntax:
gtxnas T F
where T: transaction group index, F: txna - Bytecode: 0xc1 {uint8}, {uint8}
- Stack: …, A: uint64 → …, any
- Ath value of the array field F from the Tth transaction in the current group
- Availability: v5
gtxnsas
- Syntax:
gtxnsas F
where F: txna - Bytecode: 0xc2 {uint8}
- Stack: …, A: uint64, B: uint64 → …, any
- Bth value of the array field F from the Ath transaction in the current group
- Availability: v5
args
- Bytecode: 0xc3
- Stack: …, A: uint64 → …, []byte
- Ath LogicSig argument
- Availability: v5
- Mode: Signature
gloadss
- Bytecode: 0xc4
- Stack: …, A: uint64, B: uint64 → …, any
- Bth scratch space value of the Ath transaction in the current group
- Availability: v6
- Mode: Application
itxnas
- Syntax:
itxnas F
where F: txna - Bytecode: 0xc5 {uint8}
- Stack: …, A: uint64 → …, any
- Ath value of the array field F of the last inner transaction
- Availability: v6
- Mode: Application
gitxnas
- Syntax:
gitxnas T F
where T: transaction group index, F: txna - Bytecode: 0xc6 {uint8}, {uint8}
- Stack: …, A: uint64 → …, any
- Ath value of the array field F from the Tth transaction in the last inner group submitted
- Availability: v6
- Mode: Application
vrf_verify
- Syntax:
vrf_verify S
where S: vrf_verify - Bytecode: 0xd0 {uint8}
- Stack: …, A: []byte, B: [80]byte, C: [32]byte → …, X: [64]byte, Y: bool
- Verify the proof B of message A against pubkey C. Returns vrf output and verification flag.
- Cost: 5700
- Availability: v7
Field vrf_verify
Standards
Index | Name | Notes |
---|---|---|
0 | VrfAlgorand |
VrfAlgorand
is the VRF used in Algorand. It is ECVRF-ED25519-SHA512-Elligator2, specified in the IETF internet draft draft-irtf-cfrg-vrf-03.
block
- Syntax:
block F
where F: block - Bytecode: 0xd1 {uint8}
- Stack: …, A: uint64 → …, any
- field F of block A. Fail unless A falls between txn.LastValid-1002 and txn.FirstValid (exclusive)
- Availability: v7
Field block
Fields
Index | Name | Type | In | Notes |
---|---|---|---|---|
0 | BlkSeed | [32]byte | ||
1 | BlkTimestamp | uint64 | ||
2 | BlkProposer | address | v11 | |
3 | BlkFeesCollected | uint64 | v11 | |
4 | BlkBonus | uint64 | v11 | |
5 | BlkBranch | [32]byte | v11 | |
6 | BlkFeeSink | address | v11 | |
7 | BlkProtocol | []byte | v11 | |
8 | BlkTxnCounter | uint64 | v11 | |
9 | BlkProposerPayout | uint64 | v11 |
box_splice
- Bytecode: 0xd2
- Stack: …, A: boxName, B: uint64, C: uint64, D: []byte → …
- set box A to contain its previous bytes up to index B, followed by D, followed by the original bytes of A that began at index B+C.
- Availability: v10
- Mode: Application
Boxes are of constant length. If C < len(D), then len(D)-C bytes will be removed from the end. If C > len(D), zero bytes will be appended to the end to reach the box length.
box_resize
- Bytecode: 0xd3
- Stack: …, A: boxName, B: uint64 → …
- change the size of box named A to be of length B, adding zero bytes to end or removing bytes from the end, as needed. Fail if the name A is empty, A is not an existing box, or B exceeds 32,768.
- Availability: v10
- Mode: Application
ec_add
- Syntax:
ec_add G
where G: EC - Bytecode: 0xe0 {uint8}
- Stack: …, A: []byte, B: []byte → …, []byte
- for curve points A and B, return the curve point A + B
- Cost: BN254g1=125; BN254g2=170; BLS12_381g1=205; BLS12_381g2=290
- Availability: v10
Field EC
Groups
Index | Name | Notes |
---|---|---|
0 | BN254g1 | G1 of the BN254 curve. Points encoded as 32 byte X following by 32 byte Y |
1 | BN254g2 | G2 of the BN254 curve. Points encoded as 64 byte X following by 64 byte Y |
2 | BLS12_381g1 | G1 of the BLS 12-381 curve. Points encoded as 48 byte X following by 48 byte Y |
3 | BLS12_381g2 | G2 of the BLS 12-381 curve. Points encoded as 96 byte X following by 96 byte Y |
A and B are curve points in affine representation: field element X concatenated with field element Y. Field element Z
is encoded as follows.
For the base field elements (Fp), Z
is encoded as a big-endian number and must be lower than the field modulus.
For the quadratic field extension (Fp2), Z
is encoded as the concatenation of the individual encoding of the coefficients. For an Fp2 element of the form Z = Z0 + Z1 i
, where i
is a formal quadratic non-residue, the encoding of Z is the concatenation of the encoding of Z0
and Z1
in this order. (Z0
and Z1
must be less than the field modulus).
The point at infinity is encoded as (X,Y) = (0,0)
.
Groups G1 and G2 are denoted additively.
Fails if A or B is not in G. A and/or B are allowed to be the point at infinity. Does not check if A and B are in the main prime-order subgroup.
ec_scalar_mul
- Syntax:
ec_scalar_mul G
where G: EC - Bytecode: 0xe1 {uint8}
- Stack: …, A: []byte, B: []byte → …, []byte
- for curve point A and scalar B, return the curve point BA, the point A multiplied by the scalar B.
- Cost: BN254g1=1810; BN254g2=3430; BLS12_381g1=2950; BLS12_381g2=6530
- Availability: v10
A is a curve point encoded and checked as described in ec_add
. Scalar B is interpreted as a big-endian unsigned integer. Fails if B exceeds 32 bytes.
ec_pairing_check
- Syntax:
ec_pairing_check G
where G: EC - Bytecode: 0xe2 {uint8}
- Stack: …, A: []byte, B: []byte → …, bool
- 1 if the product of the pairing of each point in A with its respective point in B is equal to the identity element of the target group Gt, else 0
- Cost: BN254g1=8000 + 7400 per 64 bytes of B; BN254g2=8000 + 7400 per 128 bytes of B; BLS12_381g1=13000 + 10000 per 96 bytes of B; BLS12_381g2=13000 + 10000 per 192 bytes of B
- Availability: v10
A and B are concatenated points, encoded and checked as described in ec_add
. A contains points of the group G, B contains points of the associated group (G2 if G is G1, and vice versa). Fails if A and B have a different number of points, or if any point is not in its described group or outside the main prime-order subgroup - a stronger condition than other opcodes. AVM values are limited to 4096 bytes, so ec_pairing_check
is limited by the size of the points in the groups being operated upon.
ec_multi_scalar_mul
- Syntax:
ec_multi_scalar_mul G
where G: EC - Bytecode: 0xe3 {uint8}
- Stack: …, A: []byte, B: []byte → …, []byte
- for curve points A and scalars B, return curve point B0A0 + B1A1 + B2A2 + … + BnAn
- Cost: BN254g1=3600 + 90 per 32 bytes of B; BN254g2=7200 + 270 per 32 bytes of B; BLS12_381g1=6500 + 95 per 32 bytes of B; BLS12_381g2=14850 + 485 per 32 bytes of B
- Availability: v10
A is a list of concatenated points, encoded and checked as described in ec_add
. B is a list of concatenated scalars which, unlike ec_scalar_mul, must all be exactly 32 bytes long.
The name ec_multi_scalar_mul
was chosen to reflect common usage, but a more consistent name would be ec_multi_scalar_mul
. AVM values are limited to 4096 bytes, so ec_multi_scalar_mul
is limited by the size of the points in the group being operated upon.
ec_subgroup_check
- Syntax:
ec_subgroup_check G
where G: EC - Bytecode: 0xe4 {uint8}
- Stack: …, A: []byte → …, bool
- 1 if A is in the main prime-order subgroup of G (including the point at infinity) else 0. Program fails if A is not in G at all.
- Cost: BN254g1=20; BN254g2=3100; BLS12_381g1=1850; BLS12_381g2=2340
- Availability: v10
ec_map_to
- Syntax:
ec_map_to G
where G: EC - Bytecode: 0xe5 {uint8}
- Stack: …, A: []byte → …, []byte
- maps field element A to group G
- Cost: BN254g1=630; BN254g2=3300; BLS12_381g1=1950; BLS12_381g2=8150
- Availability: v10
BN254 points are mapped by the SVDW map. BLS12-381 points are mapped by the SSWU map.
G1 element inputs are base field elements and G2 element inputs are quadratic field elements, with nearly the same encoding rules (for field elements) as defined in ec_add
. There is one difference of encoding rule: G1 element inputs do not need to be 0-padded if they fit in less than 32 bytes for BN254 and less than 48 bytes for BLS12-381. (As usual, the empty byte array represents 0.) G2 elements inputs need to be always have the required size.
mimc
- Syntax:
mimc C
where C: Mimc Configurations - Bytecode: 0xe6 {uint8}
- Stack: …, A: []byte → …, [32]byte
- MiMC hash of scalars A, using curve and parameters specified by configuration C
- Cost: BN254Mp110=10 + 550 per 32 bytes of A; BLS12_381Mp111=10 + 550 per 32 bytes of A
- Availability: v11
Field Mimc Configurations
Parameters
Index | Name | Notes |
---|---|---|
0 | BN254Mp110 | MiMC configuration for the BN254 curve with Miyaguchi-Preneel mode, 110 rounds, exponent 5, seed “seed” |
1 | BLS12_381Mp111 | MiMC configuration for the BLS12-381 curve with Miyaguchi-Preneel mode, 111 rounds, exponent 5, seed “seed” |
A is a list of concatenated 32 byte big-endian unsigned integer scalars. Fail if A’s length is not a multiple of 32 or any element exceeds the curve modulus.
The MiMC hash function has known collisions since any input which is a multiple of the elliptic curve modulus will hash to the same value. MiMC is thus not a general purpose hash function, but meant to be used in zero knowledge applications to match a zk-circuit implementation.
What AVM Programs Cannot Do
Design and implementation limitations to be aware of with various Versions.
-
Logic Signatures cannot lookup balances of ALGO or other assets. Standard transaction accounting will apply after the Logic Signature has authorized a transaction. A transaction could still be invalid by other accounting rules just as a standard signed transaction could be invalid (e.g., I can’t give away money I don’t have).
-
Programs cannot access information in previous blocks.
-
Programs cannot access information in other transactions in the current block, unless they are a part of the same transaction group.
-
Logic Signatures cannot know exactly what round the current transaction will commit in (but it is somewhere in between transaction first and last valid round).
-
Programs cannot know exactly what time its transaction is committed.
-
Programs cannot loop prior to Version 4. In Versoin 3 and prior, the branch instructions
bnz
(“branch if not zero”),bz
(“branch if zero”) andb
(“branch”) can only branch forward. -
Until Version 4, the AVM had no notion of subroutines (and therefore no recursion). As of Version 4, they are available through the opcodes
callsub
andretsub
. -
Programs cannot make indirect jumps.
b
,bz
,bnz
, andcallsub
jump to an immediately specified address, andretsub
jumps to the address currently on the top of the call Stack, which is manipulated only by previous calls tocallsub
andretsub
.
Algorand Virtual Machine Overview
The following section offers a non-normative overview of the Algorand Virtual Machine (AVM). It is meant to complement the AVM normative specification, helping readers and implementers develop a complete, high-level, holistic understanding of how the AVM works.
Architecture Diagram
The following diagram provides an overview of the full AVM architecture.
At a high level, the Algorand Virtual Machine (AVM) architecture is composed of four main components:
-
Transactions, which act as inputs to AVM programs. These include various fields and optional arguments. For Logic Signatures, arguments can be provided as byte arrays. This forms the stateless execution environment.
-
The Application Call Context, which supplies references from the Ledger to any data that must be prefetched (such as Boxes, ASAs, foreign Apps, and Accounts). This defines the stateful execution environment.
-
The Ledger state, which offers global, runtime-accessible information recorded on the Ledger.
-
A processing component, responsible for executing the programs and approving or rejecting their effects on the Ledger.
Together, these components define the Evaluation Context, encapsulating everything needed to evaluate a program.
Outside this architecture diagram, an Assembler component compiles programs into executable AVM bytecode.
Stack Execution
The AVM approves a program execution if it ends with:
- A single non-zero value on top of the stack,
The AVM rejects a program execution if it ends with:
- A single zero value on top of the stack,
- Multiple values on top of the stack,
- No value on top of the stack,
Or in case of run-time errors.
A Simple TEAL Program
Let’s consider the following TEAL program:
#pragma version X
// Macros
#define compareAndReturn ==; return
// Program
int 1; int 2; +;
int 3;
compareAndReturn
The TEAL program above, although minimal, showcases most of the features of the AVM assembly language:
-
The first line (
#pragma version X
) directs the assembler to generate bytecode targeting a specific AVM version, -
The
//
prefixes a line comment, -
The
#define
directive is used to define TEAL Macros, -
The
// Program
section lists the opcode instructions of the TEAL program:- TEAL supports the Reverse Polish notation (RPN),
- TEAL lines may end with a newline
\n
or;
.
For a complete description of the AVM instruction set, refer to the TEAL normative specification.
A Simple TEAL Execution
The AVM bytecode, resulting from the TEAL source code assembly and compilation, is executed on the stack.
Suppose we want the AVM to approve a transaction if the following condition is true:
$$ 1 + 2 = 3 $$
This would be an insecure program, since its approval condition is a tautology, which would approve any transaction regardless of the execution context.
The following illustrations show the program execution step-by-step.
Run Times
The Algorand Virtual Machine has two run-time (execution) modes:
-
Logic Signature (or Stateless) mode: executes Logic Signatures,
-
Application (or Stateful) mode: executes Smart Contracts.
For further details about the execution modes, refer to the AVM normative specification.
Each mode differs in opcode availability, Ledger resources accessibility, program sizes, and computational budget.
Logic Signature Mode
The following diagram shows the general flow when a Logic Signature is executed, starting from a transaction message reception in the Network Layer, going through the AVM for program execution, and all the way up to the transaction approval and subsequent push into the Transaction Pool, to be added to an upcoming block’s payset.
sequenceDiagram Network->>TxHandler: Incoming message TxHandler->>+MessageCache: Check for<br/>duplicate message MessageCache->>-TxHandler: returns TxHandler->>+RateLimiter: Rate Limit Check RateLimiter->>-TxHandler: returns TxHandler->>+SigVerifier: Verify LogicSig SigVerifier->>+AVM: Run LogicSig AVM->>-SigVerifier: returns SigVerifier->>-TxHandler: returns TxHandler->>+TxPool: Push verified tx to pool
Application Mode
The following diagram shows the execution path of a transaction containing a call to an Application. Note that code execution is done in a different pipeline; applications being stateful, a full block context is needed to evaluate their correctness, and so code execution happens at block evaluation time.
For further details on the block evaluation stage, see the Ledger non-normative specifications.
sequenceDiagram BlockEvaluator->>+TxGroup: Eval group TxGroup->>+Tx: Eval transaction Tx->>+AVM: Application call AVM->>-Tx: returns Tx->>-TxGroup: returns TxGroup->>-BlockEvaluator: returns
A special way of executing an application is the internal cross-application call,
(itxn_begin
opcode).
Note that the beginning of this flow is the same as in a regular application call, as this constitutes a subcase of an Application mode call.
sequenceDiagram BlockEvaluator->>+TxGroup: Eval group TxGroup->>+Tx: Eval transaction Tx->>+AVM: Application call AVM->>+InternalTxGroup: itxn_begin InternalTxGroup->>+InternalTx: Process each<br/>transaction InternalTx->>+AVM: Process ApplicationCall transaction AVM->>-InternalTx: returns InternalTx->>-InternalTxGroup: returns InternalTxGroup->>-AVM: returns AVM->>-Tx: returns Tx->>-TxGroup: returns TxGroup->>-BlockEvaluator: returns
Stateless
Contract Account
Delegated Signature
Stateful
Application Creation
Application Calls
⚙️ IMPLEMENTATION
Application Call NoOp reference implementation,
Application Call Update reference implementation,
Application Call Delete reference implementation,
Application Call OptIn reference implementation,
Application Call CloseOut reference implementation.
Application Resources
App-to-App Calls
$$ \newcommand \EC {\mathrm{EC}} $$
Evaluation Context
An Evaluation Context \( \EC \) is a core runtime structure used during AVM program execution. It maintains all the state and metadata required to evaluate a program within a transaction (group) scope.
Below is a simplified interface example for the Evaluation Context, inspired by
the go-algorand
reference implementation.
Static Properties
These \( \EC \) methods expose context information that remains fixed throughout the execution of a specific transaction within the group:
-
RunMode() -> {SmartSignature, Application}
Returns whether the context is for a stateless LogicSig or a stateful Application. -
GroupIndex() -> int
Returns the index of the current transaction within its group. -
PastScratch(past_group_index int) -> map[int, StackValue]
Retrieves the final scratch space of a previous transaction in the same group, by index. -
GetProgram() -> []byte
Returns the bytecode of the currently executing program \( \EC_P \).
Additional program-related accessors:
-
getCreatorAddress() -> []byte
— Gets the Creator address of the Application. -
AppID() -> uint64
— Returns the ID of the current Application. -
ProgramVersion() -> uint64
— AVM version for the executing program. -
GetOpSpec() -> OpSpec
— Returns the Opcode Specification for the current opcode. -
begin(program []byte) -> bool
— Verifies whether the given program version is supported and executable in the current context.
Dynamic Properties
These \( \EC \) reflect the evolving state of execution, may change as the transaction executes.
PC() -> int
Returns the current program counter of the executed application \( \EC_{pc} \).
Budget and Cost Tracking
The opcode budget limits the AVM program execution.
-
Cost() -> int
Returns the totalopcode
execution cost so far. -
remainingBudget() -> int
Returns the remainingopcode
budget available for execution.
Inner Transactions
-
InnerTxnPending() -> []Transaction
Returns inner transactions that are queued but not yet submitted. -
addInnerTxn()
Adds an inner transaction to the group. Validates constraints such as group size, fees, and sender address. Used by theitxn_begin
anditxn_next
opcodes.
Program Evaluation
step()
The core transition function that advances execution oneopcode
at a time. See the dedicated non-normative section for further details.
Ledger Interaction
These functions expose the current Ledger context to the AVM:
-
getRound() -> uint64
Returns the current round from the Ledger. -
getLatestTimestamp() -> uint64
Returns the latest timestamp of the most recently committed block.
Prefetched Ledger Accessors
accountRetrieval()
,assetRetrieval()
,boxRetrieval()
,applicationRetrieval()
Provide runtime access to prefetched Ledger data (e.g., foreign accounts, apps, boxes, and assets) as declared in the transaction’s foreign arrays.
$$ \newcommand \EC {\mathrm{EC}} $$
Transition Function
The Evaluation Cycle is the fundamental process by which a program \( \EC_P \) is executed in the Algorand Virtual Machine (AVM).
Starting from an initial Evaluation Context \( \EC \), it applies the step()
function repeatedly to progress through the program’s instructions.
Based on the program’s logic and runtime behavior, this cycle ultimately determines whether a transaction is:
-
REJECTED: Discarded and ignored, or
-
APPROVED: Accepted, either pushed into the Transaction Pool or validated during block assembly or verification.
Step Function Flow
The step()
function powers AVM state transitions between successive \( \EC \)
states. It encapsulates the execution logic for a single opcode
and performs multiple
validations at each step.
Below is a diagram that visualizes the logic flow of a single step()
invocation:
flowchart TD Start(["Start **step()**"]):::cend %% Decision Nodes CheckNextOpcode{{"Does **nextOpcode()** exist, is allowed, and validated?"}}:::decision CheckBudget{{"Is **EC.Budget** > **MaxBudget**?"}}:::decision ValidateStack{{"Validate stack size and element constraints"}}:::decision %% Action Nodes DispatchNextOpcode["Dispatch **nextOpcode** (observe changes)"]:::action UpdateTxnCost["Update transaction cost with **txnCostUpdate()**"]:::action UpdatePC["Update program counter **EC.PC()** to next instruction"]:::action %% Reject Node RejectFromValidation["**REJECT()**"]:::reject %% End Node End(["**End Step()**"]):::cend %% Connections Start --> CheckNextOpcode CheckNextOpcode -->|Yes| DispatchNextOpcode CheckNextOpcode -->|No| RejectFromValidation DispatchNextOpcode --> UpdateTxnCost UpdateTxnCost --> CheckBudget CheckBudget -->|Yes| RejectFromValidation CheckBudget -->|No| ValidateStack ValidateStack -->|Valid| UpdatePC ValidateStack -->|Invalid| RejectFromValidation UpdatePC --> End %% Styling classDef decision stroke:#FFFF,stroke-width:2px classDef action stroke:#FFFFFF,stroke-width:2px classDef cend stroke:#FFFF,stroke-width:2px classDef reject fill:#FF6347,stroke:#FFF,stroke-width:2px
⚙️ IMPLEMENTATION
Step function reference implementation.
Step-by-Step Execution
-
Opcode Fetch and Validation
The function begins by checking whether the next opcode (determined byPC()
) exists, is permitted under the current AVM version, and passes static validation. If any of these checks fail, the transaction is immediately REJECTED. -
Opcode Dispatch
If the opcode is valid, the AVM dispatches the corresponding handler (see AVM operation definitions). Handlers may perform additional runtime validations and update the execution state. Errors encountered here also cause the transaction to be immediately REJECTED. -
Cost Update
After executing theopcode
, the transaction’s cost is updated. If the accumulated cost exceeds the allowed execution budget, the transaction is REJECTED. -
Stack Validation
The stack is then validated to ensure:-
It does not exceed the maximum allowed size.
-
All pushed values are valid
StackValue
types (eitheruint64
or[]byte
with length less than \( 4096 \) bytes).
An invalid stack state causes the transaction to be REJECTED.
-
-
Program Counter Update
Finally, if all validations pass, the program counterPC()
is incremented to point to the next instruction, and the current step concludes.
Final State Evaluation
After each step()
, the Evaluation Cycle checks if the program has reached a
terminal state. This happens if:
-
Error Occurs:
Any failure duringstep()
leads to immediate REJECTION of the transaction. -
End of Program:
IfPC()
points beyond the end of the program bytecode \( \EC_P \), the cycle evaluates the final stack:-
If the stack contains exactly one non-zero value, the transaction is APPROVED.
-
Otherwise, the transaction is REJECTED.
-
Opcodes like
return
internally sanitize the stack (e.g., popping all elements except the top result) to comply with these final stack requirements before ending execution.
Program Compilation
A TEAL program is compiled using the POST /v2/teal/compile
endpoint of algod
node (go-algorand
reference implementation).
See the
algod
node API non-normative section for further details.
The node begins by decoding the TEAL source code and converting it into AVM bytecode
using the internal assemble
function.
⚙️ IMPLEMENTATION
Assembler reference implementation.
The following diagram outlines the steps involved in TEAL assembly:
flowchart TD A[Check: _version_ and non-empty program] --> B[Read TEAL program] B --> C[Get lexical tokens from line] C --> D[Check Statement] D --> E[Settle Version and prepare PseudoOps] E --> F[Check Labels] F --> G[Assemble instruction] G -- Next line --> C G --> H[Optimize _intcblock_ and _bytecblock_] H --> I[Resolve labels] I --> J[Return Assembled Stream] subgraph Loop C D E F G end
Preliminary Checks
The assembly process begins with two initial checks:
-
Validating that the program includes a version declaration.
-
Ensuring the TEAL source is not empty (empty programs are invalid).
For a complete list of all available
opcodes
by versions, refer to the TEAL normative section
If no version is declared, the assembler uses a placeholder (assemblerNoVersion
)
that is later replaced with the default compiler version or one specified by a #pragma
directive.
Then the assembler excludes empty strings (as they are not valid in TEAL).
Lexical Tokenization
Next, the assembler reads the program line by line and performs the following steps:
-
Tokenization
Lines are broken into lexical tokens and extracted. Lines starting with#
are treated as preprocessor directives (#pragma
,#define
). -
Statement Parsing
Comments are stripped, and valid instructions are identified. Lines may end with\n
or;
. -
Statement Handling
- Opcodes are processed based on the official opcode table.
- Pseudo-Opcodes are translated into real opcodes and then assembled.
- Labels (used as jump targets) are recorded for later resolution. The
callsub
instruction also defines a label.
Constants Optimization
Once all statements are parsed, the assembler optimizes constant blocks to reduce the program size:
-
intcblock
: Reorders integer constants by frequency of use. The most common values are placed first to use the more compactintc_X
opcode. This optimization only affects theint
pseudo-opcode. -
bytecblock
: Reorders byte or address constants by frequency of use. The most common values are placed first to use the more compactbytec_X
opcode.
Label Resolution
Label targets are resolved into relative byte offsets (2-bytes), pointing from the end of the current instruction to the target.
Finalization
After assembling the program, the resulting bytecode buffer is hashed. The algod
API response includes both the assembled bytecode and its hash, completing the compilation
process.
New OpCodes
Here we describe the process of adding a new AVM OpCode to go-algorand
reference
implementation, providing the example of a dummy double
OpCode.
The AVM OpCodes are versioned (langspec_v
) and can be categorized as:
- Arithmetic and Logic Operations
- Byte Array Manipulation
- Cryptographic Operations
- Pushing Values on Stack/Heap (Constants, Txn / ASA / App / Account / Global Fields)
- Control Flow
- State Access
- Inner Transactions
OpCode Definition
Most OpCodes logic lives in data/transactions/logic/eval.go
folder.
Some exceptions are larger families of OpCodes with their own files (e.g., box.go
).
For the dummy double
OpCode example, let’s define the operator function in
data/transactions/logic/eval.go
:
func opDouble(cx *EvalContext) error {
last := len(cx.Stack) - 1
res, carry := bits.Add64(cx.Stack[last].Uint, cx.Stack[last].Uint, 0)
if carry > 0 {
return errors.New("double overflowed")
}
cx.Stack[last].Uint = res
return nil
}
OpCode Spec
OpCodes are included by adding them to opcodes.go
with a unique byte value.
Let’s add the new OpSpec
value in the OpSpecs
array.
The format is {0x01, "sha256", opSHA256, proto("b:b{32}"), 2, costly(35)}
.
The arguments may be interpreted as follows:
-
A
byte
indicating the OpCode number, -
A
string
with the identifier of the OpCode, -
The
eval.go
function that handles OpCode execution (defined above), -
A
proto
structure defined through theproto()
function. This indicates the actual signature of the new OpCode.- The element before
:
indicates the values to be popped from the top of the stack, - The element after
:
indicates the values to be pushed to the stack, - The letter identifies the type of arguments. In case of
byte
arrays the length could be expressed as a number in curly brackets.
- The element before
-
The AVM
version
where the new OpCode is introduced, -
The
cost
of the new OpCode.
For the double
dummy OpCode:
var OpSpecs = []OpSpec{
...
// Double OpCode
{0x75, "double", opDouble, proto("i:i"), 42, detDefault()},
...
}
Update LangSpec
Run make
from within the data/transactions/logic/
directory to generate an updated
language specification (langspec_v
) and TEAL docs.
Build Binary
Run make
from the root of the go-algorand
directory to build new algod
binary.
Testing
Tests are organized in the accompanying data/transactions/logic/eval_test.go
file.
Let’s test the new OpCode locally by generating a netgoal template and running a new Local Network.
Be sure to set the network’s Consensus Version to
future
if you’re adding an OpCode to a future AVM version.
OpCode Cost Estimation
Estimate the new OpCode budget (e.g., benchmarking against similar ones).
Algorand Keys Overview
This part specifies the list of keys and their capabilities in Algorand.
Algorand Keys Specification
An Algorand node interacts with three types of cryptographic keys:
-
Root keys, a key pair (public and private) used to control the access to a particular account. These key pairs are also known as Spending Keys (as they sign accounts’ transactions).
-
Voting keys, a set of keys used for authentication, i.e. identify an account in the Algorand Byzantine Fault Tolerant protocol (see ABFT section). Algorand uses a hierarchical (two-level) signature scheme that ensures forward security, which will be detailed in the next section. These key pairs are also known as Participation Keys.
-
VRF Selection keys, keys used for proving membership of selection (see Cryptography primitives specification).
An agreement vote message (see Networking section) is valid only when it contains a proper VRF proof (\( y \)) and is signed with the correct voting key.
Root Keys
Root keys are used to identify ownership of an account. An Algorand node only interacts with the public key of a root key. The public key of a root key is also used as the account address. Root keys are used to sign transaction messages as well as delegating the voting authentication using voting keys, unless that specific account was rekeyed. A rekeyed account would use the rekeyed key in lieu of the root key.
A relationship between a root key and voting keys is established when accounts register their participation in the agreement protocol.
For further details on the key registration (
keyreg
) process, refer to Ledger specification.
Voting and Participation Keys
A protocol player (as defined in the ABFT specification) is any actor that participates in the Algorand agreement protocol.
This section specifies the key infrastructure required for an active player to take part in the protocol.
To participate in the agreement, a player must control an account (that is, a root key pair), and must generate a set of participation keys associated with it.
To declare their intent to join the agreement protocol, players register their participation keys.
After some protocol rounds since the participation keys registration on the Ledger (known as balance lookback period \( \delta_b \), defined in the ABFT specification), the player becomes an active participant in the agreement protocol. From that round, the account may be selected to propose blocks or to vote during the agreement protocol stages, until the expiration of the participation keys registration.
For further details about the structure of a participation keys registration (
keyreg
) transaction, refer to the Ledger specification.
$$ \newcommand \KeyDilution {\mathrm{KeyDilution}} \newcommand \Batch {\mathrm{Batch}} \newcommand \Offset {\mathrm{Offset}} $$
Algorand’s Two-level Ephemeral Signature Scheme for Authentication
For a player with their participation keys registered on the Ledger (and hosted on an Algorand node), a set of ephemeral sub-keys is created.
An ephemeral sub-key is a key pair that produces one-time signature for messages. It MUST be deleted after use to ensure forward security. Algorand’s ephemeral subkeys use Ed25519 public-key signature system.
For further details, refer to the Cryptography primitives specification.
Algorand uses a two-level ephemeral signature scheme. Instead of signing voting messages directly, Algorand accounts use their registered voting keys to sign an intermediate ephemeral sub-key.
This intermediate ephemeral sub-key signs a batch of leaf-level ephemeral sub-keys. Hence, each intermediate ephemeral sub-key is associated with a batch number (\( \Batch \)), and each leaf ephemeral sub-key is associated with both a batch number (of its parent key) and an offset (\( \Offset \), denotes its offset within a \( \Batch \)). An agreement voting message is signed hierarchically:
Voting Keys Root Key
└── Batch Sub-Key
└── Leaf Sub-Key
└── Agreement Voting Message
Further details on this process in the One-time Signature subsection.
Each leaf-level ephemeral sub-key is used for voting on a single agreement round, and will be deleted afterward. Once a batch of leaf-level ephemeral sub-keys run out, a new batch is generated. Algorand allows users to set the number of leaf-level ephemeral sub-key per batch, \( \KeyDilution \).
The default \( \KeyDilution \) value is \( 10{,}000 \).
An Algorand account can change its \( \KeyDilution \) in the participation keys registration.
For further details about the structure of a participation keys registration (
keyreg
) transaction, refer to the Ledger specification.
The following diagram shows the tree structure of the voting signature scheme:
flowchart TD root["One-Time Signature container"] --> p0["Batch Signer 1"] root --> p1["Batch Signer 2"] root --> pDot["..."] root --> pN["Batch Signer N"] p0 --> v0["Batch 1 Key 1"] p0 --> v1["Batch 1 Key 2"] p0 --> vDot["..."] p0 --> vN["Batch 1 Key N"]
$$ \newcommand \SubKeyPK {\mathrm{SubKeyPK}} \newcommand \OTSSOffsetID {\mathrm{OneTimeSignatureSubkeyOffsetID}} \newcommand \OTSSBatchID {\mathrm{OneTimeSignatureSubkeyBatchID}} \newcommand \OneTimeSignature {\mathrm{OneTimeSignature}} \newcommand \Sig {\mathrm{Sig}} \newcommand \PK {\mathrm{PK}} \newcommand \PKSigOld {\mathrm{PKSigOld}} \newcommand \PKTwo {\mathrm{PK2}} \newcommand \PKOneSig {\mathrm{PK1Sig}} \newcommand \PKTwoSig {\mathrm{PK2Sig}} \newcommand \Offset {\mathrm{Offset}} \newcommand \Batch {\mathrm{Batch}} $$
One-Time Signature
\( \OTSSBatchID \) identifies an intermediate level ephemeral sub-key of a batch and is signed by the voting key’s root key. It has the following fields:
-
Sub-Key Public key (\( \SubKeyPK \)), the public key of this sub-key.
-
Batch (\( \Batch \)), batch number of this sub-key.
The \( \OTSSOffsetID \) identifies a leaf-level ephemeral sub-key and is signed with a batch sub-key. It has the following fields:
-
Sub-Key Public key (\( \SubKeyPK \)), the public key of this sub-key.
-
Batch (\( \Batch \)), batch number of this sub-key.
-
Offset (\( \Offset \)), offset of this sub-key in current batch.
Finally, \( \OneTimeSignature \) is a cryptographic signature used in voting messages between Algorand players. It contains the following fields:
-
Signature (\( \Sig \)), a signature of message under \( \PK \)
-
Public Key (\( \PK \)), the public key of the message signer, is part of a leaf-level ephemeral sub-key.
-
Public Key 2 (\( \PKTwo \)), the public key of the current batch.
-
Public Key 1 Signature (\( \PKOneSig \)), a signature of \( \OTSSOffsetID \) under \( \PKTwo \).
-
Public Key 2 Signature (\( \PKTwoSig \)), a signature of \( \OTSSBatchID \) under the voting keys.
The Old Style Signature (\( \PKSigOld \)) is deprecated, still included in the message only for compatibility reasons.
⚙️ IMPLEMENTATION
One-Time Signature reference implementation.
$$ \newcommand \UnauthenticatedVote {\mathrm{UnauthenticatedVote}} \newcommand \UnauthenticatedCredential {\mathrm{UnauthenticatedCredential}} \newcommand \Sender {\mathrm{Sender}} \newcommand \Round {\mathrm{Round}} \newcommand \Period {\mathrm{Period}} \newcommand \Step {\mathrm{Step}} \newcommand \Proposal {\mathrm{Proposal}} \newcommand \VrfOut {\mathrm{VrfOut}} \newcommand \Credential {\mathrm{Credential}} \newcommand \Cred {\mathrm{Cred}} \newcommand \Weight {\mathrm{Weight}} \newcommand \DomainSeparationEnabled {\mathrm{DomainSeparationEnabled}} \newcommand \Hashable {\mathrm{Hashable}} \newcommand \Vote {\mathrm{Vote}} \newcommand \Sig {\mathrm{Signature}} $$
VRF Selection Keys
To check the validity of a voting message, its VRF Selection key needs to be verified. Algorand uses Verifiable Random Function (VRF) to generate selection keys.
More specifically, an unverified vote (\( \UnauthenticatedVote \)) has the following fields:
-
Raw Vote (\( \mathrm{R} \)), an inner struct contains \( \Sender \), \( \Round \), \( \Period \), \( \Step \), and \( \Proposal \).
-
Unverified Credential (\( \Cred \)) contains a single field \( \mathrm{Proof} \), which is a VRF proof.
-
Signature (\( \Sig \)), one-time signature of the vote.
⚙️ IMPLEMENTATION
Unauthenticated vote reference implementation.
Once receiving an unverified vote (\( \UnauthenticatedVote \)) from the network, an Algorand node verifies its VRF selection key by checking the validity of the VRF Proof (in \( \Cred \)), the committee membership parameters that it is conditioned on, and the voter’s voting stake.
If verified, the result of this verification is wrapped in a \( \Credential \) struct, containing the following fields:
-
Unverifed Credential (\( \UnauthenticatedCredential \)), the unverified selection key from the VRF proof.
-
Weight (\( \Weight \)), the weight of the vote.
-
VRF Output (\( \VrfOut \)), the cached output of VRF verification.
-
Domain Separation Enabled (\( \DomainSeparationEnabled \), domain separation flag, now must be true by the protocol.
-
Hashable (\( \Hashable \)), the original credential.
And this verified credential is wrapped in a \( \Vote \) struct with Raw Vote (\( \mathrm{R} \)), Verified Credential (\( \Credential \)), and Signature (\( \Sig \)).
⚙️ IMPLEMENTATION
Vote struct reference implementation.
$$ \newcommand \Rfv {\mathrm{FirstValidRound}} \newcommand \Rlv {\mathrm{LastValidRound}} \newcommand \KLT {\mathrm{KeyLifeTime}} \newcommand \Hash {\mathrm{Hash}} \newcommand \SchemeID {\mathrm{SchemeID}} \newcommand \Sig {\mathrm{Signature}} \newcommand \Verify {\mathrm{VerifyingKey}} \newcommand \VectorIdx {\mathrm{VectorIndex}} \newcommand \Proof {\mathrm{Proof}} \newcommand \Digest {\mathrm{Digest}} \newcommand \ZDigest {\mathrm{Zero}\Digest} \newcommand \SigBits {\Sig\mathrm{BitString}} \newcommand \pk {\mathrm{pk}} \newcommand \Leaf {\mathrm{Leaf}} \newcommand \Round {\mathrm{Round}} $$
Algorand State Proof Keys
Algorand’s Committable Ephemeral Keys Scheme - Merkle Signature Scheme
Algorand achieves forward security using a Merkle Signature Scheme. This scheme consists of using a different ephemeral key for each round in which it will be used. The scheme uses vector commitment to generate commitment to those keys.
The private key MUST be deleted after the round passes to achieve complete forward secrecy.
This is analogous to the scheme discussed in the voting keys section.
The Merkle scheme uses FALCON scheme as the underlying digital signature algorithm.
For further details on FALCON scheme, refer to the Cryptography primitives specification.
The tree’s depth is bound to \( 16 \) to bound verification paths on the tree. Hence, the maximum number of keys which can be created is at most \( 2^{16} \).
⚙️ IMPLEMENTATION
Merkle signature scheme reference implementation.
Public Commitment
The scheme generates multiple keys for the entire participation period. Given \( \Rfv \), \( \Rlv \) and a \( \KLT \), a key is generated for each round \( r \) that holds:
$$ \Rfv \leq r \leq \Rlv \land r \mod \KLT = 0 $$
Currently, \( \KLT = 256 \) rounds.
After generating the public keys, the scheme creates a vector commitment using the keys as leaves.
Leaf hashing is done in the following manner:
$$ leaf_{i} = \Hash(\texttt{“KP”} || \SchemeID || r || P_{k_{i}}), \text{ for each corresponding round.} $$
Where:
-
\( \SchemeID \) is a 16-bit, little-endian constant integer with value of \( 0 \).
-
\( r \) is a 64-bit, little-endian integer representing the start round for which the key \( P_{k_{i}} \) is valid. The key would be valid for all rounds in \( [r, \ldots, r + \KLT - 1] \).
-
\( P_{k_{i}} \) is a 14,344-bit string representing the FALCON ephemeral public key.
-
\( \Hash \) is the SUBSET-SUM hash function as defined in the Cryptographic Primitives Specification.
Signatures
A signature in the scheme consists of the following elements:
-
\( \Sig \) is a signature generated with the FALCON scheme.
-
\( \Verify \) is a FALCON ephemeral public key.
-
\( \VectorIdx \) is an index of the ephemeral public key leaf in the vector commitment.
-
\( \Proof \) is an array of size \( n \) (\( n \leq 16 \) since the number of keys is bounded) which contains hash results (\( \Digest_{0}, \ldots, \Digest_n \)). \( \Proof \) is used as a Merkle verification path on the ephemeral public key.
When the committer gives a \( n \)-depth authentication path for index \( \VectorIdx \), the verifier must write \( \VectorIdx \) as \( n \)-bit number and read it from MSB to LSB to determine the leaf-to-root path.
When signature is to be hashed, it must be serialized into a binary string according to the following format:
$$ \SigBits = (\SchemeID || \Sig || \Verify || \VectorIdx || \Proof) $$
Where:
-
\( \SchemeID \) is a 16-bit, little-endian constant integer with value of \( 0 \).
-
\( \Sig \) is a 12,304-bit string representing a FALCON signature in a CT format.
-
\( \Verify \) is a 14,344-bit string.
-
\( \VectorIdx \) is a 64-bit, little-endian integer.
-
\( \Proof \) is constructed in the following way:
-
if \( n = 16 \):
\( \Proof = (n || \Digest_{0} || \ldots || \Digest_{15}) \) -
else:
\( \Proof = (n || \ZDigest_{0} || \ldots || \ZDigest_{d-1} || \Digest_{0} || \ldots || \Digest_{n-1}) \)
-
Where:
-
\( n \) is a 8-bit string.
-
\( \Digest_{i} \) is a 512-bit string representing sumhash result.
-
\( \ZDigest \) is a constant 512-bit string with value \( 0 \).
-
\( d = 16 - n \)
Verifying Signatures
A signature \( s \) for a message \( m \) at round \( r \) is valid under the public commitment \( \pk \) and \( \KLT \) if:
-
The FALCON signature \( s.\Sig \) is valid for the message \( m \) under the public key \( s.\Verify \)
-
The proof \( s.\Proof \) is a valid vector commitment proof for the entry \( \Leaf \) at index \( s.\VectorIdx \) with respect to the vector commitment root \( \pk \) where:
-
\( \Leaf := \texttt{“KP”} || \SchemeID || \Round || s.\Verify \),
-
\( \Round := r - (r \mod \KLT) \).
-
Algorand Cryptographic Primitives Specification
Algorand relies on a set of cryptographic primitives to guarantee the integrity and finality of data.
This normative section describes these primitives.
Representation
As a preliminary for guaranteeing cryptographic data integrity, Algorand represents all inputs to cryptographic functions (i.e., a cryptographic hash, signature, or verifiable random function) via a canonical and domain-separated representation.
Canonical Msgpack
Algorand uses a version of MsgPack to produce canonical encodings of data.
Algorand’s msgpack encodings are valid msgpack encodings, but the encoding function is deterministic to ensure a canonical representation that can be reproduced to verify signatures.
A canonical msgpack encoding in Algorand must follow these rules:
-
Maps MUST contain keys in lexicographic order;
-
Maps MUST omit key-value pairs where the value is a zero-value, unless otherwise specified;
-
Positive integer values MUST be encoded as
unsigned
in msgpack, regardless of whether the value space is semantically signed or unsigned; -
Integer values MUST be represented in the shortest possible encoding;
-
Binary arrays MUST be represented using the
bin
format family (that is, use the most recent version of msgpack rather than the older msgpack version that had nobin
family).
Domain Separation
Before an object is input to some cryptographic function, it is prepended with a multi-character domain-separating prefix.
All domain separators must be “prefix-free” (that is, they must not be concatenated).
The list below specifies each prefix
:
-
For cryptographic primitives:
OT1
andOT2
: The first and second layers of keys used for ephemeral signatures.MA
: An internal node in a Merkle tree.MB
: A bottom leaf in a vector commitment.KP
: Is a public key used by the Merkle Signature Schemespc
: A coin used as part of the state proofs construction.spp
: Participant’s information (state proof public key and weight) used for state proofs.sps
: A signature from a specific participant used for state proofs.
-
In the Algorand Ledger:
BH
: A Block Header.BR
: A Balance Record.GE
: A Genesis configuration.spm
: A State Proof message.STIB
: A SignedTxnInBlock that appears as part of the leaf in the Merkle tree of transactions.TL
: A leaf in the Merkle tree of transactions.TX
: A Transaction.SpecialAddr
: A prefix used to generate designated addresses for specific functions, such as sending state proof transactions.
-
In the Algorand Byzantine Fault Tolerance protocol:
AS
: An Agreement Selector, which is also a VRF input.CR
: A Credential.SD
: A Seed.PL
: A Payload.PS
: A Proposer Seed.VO
: A Vote.
-
In other places:
arc
: ARCs-related hashes https://github.com/algorandfoundation/ARCs. The prefix for ARC-XXXX should start witharcXXXX
(whereXXXX
is the 0-padded number of the ARC). For example, ARC-0003 can use any prefix starting witharc0003
.MX
: An arbitrary message used to prove ownership of a cryptographic secret.NPR
: A message that proves a peer’s stake in an Algorand networking implementation.TE
: An arbitrary message reserved for testing purposes.Program
: A TEAL bytecode program.ProgData
: Data that is signed within TEAL bytecode programs.
Auctions are deprecated; however, their prefixes are still reserved in code:
aB
: A Bid.aD
: A Deposit.aO
: An Outcome.aP
: Auction parameters.aS
: A Settlement.
SHA512/256
Algorand uses the SHA-512/256 algorithm as its primary cryptographic hash function.
In Algorand, the SHA-512/256 algorithm is used to:
-
Commit to data for signing and for the Byzantine Fault Tolerance protocol,
-
Rerandomize its random seed.
SHA256
Algorand uses SHA-256 algorithm to allow verification of Algorand’s state and transactions on environments where SHA-512/256 is not supported.
In Algorand, the SHA-256 algorithm is used to:
-
Compute the hash of the previous Light Block Header (
prev
) (see Ledger normative specification); -
Generate an additional commitment (
txn256
) of the transactions included in the block (payset) (see Ledger normative specification).
SHA512
Algorand uses the SHA-512 algorithm to strengthen post-quantum security by increasing collision resistance against Grover’s algorithm.
In Algorand, the SHA-512 algorithm is used to:
-
Compute an additional hash of the previous Light Block Header (
prev512
) (see Ledger normative specification), -
Generate an additional commitment (
txn512
) of the transactions included in the block (payset) (see Ledger normative specification).
SUBSET-SUM
Algorand uses SUBSET-SUM algorithm, which is a quantum-resilient hash function.
This algorithm is used:
-
To create Merkle Trees for State Proofs,
-
To commit on ephemeral public keys in the Merkle Keystore structure used in the two-level Ephemeral Signature Scheme.
For further details on the Ephemeral Signature Scheme, refer to Algorand Keys normative specification.
Ed25519
Algorand uses the Ed25519 digital signature scheme to sign data.
Algorand changes the Ed25519 verification algorithm in the following way (using notation from Ed25519 specification):
-
Reject if
R
orA
(PK) are equal to one of the following (non-canonical encoding, this check is actually required by Ed25519, but not all libraries implement it):0100000000000000000000000000000000000000000000000000000000000080
ECFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
EEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7F
EEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
EDFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
EDFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7F
- a point which holds
(x,y) where: 2**255 -19 <= y <= 2**255
. Where we remind thaty
is defined as the encoding of the point with the right-most bit cleared.
-
Reject if
A
(PK) is equal to one of the following (small order points):0100000000000000000000000000000000000000000000000000000000000000
ECFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7F
0000000000000000000000000000000000000000000000000000000000000080
0000000000000000000000000000000000000000000000000000000000000000
C7176A703D4DD84FBA3C0B760D10670F2A2053FA2C39CCC64EC7FD7792AC037A
C7176A703D4DD84FBA3C0B760D10670F2A2053FA2C39CCC64EC7FD7792AC03FA
26E8958FC2B227B045C3F489F2EF98F0D5DFAC05D3C63339B13802886D53FC05
26E8958FC2B227B045C3F489F2EF98F0D5DFAC05D3C63339B13802886D53FC85
-
Reject non-canonical
S
(this check is actually required by Ed25519, but not all libraries implement it):0 <= S < L
, whereL = 2^252+27742317777372353535851937790883648493
.
-
Use the cofactor equation (this is the default verification equation in Ed25519):
[8][S]B = [8]R + [8][K]A'
FALCON
Algorand uses a deterministic version of FALCON lattice-based signature scheme.
FALCON is quantum-resilient and a SNARK-friendly digital signature scheme used to sign in State Proofs.
FALCON signatures contain a salt version. Algorand only accepts signatures with
a salt version equal to 0
.
The library defines the following sizes:
Component | Size (bytes) |
---|---|
Public Key | \( 1793 \) |
Private Key | \( 2305 \) |
Signature - CT Format | \( 1538 \) |
Signature - Compressed | Variable, up to a maximum of \( 1423 \) |
Algorand uses a random seed of \( 48 \) bytes for FALCON key generation.
Merkle Tree
Algorand uses a Merkle Tree to commit to an array of elements and to generate and verify proofs of elements against such a commitment.
Merkle Trees are used:
-
To generate a cryptographic commitment to the
payset
of a block (see the Algorand Ledger normative specification), -
For the tree structure in the two-level Ephemeral Signature Scheme (see Algorand Keys normative specification),
-
To build State Proofs.
The Merkle Tree algorithm is defined for a dense array of N
elements numbered
0
through N-1
.
We now describe how to commit to an array (to produce a commitment in the form of a root hash), what a proof looks like, and how to verify a proof for one or more elements.
Since at most one valid proof can be efficiently computed for a given position, element, and root commitment, we do not formally define an algorithm for generating a proof. Any algorithm that generates a valid proof (i.e., which passes verification) is correct.
A reasonable strategy for generating a proof is to follow the logic of the proof verifier and fill in the expected left- and right-sibling values in the proof based on the internal nodes of the Merkle Tree built up during commitment.
The Merkle Tree can be created using one of the supported hash functions.
Commitment
To commit to an array of N
elements, each element is first hashed to produce a
\( 32 \)-byte hash value, together with the appropriate domain-separation prefix;
this produces a list of N
hashes.
If N = 0
, then the commitment is all-zero (i.e., \( 32 \) zero bytes). Otherwise,
the list of N
\( 32 \)-byte values is repeatedly reduced to a shorter list,
as described below, until exactly one \( 32 \)-byte value remains; at that point,
this resulting \( 32 \)-byte value is the commitment.
The reduction procedure takes pairs of even-and-odd-indexed values in the list (for
instance, the values at positions 0
and 1
; the values at positions 2
and 3
;
and so on) and hashes each pair to produce a single value in the reduced list (respectively,
at position 0
; at position 1
; and so on).
To hash two values into a single value, the reduction procedure concatenates the
domain-separation prefix MA
together with the
two values (in the order they appear in the list), and then applies the hash function.
When a list has an odd number of values, the last value is paired together with an all-zero value (i.e., \( 32 \) zero bytes).
The pseudocode for the commitment algorithm is as follows:
def commit(elems):
hashes = [H(elem) for elem in elems]
return reduce(hashes)
def reduce(hashes):
if len(hashes) == 0:
return [0 for _ in range(32)]
if len(hashes) == 1:
return hashes[0]
nexthashes = []
while len(hashes) > 0:
left = hashes[0]
right = hashes[1] if len(hashes) > 1 else [0 for _ in range(32)]
hashes = hashes[2:]
nexthashes.append(H("MA" + left + right))
return reduce(nexthashes)
Proofs
Logically, to verify that an element appears at some position P
in the array,
the verifier runs a variant of the commit procedure to compute a candidate root hash.
It then checks if the resulting root hash equals the expected commitment value.
The key difference is that the verifier does not have access to the entire list of committed elements; the verifier has just some subset of elements (one or more), along with the positions at which these elements appear.
Thus, the verifier needs to know the siblings (the left
and right
values used
in the reduce()
function above) to compute its candidate root hash.
The list of these siblings constitutes the proof; thus, a proof is a list of zero or more \( 32 \)-byte hash values.
Algorand defines a deterministic order in which the verification procedure expects to find siblings in this proof, so no additional information is required as part of the proof (in particular, no information about which part of the Merkle Tree each proof element corresponds to).
Verifying a Proof
The following pseudocode defines the logic for verifying a proof (a list of \( 32 \)-byte hashes) for one or more elements, specified as a list of position-element pairs, sorted by position in the array, against a root commitment.
The function verify
returns True
if proof
is a valid proof for all elements
in elems
being present at their positions in the array committed to by root
.
The function implements a variant of reduce()
for a sparse array, rather than
a fully populated one.
def verify(elems, proof, root):
if len(elems) == 0:
return len(proof) == 0
if len(elems) == 1 and len(proof) == 0:
return elems[0].pos == 0 && elems[0].hash == root
i = 0
nextelems = []
while i < len(elems):
pos = elems[i].pos
poshash = elems[i].hash
sibling = pos ^ 1
if i+1 < len(elems) and elems[i+1].pos == sibling:
sibhash = elems[i+1].hash
i += 2
else:
sibhash = proof[0]
proof = proof[1:]
i += 1
if pos&1 == 0:
h = H("MA" + poshash + sibhash)
else:
h = H("MA" + sibhash + poshash)
nextelems.append({"pos": pos/2, "hash": h})
return verify(nextelems, proof, root)
The pseudocode might raise an exception due to accessing the proof past the end; this is equivalent to returning
False
.
Vector commitment
Algorand uses Vector Commitments, which allows for concisely committing to an ordered (indexed) vector of data entries, based on Merkle trees.
$$ \newcommand \Proven {\mathrm{Proven}} \newcommand \Signed {\mathrm{Signed}} \newcommand \Leaf {\mathrm{Leaf}} \newcommand \Hash {\mathrm{Hash}} \newcommand \W {\mathrm{Weight}} \newcommand \KLT {\mathrm{KeyLifeTime}} \newcommand \SP {\mathrm{StateProof}} \newcommand \pk {\mathrm{pk}} \newcommand \StateProofPK {\SP_\pk} \newcommand \SerializedMerkleSignature {\mathrm{SerializedMerkleSignature}} \newcommand \Hin {\mathrm{Hin}} \newcommand \Ver {\mathrm{Version}} \newcommand \Cmt {\mathrm{Commitment}} \newcommand \Participant {\mathrm{Participant}} \newcommand \Sig {\mathrm{Signature}} \newcommand \Msg {\mathrm{Message}} \newcommand \SHAKE {\mathrm{SHAKE256}} \newcommand \IntToInd {\mathrm{IntToInd}} \newcommand \Coin {\mathrm{coin}} \newcommand \Reveals {\mathit{Reveals}} \newcommand \NRev {\mathit{Num}\Reveals} \newcommand \MaxRev {\mathit{Max}\Reveals} \newcommand \target {\mathrm{target}} \newcommand \ceil {\mathrm{ceil}} \newcommand \floor {\mathrm{floor}} $$
State Proofs
State Proofs (a.k.a. Compact Certificates) allow external parties to efficiently validate Algorand blocks.
The technical report provides the overall approach of State Proofs; this section describes the specific details of how State Proofs are realized in Algorand.
As a brief summary of the technical report, State Proofs operate in three steps:
-
The first step is to commit to a set of participants eligible to produce signatures, along with a weight for each participant. In Algorand’s case, these end up being the online accounts, and the weights are the account μALGO balances.
-
The second step is for each participant to sign the same message, and broadcast this signature to others. In Algorand’s case, the message would contain a commitment on blocks in a specific period.
-
The third step is for Relay Nodes to collect these signatures from a significant fraction of participants (by weight) and generate a State Proof. Given enough signatures, a Relay Node can form a State Proof, which effectively consists of a small number of signatures, pseudo-randomly chosen out of all the signatures.
The resulting State Proof proves that at least some \( \Proven\W \) of participants have signed the message. The actual weight of all participants who have signed the message must be greater than \( \Proven\W \).
Participant Commitment
The State Proof scheme requires a commitment to a dense array of participants, in some well-defined order. Algorand uses Vector Commitment to guarantee this property.
Leaf hashing is done in the following manner:
$$ \Leaf = \Hash(\texttt{spp} || \W || \KLT || \StateProofPK), \\ $$
for each online participant.
Where:
-
\( \W \) is a 64-bit, little-endian integer representing the participant’s balance in μALGO,
-
\( \KLT \) is a 64-bit, little-endian constant integer with value of \( 256 \),
-
\( \StateProofPK \) is a 512-bit string representing the participant’s Merkle signature scheme commitment.
Signature Format
Similarly to the participant commitment, the State Proof scheme requires a commitment to a signature array.
Leaf hashing is done in the following manner:
$$ \Leaf = \Hash(\texttt{sps} || L || \SerializedMerkleSignature), \\ $$
for each online participant.
Where:
-
\( L \) is a 64-bit, little-endian integer representing the participant’s \( L \) value as described in the technical report.
-
\( \SerializedMerkleSignature \) representing a Merkle Signature of the participant merkle signature binary representation
When a signature is missing in the signature array, i.e., the prover didn’t receive
a signature for this slot, the slot would be decoded as an empty string. As a result,
the vector commitment leaf of this slot would be the hash value of the constant
domain separator MB
(the bottom leaf).
Choice of Revealed Signatures
As described in the technical report section IV.A, a State Proof contains a pseudorandomly chosen set of signatures. The choice is made using a coin.
In Algorand’s implementation, the coin derivation is made in the following manner:
$$ \Hin = (\texttt{spc} || \Ver || \\ \Participant\Cmt || \ln(\Proven\W) || \\ \Sig\Cmt || \Signed\W || \\ \SP\Msg\Hash) $$
Where:
-
\( \Ver \) is an 8-bit constant with value of \( 0 \),
-
\( \Participant\Cmt \) is a 512-bit string representing the vector commitment root on the participant array’
-
\( \ln(\Proven\W) \) an 8-bit string representing the natural logarithm value of \( \Proven\W \) with 16 bits of precision, as described in SNARK-Friendly Weight Threshold Verification,
-
\( \Sig\Cmt \) is a 512-bit string representing the vector commitment root on the signature array,
-
\( \Signed\W \) is a 64-bit, little-endian integer representing the State Proof signed weight,
-
\( \SP\Msg\Hash \) is a 256-bit string representing the message that the State Proof would verify (it would be the hash result of the State Proof message).
For short, we refer below to the revealed signatures simply as “reveals”.
We compute:
$$ R = \SHAKE(\Hin) $$
Then, for every reveal, we:
-
Extract a 64-bit string from \( R \),
-
Use rejection sampling and extract an additional 64-bit string from \( R \) if needed.
This would guarantee a uniform random coin in \( [0, \Signed\W) \).
State Proof Format
A State Proof consists of seven fields:
-
The Vector Commitment root to the array of signatures, under the msgpack key
c
. -
The total weight of all signers whose signatures appear in the array of signatures, under the msgpack key
w
. -
The Vector commitment proof for the signatures revealed above, under the msgpack key
S
. -
The Vector commitment proof for the participants revealed above, under the msgpack key
P
. -
The FALCON signature salt version, under the msgpack key
v
, is the expected salt version of every signature in the state proof. -
The set of revealed signatures, chosen as described in section IV.A of the technical report, under the msgpack key
r
. This set is stored as a msgpack map. The key of the map is the position in the array of the participant whose signature is being revealed. The value in the map is a msgpack struct with the following fields: -
A sequence of positions, under the msgpack key
pr
. The sequence defines the order of the participant whose signature is being revealed. Example:
$$ \mathit{PositionsToReveal} = [\IntToInd(\Coin_0), \ldots , \IntToInd(\Coin_{\NRev-1})] $$
Where \( \IntToInd \) and \( \NRev \) are defined in the technical report, section IV.
Note that, although the State Proof contains a commitment to the signatures, it does not contain a commitment to the participants.
The set of participants must already be known to verify a State Proof. In practice, a commitment to the participants is stored in the Block Header of an earlier block, and in the State Proof message proven by the previous State Proof.
State Proof Validity
A State Proof is valid for the message hash, with respect to a commitment to the array of participants, if:
-
The depth of the vector commitment for the signature and the participant information should be less than or equal to \( 20 \),
-
All FALCON signatures should have the same salt version, and it should be equal to the salt version specified in the State Proof,
-
The number of reveals in the State Proof should be less than or equal to \( 640 \),
-
Using the trusted \( \Proven\W \) (supplied by the verifier), the State Proof should pass the SNARK-Friendly Weight Threshold Verification check.
-
All of the participant and signature information that appears in the reveals is validated by the Vector Commitment proofs for the participants (against the commitment to participants, supplied by the verifier) and signatures (against the commitment in the state proof itself), respectively.
-
All the signatures are valid signatures for the message hash.
-
For every \( i \in \{0, \ldots , \NRev-1\} \) there is a reveal in map denoted by \( r_{i} \), where \( r_{i} \gets T[\mathit{PositionsToReveal}[i]] \) and \( r_{i}.Sig.L \leq \Coin_i < r_{i}.Sig.L + r_{i}.Part.\W \).
\( T \) is defined in the technical report, section IV.
Setting Security Strength
-
\( \target_C \): “classical” security strength. This is set to \( k + q \) (where \( k + q \) are defined in section IV.A of the technical report). The goal is to have \( <= 1/2^k \) probability of breaking the State Proof by an attacker that makes up to \( 2^q \) hash evaluations/queries. We use \( \target_C = 192 \), which corresponds to, for example, \( (k = 128, q = 64) \), or \( (k = 96, q = 96) \).
-
\( \target_{PQ} \): “post-quantum” security strength. This is set to \( k + 2q \), because at a cost of about \( 2^q \), a quantum attacker can search among up to \( 2^{2q} \) hash evaluations (this is a highly attacker-favorable estimate). We use \( \target_{PQ} = 256 \), which corresponds to, for example, \( (k = 128, q = 64) \), or \( (k = 96, q = 80) \).
Bounding The Number of Reveals
In order for the SNARK prover for State Proofs to be efficient enough, we must impose an upper-bound \( \MaxRev_C \) on the number of “reveals” the State Proof can contain, while still reaching its target security strength \( \target_C = 192 \). Concretely, we currently wish to set \( \MaxRev_C = 480 \).
Similarly, the quantum-secure verifier aims for a larger security strength of \( \target_{PQ} = 256 \), and we can also impose an upper-bound \( \MaxRev_{PQ} \) on the number of reveals it can handle. (Recall that a smaller number of reveals means that \( \frac{\Signed\W}{\Proven\W} \) must be larger to reach particular security strength, so we cannot set \( \MaxRev_C \) or \( \MaxRev_{PQ} \) too low.)
To generate a SNARK proof, we need to be able to “downgrade” a valid State Proof with \( \target_{PQ} \) strength into one with merely \( \target_C \) strength, by truncating some of the reveals to stay within the bounds.
First, let us prove that a valid State Proof with (( NRev_{PQ} \) number of reveals that satisfies Equation (5) in SNARK-Friendly Weight Threshold Verification for a given \( \target_C \) can be “downgrade” to have:
$$ \NRev_C = \ceil\left( \NRev_{PQ} \times \frac{\target_{C}}{\target_{PQ}} \right) $$
We remark that values \( d, b, T, Y, D \) (in SNARK-Friendly Weight Threshold Verification) only depend on \( \Signed\W \), but not the number of reveals nor the target.
Hence, we just need to prove that:
$$ \NRev_C >= \target_C \times T \times \frac{Y}{D} $$
Which implies it is sufficient to prove:
$$ \NRev_{PQ} \times \frac{\target_C}{\target_{PQ}} >= \target_C \times T \times \frac{Y}{D} $$
Since \( \target_C > 0 \) and \( \target_{PQ} > 0 \), we just need to prove that:
$$ \NRev_{PQ} >= \target_{PQ} \times T \times \frac{Y}{D}. $$
This last inequality holds since the State Proof satisfies Equation (5).
For a given \( \MaxRev_C \) and the desired security strengths, we need to calculate a suitable \( \target_{PQ} \) bound so that the following property holds:
Since the “downgraded“ State Proof has:
$$ \NRev_C = \ceil\left( \NRev_{PQ} \times \frac{\target_C}{\target_{PQ}} \right), $$
And \( \NRev_{PQ} <= \MaxRev_{PQ} \), and \( \NRev_C <= \MaxRev_C \) we get:
$$ \MaxRev_C <= \ceil\left( \MaxRev_{PQ} \times \frac{\target_C}{\target_{PQ}} \right) $$
And we can set
$$ \MaxRev_C <= \ceil\left( \MaxRev_{PQ} \times \frac{\target_C}{\target_{PQ}} \right) $$
Since reveals do not bottleneck the quantum-secure verifier, we can take:
$$ \MaxRev_{PQ} <= \floor\left( \MaxRev_C \times \frac{\target_{PQ}}{\target_C} \right) $$
To be an equality, i.e., \( \MaxRev_{PQ} = \floor(\ldots) \).
Therefore, we must set \( \MaxRev_{PQ} = 640 \).
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
libp2p
networking 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 |
"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 theAction
calls for aDisconnect
as 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} }
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 |
ProposalPayloadTagMaxSize 1 | \( 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 \):
-
AV
for Agreement Vote: -
MI
for Message of Interest: -
MS
for Message Digest Skip: -
NP
for Net Priority Response: -
NI
for Network ID Verification: -
PP
for Proposal Payload: -
SP
for State Proof Signature: -
TS
for Topic Message Response: -
TX
for Transaction: -
UE
for Unicast Catchup Request: -
VB
for 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
libp2p
specifications 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
libp2p
PeerID 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
PeerID
are cast-able tostr
type and are used as plain strings in packages where importinglibp2p
packages may not be needed.
⚙️ IMPLEMENTATION
A
GetPrivKey
function 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 valuesP2PPersistPeerID
andP2PPrivateKeyLocation
(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
libp2p
specifications 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 tolocalhost
on 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 thePeerID
equal toQmewz5ZHN1AAGTarRbMupNPbZRfg3p5jUGoJ3JYEatJVVk
.
/ip4/192.255.2.8/tcp/8180/ws
, for a multiaddress composed only of a network address192.255.2.8:8180
indicating 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
PublicAddress
to 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
identityChallengeSigned
message is added to HTTP request headers, containing:- A 32-byte random challenge,
- The requester’s \( \Identity(\pk) \),
- The
PublicAddress
of 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
identityChallengeResponseSigned
message 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
identityChallengeResponse
is valid, the requester sends aNetIDVerificationTag
message 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
Phonebook
bootstrapping, the DNS ismainnet.algorand.network
and 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 (relay
orarchival
), -
persistent
, set totrue
for 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
Phonebook
reference implementation,PeerStore
reference implementation,addressData
reference 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 theexcept
parameter. Ifwait
is 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. Theexcept
parameter 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 toreplace
the 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
OnNetworkAdvace
in 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
GenesisID
identifying which network it is a part of (see Ledger specifications), -
A
Phonebook
to bootstrap the peer discovery, -
A
PeerContainer
data structure to manage and iterate over peer connections (inbound and outbound), -
A
Broadcaster
to send messages to the network, -
A
MessageHandler
structure to route messages into the correct handlers, -
An
IdentityChallengeScheme
to execute the peer identity challenge, -
An
IdentityTracker
, for connection deduplication (e.g., to avoid self-gossip), -
Similarly to the identity challenge, a
priorityChallengeScheme
andpriorityTracker
, -
A flag indicating if the node wants to receive
TX
tagged 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 thepeersheap
structure. -
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 Network identity challenge).
-
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
GenesisID
identifying which network it is a part of (see Ledger specifications), -
A
PeerStore
container to keep peer data and expose relevant connection metadata (see Peer management section), -
A
Broadcaster
to send messages to the network, -
A
topicTags
mapping 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
MessageHandler
to route \( \WS \) messages to the appropriate message handler, - \( \Peer \) connectivity monitoring utilities,
- A mapping of
PeerID
to \( \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
TX
tagged messages (transactions) or not, -
A
capabilitiesDiscovery
structure 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
GossipSub
protocol (/meshsub/1.1.0
). All other messages are forwarded over a stream/algorand-ws/1.0.0
that 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 thelibp2p
specifications.
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
pubsub
protocol, refer to thelibp2p
specifications.
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
algotx01
topic, -
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)
arelibp2p
internals that should not be exposed.
⚙️ IMPLEMENTATION
Pubsub protocol message validation
libp2p
extrenal 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 thealgotx01
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 refer to the fork.
libp2p
Algorand uses go-libp2p library (building
from the latest
release), to implement the Peer-to-Peer network.
See
libp2p
specifications 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
hex
format, -
Algorand addresses decoded according to specification,
-
Transaction notes decoded in
UTF-8
.
Examples of
msgpack
network packets. For further details about Algorand canonical encoding, refer to themsgpack
normative 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.
Algod Node Overview
This part provides additional information over the Algorand Node (go-algorand
)
reference implementation and its configurations.
AI DeepWiki (Experimental)
⚠️ This is experimental AI-generated documentation of the
go-algorand
reference implementation. Readers may use this documentation to complement the contents of this non-normative part, to deepen understanding of the Algorand Node reference implementation.
DeepWiki of the go-algorand
repository:
https://deepwiki.com/algorand/go-algorand
The embedded chatbot, with deep-research capabilities over the
go-algorand
, can answer to specific questions the readers may have over the codebase.
Non-Normative
This chapter provides a non-normative overview of the algod
node daemon components.
Most of the constructs covered span across multiple subsystems described in the rest of the Algorand Specifications.
In particular, this chapter focuses on the following algod
node operations:
-
Initialization,
-
Configuration,
-
Shutdown.
And also provides an overview of algod
REST APIs.
Initialization
The algod
node initialization refers to the process by which the Algorand node
daemon starts up and activates all its internal services.
This process is guided by a configuration file.
The main configuration switch, EnableFollowMode
, determines the node’s operational
mode:
-
If set to
false
, the node runs as Full Node, -
If set to
true
, the node runs as Follower Node.
Once the node is initialized based on the configuration, the system starts up all services, allowing them to begin functioning.
The diagram below illustrates this initialization sequence, highlighting the key
role played by the Algorand node daemon algod
.
sequenceDiagram participant algod participant Server participant Node algod ->> algod: Prepare and load<br/>Node configuration algod ->> Server: Initialize the Server activate Server Server->>+Node: Node Initialization Node->>-Server: returns Node algod ->> Server: Start the Server Server ->> Node: Start the Node Note over algod, Node: Loops until Quit or Error deactivate Server
$$ \newcommand \PSfunction {\textbf{function }} \newcommand \PSreturn {\textbf{return }} \newcommand \PSendfunction {\textbf{end function}} \newcommand \PSswitch {\textbf{switch }} \newcommand \PScase {\textbf{case }} \newcommand \PSdefault {\textbf{default}} \newcommand \PSendswitch {\textbf{end switch}} \newcommand \PSfor {\textbf{for }} \newcommand \PSendfor {\textbf{end for}} \newcommand \PSwhile {\textbf{while }} \newcommand \PSendwhile {\textbf{end while}} \newcommand \PSdo {\textbf{ do}} \newcommand \PSif {\textbf{if }} \newcommand \PSelseif {\textbf{else if }} \newcommand \PSelse {\textbf{else}} \newcommand \PSthen {\textbf{ then}} \newcommand \PSendif {\textbf{end if}} \newcommand \PSnot {\textbf{not }} \newcommand \PScomment {\qquad \small \textsf} $$
$$ \newcommand \RootDir {\mathrm{rootDir}} \newcommand \Config {\mathrm{nodeConfig}} \newcommand \Phonebook {\mathrm{phonebookAddrs}} \newcommand \Genesis {\mathrm{genesisBlock}} \newcommand \Node {\mathrm{node}} \newcommand \FullNode {\mathrm{FullNode}} \newcommand \Logger {\mathrm{Logger}} \newcommand \Hash {\mathrm{Hash}} \newcommand \Network {\mathrm{Network}} \newcommand \WS {\mathrm{WS}} \newcommand \PtoP {\mathrm{P2P}} \newcommand \HYB {\mathrm{HYB}} \newcommand \Peer {\mathrm{Peer}} \newcommand \CryptoPool {\mathrm{CryptoPool}} \newcommand \Registry {\mathrm{Registry}} \newcommand \Ledger {\mathrm{Ledger}} \newcommand \Block {\mathrm{Block}} \newcommand \Agreement {\mathrm{Agreement}} \newcommand \AccountManager {\mathrm{AccountManager}} \newcommand \StateProof {\mathrm{StateProof}} \newcommand \Heartbeat {\mathrm{Heartbeat}} \newcommand \TP {\mathrm{TxPool}} \newcommand \Catchup {\mathrm{Catchup}} \newcommand \Service {\mathrm{Service}} \newcommand \Create {\mathrm{Create}} $$
Initialize Full Node
The algod
Full Node is responsible for:
-
Validating and propagating transactions and blocks,
-
Maintaining the blockchain state, either fully (Archival) or partially (Non Archival), as defined in the Ledger,
-
Participating in the consensus protocol, as outlined in the ABFT specification.
Initialization
The pseudocode below outlines the main steps involved when initializing an algod
Full Node:
\( \textbf{Algorithm 1} \text{: Full Node Initialization} \)
$$ \begin{aligned} &\text{1: } \PSfunction \FullNode.\mathrm{Start}(\RootDir, \Config, \Phonebook, \Genesis) \\ &\text{2: } \quad \Node \gets {\textbf{new }} \FullNode \\ &\text{3: } \quad \Node.\mathrm{log} \gets \Logger(\Config) \\ &\text{4: } \quad \Node.\Genesis.\mathrm{ID} \gets \Genesis.\mathrm{ID}() \\ &\text{5: } \quad \Node.\Genesis.\mathrm{ID} \gets \Genesis.\Hash() \\ &\text{6: } \PScomment{Network Initialization} \\ &\text{7: } \quad \PSif \Config.\mathrm{EnableHybridMode} \PSthen \\ &\text{8: } \quad \quad \Node.\Network \gets \Create\HYB\Network(\Phonebook) \\ &\text{9: } \quad \PSelseif \Config.\mathrm{EnableP2P} \PSthen \\ &\text{10:} \quad \quad \Node.\Network \gets \Create\PtoP\Network(\Phonebook) \\ &\text{11:} \quad \PSelse \\ &\text{12:} \quad \quad \Node.\Network \gets \Create\WS\Network(\Phonebook) \\ &\text{13:} \quad \PSendif \\ &\text{14:} \PScomment{Crypto Resource Pools Initialization} \\ &\text{15:} \quad \Node.\CryptoPool \gets \Create\mathrm{ExecutionPool}() \\ &\text{16:} \quad \Node.\CryptoPool.\mathrm{lowPriority} \gets \Create\mathrm{BacklogPool()} \\ &\text{17:} \quad \Node.\CryptoPool.\mathrm{highPriority} \gets \Create\mathrm{BacklogPool()} \\ &\text{18:} \PScomment{Ledger Initialization} \\ &\text{19:} \quad \mathrm{ledgerPaths} \gets \mathrm{ResolvePaths}(\RootDir, \Config) \\ &\text{20:} \quad \Node.\Ledger \gets \mathrm{LoadLedger}(\mathrm{ledgerPaths}, \Genesis) \\ &\text{21:} \PScomment{Account Management} \\ &\text{22:} \quad \Registry \gets \mathrm{ParticipationRegistry}() \\ &\text{23:} \quad \Node.\AccountManager \gets \Create\AccountManager(\Registry) \\ &\text{24:} \quad \mathrm{LoadParticipationKeys}(\Node) \\ &\text{25:} \PScomment{Transaction Pool Initialization} \\ &\text{26:} \quad \Node.\TP \gets \Create\TP(\Node.\Ledger) \\ &\text{27:} \quad \mathrm{RegisterBlockListeners}(\Node.\TP) \\ &\text{28:} \PScomment{Services Initialization} \\ &\text{29:} \quad \Node.\Block\Service \gets \Create\Block\Service() \\ &\text{30:} \quad \Node.\Ledger\Service \gets \Create\Ledger\Service() \\ &\text{31:} \quad \Node.\TP\Service \gets \Create\TP\mathrm{Syncer}() \\ &\text{32:} \quad \Node.\Agreement\Service \gets \Create\Agreement\Service() \\ &\text{33:} \quad \Node.\Catchup\Service \gets \Create\Catchup\Service() \\ &\text{34:} \quad \Node.\StateProof\Service \gets \Create\StateProof\Service() \\ &\text{35:} \quad \Node.\Heartbeat\Service \gets \Create\Heartbeat\Service() \\ &\text{36:} \quad \PSreturn \Node \\ &\text{37: } \PSendfunction \end{aligned} $$
⚙️ IMPLEMENTATION
Full Node initialization reference implementation.
Network
The network layer is set up depending on the configuration:
-
\( \WS \) (WebSocket) for the Relay Network setups,
-
\( \PtoP \) for Peer-to-Peer Network, or
-
\( \HYB \) for the \( \PtoP \)-\( \WS \) Hybrid Network, for nodes operating in a unified network layer.
The network layer manages:
-
\( \Peer \) discovery using phonebook addresses,
-
Connection pools, and
-
Message routing.
For further details on the network layer, refer to Algorand Network non-normative specification.
Cryptography Resource Pools
The node initializes worker pools for handling cryptographic tasks with priority queues:
-
\( \CryptoPool \) handles general-purpose cryptographic operations,
-
\( \CryptoPool.\mathrm{lowPriority} \) and \( \CryptoPool.\mathrm{highPriority} \) handle transaction verification, grouped by priority.
For further details on the transaction validation, see the Ledger specification.
For further details on the cryptographic primitives and algorithms, see the Crypto specification.
Ledger
The node loads its local view of the blockchain state from disk (accounts, blocks, protocol data, etc.), based on the specified genesis configuration.
If the Ledger is empty, it initializes the required structures.
It also validates:
-
The genesis configuration, and
-
Ledger integrity.
For further details on the Ledger entities, see the Ledger specification.
Account Management (Agreement)
The node prepares for consensus participation and registered account tracking by:
-
Creating and managing a registry of participation keys,
-
Loading any pre-existing participation keys from disk,
-
Setting up participation key rotation.
For further details on the Algorand keys, see the Keys specification.
For further details on the key registration transactions, see the Ledger specification.
Transaction Pool
The node creates the Transaction Pool (\( \TP \)), which:
-
Accepts and validates new transactions,
-
Maintains the queue of uncommitted transactions,
-
Manages transaction synchronization across the network,
-
Creates Block Listeners reacting to new blocks to prune already committed transactions and update pending transactions.
For further details on the Transaction Pool, see the Ledger non-normative specification.
Services
Finally, the node launches all essential background services:
-
Catchup Service,
-
Agreement Service,
-
Transaction Pool Syncer Service,
-
Block Service,
-
Ledger Service,
-
Transaction Handler,
-
State Proof Worker.
-
Heartbeat Service.
$$ \newcommand \PSfunction {\textbf{function }} \newcommand \PSreturn {\textbf{return }} \newcommand \PSendfunction {\textbf{end function}} \newcommand \PSswitch {\textbf{switch }} \newcommand \PScase {\textbf{case }} \newcommand \PSdefault {\textbf{default}} \newcommand \PSendswitch {\textbf{end switch}} \newcommand \PSfor {\textbf{for }} \newcommand \PSendfor {\textbf{end for}} \newcommand \PSwhile {\textbf{while }} \newcommand \PSendwhile {\textbf{end while}} \newcommand \PSdo {\textbf{ do}} \newcommand \PSif {\textbf{if }} \newcommand \PSelseif {\textbf{else if }} \newcommand \PSelse {\textbf{else}} \newcommand \PSthen {\textbf{ then}} \newcommand \PSendif {\textbf{end if}} \newcommand \PSnot {\textbf{not }} \newcommand \PScomment {\qquad \small \textsf} $$
$$ \newcommand \disable {\textbf{disable }} \newcommand \RootDir {\mathrm{rootDir}} \newcommand \Config {\mathrm{nodeConfig}} \newcommand \Phonebook {\mathrm{phonebookAddrs}} \newcommand \Genesis {\mathrm{genesisBlock}} \newcommand \Node {\mathrm{node}} \newcommand \FollowerNode {\mathrm{FollowerNode}} \newcommand \Logger {\mathrm{Logger}} \newcommand \Hash {\mathrm{Hash}} \newcommand \Network {\mathrm{Network}} \newcommand \WS {\mathrm{WS}} \newcommand \CryptoPool {\mathrm{CryptoPool}} \newcommand \Registry {\mathrm{Registry}} \newcommand \Ledger {\mathrm{Ledger}} \newcommand \Block {\mathrm{Block}} \newcommand \Agreement {\mathrm{Agreement}} \newcommand \AccountManager {\mathrm{AccountManager}} \newcommand \StateProof {\mathrm{StateProof}} \newcommand \Heartbeat {\mathrm{Heartbeat}} \newcommand \TP {\mathrm{TxPool}} \newcommand \Catchup {\mathrm{Catchup}} \newcommand \Auth {\mathrm{Authenticator}} \newcommand \Service {\mathrm{Service}} \newcommand \Create {\mathrm{Create}} $$
Initialize Follower Node
Unlike a Full Node, a Follower Node is a lightweight type of Algorand node designed primarily for data access, not participation in consensus or transaction validation.
Key characteristics:
-
It may not participate in the consensus protocol.
-
It may not maintain a Transaction Pool.
-
Its main function is to stay synchronized with the blockchain, keeping an up-to-date copy of Ledger and block data.
-
It is designed to pause and wait for connected applications (e.g., indexers, explorers, or analytics tools) to finish processing the data it provides.
These nodes are ideal for read-heavy infrastructure, such as block explorers or services that require continuous blockchain observation and access to its data but don’t contribute to block validation or propagation.
Initialization
The pseudocode below outlines the main steps involved when initializing an algod
Follower Node:
\( \textbf{Algorithm 2} \text{: Follower Node Initialization} \)
$$ \begin{aligned} &\text{1: } \PSfunction \FollowerNode.\mathrm{Start}(\RootDir, \Config, \Phonebook, \Genesis) \\ &\text{2: } \quad \Node \gets {\textbf{new }} \FollowerNode \\ &\text{3: } \quad \Node.\mathrm{log} \gets \Logger(\Config) \\ &\text{4: } \quad \Node.\Genesis.\mathrm{ID} \gets \Genesis.\mathrm{ID}() \\ &\text{5: } \quad \Node.\Genesis.\mathrm{ID} \gets \Genesis.\Hash() \\ &\text{6: } \PScomment{Network Initialization - WebSocket Only} \\ &\text{7: } \quad \Node.\Network \gets \Create\WS\Network(\Phonebook) \\ &\text{8: } \quad \Node.\Network.\mathrm{DeregisterMessageInterest}(\texttt{AgreementVoteTag}, \texttt{ProposalPayloadTag}, \texttt{VoteBundleTag}) \\ &\text{9: } \PScomment{Crypto Resource Pools Initialization - Minimal} \\ &\text{10:} \quad \Node.\CryptoPool \gets \Create\mathrm{ExecutionPool}() \\ &\text{11:} \quad \Node.\CryptoPool.\mathrm{lowPriority} \gets \Create\mathrm{BacklogPool()} \\ &\text{12:} \PScomment{Ledger Initialization} \\ &\text{13:} \quad \mathrm{ledgerPaths} \gets \mathrm{ResolvePaths}(\RootDir, \Config) \\ &\text{14:} \quad \Node.\Ledger \gets \mathrm{LoadLedger}(\mathrm{ledgerPaths}, \Genesis) \\ &\text{15:} \PScomment{Service Components - Limited} \\ &\text{16:} \quad \Node.\Block\Service \gets \Create\Block\Service() \\ &\text{17:} \quad \Node.\Catchup\Service \gets \Create\Catchup\Service() \\ &\text{18:} \quad \Node.\Catchup\Block\Auth \gets \Create\Block\Auth() \\ &\text{19:} \PScomment{Transaction Handling - Simulation Only} \\ &\text{20:} \quad \disable \mathrm{TxBroadcast}() \\ &\text{21:} \quad \disable \TP() \\ &\text{22:} \PScomment{Agreement - All Disabled} \\ &\text{23:} \quad \disable \AccountManager() \\ &\text{24:} \quad \disable \Agreement() \\ &\text{25:} \quad \disable \StateProof() \\ &\text{25:} \quad \disable \Heartbeat() \\ &\text{26:} \quad \mathrm{SetSyncRound}(\Node.\Ledger.\mathrm{LatestTrackerCommittedRound}() + 1) \\ &\text{27:} \quad \PSif \mathrm{InCatchpointCatchupState}() \PSthen \\ &\text{28:} \quad \quad \mathrm{InitializeCatchpointCatchup}() \\ &\text{29:} \quad \PSendif \\ &\text{30:} \quad \PSreturn \Node \\ &\text{31:} \PSendfunction \end{aligned} $$
⚙️ IMPLEMENTATION
Follower Node initialization reference implementation.
Compared to a Full Node, a Follower Node is significantly lighter in functionality and system demands.
Below are the main differences.
Networking
Unlike Full Nodes, which support multiple network layer options (such as standard Peer-to-Peer and Hybrid Networks), Follower Nodes operate only in \( \WS \) mode. This simplifies peer communication but limits participation to passive synchronization.
Cryptography Resource Pools
A Follower Node initializes only one low-priority cryptographic verification pool. This minimal setup is enough for passive data validation without handling transaction traffic or cryptographic voting.
Ledger
Follower Nodes expose a specialized API to retrieve Ledger cache data, used by external services to index, analyze, or react to blockchain events without interfering with node operation.
For more on this functionality, refer to the Algorand External Systems Overview.
Account Management (Agreement)
The Follower Node does not broadcast transactions and does not participate in consensus. It is read-only and does not propose, vote on, or validate blocks.
Services
Only two core services are active:
-
Catchup Service: Keeps the node synchronized with the latest blockchain state.
-
Block Service: Allows the node to retrieve and serve block data to external consumers. It allows external services (e.g., indexers or applications) to query and react to new blocks as they arrive.
Synchronization and Catchup
Despite its limited role, a Follower Node still maintains full Catchup capabilities, allowing it to fast-forward to the latest state using checkpoint data.
Wait-for-Ingestion
This operative mode allows the node to pause block progression until external data consumers (such as indexers or analytics tools) have finished ingesting the current block. This ensures data consistency across services that rely on up-to-date chain data.
Shutdown
Node shutdown refers to the graceful termination of an algod
node.
During this process, the node ensures that all active services are properly stopped. For example, if a synchronization operation is currently in progress, it is cleanly terminated to avoid leaving the node in an inconsistent state.
Because algod
nodes operate in a multithreaded environment, shutting down the
node requires acquiring a full lock on the node instance. This locking ensures that
no other threads can access or modify shared state during shutdown, preventing data
corruption both within the shutdown routine and across the broader system.
$$ \newcommand \PSfunction {\textbf{function }} \newcommand \PSreturn {\textbf{return }} \newcommand \PSendfunction {\textbf{end function}} \newcommand \PSswitch {\textbf{switch }} \newcommand \PScase {\textbf{case }} \newcommand \PSdefault {\textbf{default}} \newcommand \PSendswitch {\textbf{end switch}} \newcommand \PSfor {\textbf{for }} \newcommand \PSendfor {\textbf{end for}} \newcommand \PSwhile {\textbf{while }} \newcommand \PSendwhile {\textbf{end while}} \newcommand \PSdo {\textbf{ do}} \newcommand \PSif {\textbf{if }} \newcommand \PSelseif {\textbf{else if }} \newcommand \PSelse {\textbf{else}} \newcommand \PSthen {\textbf{ then}} \newcommand \PSendif {\textbf{end if}} \newcommand \PSnot {\textbf{not }} \newcommand \PScomment {\qquad \small \textsf} $$
$$ \newcommand \Node {\mathrm{node}} \newcommand \FullNode {\mathrm{FullNode}} \newcommand \Network {\mathrm{Network}} \newcommand \Stop {\mathrm{Stop}} \newcommand \Close {\mathrm{Close}} \newcommand \Config {\mathrm{nodeConfig}} \newcommand \Catchup {\mathrm{Catchup}} \newcommand \Service {\mathrm{Service}} \newcommand \Ledger {\mathrm{Ledger}} \newcommand \AccountManager {\mathrm{AccountManager}} \newcommand \Registry {\mathrm{Registry}} \newcommand \TP {\mathrm{TxPool}} \newcommand \Handler {\mathrm{Handler}} \newcommand \Handlers {\mathrm{Handlers}} \newcommand \Catchpoint {\mathrm{Catchpoint}} $$
Shutdown Full Node
The pseudocode below outlines how a Full Node is gracefully shut down.
This process ensures all services are stopped, garbage collection is performed, resources are released, and the internal state is properly cleaned up.
By following this structured approach, the node avoids corrupting data or leaving the Algorand network in an inconsistent state, which is critical for maintaining the integrity of the system.
\( \textbf{Algorithm 3} \text{: Full Node Shutdown} \)
$$ \begin{aligned} &\text{1: } \PSfunction \FullNode.\Stop() \\ &\text{2: } \PScomment{Network Cleanup} \\ &\text{3: } \quad \Node.\Network.\Stop\Handlers() \\ &\text{4: } \quad \Node.\Network.\Stop\mathrm{Validator}\Handlers() \\ &\text{5: } \quad \PSif \neg \Node.\Config.\Stop\Network \PSthen \\ &\text{6: } \quad \quad \Node.\Network.\Stop() \\ &\text{7: } \quad \PSendif \\ &\text{8: } \PScomment{Service Shutdown} \\ &\text{9: } \quad \PSif \exists \Node.\Catchpoint\Catchup\Service \PSthen \\ &\text{10:} \quad \quad \Node.\Catchpoint\Catchup\Service.\Stop() \\ &\text{11:} \quad \PSelse \\ &\text{12:} \PScomment{Full Node Services} \\ &\text{13:} \quad \quad \Node.\Stop\mathrm{AllServices}() \\ &\text{14:} \quad \PSendif \\ &\text{15:} \PScomment{Resource Cleanup} \\ &\text{16:} \quad \Node.\TP.\Stop() \\ &\text{17:} \PScomment{Final Cleanup} \\ &\text{18:} \quad \Node.\Ledger.\Close() \\ &\text{19:} \PScomment{Post-Shutdown Cleanup} \\ &\text{20:} \quad \mathrm{WaitMonitoringRoutines}() \\ &\text{21:} \quad \Node.\AccountManager.\Registry.\Close() \\ &\text{22:} \quad \PSfor \Handler \in \Node.\mathrm{Database}\Handlers \PSdo \\ &\text{23:} \quad \quad \Handler.\Close() \\ &\text{24:} \quad \PSendfor \\ &\text{25:} \PSendfunction \end{aligned} $$
⚙️ IMPLEMENTATION
Full node shutdown reference implementation.
This structured shutdown process helps ensure that Full Nodes exit cleanly, preserving correctness, avoiding data leaks, and minimizing node and network risk.
$$ \newcommand \PSfunction {\textbf{function }} \newcommand \PSreturn {\textbf{return }} \newcommand \PSendfunction {\textbf{end function}} \newcommand \PSswitch {\textbf{switch }} \newcommand \PScase {\textbf{case }} \newcommand \PSdefault {\textbf{default}} \newcommand \PSendswitch {\textbf{end switch}} \newcommand \PSfor {\textbf{for }} \newcommand \PSendfor {\textbf{end for}} \newcommand \PSwhile {\textbf{while }} \newcommand \PSendwhile {\textbf{end while}} \newcommand \PSdo {\textbf{ do}} \newcommand \PSif {\textbf{if }} \newcommand \PSelseif {\textbf{else if }} \newcommand \PSelse {\textbf{else}} \newcommand \PSthen {\textbf{ then}} \newcommand \PSendif {\textbf{end if}} \newcommand \PSnot {\textbf{not }} \newcommand \PScomment {\qquad \small \textsf} $$
$$ \newcommand \Node {\mathrm{node}} \newcommand \FollowerNode {\mathrm{FollowerlNode}} \newcommand \Stop {\mathrm{Stop}} \newcommand \Handlers {\mathrm{Handlers}} \newcommand \Network {\mathrm{Network}} \newcommand \Config {\mathrm{nodeConfig}} \newcommand \Catchup {\mathrm{Catchup}} \newcommand \Catchpoint {\mathrm{Catchpoint}} \newcommand \Service {\mathrm{Service}} \newcommand \Block {\mathrm{Block}} \newcommand \Auth {\mathrm{Authenticator}} \newcommand \CryptoPool {\mathrm{CryptoPool}} $$
Shutdown Follower Node
The following pseudocode describes how a node running in Follower Node mode is gracefully shutdown.
The shutdown procedure ensures that all services are stopped and resources are properly deallocated. This prevents data corruption and ensures the node stops in a stable and predictable state.
\( \textbf{Algorithm 4} \text{: Follower Node Shutdown} \)
$$ \begin{aligned} &\text{1: } \PSfunction \FollowerNode.\Stop() \\ &\text{2: } \PScomment{Network Cleanup} \\ &\text{3: } \quad \Node.\Network.\Stop\Handlers() \\ &\text{4: } \quad \PSif \neg \Node.\Config.\Stop\Network \PSthen \\ &\text{5: } \quad \quad \Node.\Network.\Stop() \\ &\text{6: } \quad \PSendif \\ &\text{7: } \PScomment{Service Shutdown} \\ &\text{8: } \quad \PSif \exists \Node.\Catchpoint\Catchup\Service \PSthen \\ &\text{9: } \quad \quad \Node.\Catchpoint\Catchup\Service.\Stop() \\ &\text{10:} \quad \PSelse \\ &\text{11:} \PScomment{Follower Services Only} \\ &\text{12:} \quad \quad \Node.\Catchup\Service.\Stop() \\ &\text{13:} \quad \quad \Node.\Block\Service.\Stop() \\ &\text{14:} \quad \PSendif \\ &\text{15:} \PScomment{Resource Cleanup} \\ &\text{16:} \quad \Node.\Catchup.\Block\Auth.\Stop() \\ &\text{17:} \quad \Node.\CryptoPool.\mathrm{lowPriority}.\Stop() \\ &\text{18:} \quad \Node.\CryptoPool.\Stop() \\ &\text{19:} \PSendfunction \end{aligned} $$
⚙️ IMPLEMENTATION
Follower node shutdown reference implementation.
Synchronization
Node synchronization is the process by which a node that has never connected to the Algorand Network, or has fallen behind the current blockchain state, synchronizes itself with the latest state of the Ledger.
Depending on the trade-off between node’s trust model and synchronization time, the synchronization process can be performed either:
-
From the Genesis Block: this synchronization process minimizes the trust model, reducing it just to the Genesis Block, but it is slower and requires a longer sync time.
-
From a Ledger Catchpoint: this light synchronization process, also known as Fast Catchup is based on a recent snapshot of the Algorand Ledger, so it has a shorter sync time, but it requires trust in the Catchpoint file provider.
Regular Synchronization
The degree of trust in the block validation process can be tuned with the
CatchupBlockValidateMode
configuration parameter.
Fast Catchup
The Fast Catchup synchronization is done by downloading a Catchpoint, consisting of a Catchpoint Label and Catchpoint File, from a trusted source that maintains an up-to-date snapshot of the Algorand Ledger and applying a batch of catchup data.
This snapshot corresponds to a specific Ledger instance, defined by its Genesis (i.e., the initial block — see the Algorand Ledger specification).
Catchpoints default generation interval is \( 10{,}000 \) blocks:
-
Catchpoint Labels can be generated even by Non-Archival Nodes by setting
CatchpointTracking: 1
: in the node configuration, -
Catchpoint Files can be generated by Archival Nodes by setting
CatchpointTracking: 2
in the node configuration.
Instead of replaying the entire transaction history from the Genesis Block, the Fast Catchup allows the node to skip ahead efficiently, becoming operational with minimal delay.
📎 EXAMPLE
To generate Catchpoints “in-house”, users need an Archival Node, and override the gossip lookup to force it to download the Catchpoint File from their local server on the gossip port (usually
4160
). Using thealgod
CLI:goal node catchup <catchpoint label> -d data -p ip_of_user_server:gossip_port
⚙️ IMPLEMENTATION
Catchup reference implementation.
Catchpoint Labels reference implementation.
Catchpoint Tracking Modes configuration.
The diagram below illustrates the Fast Catchup process and how it interacts with other node components, including the HTTP API and the internal Catchup Service.
sequenceDiagram participant Goal CLI participant HTTP API participant Node participant Catchup Service Goal CLI ->> HTTP API: POST <br/> /v2/catchup/ HTTP API ->> Node: Start Catchup Note over Node: Lock node activate Node Node ->> Catchup Service: Start Catchup activate Catchup Service loop Until finish, error or abort Catchup Service ->> Catchup Service: Handle next stage end Catchup Service ->> Node: returns deactivate Catchup Service Note over Node: Unlock node deactivate Node
This process ensures consistency and prevents interference from other operations while the node updates its internal state. Once catchup completes, the node can resume normal operations such as block processing and serving API requests.
$$ \newcommand \Peer {\mathrm{Peer}} $$
Pseudo Node
A Pseudo Node, referred to as Loopback
in the go-algorand
codebase, is an
internal component that simulates network communication by redirecting messages
(such as proposals and votes) back into the node itself.
Its purpose is to introduce streamline internal message handling without introducing code duplication or additional complexity.
By treating self-originated messages as if they were received from an external \( \Peer \), the node can process them using the same logic and flow as actual network messages. This ensures consistency within the state machine and leverages existing validation and routing infrastructure.
📎 EXAMPLE
The following example from the reference implementation illustrates how the Pseudo Node mechanism is used during proposal assembly. The
Loopback
component creates proposals internally. These are then turned into events that the system treats just like any other externally received proposals. This pattern is particularly useful in scenarios where the node itself is eligible to participate in consensus, allowing it to handle its own messages with the same rigor as peer-generated ones.
API
This section provides a non-normative overview of the Algorand Node REST APIs.
It is intended to help readers understand how these systems work and how they interface with the node’s internal components.
The typical lifecycle of an REST API call to the Algorand Node can be broken down into three phases:
-
Decoding phase – the incoming request and its data are parsed and transformed into a format suitable for internal use.
-
Handling phase – the request is processed by invoking the appropriate internal node components.
-
Response phase – a response object is assembled and, when applicable, includes encoded data to return to the caller.
Currently, API calls accept and return data in either JSON or MessagePack format.
A running Algorand Node includes two primary daemons with their REST API:
-
algod
– handles consensus, block processing, submits transactions, and main node operations. -
kmd
– securely manages key storage and wallet-related functionality.
OpenAPI Schema and Endpoint Code Generation
The daemon/algod/api
directory within go-algorand
contains an OpenAPI v2
specification.
Due to limited tool support for OpenAPI v3, both v2 and v3 specifications are maintained.
⚙️ IMPLEMENTATION
The endpoints’ code is automatically generated from this specification using
oapi-codegen
.
algod.oas2.json
specification file.
Algod Daemon
The main daemon running in an Algorand Node is called algod
.
It is responsible for providing access to core blockchain data and functionality. This includes transaction details, account balances, smart contract state, and other node-level information.
The algod
REST API is used to GET:
-
Node info (e.g.,
/genesis
,/health
,/ready
,/status
, etc.), -
Account info (e.g.,
/accounts/{address}
, etc.), -
App info (e.g.,
/applications/{application-id}
, etc.), -
ASA info (e.g.,
/assets/{asset-id}
, etc.), -
Blocks info (e.g.,
/blocks/{round}
, etc.), -
Other ancillary information about the Ledger and pending transactions in the Transaction Pool.
The algod
REST API is used to POST:
-
Synchronization commands (e.g.,
/ledger/sync/{round}
,/catchup/{catchpoint}
, etc.), -
Transactions (e.g.,
/transactions
,/transactions/simulate
, etc.), -
Consensus commands (e.g.,
/participation
,/participation/generate/{address}
, etc.), -
AVM Compiler commands (e.g.,
/teal/compile
,/teal/disassemble
, etc.), -
Other ancillary dev commands.
Tasks involving the management and the usage of private keys are handled by a separate daemon, called Key Management Daemon (
kmd
).
Authorization
Some algod
endpoints perform sensitive actions that require authorization.
The X-Algo-API-Token
header is used to authenticate requests to the private algod
endpoints.
By default, the API can be configured with two separate X-Algo-API-Token
values:
-
The
algod.token
is used to access publicalgod
endpoints. -
The
admin.algod.token
is used to access privatealgod
endpoints.
Endpoints
Each algod
endpoint path has two tags to separate endpoints into groups:
-
Tag 1:
public
(use thealgod.token
) or private (usealgod.admin.token
), -
Tag 2:
participating
,nonparticipating
,data
, orexperimental
.
⚙️ IMPLEMENTATION
Constants
The following constants define key limits and default behaviors for various algod
API endpoints.
These values serve as practical defaults for most use cases. Developers implementing their own tooling or integrations can use these as general guidelines.
Constant | Value | Description |
---|---|---|
MaxTealSourceBytes | 524,288 bytes | Maximum allowed size for TEAL source code in API requests. |
MaxTealDryrunBytes | 1,000,000 bytes | Maximum allowed size for dryrun simulation requests. |
MaxAssetResults | 1,000 | Maximum number of assets returned in a single call to /v2/accounts/{address}/assets . |
DefaultAssetResults | 1,000 | Default number of assets returned if no explicit limit is provided (up to MaxAssetResults ). |
WaitForBlockTimeout | 1e+10 nanoseconds (1 min.) | Timeout duration for the WaitForBlock endpoint when waiting for the next block to be generated. |
Appendix A - MsgPack Reference
In the go-algorand
reference implementation, all data structures that are exchanged
between components of the Algorand node are annotated to specify how they should
be encoded.
The Algorand Cryptographic specification defines the encoding rules for each type using a canonical variant of MessagePack.
Additional details about how encoding works in the Algorand Node can be found in
the go-algorand
MessagePack documentation.
Here are some important rules used by the codec:
-
If a struct includes the special field
\_struct struct{}
, then all codec options (such asomitempty
,omitemptyarray
, etc.) apply to each field within that struct. -
The first part of the codec tag string specifies the field name to use in the encoded output. If this name is omitted (e.g.,
codec:","
orcodec:",..."
), the field will be encoded using its original Go field name.
For a broader understanding of the
msgpack
format in Go, refer to this primer.
Appendix B - Node Configuration
This appendix provides a detailed description of algod
node configuration.
Configuration parameters are divided in functional groups, apart from the last deprecated parameters group (included for completeness).
⚙️ General & Versioning
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
Version | 36 | 0 | 36 |
Description:
Tracks the current version of the default configuration values, allowing migration from older versions to newer ones. It is crucial when modifying default values for existing parameters.
This field must be updated accordingly whenever a new version is introduced.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
RunHosted | false | 3 | 3 |
Description:
Configures whether to run the Algorand node in Hosted mode.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
EnableFollowMode | false | 27 | 27 |
Description:
Launches the node in Follower Mode, which significantly alters its behavior in terms of participation and API accessibility. When enabled, this mode disables the Agreement Service (meaning the node does not participate in consensus), and it disables APIs related to broadcasting transactions, effectively making the node passive in terms of network operations. Instead, Follower Mode enables APIs that allow the node to retrieve detailed information from the Ledger cache and access the state of the blockchain at a specific round. This can be useful for nodes that must observe the network and the blockchain’s state without actively participating in consensus or transaction propagation.
💾 Storage & Archival
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
Archival | false | 0 | 0 |
Description:
Enables saving a full copy of the blockchain history. Non-Archival nodes will only maintain the necessary state to validate blockchain messages and participate in the consensus protocol. Non-Archival nodes use significantly less storage space.
The
Archival
parameter enables the node to save the complete history of the blockchain starting from the moment this setting is activated. Therefore, this setting must be enabled before you start the node for the first time. If the node has already been running without this setting enabled, the existing databases must be deleted and fully synchronized from the genesis.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
HotDataDir | Empty string | 31 | 31 |
Description:
An optional directory to store data frequently accessed by the node. For isolation,
the node will create a subdirectory in this location, named by the network genesisID
The node uses the default data directory provided at runtime if not specified. Individual
resources may override this setting. Setting HotDataDir
to a dedicated high-performance
disk allows for basic disk tuning.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
ColdDataDir | Empty string | 31 | 31 |
Description:
An optional directory for storing infrequently accessed data. The node creates a
subdirectory within this location, named after the network genesisID
The node uses the default data directory provided at runtime if not specified. Individual
resources may have their own override settings, which take precedence over this value.
A slower disk for ColdDataDir
can optimize storage costs and resource allocation.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
TrackerDBDir | Empty string | 31 | 31 |
Description:
An optional directory to store the tracker database. For isolation, the node will
create a subdirectory in this location, named by the network genesisID
.
If not specified, the node will use the HotDataDir
.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
BlockDBDir | Empty string | 31 | 31 |
Description:
An optional directory to store the block database.
For isolation, the node will create a subdirectory in this location, named by the
network genesisID
. If not specified,
the node will use the ColdDataDir
.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
CatchpointDir | Empty string | 31 | 31 |
Description:
An optional directory to store Catchpoint Files, except for the in-progress temporary
file, which will use the HotDataDir
and is not separately configurable. For isolation,
the node will create a subdirectory in this location, named by the network genesisID
.
If not specified, the node will use the ColdDataDir
.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
StateproofDir | Empty string | 31 | 31 |
Description:
An optional directory to persist state about observed and issued state proof messages.
For isolation, the node will create a subdirectory in this location, named by the
network genesisID
. If not specified,
the node will use the HotDataDir
.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
CrashDBDir | Empty string | 31 | 31 |
Description:
An optional directory to persist Agreement participation
state. For isolation, the node will create a subdirectory in this location, named
by the network genesisID
. If
not specified, the node will use the HotDataDir
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
OptimizeAccountsDatabaseOnStartup | false | 10 | 10 |
Description:
Controls whether the Account database would be optimized on algod
startup.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
LedgerSynchronousMode | 2 | 12 | 12 |
Description:
Defines the synchronous mode the Ledger database uses.
The supported options are:
-
0
: SQLite continues without syncing as soon as it has handed data off to the operating system. -
1
: SQLite database engine will still sync at the most critical moments, but less often than in FULL mode. -
2
: SQLite database engine will use the VFS’s xSync method to ensure all content is safely written to the disk surface before continuing. -
3
: In addition to what is being done in2
, it provides some guarantee of durability if the commit is followed closely by a power loss.
For further information, see the description of
SynchronousMode
.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
AccountsRebuildSynchronousMode | 1 | 12 | 12 |
Description:
Defines the synchronous mode the Ledger database uses while the Account database
is being rebuilt. This is not a typical operational use-case, and is expected to
happen only on either startup (after enabling the Catchpoint Interval, or on certain
database upgrades) or during fast catchup. The values specified here and their meanings
are identical to the ones in LedgerSynchronousMode
.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
MaxAcctLookback | 4 | 23 | 23 |
Description:
Sets the maximum lookback range for Account states.
📎 EXAMPLE
The Ledger can answer Account state questions for the range
[Latest - MaxAcctLookback, Latest]
.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
MaxBlockHistoryLookback | 0 | 31 | 31 |
Description:
Sets the maximum lookback range for Block information.
📎 EXAMPLE
The Block DB can return transaction IDs for questions for the range
[Latest - MaxBlockHistoryLookback, Latest]
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
StorageEngine | sqlite | 28 | 28 |
Description:
Sets the type of storage to use for the Ledger. Available options are:
sqlite
(default).pebbledb
(experimental, in development).
🌐 Networking
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
GossipFanout | 4 | 0 | 0 |
Description:
Defines the maximum number of peers the node will establish outgoing connections with. The node will connect to fewer peers if the available peer list is smaller than this value. The node does not create multiple outgoing connections to the same peer.
For further details, refer to the Network non-normative specification.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
NetAddress | Empty string | 0 | 0 |
Description:
Specifies the IP
address and/or the port
where the node listens for incoming connections.
Leaving this field blank disables incoming connections. The value can be an IP
,
IP:port
pair, or just a :port
. The node defaults to port 4160
if no port is specified.
Setting the port to :0
allows the system to assign an available port automatically.
📎 EXAMPLE
127.0.0.1:0
binds the node to any available port onlocalhost
.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
PublicAddress | Empty string | 0 | 0 |
Description:
Defines the public address that the node advertises to other nodes for incoming connections.
For mainnet
Relay Nodes, this should include the full SRV
host name and the publicly
accessible port number. A valid entry helps prevent self-gossip and is used for
identity exchange, ensuring redundant connections are de-duplicated.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
TLSCertFile | Empty string | 0 | 0 |
Description:
The certificate file used for the Relay Network if provided.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
TLSKeyFile | Empty string | 0 | 0 |
Description:
The key file used for the Relay network if provided.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
DNSBootstrapID | <network>.algorand.network?backup=<network>.algorand.net&dedup=<name>.algorand-<network>.(network|net) | 0 (default was <network>.algorand.network ) | 28 |
Description:
Specifies the names of a set of DNS SRV
records that identify the set of nodes
available to connect to.
For further information on the specifics of this value’s syntax, refer to DNS bootstrap reference implementation.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
FallbackDNSResolverAddress | Empty string | 0 | 0 |
Description:
Defines the fallback DNS resolver address used if the system resolver fails to retrieve
SRV
records.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
UseXForwardedForAddressField | Empty string | 0 | 0 |
Description:
Indicates whether the node should use the X-Forwarded-For
HTTP Header when determining
the source of a connection. Unless the proxy vendor provides another header field,
it should be set to the string X-Forwarded-For
.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
ForceRelayMessages | Empty string | 0 | 0 |
Description:
Indicates whether the Network library should relay messages even if no NetAddress
is specified.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
DNSSecurityFlags | 9 | 6 (default was 1 ) | 34 |
Description:
Instructs algod
validating DNS responses.
Possible flag values are:
0
: Disabled.1
: ValidateSRV
response.2
: Validate relay names for addresses resolution.4
: Validate telemetry and metrics names for addresses resolution.8
: ValidateTXT
response.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
EnablePingHandler | true | 6 | 6 |
Description:
Controls whether the gossip node responds to ping messages with a pong message.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
NetworkProtocolVersion | Empty string | 6 | 6 |
Description:
Overrides the Network protocol version (if present).
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
EnableGossipService | true | 33 | 33 |
Description:
Enables the gossip network HTTP WebSockets endpoint. Its functionality depends on
NetAddress,
which must also be provided. This service is required to serve gossip
traffic.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
EnableLedgerService | false | 7 | 7 |
Description:
Enables the Ledger serving service. This functionality
depends on NetAddress
, which must also be provided. This service is required for
the Fast Catchup.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
EnableBlockService | false | 7 | 7 |
Description:
Controls whether to enable the block serving service.
This functionality depends on NetAddress
, which must also be provided. This service
is required for the Fast Catchup.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
EnableGossipBlockService | true | 8 | 8 |
Description:
Determines whether the node serves blocks to peers over the gossip network. This
service is essential for Relay Nodes and other nodes to request and receive block
data, especially during Fast Catchup. For this
to work, the node must have a NetAddress
set, allowing it to accept incoming connections.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
DisableNetworking | false | 16 | 16 |
Description:
Disables all the incoming and outgoing communication a node may perform. This is useful when we have a single-node Private Network, where no other nodes need to be communicated with. Features like Fast Catchup would be completely non-operational, rendering many node inner systems useless.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
EnableP2P | false | 31 | 31 |
Description:
Enables the Peer-to-Peer Network.
When both EnableP2P
and EnableP2PHybridMode
are set, EnableP2PHybridMode
takes
precedence.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
EnableP2PHybridMode | false | 34 | 34 |
Description:
Turns on both Relay and Peer-to-Peer (Hybrid) Networking.
Enabling this setting also requires PublicAddress
to be set.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
P2PHybridNetAddress | Empty string | 31 | 31 |
Description:
Sets the listen address used for P2P networking, if Hybrid Network is set.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
EnableDHTProviders | false | 34 | 34 |
Description:
Enables the Distributed Hash Table (DHT). This feature allows the node to participate in a DHT-based network that advertises its available capabilities to other nodes.
For further details, refer to the Algorand P2P Network capabilities non-normative specification.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
P2PPersistPeerID | false | 29 | 29 |
Description:
Writes the private key used for the node’s PeerID to the P2PPrivateKeyLocation
.
This is only used when EnableP2P
is true. If the location flag is not specified,
it uses the default location.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
P2PPrivateKeyLocation | Empty string | 29 | 29 |
Description:
Allows the user to specify a custom path to the private key used for the node’s
PeerID. The private key provided must be an Ed25519
private key. This is only used when EnableP2P
is set to true
. If this parameter
is not set, it uses the default location.
📡 Peer & Connection Management
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
MaxConnectionsPerIP | 8 | 3 (default was 30 ) | 35 |
Description:
Defines the maximum number of connections allowed per IP
address.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
IncomingConnectionsLimit | 2400 | 0 (default was -1 ) | 27 |
Description:
A non-negative number that specifies the maximum incoming connections for the gossip
protocol configured in NetAddress
. A value of 0
means no connections allowed.
Estimating 1.5 MB per incoming connection.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
P2PHybridIncomingConnectionsLimit | 1200 | 34 | 34 |
Description:
Used as IncomingConnectionsLimit
for P2P connections in Hybrid Network.
For pure P2P Network the field IncomingConnectionsLimit
is used instead.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
BroadcastConnectionsLimit | -1 | 4 | 4 |
Description:
Defines the maximum number of peer connections that will receive broadcast (gossip) messages from this node.
Succinctly, it works in the following way:
-
If a node has more connections than the specified limit, it prioritizes broadcasting to peers in the following order:
- Outgoing connections: Peers a node actively connects to.
- Peers with higher stake: Determined by the amount of ALGO held, as indicated by their participation key.
-
Special values:
0
: Disables all outgoing broadcast messages, including transaction broadcasts to peers.-1
: No limit on the number of connections receiving broadcasts (default setting).
For further details, refer to the Algorand Network non-normative specification.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
AnnounceParticipationKey | true | 4 | 4 |
Description:
Indicates if this node should announce its participation key with the largest stake to its peers. In case of a DoS attack, this allows peers to prioritize connections.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
PriorityPeers | Empty string | 4 | 4 |
Description:
Specifies peer IP
addresses that should always get outgoing broadcast messages from
this node.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
ConnectionsRateLimitingCount | 60 | 4 | 4 |
Description:
Used with ConnectionsRateLimitingWindowSeconds
to determine whether a connection
request should be accepted. The gossip network examines all the incoming requests
in the past ConnectionsRateLimitingWindowSeconds
seconds that share the same origin.
If the total count exceeds this value, the connection is refused.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
ConnectionsRateLimitingWindowSeconds | 1 | 4 | 4 |
Description:
Used with ConnectionsRateLimitingCount
to determine whether a connection request
should be accepted. Providing a zero value in this variable disables the connection
rate limiting.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
DisableOutgoingConnectionThrottling | false | 5 | 5 |
Description:
Disables connection throttling of the network library, which allows the network library to continuously disconnect Relay Nodes based on their relative (and absolute) performance.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
DisableLocalhostConnectionRateLimit | true | 16 | 16 |
Description:
Controls whether the incoming connection rate limit applies to connections originating
from the local machine. Setting this to true
allows this node to create a large
local-machine network that won’t trip the incoming connection limit observed by
Relay Nodes.
📜 Logging
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
BaseLoggerDebugLevel | 4 | 0 (default was 1 ) | 1 |
Description:
Specifies the logging level for algod
(node.log
).
The levels are:
-
0
: Panic: Highest level of severity. Logs and then callspanic()
. -
1
: Fatal: Logs and then callsos.Exit(1)
. It will exit even if the logging level is set to Panic. -
2
: Error: Used for errors that should definitely be noted. Commonly used for hooks to send errors to an error tracking service. -
3
: Warn: Non-critical entries that deserve attention. -
4
: Info: General operational entries. -
5
: Debug: Verbose logging, usually only enabled when debugging.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
CadaverSizeTarget | 0 | 0 (default was 1073741824 ) | 24 |
Description:
Specifies the maximum size of the agreement.cdv
file in bytes. Once full, the file
will be renamed to agreement.archive.log
and a new agreement.cdv
will be created.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
CadaverDirectory | Empty string | 27 | 27 |
Description:
If not set, MakeService
will attempt to use ColdDataDir
instead.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
LogFileDir | Empty string | 31 | 31 |
Description:
An optional directory to store the log file, node.log
. If not set, the node will
use the HotDataDir
. The -o
GOAL CLI option can override this output location.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
LogArchiveDir | Empty string | 31 | 31 |
Description:
An optional directory to store the log archive. If not specified, the node will
use the ColdDataDir
.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
LogSizeLimit | 1073741824 | 0 | 0 |
Description:
Sets the node log file size limit in bytes. When set to 0
, logs will be written
to stdout.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
LogArchiveName | node.archive.log | 4 | 4 |
Description:
Text template for creating the log archive filename. If the filename ends with .gz
or .bz2
it will be compressed.
Available template variables:
-
Time at the start of log:
- {{.Year}}
- {{.Month}}
- {{.Day}}
- {{.Hour}}
- {{.Minute}}
- {{.Second}}
-
Time at the end of log:
- {{.EndYear}}
- {{.EndMonth}}
- {{.EndDay}}
- {{.EndHour}}
- {{.EndMinute}}
- {{.EndSecond}}
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
LogArchiveMaxAge | Empty string | 4 | 4 |
Description:
Specifies the maximum age for a node log. Valid units are s
seconds, m
minutes, h
hours.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
EnableRequestLogger | false | 4 | 4 |
Description:
Enables the logging of the incoming requests to the telemetry server.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
TelemetryToLog | true | 5 | 5 |
Description:
Configures whether to record messages to node.log
that are usually only sent to
remote event monitoring.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
EnableVerbosedTransactionSyncLogging | false | 17 | 17 |
Description:
Allows the Transaction Sync to write extensive message exchange information to the log file. This option is disabled by default to prevent the rapid growth of logs.
📊 Metrics & Telemetry
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
NodeExporterListenAddress | :9100 | 0 | 0 |
Description:
Sets the specific address for publishing metrics.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
EnableMetricReporting | false | 0 | 0 |
Description:
Determines whether the metrics collection service is enabled for the node. When
enabled, the node will collect performance and usage metrics from the specific instance
of algod
. Additionally, machine-wide metrics
are also collected. This enables monitoring across all instances on the same machine,
helping with performance analysis and troubleshooting.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
NodeExporterPath | ./node_exporter | 0 | 0 |
Description:
Specifies the path to the node_exporter
binary, which exposes node metrics for
monitoring and integration with systems like Prometheus. The node_exporter
collects
and exposes various performance and health metrics from the node.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
EnableAssembleStats | Empty string | 3 | 3 |
Description:
Specifies whether to emit the AssembleBlockMetrics
telemetry event.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
EnableProcessBlockStats | Empty string | 3 | 3 |
Description:
Specifies whether to emit the ProcessBlockMetrics
telemetry event.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
PeerConnectionsUpdateInterval | 3600 | 5 | 5 |
Description:
Defines the interval at which the peer connections’ information is sent to telemetry. Defined in seconds.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
HeartbeatUpdateInterval | 600 | 27 | 27 |
Description:
Defines the interval at which the heartbeat information is sent to the telemetry
(when enabled). Defined in seconds. Minimum value is 60
.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
EnableRuntimeMetrics | false | 22 | 22 |
Description:
Exposes Go runtime metrics in /metrics
and via node_exporter
.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
EnableNetDevMetrics | false | 34 | 34 |
Description:
Exposes network interface total bytes sent/received metrics in /metrics
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
EnableAccountUpdatesStats | false | 16 | 16 |
Description:
Specifies whether to emit the AccountUpdates
telemetry event.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
AccountUpdatesStatsInterval | 5000000000 | 16 | 16 |
Description:
Time interval in nanoseconds between accountUpdates
telemetry events.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
EnableUsageLog | false | 31 | 31 |
Description:
Enables 10Hz log of CPU and RAM usage. Also adds algod_ram_usage
(number of bytes
in use) to /metrics
.
🔗 API & Endpoints
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
EndpointAddress | 127.0.0.1:0 | 0 | 0 |
Description:
Configures the address where the node listens for REST API calls.
It may hold an IP:port
pair or just a port
. The value 127.0.0.1:0
and :0
will attempt to bind to port 8080
if possible; otherwise, it will bind to a random
port. Any other address ending in :0
will bind directly to a random port.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
EnablePrivateNetworkAccessHeader | false | 35 | 35 |
Description:
Responds to Private Network Access preflight requests sent to the node. Useful when a public website is trying to access a node hosted on a Local Network.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
RestReadTimeoutSeconds | 15 | 4 | 4 |
Description:
Defines the maximum duration (in seconds) the node’s API server will wait to read the request body for an incoming HTTP request. The request will be aborted if the body is not fully received within this time frame.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
RestWriteTimeoutSeconds | 120 | 4 | 4 |
Description:
Defines the maximum duration (in seconds) the node’s API server allows writing the response body back to the client. The connection is closed if the response is not sent within this time frame.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
BlockServiceCustomFallbackEndpoints | Empty string | 16 | 16 |
Description:
The comma-delimited list of endpoints that the block
service uses to redirect the HTTP requests if it does not have the round. The block
service will return 404
(StatusNotFound) if empty.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
RestConnectionsSoftLimit | 1024 | 20 | 20 |
Description:
Defines the maximum number of active requests the API server can handle. When the
number of HTTP connections to the REST API exceeds
this soft limit, the server returns HTTP status code 429
(Too Many Requests).
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
RestConnectionsHardLimit | 2048 | 20 | 20 |
Description:
Defines the maximum number of active connections the API server will accept before closing requests with no response.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
MaxAPIResourcesPerAccount | 100000 | 21 | 21 |
Description:
Sets the maximum total number of resources (created assets, created apps, asset holdings,
and application local state) allowed per account in AccountInformation
REST API
responses before returning a 400
(Bad Request). If set to 0
, there is no limit.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
MaxAPIBoxPerApplication | 100000 | 25 | 25 |
Description:
Defines the maximum number of boxes per application that will be returned in GetApplicationBoxes
REST API responses.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
EnableExperimentalAPI | false | 26 | 26 |
Description:
Enables experimental API endpoint.
These endpoints have no guarantees in terms of functionality or future support.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
DisableAPIAuth | false | 30 | 30 |
Description:
Disables authentication for public (non-admin) API endpoints.
🗳️ Consensus & Agreement
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
EnableAgreementReporting | false | 3 | 3 |
Description:
Controls Agreement events reporting. When enabled, it prints additional events related to the agreement periods within the consensus process. This is useful for tracking and debugging the stages of agreement reached by the node during the consensus rounds.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
EnableAgreementTimeMetrics | false | 3 | 3 |
Description:
Controls whether the node collects and reports metrics related to the timing of the agreement process within the Agreement protocol. When enabled, it provides detailed data on the time taken for agreement events during consensus rounds.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
ProposalAssemblyTime | 500000000 | 19 (default was 250000000 ) | 23 |
Description:
The maximum amount of time to spend on generating a proposal block. Expressed in nanoseconds.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
AgreementIncomingVotesQueueLength | 20000 | 21 (default was 10000 ) | 27 |
Description:
Sets the buffer size holding incoming votes.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
AgreementIncomingProposalsQueueLength | 50 | 21 (default was 25 ) | 27 |
Description:
Sets the buffer size holding incoming proposals.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
AgreementIncomingBundlesQueueLength | 15 | 21 (default was 7 ) | 27 |
Description:
Sets the buffer size holding incoming bundles.
🔄 Transaction Pool & Sync
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
TxPoolExponentialIncreaseFactor | 2 | 0 | 0 |
Description:
Sets the increase factor of the Transaction Pool
fee threshold. When the transaction pool is full, the priority of a new transaction
must be at least TxPoolExponentialIncreaseFactor
times greater than the minimum-priority
of a transaction already in the pool; otherwise, the new transaction is discarded.
Should be set to
2
on MainNet.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
TxBacklogServiceRateWindowSeconds | 10 | 27 | 27 |
Description:
Defines the time window (in seconds) used to determine the service rate of the transaction
backlog (txBacklog
). It helps to manage how quickly the node processes and serves
transactions waiting in the backlog by monitoring the rate over a specific time period.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
TxBacklogReservedCapacityPerPeer | 20 | 27 | 27 |
Description:
Determines the dedicated capacity allocated to each peer for serving transactions
from the transaction backlog (txBacklog
). This ensures each peer has a specific
portion of the overall transaction processing capacity, preventing one peer from
monopolizing the node’s transaction handling resources.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
TxBacklogAppTxRateLimiterMaxSize | 1048576 | 32 | 32 |
Description:
Defines the maximum size for the transaction rate limiter, which is used to regulate the number of transactions that an Application can submit to the node within a given period. This rate limiter prevents individual Applications from overwhelming the network with excessive transactions. The value may be interpreted as the maximum sum of transaction bytes per period.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
TxBacklogAppTxPerSecondRate | 100 | 32 | 32 |
Description:
Determines the target transaction rate per second for an Application’s transactions within the transaction rate limiter. This means the node will aim to allow an Application to submit a specific number of transactions per second. If the Application tries to send more transactions than the rate limit allows, those transactions will be delayed or throttled.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
TxBacklogRateLimitingCongestionPct | 50 | 32 | 32 |
Description:
Determines the threshold percentage at which the transaction backlog rate limiter will be activated. When the backlog reaches a certain percentage, the node will start to limit the rate of transactions, either by slowing down incoming transactions or applying throttling measures. This helps prevent the backlog from growing too large and causing congestion on the network. If the backlog falls below this threshold, rate limiting is relaxed.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
EnableTxBacklogAppRateLimiting | true | 32 | 32 |
Description:
Determines whether an Application-specific rate limiter should be applied when adding transactions to the transaction backlog. If enabled, the node enforces rate limits on how many Application transactions can be enqueued, preventing excessive transaction submission from a single app.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
TxBacklogAppRateLimitingCountERLDrops | false | 35 | 35 |
Description:
Determines whether transaction messages dropped by the ERL (Exponential Rate Limiter) congestion manager and the transaction backlog rate limiter should also be counted by the Application rate limiter. When enabled, all dropped transactions are included in the Application rate limiter’s calculations, making its rate-limiting decisions more accurate. However, this comes at the cost of additional deserialization overhead, as more transactions need to be processed even if they are ultimately dropped.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
EnableTxBacklogRateLimiting | true | 27 (default was false ) | 30 |
Description:
Determines whether a rate limiter and congestion manager should be applied when
adding transactions to the transaction backlog. When enabled, the total transaction
backlog size increases by MAX_PEERS * TxBacklogReservedCapacityPerPeer
, allowing
each peer to have a dedicated portion of the backlog. This helps manage network
congestion by preventing any single source from overwhelming the queue.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
TxBacklogSize | 26000 | 27 | 27 |
Description:
Defines the queue size for storing received transactions before they are processed.
The default value (26000
) approximates the number of transactions that fit in a
single block. However, if EnableTxBacklogRateLimiting
is enabled, the total backlog
size increases by MAX_PEERS * TxBacklogReservedCapacityPerPeer
, allocating additional
capacity per peer.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
TxPoolSize | 75000 | 0 (default was 50000 ) | 23 |
Description:
Defines the maximum number of transactions stored in the Transaction Pool buffer before being processed or discarded.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
TxSyncTimeoutSeconds | 30 | 0 | 0 |
Description:
Defines the maximum time a node will wait for responses after attempting to synchronize
transactions by gossiping them to peers. After gossiping, the node waits for acknowledgments,
such as confirmation that the transaction has been added to a peer’s Transaction
Pool or relayed to others. If no acknowledgment is received within the time limit
set by TxSyncTimeoutSeconds
, the node stops the synchronization attempt and moves
on to other tasks. However, the node will retry synchronization in future cycles.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
TxSyncIntervalSeconds | 60 | 0 | 0 |
Description:
Defines the number of seconds between transaction synchronizations.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
TxSyncServeResponseSize | 1000000 | 3 | 3 |
Description:
Sets the maximum size, in bytes, that the synchronization server will return when serving transaction synchronization requests.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
VerifiedTranscationsCacheSize | 150000 | 14 (default was 30000 ) | 23 |
Description:
Defines the number of transactions that the verified transactions cache would hold before cycling the cache storage in a round-robin fashion.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
ForceFetchTransactions | false | 17 | 17 |
Description:
Forces a node to retrieve all transactions observed into its Transaction Pool, regardless of whether the node is participating in consensus or not.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
TransactionSyncDataExchangeRate | 0 | 17 | 17 |
Description:
Overrides the auto-computed data exchange rate between two peers. The unit of the
data exchange rate is in bytes per second. Setting the value to 0
implies allowing
the transaction sync to calculate the value dynamically.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
TransactionSyncSignificantMessageThreshold | 0 | 17 | 17 |
Description:
Defines the threshold used for a Transaction Sync message before it can be used
to calculate the data exchange rate. Setting this to 0
would use the default values.
The threshold is defined in bytes as a unit.
🎣 Fast Catchup
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
CatchupFailurePeerRefreshRate | 10 | 0 | 0 |
Description:
Specifies the maximum number of consecutive attempts to Fast Catchup after which peer connections are replaced.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
CatchupParallelBlocks | 16 | 3 (default was 50 ) | 5 |
Description:
Is the maximum number of blocks that the Fast Catchup
will fetch in parallel. If less than Protocol.SeedLookback
,
then Protocol.SeedLookback
will be used to limit the catchup. Setting this to 0
would disable the catchup.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
CatchpointInterval | 10000 | 7 | 7 |
Description:
Defines how often a Catchpoint is generated,
measured in rounds. These Ledger snapshots allow nodes to sync quickly by downloading
and verifying the Ledger state at a specific round instead of replaying all transactions.
Setting this to 0
disables the Catchpoints from being generated.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
CatchpointFileHistoryLength | 365 | 7 | 7 |
Description:
Defines how many Catchpoint Files to store. A value of 0
means don’t store any,
-1
means unlimited; a positive number suggests the maximum number of most recent
Catchpoint Files to store.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
CatchupHTTPBlockFetchTimeoutSec | 4 | 9 | 9 |
Description:
Sets the maximum time (in seconds) that a node will wait for an HTTP response when requesting a block from a Relay Node during Fast Catchup. If the request takes longer than this timeout, the node abandons the request and tries with another Relay Node.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
CatchupGossipBlockFetchTimeoutSec | 4 | 9 | 9 |
Description:
Controls how long the gossip query for fetching a block from a Relay Node would take before giving up and trying another Relay Node.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
CatchupLedgerDownloadRetryAttempts | 50 | 9 | 9 |
Description:
Controls the number of attempts the Ledger fetcher would perform before giving up the Fast Catchup to the provided Catchpoint.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
CatchupBlockDownloadRetryAttempts | 1000 | 9 | 9 |
Description:
Controls the number of attempts the Block fetcher would perform before giving up on a provided Catchpoint.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
CatchpointTracking | 0 | 11 | 11 |
Description:
Determines if Catchpoints are going to be tracked. The value is interpreted as follows:
-1
: Do not track Catchpoints.1
: Track Catchpoints as long asCatchpointInterval
>0
.2
: Track Catchpoints and always generate Catchpoint Files as long asCatchpointInterval
>0
.0
: Automatic (default). In this mode, a Non-Archival node would not track the Catchpoints, while an Archival node would track the Catchpoints as long asCatchpointInterval
>0
.- Any other values of this field would behave as if the default value was provided.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
MaxCatchpointDownloadDuration | 43200000000000 | 13 (default was 7200000000000 ) | 28 |
Description:
Defines the maximum duration for which a client will keep the outgoing connection of a Catchpoint download request open for processing before shutting it down. In Networks with large Catchpoint files, slow connections, or slow storage could be a good reason to increase this value.
This is a client-side only configuration value, and it’s independent of the actual Catchpoint file size.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
MinCatchpointFileDownloadBytesPerSecond | 20480 | 13 | 13 |
Description:
Defines the minimal download speed that would be considered to be “acceptable” by
the Catchpoint file fetcher, measured in bytes per second. The connection would
be recycled if the provided stream speed drops below this threshold. If this field
is 0
, the default value would be used instead.
The download speed is evaluated per Catchpoint “chunk”.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
CatchupBlockValidateMode | 0000 | 16 | 16 |
Description:
A configuration used by the Fast Catchup service.
It can be used to omit certain validations to speed up the synchronization
process, or to apply extra validations. The value is a bitmask (where bit 0
is
the LSB and bit 3
is the MSB).
The value of each bit is interpreted as follows:
-
bit 0
:0
: Verify the block certificate.1
: Skip this validation.
-
bit 1
:0
: Verify payset committed hash in block header matches payset hash.1
: Skip this validation.
-
bit 2
:0
: Skip verifying the transaction signatures on the block are valid.1
: Verify transaction signatures in the block.
-
bit 3
:0
: Skip verifying that the recomputed payset hash matches the payset committed hash in the block header.1
: Perform verification as described above.
Not all permutations of the above bitmask are currently functional. In particular, the functional ones are:
0000
: Default behavior, perform standard validations and skip extra validations.0011
: Speed up synchronization, skip standard and extra validations.1100
: Pedantic, perform standard and extra validations.
🛡️ Security & Filtering
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
IncomingMessageFilterBucketCount | 5 | 0 | 0 |
Description:
Specifies the number of hash buckets used to filter and manage incoming messages. When a message is received, the node computes its hash. The hash is then assigned to one of the available buckets based on its value. Each bucket holds a set of message hashes, allowing the node to quickly check if it has already processed a message and filter out duplicates.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
IncomingMessageFilterBucketSize | 512 | 0 | 0 |
Description:
Defines the size of each incoming message hash bucket.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
OutgoingMessageFilterBucketCount | 3 | 0 | 0 |
Description:
Defines the number of outgoing message hash buckets.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
OutgoingMessageFilterBucketSize | 128 | 0 | 0 |
Description:
Defines the size of each outgoing message hash bucket.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
EnableOutgoingNetworkMessageFiltering | true | 0 | 0 |
Description:
Enables the filtering of outgoing messages by comparing their hashes with those in the message hash buckets.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
EnableIncomingMessageFilter | false | 0 | 0 |
Description:
Enables the filtering of incoming messages.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
TxIncomingFilteringFlags | 1 | 26 | 26 |
Description:
Instructs algod
filtering of incoming transaction
messages.
Flag values:
0x00
: Disabled.0x01
(txFilterRawMsg
): Check for raw transaction message duplicates.0x02
(txFilterCanonical
): Check for canonical transaction group duplicates.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
TxIncomingFilterMaxSize | 500000 | 28 | 28 |
Description:
Sets the maximum size for the de-duplication cache used by the incoming transaction
filter. Only relevant if TxIncomingFilteringFlags
is non-zero.
🛠️ Developer & Debugging
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
EnableDeveloperAPI | false | 9 | 9 |
Description:
Enables teal/compile
and teal/dryrun
API endpoints.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
DeadlockDetection | 0 | 1 | 1 |
Description:
Controls whether the node actively detects potential deadlocks in its operations.
A deadlock occurs when two or more processes are stuck waiting for each other to
release resources, preventing progress. Deadlock detection is enabled when set to
a positive value, allowing the node to monitor and identify such situations. A value
of -1
disables deadlock detection, and a value of 0
sets the default behavior,
where the system determines whether to enable deadlock detection automatically based
on the environment.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
DeadlockDetectionThreshold | 30 | 20 | 20 |
Description:
Defines the time limit, in seconds, that the node waits before considering a potential deadlock situation. If a process or operation exceeds this threshold without progressing, the node will trigger deadlock detection to identify and handle the issue.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
EnableProfiler | false | 0 | 0 |
Description:
Allows the node to expose Go’s pprof
profiling endpoints, which provide detailed
performance metrics such as CPU, memory, and goroutine
usage. This is useful for
debugging and performance analysis. When enabled, the node will serve profiling
data through its API, allowing developers to inspect real-time runtime behavior.
However, since pprof can expose sensitive performance details, it should be disabled
in production or whenever the API is accessible to untrusted individuals, as an attacker
could use this information to analyze and exploit the system.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
NetworkMessageTraceServer | Empty string | 13 | 13 |
Description:
A host:port
address to report graph propagation trace info to.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
EnableTxnEvalTracer | false | 27 | 27 |
Description:
Turns on features in the BlockEvaluator
, which collect data on transactions, exposing
them via algod
APIs. It will store state deltas
created during block evaluation, potentially consuming much larger amounts of memory.
🚀 Performance & Resource Management
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
ReservedFDs | 256 | 2 | 2 |
Description:
This configuration parameter specifies the number of reserved file descriptors
(FDs) that the node will allocate. These reserved FDs are set aside for operations
that require temporary file descriptors, such as DNS queries, SQLite database interactions,
and other short-lived network operations. The total number of file descriptors available
to the node, as specified by the node’s file descriptor limit (RLIMIT_NOFILE
),
should always be greater than or equal to:
IncomingConnectionsLimit + RestConnectionsHardLimit + ReservedFDs
.
This parameter is typically left at its default value and should not be changed unless necessary. If the node’s file descriptor limit is lower than the sum of the three components, it could cause issues with node functionality.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
DisableLedgerLRUCache | false | 27 | 27 |
Description:
Disables LRU caches in Ledger.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
BlockServiceMemCap | 500000000 | 28 | 28 |
Description:
The memory capacity in bytes that the block service is allowed to use for HTTP block requests. It redirects the block requests to a different node when it exceeds this capacity.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
GoMemLimit | 0 | 34 | 34 |
Description:
Provides the Go runtime with a soft memory limit. The default behavior is unlimited,
unless the GOMEMLIMIT
environment variable is set.
🚫 Deprecated
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
ReconnectTime | 60000000000 | 0 (default was 60 ) | 1 |
Description:
Deprecated and unused.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
PeerPingPeriodSeconds | 0 | 0 | 0 |
Description:
Deprecated and unused.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
EnableTopAccountsReporting | false | 0 | 0 |
Description:
Deprecated and unused.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
SuggestedFeeBlockHistory | 3 | 0 | 0 |
Description:
Deprecated and unused.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
SuggestedFeeSlidingWindowSize | 50 | 3 | 3 |
Description:
Deprecated and unused.
Parameter Name | Default Value | Introduced in Version | Last Updated in Version |
---|---|---|---|
ParticipationKeysRefreshInterval | 60000000000 | 16 | 16 |
Description:
Deprecated and unused.
Represents the duration between two consecutive checks to see if new participation keys have been placed in the genesis directory.
$$ \newcommand \DefaultUpgradeWaitRounds {\delta_x} \newcommand \UpgradeThreshold {\tau} \newcommand \UpgradeVoteRounds {\delta_d} $$
Protocol Updates
The Algorand Foundation governs the development and maintenance of the Algorand protocol.
Protocol updates SHALL be executed through the following process:
1. Specification Publication
The Algorand Foundation SHALL publish the official protocol specification in the public specifications’ repository.
2. Protocol Version Identification
Each protocol version SHALL be uniquely identified by the URL of the corresponding
git
release commit. This URL MUST include the cryptographic hash of the commit.
3. On-Chain Approval
A protocol update SHALL become effective only if it is approved on-chain by a supermajority of block proposers. Each block proposer signals support for a protocol update by including the identifier of the proposed protocol version as the next protocol version.
4. Acceptance Criteria
A protocol update SHALL be accepted if, for an interval of \( \UpgradeVoteRounds \) consecutive rounds, at least \( \UpgradeThreshold \) of the finalized blocks reference the same next protocol version.
5. Upgrade Grace Period
Upon acceptance, node operators SHALL be granted an additional \( \DefaultUpgradeWaitRounds \) rounds to update their node software in accordance with the new specification.
6. Activation
Upon completion of the grace period, the updated protocol specification SHALL take effect. From that point forward, blocks MUST be produced exclusively under the updated protocol rules.
The values of the upgrade parameters are defined in the Ledger Parameters Specification.
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 normative files
└── non-normative/ -> Part A non-normative 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 item
Result:
- 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/
folder 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.
License
Refer to the GitHub repository license.