Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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

Node

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.



  1. 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.

SYMBOLVALUE (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.

SYMBOLVALUE (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:

STEPENUMERATIVE
\( \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:

  1. Let \( (\pk, B, r_\fv, r_\lv) = \Record(L, r-\delta_b, I) \); let \( q_0 = \Seed(L, r-\delta_s) \).

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

  3. If \( p \ne 0 \), let \( q_1 = \Hash(q_0) \). Continue.

  4. 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.

  1. 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.

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



  1. 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.

  2. 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:

  1. 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.
  2. 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.
  3. 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:

  1. Majority of online honest stake,

  2. Security of cryptographic primitives,

  3. 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.



  1. \( \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:

SYMBOLDESCRIPTION
\( 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:

  1. Get the seed \( Q \) for round \( r_a - \delta_s = (48182880 - 2) = 48182878 \),
  2. Construct a \( \VRF \) proof \( y \) with that seed,
  3. Convert the \( \VRF \) proof \( y \) to a \( \VRF \) proof hash (named \( \VRF_h \)),
  4. Hash the object \( \{I || \VRF_h\} \) (named \( \alpha \)),
  5. Lookup the block digest of the old round \( r_a - \delta_s\delta_r = 48182880 - 160 = 48182720 \) (named \( H_\text{old}\)),
  6. 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:

  1. Get the seed \( Q \) for round \( r_c - \delta_s = (48182882 - 2) = 48182880 \),
  2. Construct a \( \VRF \) proof \( y \) with that seed,
  3. Convert the \( \VRF \) proof \( y \) to a \( \VRF \) proof hash (named \( \VRF_h \)),
  4. Hash the object \( \{I || \VRF_h\} \) (named \( \alpha \)),
  5. 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

NAMEVALUE (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 \).


  1. With current values for \( \lambda \) and \( \lambdaMin \), \( \deltaL = 2 \).

  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.

  3. 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:

  1. 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.
  2. 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.



  1. 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:

  1. The original period in which this block was proposed.

  2. The original proposer’s address.

  3. 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 \)).

  4. 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:

  1. The original period in which this block was proposed.

  2. The original proposer’s address.

  3. A full block (header and payset).

  4. 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.



  1. 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 Modes Example

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.



  1. 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:

  1. Assemble a \( \prop \) and a \( \vt \) on proposal-value \( v \),

  2. Set \( v \) as the proposal-value obtained from block \( e \),

  3. 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

  1. Relays the vote,

  2. Adds the vote to the vote set (if new),

  3. 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). $$


  1. The node checks if there is a \( \vt_v \), such that for all the \( \vt \in V \) with \( \vt_r = r, \vt_p = 0, \vt_s = \Soft \), the sum of votes’ weights is bigger than the committee threshold:

$$ \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.

Ledger Overview

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

ParameterCurrent ValueUnitDescriptionReference Implementation NameLast Update Version
\( \MaxTxnBytesPerBlock \)\( 5{,}242{,}880 \)bytesMaximum number of transaction bytes in a blockMaxTxnBytesPerBlockv33
\( \MaxTimestampIncrement \)\( 25 \)secondsMaximum difference between successive timestampsMaxTimestampIncrementv7
\( \MaxVersionStringLen \)\( 128 \)bytesMaximum length of protocol version stringsMaxVersionStringLenv12
\( \MaxProposedExpiredOnlineAccounts \)\( 32 \)Maximum number of expired participation accounts in block headerMaxProposedExpiredOnlineAccountsv31
\( \PayoutMaxMarkAbsent \)\( 32 \)Maximum number of suspended participation accounts in block headerPayout.MaxMarkAbsentv40

Block Rewards

ParameterCurrent ValueUnitDescriptionReference Implementation NameLast Update Version
\( \BonusBaseAmount \)\( 10{,}000{,}000 \)μALGOBonus to be paid when block rewards first appliesBonus.BaseAmountv40
\( \BonusBaseRound \)roundEarliest round block rewards can applyBonus.BaseRoundv40
\( \BonusDecayInterval \)\( 1{,}000{,}000 \)roundsTime in rounds between 1% decays of the block rewards bonus componentBonus.DecayIntervalv40
\( \PayoutsChallengeBits \)\( 5 \)bitsFrequency of online account challenges, about \( \PayoutsChallengeInterval \times 2^{\PayoutsChallengeBits} \) roundsPayouts.ChallengeBitsv40
\( \PayoutsChallengeGracePeriod \)\( 200 \)roundsActive challenge round lookback for the response interval \( [r-2\PayoutsChallengeGracePeriod, r-\PayoutsChallengeGracePeriod] \)Payouts.ChallengeGracePeriodv40
\( \PayoutsChallengeInterval \)\( 1{,}000 \)roundsOnline account challenges interval, it defines the challenges frequencyPayouts.ChallengeIntervalv40
\( \PayoutsGoOnlineFee \)\( 2{,}000{,}000 \)μALGOMinimum keyreg transaction fee to be eligible for block rewardsPayouts.GoOnlineFeev40
\( \PayoutsMaxBalance \)\( 70{,}000{,}000{,}000{,}000 \)μALGOMaximum balance an account can have to be eligible for block rewardsPayouts.MaxBalancev40
\( \PayoutsMinBalance \)\( 30{,}000{,}000{,}000 \)μALGOMinimum balance an account must have to be eligible for block rewardsPayouts.MinBalancev40
\( \PayoutsPercent \)\( 50 \)%Percent of fees paid in a block that go to the proposer instead of the FeeSinkPayouts.Percentv40
\( \RewardUnit \)\( 1{,}000{,}000 \)μALGOSize of an earning unitRewardUnitv7
\( \RewardsRateRefreshInterval \)\( 500{,}000 \)roundsRate at which the reward rate is refreshedRewardsRateRefreshIntervalv7

Protocol Upgrade

ParameterCurrent ValueUnitDescriptionReference Implementation NameLast Update Version
\( \DefaultUpgradeWaitRounds \)\( 140{,}000 \)roundsDefault number of rounds needed to prepare for an upgradeDefaultUpgradeWaitRoundsv20
\( \MaxUpgradeWaitRounds \)\( 250{,}000 \)roundsMaximum number of rounds needed to prepare for an upgradeMaxUpgradeWaitRoundsv39
\( \MinUpgradeWaitRounds \)\( 10{,}000 \)roundsMinimum number of rounds needed to prepare for an upgradeMinUpgradeWaitRoundsv22
\( \UpgradeThreshold \)\( 9{,}000 \)Number of votes needed to execute an upgradeUpgradeThresholdv7
\( \UpgradeVoteRounds \)\( 10{,}000 \)roundsNumber of rounds over which an upgrade proposal is openUpgradeVoteRoundsv7

State Proof

ParameterCurrent ValueUnitDescriptionReference Implementation NameLast Update Version
\( \StateProofInterval \)\( 256 \)roundsNumber of rounds between state proofsStateProofIntervalv34
\( \StateProofMaxRecoveryIntervals \)\( 10 \)Number of state proof intervals that the network will try to catch-up withStateProofMaxRecoveryIntervalsv34
\( \StateProofStrengthTarget \)\( 256 \)bitsSecurity parameter for State Proof1StateProofStrengthTargetv34
\( \StateProofTopVoters \)\( 1{,}024 \)Maximum number of online accounts included in the vector commitment of state proofs participantsStateProofTopVotersv34
\( \StateProofVotersLookback \)\( 16 \)roundsDelay in rounds for online participant information committed to in the block header for State ProofStateProofVotersLookbackv34
\( \StateProofWeightThreshold \)\( 2^{32} \times \frac{30}{100} \)Fraction of participants proven to have signed by a State ProofStateProofWeightThresholdv34

Transaction

ParameterCurrent ValueUnitDescriptionReference Implementation NameLast Update Version
\( \MaxTxTail \)\( 1{,}000 \)Length of the Transaction Tail
\( \MaxKeyregValidPeriod \)\( 16{,}777{,}215 \)roundsMaximum voting range in a keyreg transaction, defined as \( (256 \times 2^{16})-1 \)MaxKeyregValidPeriodv31
\( \MaxTxGroupSize \)\( 16 \)txnMaximum number of transactions allowed in a groupMaxTxGroupSizev18
\( \MaxTxnLife \)\( 1,000 \)roundsMaximum difference between last valid and first valid round, defines transaction lifespan in the poolMaxTxnLifev7
\( \MinTxnFee \)\( 1{,}000 \)μALGOMinimum processing fee for any transactionMinTxnFeev7
\( \MaxTxnNoteBytes \)\( 1{,}024 \)bytesMaximum length of a transaction note fieldMaxTxnNoteBytesv7

Account

ParameterCurrent ValueUnitDescriptionReference Implementation NameLast Update Version
\( \DefaultKeyDilution \)\( 10{,}000 \)roundsGranularity of top-level ephemeral keys, equal to the number of second-level keys in each batchDefaultKeyDilutionv7
\( \MinBalance \)\( 100{,}000 \)μALGOMinimum balance requirement (MBR) for an accountMinBalancev9
\( \MaxAssetsPerAccount \)\( 0 \) (unlimited)Maximum number of assets per accountMaxAssetsPerAccountv32
\( \MaxAppsCreated \)\( 0 \) (unlimited)Maximum number of application created per accountMaxAppsCreatedv32
\( \MaxAppsOptedIn \)\( 0 \) (unlimited)Maximum number of application opted-in per accountMaxAppsOptedInv32
\( \MaximumMinimumBalance \)\( 0 \) (unlimited)μALGOMaximum MBR of an accountMaximumMinimumBalancev32

Asset

ParameterCurrent ValueUnitDescriptionReference Implementation NameLast Update Version
\( \MaxAssetDecimals \)\( 19 \)Maximum decimal precision of the asset supplyMaxAssetDecimalsv20
\( \MaxAssetNameBytes \)\( 32 \)bytesMaximum length of asset nameMaxAssetNameBytesv18
\( \MaxAssetUnitNameBytes \)\( 8 \)bytesMaximum length of asset unit (symbol)MaxAssetUnitNameBytesv18
\( \MaxAssetURLBytes \)\( 96 \)bytesMaximum length of asset URLMaxAssetURLBytesv28

LogicSig

ParameterCurrent ValueUnitDescriptionReference Implementation NameLast Update Version
\( \LogicSigMaxCost \)\( 20{,}000 \)opcodesMaximum opcode cost for LSigLogicSigMaxCostv18
\( \LogicSigMaxSize \)\( 1{,}000 \)bytesMaximum combined length of LSig program and LSig argumentsLogicSigMaxSizev18

Application

ParameterCurrent ValueUnitDescriptionReference Implementation NameLast Update Version
\( \AppFlatOptInMinBalance \)\( 100{,}000 \)μALGOMBR for opting in to a single applicationAppFlatOptInMinBalancev24
\( \AppFlatParamsMinBalance \)\( 100{,}000 \)μALGOMBR for creating a single applicationAppFlatParamsMinBalancev24
\( \BoxByteMinBalance \)\( 400 \)μALGO / byteMBR per byte of box storageBoxByteMinBalancev36
\( \BoxFlatMinBalance \)\( 2{,}500 \)μALGOMBR per box createdBoxFlatMinBalancev36
\( \BytesPerBoxReference \)\( 2{,}048\)bytesBox read and write payload per referenceBytesPerBoxReferencev41
\( \MaxBoxSize \)\( 32{,}768 \)bytesMaximum size of a boxMaxBoxSizev36
\( \MaxAppArgs \)\( 16 \)Maximum number of arguments for an appl transactionMaxAppArgsv24
\( \MaxAppBoxReferences \)\( 8 \)Maximum number of box references for an appl transactionMaxAppBoxReferencesv36
\( \MaxAppBytesValueLen \)\( 128 \)bytesMaximum length of a bytes value used in an application’s stateMaxAppBytesValueLenv28
\( \MaxAppKeyLen \)\( 64 \)bytesMaximum length of a key used in an application’s stateMaxAppKeyLenv24
\( \MaxAppProgramCost \)\( 700 \)opcodesMaximum cost of application Approval or ClearState application programMaxAppProgramCostv24
\( \MaxAppProgramLen \)\( 2{,}048 \)bytesMaximum length of application Approval or ClearState program pageMaxAppProgramLenv28
\( \MaxAppSumKeyValueLens \)\( 128 \)bytesMaximum sum of the lengths of the key and value of one app state entryMaxAppSumKeyValueLensv28
\( \MaxAppTotalArgLen \)\( 2{,}048 \)bytesMaximum sum of the lengths of argument for an appl transactionMaxAppTotalArgLenv24
\( \MaxAppTotalProgramLen \)\( 2{,}048 \)bytesMaximum combined length of application Approval and ClearState programsMaxAppTotalProgramLenv24
\( \MaxAppTotalTxnReferences \)\( 8 \)Maximum number of references for an appl transactionMaxAppTotalTxnReferencesv24
\( \MaxAppTxnAccounts \)\( 8 \)Maximum number of account references for an appl transactionMaxAppTxnAccountsv41
\( \MaxAppTxnForeignApps \)\( 8 \)Maximum number of application references for an appl transactionMaxAppTxnForeignAppsv28
\( \MaxAppTxnForeignAssets \)\( 8 \)Maximum number of asset references for an appl transactionMaxAppTxnForeignAssetsv28
\( \MaxAppAccess \)\( 16 \)Maximum number of resources access list for an appl transactionMaxAppAccessv41
\( \MaxExtraAppProgramPages \)\( 3 \)Maximum extra length for application program in pagesMaxExtraAppProgramPagesv28
\( \MaxGlobalSchemaEntries \)\( 64 \)Maximum number of key/value pairs of application global stateMaxGlobalSchemaEntriesv24
\( \MaxLocalSchemaEntries \)\( 16 \)Maximum number of key/value pairs of application local stateMaxLocalSchemaEntriesv24
\( \MaxInnerTransactions \)\( 16 \)txnMaximum number of inner transactions for an appl transactionMaxInnerTransactionsv30
\( \SchemaBytesMinBalance \)\( 25{,}000 \)μALGOAdditional MBR for []bytes values in application stateSchemaBytesMinBalancev24
\( \SchemaMinBalancePerEntry \)\( 25{,}000 \)μALGOMBR for key-value pair in application stateSchemaMinBalancePerEntryv24
\( \SchemaUintMinBalance \)\( 35{,}00 \)μALGOAdditional MBR for uint64 values in application stateSchemaUintMinBalancev24


  1. \( \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:

Previous Hash

The block’s previous hash, which is the cryptographic hash of the previous Block Header in the sequence, using:

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 value True.

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 an ApplicationCall transaction referring to this application ID is to be allowed. This program executes for all ApplicationCall transactions referring to this application ID except for those whose OnCompletion == ClearState, in which case the ClearStateProgram is executed instead. This program may modify the local or global state associated with this application. This field is encoded with msgpack field approv.

    • 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 an ApplicationCall transaction referring to this application ID is executed with OnCompletion == 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 field clearp.

    • 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 field gsch.

    • 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 field lsch.

    • 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 of ApprovalProgram and ClearStateProgram may not exceed \( \MaxAppTotalProgramLen \times (1+\ExtraProgramPages) \) bytes. This field is encoded with msgpack field epp and may not exceed \( \MaxExtraAppProgramPages \). This ExtraProgramPages 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 field v.

  • The “global state” (GlobalState) associated with this application, stored as a Key/Value Store. This field is encoded with msgpack field gs.

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}} \). A dc 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, the n field is set to the lowest value such that n is a multiple of \( \StateProofInterval \) and so that the n 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-zero v 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:

FIELDCODECTYPEREQUIRED
Transaction TypetypestringYes
SendersndaddressYes
Feefeeuint64Yes
First Valid Roundfvuint64Yes
Last Valid Roundlvuint64Yes
Genesis Hashgh[32]byteYes
Leaselx[32]byteNo
Genesis IdentifiergenstringNo
Groupgrp[32]byteNo
Rekey-torekeyaddressNo
Notenote[32]bytesNo

Transaction Type

The transaction type \( \TxType \) is a short string indicating the type of transaction.

The following transaction types are supported:

CODECDESCRIPTION
payALGO transfers (payment)
keyregConsensus keys registration
acfgAsset creation and configuration
axferAsset transfer
afrzAsset freeze and unfreeze
applApplication calls
stpfState Proof
hbConsensus 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:

FIELDCODECTYPEREQUIRED
Amountamtuint64Yes
ReceiverrcvaddressYes
Close-tocloseaddressNo

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:

FIELDCODECTYPEREQUIRED
Vote Public Keyvotekey[32]byteYes (for Online)
Selection Public Keyselkey[32]byteYes (for Online)
State Proof Public Keysprfkey[64]byteYes (for Online)
Vote Firstvotefstuint64Yes (for Online)
Vote Lastvotelstuint64Yes (for Online)
Vote Key Dilutionvotekduint64Yes (for Online)
Non-ParticipationnonpartboolNo

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:

FIELDCODECTYPEREQUIRED
Asset IDcaiduint64Yes (except for Creation)
Asset ParametersaparstructYes (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:

FIELDCODECTYPEDESCRIPTION
Totaltuint64Total amount of units of the asset
Decimalsdcuint32Number of digits after the decimal place
Default FrozendfboolFlag that specifies if the asset requires whitelisting (yes if true)
Unit NameunstringAsset unit symbol (or asset short-name)
Asset NameanstringAsset name
URLaustringURL to retrieve additional asset information
Metadata Hasham[32]byteCommitment to asset metadata
ManagermaddressAccount allowed to set the asset role-based access control and destroy the asset
ReserveraddressAccount whose asset holdings should be interpreted as “not mined” (this is purely a label)
FreezefaddressAccount allowed to change the account’s frozen state for the asset holdings
ClawbackcaddressAccount 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:

FIELDCODECTYPEREQUIRED
Asset IDxaiduint64Yes
Asset Amountaamtuint64No
Asset SenderasndaddressNo
Asset ReceiverarcvaddressYes
Asset Close-toacloseaddressNo

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:

FIELDCODECTYPEREQUIRED
Asset IDfaiduint64Yes
Freeze AddressfaddaddressYes
Frozen StatusafrzboolYes

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:

FIELDCODECTYPEREQUIRED
Application IDapiduint64Yes (except for Creation)
On Completion Actionapanuint64Yes
Approval Programapap[]byteNo
Clear State Programapsu[]byteNo
Reject Versionaprvuint64No
Extra Program Pagesapepuint64No
Global State SchemaapgsstructNo
Local State SchemaaplsstructNo
Argumentsapaa[][]byteNo
Foreign Accountsapat[]addressNo
Foreign Applicationsapfa[]uint64No
Foreign Assetsapas[]uint64No
Box Referencesapbx[]structNo
Access Listal[]structNo

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:

ACTIONVALUEEFFECT
NoOpOC0Only execute the Approval Program associated with this application ID, with no additional effects.
OptInOC1Before executing the Approval Program, allocate local state for this application ID into the sender’s account data.
CloseOutOC2After executing the Approval Program, clear any local state for this application ID out of the sender’s account data.
ClearStateOC3Do 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).
UpdateApplicationOC4After executing the Approval Program, replace the Approval Program and Clear State Program associated with this application ID with the programs specified in this transaction.
DeleteApplicationOC5After 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:

FIELDCODECTYPEDESCRIPTION
Number of Uintsnuiuint64The number of global 64-bit unsigned integer variables for the application.
Number of Bytesnbsuint64The 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:

FIELDCODECTYPEDESCRIPTION
Number of Uintsnuiuint64The number of local 64-bit unsigned integer variables for the application.
Number of Bytesnbsuint64The 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:

FIELDCODECTYPEDESCRIPTION
Indexiuint64A \( 1 \)-based index in the foreign applications (apfa) list.
Namen[]byteThe 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:

FIELDCODECTYPEDESCRIPTION
Addressd[]byteAn Account
Assetsuint64An Asset ID
Applicationpuint64An Application ID
HoldinghstructA Holding contains subfields d and s that refer to the Address and Asset of the Holding, respectively.
LocalslstructA Local contains subfields d and p that refer to the Address and Application of the Local State, respectively.
Box ReferencebstructA 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:

FIELDCODECTYPEREQUIRED
State Proof Typesptypeuint64Yes
State ProofspstructYes
MessagespmsgstructYes
State Proof Last Roundsprnduint64Yes

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 constant StateProofSender.

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

FIELDCODECTYPEREQUIRED
Heartbeat AddressaaddressYes
Heartbeat Seedsd[32]bytesYes
Heartbeat Vote IDvid[32]bytesYes
Heartbeat Key Dilutionkduint64Yes
Heartbeat ProofprfstructYes

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:

  1. Specifies transactions’ order: \( [A, B, C] \);

  2. Computes the hashes of each transaction in the list (without their group field set): \( [\Hash(A), \Hash(B), \Hash(C)] \);

  3. Hash them together in the specified order, getting the group hash: \( G = \Hash([\Hash(A), \Hash(B), \Hash(C)]) \);

  4. 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, using TG 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:

  1. State Proof transactions require no fee;

  2. 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 (field msig), or logic signature (field lsig).

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 prefix TX) 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 the subsig 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 in l.

    • An OPTIONAL multisignature lmsig from the authorizer address of the transaction over the bytes of the authorizer address and the bytes in l.

    • An OPTIONAL array of byte strings arg which are arguments supplied to the program in l (arg bytes are not covered by sig or msig).

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:

  1. All non-empty signatures are valid;

  2. 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 delta ld 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 a ld 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 the log opcode in the called application. The order of the entries follows the execution order of the log opcode invocations. The total number of log 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 of itx records a successful inner transaction. Each element will contain the transaction fields, encoded under the msgpack field txn, 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 to pay, axfer, acfg, and afrz. From Program Version 6, keyreg and appl 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, and rc, 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’s Uint field.
    • SetBytesAction (value = 2), indicating that the value for this key should be set to the value delta’s Bytes field.
    • DeleteAction (value = 3), indicating that the value for this key should be deleted.
  • Bytes, encoded as msgpack field bs, which specifies a byte slice value to set.

  • Uint, encoded as msgpack field ui, 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 toOptInOC

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

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 payand 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, or hb.

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

Minimal Ledger

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 the RewardsPool 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
    An uint64 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 to false 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 and RewardsPool),

  • 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 IDTRANSACTION TYPE
payPayment (ALGO transfer)
keyregConsensus participation keys registration and deregistration
acfgAlgorand Standard Asset transfer
axferAlgorand Standard Asset creation and reconfiguration
afrzAlgorand Standard Asset freeze (whitelisting and blacklisting)
applApplication (Smart Contract) call
stpfAlgorand State Proof
hbConsensus 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 transaction Group.

  • FirstValidRound \( \r_F \) and LastValidRound \( \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 a Lease(Sender, Lease), valid until the LastValidRound \( r_L \) expires. While the transaction maintains the Lease, no other transaction with the same Lease can be committed.

A typical use case of the Lease is a batch of signed transactions, with the same Lease, sent to the network to ensure only one is executed.

  • RekeyTo
    An Algorand address (32-byte). If non-zero, the transaction will set the Sender account’s spending key to this address as last transaction effect. Therefore, future transactions sent by the Sender 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 (either SingleSig, MultiSig, or LogicSig),

  • It is submitted to the right Ledger (GenesisHash);

  • Pays the MinFee (\( 1000 \) μALGO) or PerByteFee (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 in Group transactions;
    • Fee is independent of the TransactionType 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 and Lease) 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 specified round.

    • AllBalances(round): Returns all account states at the specified round.

    • Totals(round): Returns aggregate account totals for a given round, 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 the ledgerForTracker 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 round rnd. The accompanying delta parameter contains the state changes introduced by this block (see block evaluation section).

  • committedUpTo(rnd)
    Informs the Tracker that all blocks up to and including rnd 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:

  1. Distribution Rewards (Legacy)

  2. 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:

  1. The Heartbeat is not part of a larger group, and has a zero GroupID.
  2. The HbAddress is Online and under challenge with the grace period at least half over.
  3. The HbAddress is IncentiveEligible.
  4. There is no Note, Lease, or RekeyTo.

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:

  1. Making heartbeats free for accounts that are “nearly absent”.

or

  1. 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 of round -> (TXLease -> round) that saves the transaction Lease by observation round, and the mapping uses TXLease as keys to store the Lease expiring round.

  • blockHeaderData
    Contains recent block header data. The expected availability range is [Latest - MaxTxnLife, Latest], allowing MaxTxnLife + 1 rounds of lookback ( \( 1001 \) with current parameters).

  • lastValidMap
    A mapping of round -> (txid -> uint16) that enables the lookup of all transactions expiring in a given round. For each round, the inner map stores txids mapped to 16-bit unsigned integers representing the difference between the transaction’s lastValid 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 the lastValid field is lastValid < 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 next OnNewBlock call resyncs them.

Example of Remember function used by the txnHandler 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; if flush=false, \( \TP_{rq} \) is appended.

In summary:

  • OnNewBlock → calls rememberCommit(true) → replaces \( \TP_{pq} \) with \( \TP_{rq} \).

  • Remember → appends to \( \TP_{rq} \), then calls rememberCommit(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:

  1. Receiving transactions from the Network and prioritizing them,
  2. Parallel transactions verification,
  3. Enqueuing transactions in the \( \TP \),
  4. Serial transaction (FIFO) queue verification and gossip,
  5. Re-verification on a new appended block,
  6. Block assembly.

Step 1: Reception and Prioritization

TxPool-1

Step 2: Parallel Verification

TxPool-2

Step 3: Enqueuing

TxPool-3

Step 4: Serial Verification and Gossip

TxPool-4

Step 5: Verification on New Block

TxPool-5

Step 6: Block Assembly

TxPool-6

$$ \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 calling StartEvaluator 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 of Account 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 their LastValid 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 the StateProofNextRound 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.

AVM Overview

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

NameBoundAVM 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
anyany
nonenone

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:

  1. Logic Signature (or stateless) mode, used to execute Logic Signatures;

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



  1. 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, and CurrentApplicationAddress are available.

  • In pre-Version 4 programs, all Holdings are available to the asset_holding_get opcode, and all Locals are available to the app_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 asset X is made available in another, group resource sharing does not make A’s X 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:

    1. pay: sender (snd), receiver (rcv), and close-to (close) (if set).

    2. keyreg: sender (snd).

    3. acfg: sender (snd), configured asset (caid), and the configured asset holding of the sender.

    4. 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.

    5. 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 list al) that denotes the Box. A box reference contains an index i, and name n. The index refers to the i-th Application in the transaction’s foreign applications or access list array (only one of which can be used), with the usual convention that 0 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:

  1. Constants can be pushed directly with pushint or pushbytes opcodes. This method is more efficient for constants that are only used once.

  2. Constants can be loaded into storage separate from the Stack and Scratch Space, with intcblock or bytecblock opcodes. Then, constants from this storage can be pushed onto the Stack by referring to the type and index using intc, intc_[0123], bytec, and bytec_[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.

ACTIONVALUEDESCRIPTION
NoOpOC0Only execute the Approval Program associated with the application ID, with no additional effects.
OptInOC1Before executing the Approval Program, allocate local state for the application ID into the sender’s account data.
CloseOutOC2After executing the Approval Program, clear any local state for the application ID out of the sender’s account data.
ClearStateOC3Do 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).
UpdateApplicationOC4After executing the Approval Program, replace the Approval Program and Clear State Program associated with the application ID with the programs specified in this transaction.
DeleteApplicationOC5After 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

TYPEVALUEDESCRIPTION
unknown0Unknown type, invalid transaction
pay1ALGO transfers (payment)
keyreg2Consensus keys registration
acfg3Asset creation and configuration
axfer4Asset transfer
afrz5Asset freeze and unfreeze
appl6Application calls
stpf7State Proof
hb8Consensus 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

OPCODEDESCRIPTION
+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}
shlA times 2^B, modulo 2^64
shrA divided by 2^B
sqrtThe largest integer I such that I^2 <= A
bitlenThe 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
expA 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
itobconverts uint64 A to big-endian byte array, always of length 8
btoiconverts 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
mulwA times B as a 128-bit result in two uint64s. X is the high 64 bits, Y is the low
addwA plus B as a 128-bit result. X is the carry-bit, Y is the low-order 64 bits.
divwA,B / C. Fail if C == 0 or if result overflows.
divmodwW,X = (A,B / C,D); Y,Z = (A,B modulo C,D)
expwA 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

OPCODEDESCRIPTION
getbitBth 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
setbitCopy 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
getbyteBth byte of A, as an integer. If B is greater than or equal to the array length, the program fails
setbyteCopy 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
concatJoin A and B
lenYields length of byte value A
substring s eA 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
substring3A 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 lA 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
extract3A 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_uint16A 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_uint32A 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_uint64A 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 sCopy 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.
replace3Copy 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 edecode A which was base64-encoded using encoding E. Fail if A is not base64 encoded with encoding E
json_ref rkey 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.

OPCODEDESCRIPTION
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.
bsqrtThe 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.

OPCODEDESCRIPTION
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

OPCODEDESCRIPTION
sha256SHA256 hash of value A, yields [32]byte
keccak256Keccak256 hash of value A, yields [32]byte
sha512_256SHA512_256 hash of value A, yields [32]byte
sha3_256SHA3_256 hash of value A, yields [32]byte
falcon_verifyfor (data A, compressed-format signature B, pubkey C) verify the signature of data against the pubkey => {0 or 1}
ed25519verifyfor (data A, signature B, pubkey C) verify the signature of (“ProgData” || program_hash || data) against the pubkey => {0 or 1}
ed25519verify_barefor (data A, signature B, pubkey C) verify the signature of the data against the pubkey => {0 or 1}
ecdsa_verify vfor (data A, signature B, C and pubkey D, E) verify the signature of the data against the pubkey => {0 or 1}
ecdsa_pk_recover vfor (data A, recovery id B, signature C, D) recover a public key
ecdsa_pk_decompress vdecompress pubkey A into components X, Y
vrf_verify sVerify the proof B of message A against pubkey C. Returns vrf output and verification flag.
ec_add gfor curve points A and B, return the curve point A + B
ec_scalar_mul gfor curve point A and scalar B, return the curve point BA, the point A multiplied by the scalar B.
ec_pairing_check g1 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 gfor curve points A and scalars B, return curve point B0A0 + B1A1 + B2A2 + … + BnAn
ec_subgroup_check g1 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 gmaps field element A to group G
mimc cMiMC 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.

OPCODEDESCRIPTION
intcblock uint ...prepare block of uint64 constants for use by intc
intc iIth constant from intcblock
intc_0constant 0 from intcblock
intc_1constant 1 from intcblock
intc_2constant 2 from intcblock
intc_3constant 3 from intcblock
pushint uintimmediate 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 iIth constant from bytecblock
bytec_0constant 0 from bytecblock
bytec_1constant 1 from bytecblock
bytec_2constant 2 from bytecblock
bytec_3constant 3 from bytecblock
pushbytes bytesimmediate BYTES
pushbytess bytes ...push sequences of immediate byte arrays to stack (first byte array being deepest)
bzerozero filled byte-array of length A
arg nNth LogicSig argument
arg_0LogicSig argument 0
arg_1LogicSig argument 1
arg_2LogicSig argument 2
arg_3LogicSig argument 3
argsAth LogicSig argument
txn ffield F of current transaction
gtxn t ffield F of the Tth transaction in the current group
txna f iIth value of the array field F of the current transaction txna can be called using txn with 2 immediates.
txnas fAth value of the array field F of the current transaction
gtxna t f iIth 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 fAth value of the array field F from the Tth transaction in the current group
gtxns ffield F of the Ath transaction in the current group
gtxnsa f iIth value of the array field F from the Ath transaction in the current group gtxnsa can be called using gtxns with 2 immediates.
gtxnsas fBth value of the array field F from the Ath transaction in the current group
global fglobal field F
load iIth scratch space value. All scratch spaces are 0 at program start.
loadsAth scratch space value. All scratch spaces are 0 at program start.
store istore A to the Ith scratch space
storesstore B to the Ath scratch space
gload t iIth scratch space value of the Tth transaction in the current group
gloads iIth scratch space value of the Ath transaction in the current group
gloadssBth scratch space value of the Ath transaction in the current group
gaid tID of the asset or application created in the Tth transaction of the current group
gaidsID 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

INDEXNAMETYPEINDESCRIPTION
0Senderaddress32 byte address
1Feeuint64microalgos
2FirstValiduint64round number
3FirstValidTimeuint64v7UNIX timestamp of block before txn.FirstValid. Fails if negative
4LastValiduint64round number
5Note[]byteAny data up to 1024 bytes
6Lease[32]byte32 byte lease value
7Receiveraddress32 byte address
8Amountuint64microalgos
9CloseRemainderToaddress32 byte address
10VotePK[32]byte32 byte address
11SelectionPK[32]byte32 byte address
12VoteFirstuint64The first round that the participation key is valid.
13VoteLastuint64The last round that the participation key is valid.
14VoteKeyDilutionuint64Dilution for the 2-level participation key
15Type[]byteTransaction type as bytes
16TypeEnumuint64Transaction type as integer
17XferAssetuint64Asset ID
18AssetAmountuint64value in Asset’s units
19AssetSenderaddress32 byte address. Source of assets if Sender is the Asset’s Clawback address.
20AssetReceiveraddress32 byte address
21AssetCloseToaddress32 byte address
22GroupIndexuint64Position of this transaction within an atomic transaction group. A stand-alone transaction is implicitly element 0 in a group of 1
23TxID[32]byteThe computed ID for this transaction. 32 bytes.
24ApplicationIDuint64v2ApplicationID from ApplicationCall transaction
25OnCompletionuint64v2ApplicationCall transaction on completion action
27NumAppArgsuint64v2Number of ApplicationArgs
29NumAccountsuint64v2Number of Accounts
30ApprovalProgram[]bytev2Approval program
31ClearStateProgram[]bytev2Clear state program
32RekeyToaddressv232 byte Sender’s new AuthAddr
33ConfigAssetuint64v2Asset ID in asset config transaction
34ConfigAssetTotaluint64v2Total number of units of this asset created
35ConfigAssetDecimalsuint64v2Number of digits to display after the decimal place when displaying the asset
36ConfigAssetDefaultFrozenboolv2Whether the asset’s slots are frozen by default or not, 0 or 1
37ConfigAssetUnitName[]bytev2Unit name of the asset
38ConfigAssetName[]bytev2The asset name
39ConfigAssetURL[]bytev2URL
40ConfigAssetMetadataHash[32]bytev232 byte commitment to unspecified asset metadata
41ConfigAssetManageraddressv232 byte address
42ConfigAssetReserveaddressv232 byte address
43ConfigAssetFreezeaddressv232 byte address
44ConfigAssetClawbackaddressv232 byte address
45FreezeAssetuint64v2Asset ID being frozen or un-frozen
46FreezeAssetAccountaddressv232 byte address of the account whose asset slot is being frozen or un-frozen
47FreezeAssetFrozenboolv2The new frozen value, 0 or 1
49NumAssetsuint64v3Number of Assets
51NumApplicationsuint64v3Number of Applications
52GlobalNumUintuint64v3Number of global state integers in ApplicationCall
53GlobalNumByteSliceuint64v3Number of global state byteslices in ApplicationCall
54LocalNumUintuint64v3Number of local state integers in ApplicationCall
55LocalNumByteSliceuint64v3Number of local state byteslices in ApplicationCall
56ExtraProgramPagesuint64v4Number 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.
57Nonparticipationboolv5Marks an account nonparticipating for rewards
59NumLogsuint64v5Number of Logs (only with itxn in v5). Application mode only
60CreatedAssetIDuint64v5Asset ID allocated by the creation of an ASA (only with itxn in v5). Application mode only
61CreatedApplicationIDuint64v5ApplicationID allocated by the creation of an application (only with itxn in v5). Application mode only
62LastLog[]bytev6The last message emitted. Empty bytes if none were emitted. Application mode only
63StateProofPK[]bytev664 byte state proof public key
65NumApprovalProgramPagesuint64v7Number of Approval Program pages
67NumClearStateProgramPagesuint64v7Number of ClearState Program pages
68RejectVersionuint64v12Application version for which the txn must reject

Array Fields

INDEXNAMETYPEINDESCRIPTION
26ApplicationArgs[]bytev2Arguments passed to the application in the ApplicationCall transaction
28Accountsaddressv2Accounts listed in the ApplicationCall transaction
48Assetsuint64v3Foreign Assets listed in the ApplicationCall transaction
50Applicationsuint64v3Foreign Apps listed in the ApplicationCall transaction
58Logs[]bytev5Log messages emitted by an application call (only with itxn in v5). Application mode only
64ApprovalProgramPages[]bytev7Approval Program as an array of pages
66ClearStateProgramPages[]bytev7ClearState 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.

INDEXNAMETYPEINDESCRIPTION
0MinTxnFeeuint64microalgos
1MinBalanceuint64microalgos
2MaxTxnLifeuint64rounds
3ZeroAddressaddress32 byte address of all zero bytes
4GroupSizeuint64Number of transactions in this atomic transaction group. At least 1
5LogicSigVersionuint64v2Maximum supported version
6Rounduint64v2Current round number. Application mode only.
7LatestTimestampuint64v2Last confirmed block UNIX timestamp. Fails if negative. Application mode only.
8CurrentApplicationIDuint64v2ID of current application executing. Application mode only.
9CreatorAddressaddressv3Address of the creator of the current application. Application mode only.
10CurrentApplicationAddressaddressv5Address that the current application controls. Application mode only.
11GroupID[32]bytev5ID of the transaction group. 32 zero bytes if the transaction is not part of a group.
12OpcodeBudgetuint64v6The remaining cost that can be spent by opcodes in this program.
13CallerApplicationIDuint64v6The application ID of the application that called this application. 0 if this application is at the top-level. Application mode only.
14CallerApplicationAddressaddressv6The application address of the application that called this application. ZeroAddress if this application is at the top-level. Application mode only.
15AssetCreateMinBalanceuint64v10The additional minimum balance required to create (and opt-in to) an asset.
16AssetOptInMinBalanceuint64v10The additional minimum balance required to opt-in to an asset.
17GenesisHash[32]bytev10The Genesis Hash for the network.
18PayoutsEnabledboolv11Whether block proposal payouts are enabled.
19PayoutsGoOnlineFeeuint64v11The fee required in a keyreg transaction to make an account incentive eligible.
20PayoutsPercentuint64v11The percentage of transaction fees in a block that can be paid to the block proposer.
21PayoutsMinBalanceuint64v11The minimum balance an account must have in the agreement round to receive block payouts in the proposal round.
22PayoutsMaxBalanceuint64v11The 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

INDEXNAMETYPEDESCRIPTION
0AssetBalanceuint64Amount of the asset unit held by this account
1AssetFrozenboolIs the asset frozen or not

Asset Parameters

INDEXNAMETYPEINDESCRIPTION
0AssetTotaluint64Total number of units of this asset
1AssetDecimalsuint64See AssetParams.Decimals
2AssetDefaultFrozenboolFrozen by default or not
3AssetUnitName[]byteAsset unit name
4AssetName[]byteAsset name
5AssetURL[]byteURL with additional info about the asset
6AssetMetadataHash[32]byteArbitrary commitment
7AssetManageraddressManager address
8AssetReserveaddressReserve address
9AssetFreezeaddressFreeze address
10AssetClawbackaddressClawback address
11AssetCreatoraddressv5Creator address

Application Fields

Application fields used in the app_params_get opcode.

INDEXNAMETYPEINDESCRIPTION
0AppApprovalProgram[]byteBytecode of Approval Program
1AppClearStateProgram[]byteBytecode of Clear State Program
2AppGlobalNumUintuint64Number of uint64 values allowed in Global State
3AppGlobalNumByteSliceuint64Number of byte array values allowed in Global State
4AppLocalNumUintuint64Number of uint64 values allowed in Local State
5AppLocalNumByteSliceuint64Number of byte array values allowed in Local State
6AppExtraProgramPagesuint64Number of Extra Program Pages of code space
7AppCreatoraddressCreator address
8AppAddressaddressAddress for which this application has authority
9AppVersionuint64v12Version of the app, incremented each time the approval or clear program changes

Account Fields

Account fields used in the acct_params_get opcode.

INDEXNAMETYPEINDESCRIPTION
0AcctBalanceuint64Account balance in microalgos
1AcctMinBalanceuint64Minimum required balance for account, in microalgos
2AcctAuthAddraddressAddress the account is rekeyed to.
3AcctTotalNumUintuint64v8The total number of uint64 values allocated by this account in Global and Local States.
4AcctTotalNumByteSliceuint64v8The total number of byte array values allocated by this account in Global and Local States.
5AcctTotalExtraAppPagesuint64v8The number of extra app code pages used by this account.
6AcctTotalAppsCreateduint64v8The number of existing apps created by this account.
7AcctTotalAppsOptedInuint64v8The number of apps this account is opted into.
8AcctTotalAssetsCreateduint64v8The number of existing ASAs created by this account.
9AcctTotalAssetsuint64v8The numbers of ASAs held by this account (including ASAs this account created).
10AcctTotalBoxesuint64v8The number of existing boxes created by this account’s app.
11AcctTotalBoxBytesuint64v8The total number of bytes used by this account’s app’s box keys and values.
12AcctIncentiveEligibleboolv11Has this account opted into block payouts
13AcctLastProposeduint64v11The round number of the last block this account proposed.
14AcctLastHeartbeatuint64v11The round number of the last block this account sent a heartbeat.

Flow Control

OPCODEDESCRIPTION
errFail immediately.
bnz targetbranch to TARGET if value A is not zero
bz targetbranch to TARGET if value A is zero
b targetbranch unconditionally to TARGET
returnuse A as success value; end
popdiscard A
popn nremove N values from the top of the stack
dupduplicate A
dup2duplicate A and B
dupn nduplicate A, N times
dig nNth value from the top of the stack. dig 0 is equivalent to dup
bury nreplace the Nth value from the top of the stack with A. bury 0 fails.
cover nremove top of stack, and place it deeper in the stack such that N elements are above it. Fails if stack depth <= N.
uncover nremove 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 iNth (signed) value from the frame pointer.
frame_bury ireplace the Nth (signed) value from the frame pointer in the stack with A
swapswaps A and B on stack
selectselects one of two values based on top-of-stack: B if C != 0, else A
assertimmediately fail unless A is a non-zero number
callsub targetbranch unconditionally to TARGET, saving the next instruction on the call stack
proto a rPrepare top call frame for a retsub that will assume A args and R return values.
retsubpop 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

OPCODEDESCRIPTION
online_stakethe total online stake in the agreement round
logwrite A to log state of the current application
block ffield F of block A. Fail unless A falls between txn.LastValid-1002 and txn.FirstValid (exclusive)

Account Access

OPCODEDESCRIPTION
balancebalance 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_balanceminimum 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 fX is field F from account A. Y is 1 if A owns positive algos, else 0
voter_params_get fX 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

OPCODEDESCRIPTION
asset_holding_get fX 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 fX is field F from asset A. Y is 1 if A exists, else 0

Application Access

OPCODEDESCRIPTION
app_opted_in1 if account A is opted in to application B, else 0
app_local_getlocal state of the key B in the current application in account A
app_local_get_exX is the local state of application B, key C in account A. Y is 1 if key existed, else 0
app_global_getglobal state of the key A in the current application
app_global_get_exX is the global state of application A, key B. Y is 1 if key existed, else 0
app_local_putwrite C to key B in account A’s local state of the current application
app_global_putwrite B to key A in the global state of the current application
app_local_deldelete key B from account A’s local state of the current application
app_global_deldelete key A from the global state of the current application
app_params_get fX 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.

OPCODEDESCRIPTION
box_createcreate 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_extractread 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_replacewrite 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_spliceset 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_deldelete box named A if it exists. Return 1 if A existed, 0 otherwise
box_lenX is the length of box A if A exists, else 0. Y is 1 if A exists, else 0.
box_getX is the contents of box A if A exists, else ‘’. Y is 1 if A exists, else 0.
box_putreplaces 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_resizechange 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.

OPCODEDESCRIPTION
itxn_beginbegin preparation of a new inner transaction in a new transaction group
itxn_nextbegin preparation of a new inner transaction in the same transaction group
itxn_field fset field F of the current inner transaction to A
itxn_submitexecute 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 ffield F of the last inner transaction
itxna f iIth value of the array field F of the last inner transaction
itxnas fAth value of the array field F of the last inner transaction
gitxn t ffield F of the Tth transaction in the last inner group submitted
gitxna t f iIth value of the array field F from the Tth transaction in the last inner group submitted
gitxnas t fAth 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 or 0 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 the safe label as an argument. Since a 1 is pushed into the Stack, the execution jumps to the pop instruction after the safe: 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:

  1. For every transaction in the group, compute the earliest Version that supports all the fields and values in this transaction.

  2. Compute maxVerNo, the largest Version number across all the transactions in a group (of size 1 or more).

  3. 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

IndexNameInNotes
0Secp256k1secp256k1 curve, used in Bitcoin
1Secp256r1v7secp256r1 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)

IndexNameTypeInNotes
0Senderaddress32 byte address
1Feeuint64microalgos
2FirstValiduint64round number
3FirstValidTimeuint64v7UNIX timestamp of block before txn.FirstValid. Fails if negative
4LastValiduint64round number
5Note[]byteAny data up to 1024 bytes
6Lease[32]byte32 byte lease value
7Receiveraddress32 byte address
8Amountuint64microalgos
9CloseRemainderToaddress32 byte address
10VotePK[32]byte32 byte address
11SelectionPK[32]byte32 byte address
12VoteFirstuint64The first round that the participation key is valid.
13VoteLastuint64The last round that the participation key is valid.
14VoteKeyDilutionuint64Dilution for the 2-level participation key
15Type[]byteTransaction type as bytes
16TypeEnumuint64Transaction type as integer
17XferAssetuint64Asset ID
18AssetAmountuint64value in Asset’s units
19AssetSenderaddress32 byte address. Source of assets if Sender is the Asset’s Clawback address.
20AssetReceiveraddress32 byte address
21AssetCloseToaddress32 byte address
22GroupIndexuint64Position of this transaction within an atomic transaction group. A stand-alone transaction is implicitly element 0 in a group of 1
23TxID[32]byteThe computed ID for this transaction. 32 bytes.
24ApplicationIDuint64v2ApplicationID from ApplicationCall transaction
25OnCompletionuint64v2ApplicationCall transaction on completion action
27NumAppArgsuint64v2Number of ApplicationArgs
29NumAccountsuint64v2Number of Accounts
30ApprovalProgram[]bytev2Approval program
31ClearStateProgram[]bytev2Clear state program
32RekeyToaddressv232 byte Sender’s new AuthAddr
33ConfigAssetuint64v2Asset ID in asset config transaction
34ConfigAssetTotaluint64v2Total number of units of this asset created
35ConfigAssetDecimalsuint64v2Number of digits to display after the decimal place when displaying the asset
36ConfigAssetDefaultFrozenboolv2Whether the asset’s slots are frozen by default or not, 0 or 1
37ConfigAssetUnitName[]bytev2Unit name of the asset
38ConfigAssetName[]bytev2The asset name
39ConfigAssetURL[]bytev2URL
40ConfigAssetMetadataHash[32]bytev232 byte commitment to unspecified asset metadata
41ConfigAssetManageraddressv232 byte address
42ConfigAssetReserveaddressv232 byte address
43ConfigAssetFreezeaddressv232 byte address
44ConfigAssetClawbackaddressv232 byte address
45FreezeAssetuint64v2Asset ID being frozen or un-frozen
46FreezeAssetAccountaddressv232 byte address of the account whose asset slot is being frozen or un-frozen
47FreezeAssetFrozenboolv2The new frozen value, 0 or 1
49NumAssetsuint64v3Number of Assets
51NumApplicationsuint64v3Number of Applications
52GlobalNumUintuint64v3Number of global state integers in ApplicationCall
53GlobalNumByteSliceuint64v3Number of global state byteslices in ApplicationCall
54LocalNumUintuint64v3Number of local state integers in ApplicationCall
55LocalNumByteSliceuint64v3Number of local state byteslices in ApplicationCall
56ExtraProgramPagesuint64v4Number 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.
57Nonparticipationboolv5Marks an account nonparticipating for rewards
59NumLogsuint64v5Number of Logs (only with itxn in v5). Application mode only
60CreatedAssetIDuint64v5Asset ID allocated by the creation of an ASA (only with itxn in v5). Application mode only
61CreatedApplicationIDuint64v5ApplicationID allocated by the creation of an application (only with itxn in v5). Application mode only
62LastLog[]bytev6The last message emitted. Empty bytes if none were emitted. Application mode only
63StateProofPK[64]bytev6State proof public key
65NumApprovalProgramPagesuint64v7Number of Approval Program pages
67NumClearStateProgramPagesuint64v7Number of ClearState Program pages
68RejectVersionuint64v12Application 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

IndexNameTypeInNotes
0MinTxnFeeuint64microalgos
1MinBalanceuint64microalgos
2MaxTxnLifeuint64rounds
3ZeroAddressaddress32 byte address of all zero bytes
4GroupSizeuint64Number of transactions in this atomic transaction group. At least 1
5LogicSigVersionuint64v2Maximum supported version
6Rounduint64v2Current round number. Application mode only.
7LatestTimestampuint64v2Last confirmed block UNIX timestamp. Fails if negative. Application mode only.
8CurrentApplicationIDuint64v2ID of current application executing. Application mode only.
9CreatorAddressaddressv3Address of the creator of the current application. Application mode only.
10CurrentApplicationAddressaddressv5Address that the current application controls. Application mode only.
11GroupID[32]bytev5ID of the transaction group. 32 zero bytes if the transaction is not part of a group.
12OpcodeBudgetuint64v6The remaining cost that can be spent by opcodes in this program.
13CallerApplicationIDuint64v6The application ID of the application that called this application. 0 if this application is at the top-level. Application mode only.
14CallerApplicationAddressaddressv6The application address of the application that called this application. ZeroAddress if this application is at the top-level. Application mode only.
15AssetCreateMinBalanceuint64v10The additional minimum balance required to create (and opt-in to) an asset.
16AssetOptInMinBalanceuint64v10The additional minimum balance required to opt-in to an asset.
17GenesisHash[32]bytev10The Genesis Hash for the network.
18PayoutsEnabledboolv11Whether block proposal payouts are enabled.
19PayoutsGoOnlineFeeuint64v11The fee required in a keyreg transaction to make an account incentive eligible.
20PayoutsPercentuint64v11The percentage of transaction fees in a block that can be paid to the block proposer.
21PayoutsMinBalanceuint64v11The minimum balance an account must have in the agreement round to receive block payouts in the proposal round.
22PayoutsMaxBalanceuint64v11The 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 using txn with 2 immediates.
  • Availability: v2

Field txna

Fields (see transaction reference)

IndexNameTypeInNotes
26ApplicationArgs[]bytev2Arguments passed to the application in the ApplicationCall transaction
28Accountsaddressv2Accounts listed in the ApplicationCall transaction
48Assetsuint64v3Foreign Assets listed in the ApplicationCall transaction
50Applicationsuint64v3Foreign Apps listed in the ApplicationCall transaction
58Logs[]bytev5Log messages emitted by an application call (only with itxn in v5). Application mode only
64ApprovalProgramPages[]bytev7Approval Program as an array of pages
66ClearStateProgramPages[]bytev7ClearState 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 using gtxn 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 using gtxns 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 using extract 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 using replace 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 using replace 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

IndexNameNotes
0URLEncoding
1StdEncoding

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

IndexNameTypeNotes
0JSONString[]byte
1JSONUint64uint64
2JSONObject[]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

IndexNameTypeNotes
0AssetBalanceuint64Amount of the asset unit held by this account
1AssetFrozenboolIs 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

IndexNameTypeInNotes
0AssetTotaluint64Total number of units of this asset
1AssetDecimalsuint64See AssetParams.Decimals
2AssetDefaultFrozenboolFrozen by default or not
3AssetUnitName[]byteAsset unit name
4AssetName[]byteAsset name
5AssetURL[]byteURL with additional info about the asset
6AssetMetadataHash[32]byteArbitrary commitment
7AssetManageraddressManager address
8AssetReserveaddressReserve address
9AssetFreezeaddressFreeze address
10AssetClawbackaddressClawback address
11AssetCreatoraddressv5Creator 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

IndexNameTypeInNotes
0AppApprovalProgram[]byteBytecode of Approval Program
1AppClearStateProgram[]byteBytecode of Clear State Program
2AppGlobalNumUintuint64Number of uint64 values allowed in Global State
3AppGlobalNumByteSliceuint64Number of byte array values allowed in Global State
4AppLocalNumUintuint64Number of uint64 values allowed in Local State
5AppLocalNumByteSliceuint64Number of byte array values allowed in Local State
6AppExtraProgramPagesuint64Number of Extra Program Pages of code space
7AppCreatoraddressCreator address
8AppAddressaddressAddress for which this application has authority
9AppVersionuint64v12Version 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

IndexNameTypeInNotes
0AcctBalanceuint64Account balance in microalgos
1AcctMinBalanceuint64Minimum required balance for account, in microalgos
2AcctAuthAddraddressAddress the account is rekeyed to.
3AcctTotalNumUintuint64v8The total number of uint64 values allocated by this account in Global and Local States.
4AcctTotalNumByteSliceuint64v8The total number of byte array values allocated by this account in Global and Local States.
5AcctTotalExtraAppPagesuint64v8The number of extra app code pages used by this account.
6AcctTotalAppsCreateduint64v8The number of existing apps created by this account.
7AcctTotalAppsOptedInuint64v8The number of apps this account is opted into.
8AcctTotalAssetsCreateduint64v8The number of existing ASAs created by this account.
9AcctTotalAssetsuint64v8The numbers of ASAs held by this account (including ASAs this account created).
10AcctTotalBoxesuint64v8The number of existing boxes created by this account’s app.
11AcctTotalBoxBytesuint64v8The total number of bytes used by this account’s app’s box keys and values.
12AcctIncentiveEligibleboolv11Has this account opted into block payouts
13AcctLastProposeduint64v11The round number of the last block this account proposed.
14AcctLastHeartbeatuint64v11The 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

IndexNameTypeNotes
0VoterBalanceuint64Online stake in microalgos
1VoterIncentiveEligibleboolHad 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

IndexNameNotes
0VrfAlgorand

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

IndexNameTypeInNotes
0BlkSeed[32]byte
1BlkTimestampuint64
2BlkProposeraddressv11
3BlkFeesCollecteduint64v11
4BlkBonusuint64v11
5BlkBranch[32]bytev11
6BlkFeeSinkaddressv11
7BlkProtocol[]bytev11
8BlkTxnCounteruint64v11
9BlkProposerPayoutuint64v11

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

IndexNameNotes
0BN254g1G1 of the BN254 curve. Points encoded as 32 byte X following by 32 byte Y
1BN254g2G2 of the BN254 curve. Points encoded as 64 byte X following by 64 byte Y
2BLS12_381g1G1 of the BLS 12-381 curve. Points encoded as 48 byte X following by 48 byte Y
3BLS12_381g2G2 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

IndexNameNotes
0BN254Mp110MiMC configuration for the BN254 curve with Miyaguchi-Preneel mode, 110 rounds, exponent 5, seed “seed”
1BLS12_381Mp111MiMC 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”) and b (“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 and retsub.

  • Programs cannot make indirect jumps. b, bz, bnz, and callsub jump to an immediately specified address, and retsub jumps to the address currently on the top of the call Stack, which is manipulated only by previous calls to callsub and retsub.

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.

AVM Architecture

At a high level, the Algorand Virtual Machine (AVM) architecture is composed of four main components:

  1. 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.

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

  3. The Ledger state, which offers global, runtime-accessible information recorded on the Ledger.

  4. 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:

  1. The first line (#pragma version X) directs the assembler to generate bytecode targeting a specific AVM version,

  2. The // prefixes a line comment,

  3. The #define directive is used to define TEAL Macros,

  4. 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.

Stack

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.

AVM Run-Time

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

Contract Account

Delegated Signature

Delegated Signature

Stateful

Application Creation

Application Creation

Application Calls

Application Calls

⚙️ IMPLEMENTATION

Application Resources

Application Resources

App-to-App Calls

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 total opcode execution cost so far.

  • remainingBudget() -> int
    Returns the remaining opcode 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 the itxn_begin and itxn_next opcodes.

Program Evaluation

  • step() The core transition function that advances execution one opcode 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

  1. Opcode Fetch and Validation
    The function begins by checking whether the next opcode (determined by PC()) exists, is permitted under the current AVM version, and passes static validation. If any of these checks fail, the transaction is immediately REJECTED.

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

  3. Cost Update
    After executing the opcode, the transaction’s cost is updated. If the accumulated cost exceeds the allowed execution budget, the transaction is REJECTED.

  4. Stack Validation
    The stack is then validated to ensure:

    • It does not exceed the maximum allowed size.

    • All pushed values are valid StackValue types (either uint64 or []byte with length less than \( 4096 \) bytes).

    An invalid stack state causes the transaction to be REJECTED.

  5. Program Counter Update
    Finally, if all validations pass, the program counter PC() 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 during step() leads to immediate REJECTION of the transaction.

  • End of Program:
    If PC() 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 compact intc_X opcode. This optimization only affects the int pseudo-opcode.

  • bytecblock: Reorders byte or address constants by frequency of use. The most common values are placed first to use the more compact bytec_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 the proto() 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 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.

Keys Overview

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:

  1. Maps MUST contain keys in lexicographic order;

  2. Maps MUST omit key-value pairs where the value is a zero-value, unless otherwise specified;

  3. Positive integer values MUST be encoded as unsigned in msgpack, regardless of whether the value space is semantically signed or unsigned;

  4. Integer values MUST be represented in the shortest possible encoding;

  5. 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 no bin 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 and OT2: 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 Scheme
    • spc: 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 with arcXXXX (where XXXX is the 0-padded number of the ARC). For example, ARC-0003 can use any prefix starting with arc0003.
    • 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:

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 or A (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 that y 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, where L = 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:

ComponentSize (bytes)
Public Key\( 1793 \)
Private Key\( 2305 \)
Signature - CT Format\( 1538 \)
Signature - CompressedVariable, 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:

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:

  1. 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.

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

  3. 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:

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:

    • The participant information, encoded as described above, under the msgpack key p.

    • The signature information, encoded as described above, under the msgpack key s.

  • 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.

Network Overview

$$ \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:

TAGDESCRIPTION
"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 the epoch).

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 form string -> 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 the Action calls for a Disconnect 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 to Broadcast.

func Propagate(msg IncomingMessage) OutgoingMessage {
 return OutgoingMessage{Action: Broadcast, Tag: msg.Tag, Payload: msg.Data, Topics: nil}
}


  1. Removed in go-algorand 3.2.1., included for completeness. ↩2

Parameters

The following tables present the parametrization of the go-algorand network messages timing and sizing.

Performance Monitoring

NAMEVALUE (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

NAMEVALUE (Bytes)DESCRIPTION
AgreementVoteTagMaxSize\( 1228 \)Maximum size of an AgreementVoteTag message
MsgOfInterestTagMaxSize\( 45 \)Maximum size of a MsgOfInterestTag message
MsgDigestSkipTagMaxSize\( 69 \)Maximum size of a MsgDigestSkipTag message
NetPrioResponseTagMaxSize\( 850 \)Maximum size of a NetPrioResponseTag message
NetIDVerificationTagMaxSize\( 215 \)Maximum size of a NetIDVerificationTag message
ProposalPayloadTagMaxSize1\( 5{,}250{,}313 \)Maximum size of a ProposalPayloadTag message
StateProofSigTagMaxSize\( 6378 \)Maximum size of a StateProofSigTag message
TopicMsgRespTagMaxSize\( 6 \times 1024 \times 1024 \)Maximum size of a TopicMsgRespTag message
TxnTagMaxSize\( 5{,}000{,}000 \)Maximum size of a TxnTag message
UniEnsBlockReqTagMaxSize\( 67 \)Maximum size of a UniEnsBlockReqTag message
VoteBundleTagMaxSize\( 6 \times 1024 \times 1024 \)Maximum size of a VoteBundleTag message
MaxMessageLength\( 6 \times 1024 \times 1024 \)Maximum length of a message
averageMessageLength\( 2 \times 1024 \)Average length of a message (base allocation)


  1. 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 \):

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 the PerformanceMonitor.

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 to str type and are used as plain strings in packages where importing libp2p 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:

  1. User supplied path to privKey,
  2. The default path to privKey,
  3. 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 values P2PPersistPeerID and P2PPrivateKeyLocation (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 to localhost on the port 8080.

  • /ip4/192.168.1.1/tcp/8180/p2p/Qmewz5ZHN1AAGTarRbMupNPbZRfg3p5jUGoJ3JYEatJVVk, for a multiaddress composed of a network address 192.168.1.1:8180, joined together with the PeerID equal to Qmewz5ZHN1AAGTarRbMupNPbZRfg3p5jUGoJ3JYEatJVVk.

  • /ip4/192.255.2.8/tcp/8180/ws, for a multiaddress composed only of a network address 192.255.2.8:8180 indicating that the connection is through websockets ws.

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:

  1. 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 \).
  2. 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 \).
  3. Identity Verification: if the identityChallengeResponse is valid, the requester sends a NetIDVerificationTag 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 is mainnet.algorand.network and the service record is algobootstrap. 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 or archival),

  • persistent, set to true 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

$$ \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 the except parameter. If wait 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 to Broadcast, but semantically intended for message forwarding. The except 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 given badnode \( \Peer \).

  • RequestConnectOutgoing(replace bool)
    Initiates outgoing connections to new \( \Peer \), with the option to replace 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-specific genesisID, 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 and priorityTracker,

  • 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 Topology

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 the peersheap 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 REASONDESCRIPTION
disconnectBadDataThe sender \( \Peer \) is serving wrongly constructed data
disconnectReadErrorError reading the incoming message from the readBuffer
disconnectWriteErrorError sending a message to the \( \Peer \)
disconnectIdleConnThe \( \Peer \) has been idle for a certain amount of time
disconnectSlowConnThe \( \Peer \) connection is slow
disconnectLeastPerformingPeerThe \( \Peer \) is the worst performing peer in the peers container
disconnectCliqueResolveNode detected it is part of an isolated network partition
disconnectRequestReceivedDisconnection requested from the \( \Peer \) itself
disconnectStaleWriteA write operation has not been successful
disconnectDuplicateConnectionThe \( \Peer \) connection is already present
disconnectBadIdentityDataThe \( \Peer \) address is misconstrued (or the identity challenge has failed)
disconnectUnexpectedTopicRespThe \( \Peer \) has gossiped a non-normative topic
disconnectReasonNoneNo 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:

FIELDDESCRIPTION
monitoredConnectionsMaps connections being monitored. Messages from unmonitored connections are ignored
monitoredMessageTagsMaps message \( \Tag \) of interest. Typically, non-broadcast-type messages are monitored
stageThe current performance monitoring stage
peerLastMsgTimeMaps the timestamp of the last received message from each \( \Peer \)
lastIncomingMsgTimeTimestamp of the last received message from any \( \Peer \)
stageStartTimeTimestamp of the current stage start
pendingMessagesBucketsArray of message buckets for messages not received from all peers within pmMaxMessageWaitTime
connectionDelayTotal delay sustained by each \( \Peer \) during monitoring stages and average delay afterward (in nanoseconds)
firstMessageCountMaps peers to their accumulated first message count
msgCountTotal number of accumulated messages
accumulationTimeDuration 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 with GossipSub, 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 to PeerID (this is to get \( \mathcal{O}(1) \) lookup in both ways),
  • 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 the libp2p 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 \).

P2P Network Topology

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 the libp2p 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) and validationThrottled(-1) are libp2p 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

DHT reference implementation.



  1. Currently, TX tagged messages go into the algotx01 topic name.

  2. 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 \).

Hybrid Network Topology

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,

  • 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 the msgpack 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.

Ask DeepWiki

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:

$$ \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

Node 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 the algod 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.

Node Fast Catchup

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 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 public algod endpoints.

  • The admin.algod.token is used to access private algod endpoints.

Endpoints

Each algod endpoint path has two tags to separate endpoints into groups:

  • Tag 1: public (use the algod.token) or private (use algod.admin.token),

  • Tag 2: participating, nonparticipating, data, or experimental.

⚙️ IMPLEMENTATION

Each endpoint implements a Handler and a Client.

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.

ConstantValueDescription
MaxTealSourceBytes524,288 bytesMaximum allowed size for TEAL source code in API requests.
MaxTealDryrunBytes1,000,000 bytesMaximum allowed size for dryrun simulation requests.
MaxAssetResults1,000Maximum number of assets returned in a single call to /v2/accounts/{address}/assets.
DefaultAssetResults1,000Default number of assets returned if no explicit limit is provided (up to MaxAssetResults).
WaitForBlockTimeout1e+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 as omitempty, 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:"," or codec:",..."), 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 NameDefault ValueIntroduced in VersionLast Updated in Version
Version36036

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 NameDefault ValueIntroduced in VersionLast Updated in Version
RunHostedfalse33

Description:

Configures whether to run the Algorand node in Hosted mode.


Parameter NameDefault ValueIntroduced in VersionLast Updated in Version
EnableFollowModefalse2727

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 NameDefault ValueIntroduced in VersionLast Updated in Version
Archivalfalse00

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 NameDefault ValueIntroduced in VersionLast Updated in Version
HotDataDirEmpty string3131

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 NameDefault ValueIntroduced in VersionLast Updated in Version
ColdDataDirEmpty string3131

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 NameDefault ValueIntroduced in VersionLast Updated in Version
TrackerDBDirEmpty string3131

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 NameDefault ValueIntroduced in VersionLast Updated in Version
BlockDBDirEmpty string3131

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 NameDefault ValueIntroduced in VersionLast Updated in Version
CatchpointDirEmpty string3131

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 NameDefault ValueIntroduced in VersionLast Updated in Version
StateproofDirEmpty string3131

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 NameDefault ValueIntroduced in VersionLast Updated in Version
CrashDBDirEmpty string3131

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 NameDefault ValueIntroduced in VersionLast Updated in Version
OptimizeAccountsDatabaseOnStartupfalse1010

Description:

Controls whether the Account database would be optimized on algod startup.


Parameter NameDefault ValueIntroduced in VersionLast Updated in Version
LedgerSynchronousMode21212

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 in 2, 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 NameDefault ValueIntroduced in VersionLast Updated in Version
AccountsRebuildSynchronousMode11212

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 NameDefault ValueIntroduced in VersionLast Updated in Version
MaxAcctLookback42323

Description:

Sets the maximum lookback range for Account states.

📎 EXAMPLE

The Ledger can answer Account state questions for the range [Latest - MaxAcctLookback, Latest].


Parameter NameDefault ValueIntroduced in VersionLast Updated in Version
MaxBlockHistoryLookback03131

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 NameDefault ValueIntroduced in VersionLast Updated in Version
StorageEnginesqlite2828

Description:

Sets the type of storage to use for the Ledger. Available options are:

  • sqlite (default).
  • pebbledb (experimental, in development).

🌐 Networking


Parameter NameDefault ValueIntroduced in VersionLast Updated in Version
GossipFanout400

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 NameDefault ValueIntroduced in VersionLast Updated in Version
NetAddressEmpty string00

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 on localhost.


Parameter NameDefault ValueIntroduced in VersionLast Updated in Version
PublicAddressEmpty string00

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 NameDefault ValueIntroduced in VersionLast Updated in Version
TLSCertFileEmpty string00

Description:

The certificate file used for the Relay Network if provided.


Parameter NameDefault ValueIntroduced in VersionLast Updated in Version
TLSKeyFileEmpty string00

Description:

The key file used for the Relay network if provided.


Parameter NameDefault ValueIntroduced in VersionLast 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 NameDefault ValueIntroduced in VersionLast Updated in Version
FallbackDNSResolverAddressEmpty string00

Description:

Defines the fallback DNS resolver address used if the system resolver fails to retrieve SRV records.


Parameter NameDefault ValueIntroduced in VersionLast Updated in Version
UseXForwardedForAddressFieldEmpty string00

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 NameDefault ValueIntroduced in VersionLast Updated in Version
ForceRelayMessagesEmpty string00

Description:

Indicates whether the Network library should relay messages even if no NetAddress is specified.


Parameter NameDefault ValueIntroduced in VersionLast Updated in Version
DNSSecurityFlags96 (default was 1)34

Description:

Instructs algod validating DNS responses.

Possible flag values are:

  • 0: Disabled.
  • 1: Validate SRV response.
  • 2: Validate relay names for addresses resolution.
  • 4: Validate telemetry and metrics names for addresses resolution.
  • 8: Validate TXT response.

Parameter NameDefault ValueIntroduced in VersionLast Updated in Version
EnablePingHandlertrue66

Description:

Controls whether the gossip node responds to ping messages with a pong message.


Parameter NameDefault ValueIntroduced in VersionLast Updated in Version
NetworkProtocolVersionEmpty string66

Description:

Overrides the Network protocol version (if present).


Parameter NameDefault ValueIntroduced in VersionLast Updated in Version
EnableGossipServicetrue3333

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 NameDefault ValueIntroduced in VersionLast Updated in Version
EnableLedgerServicefalse77

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 NameDefault ValueIntroduced in VersionLast Updated in Version
EnableBlockServicefalse77

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 NameDefault ValueIntroduced in VersionLast Updated in Version
EnableGossipBlockServicetrue88

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 NameDefault ValueIntroduced in VersionLast Updated in Version
DisableNetworkingfalse1616

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 NameDefault ValueIntroduced in VersionLast Updated in Version
EnableP2Pfalse3131

Description:

Enables the Peer-to-Peer Network. When both EnableP2P and EnableP2PHybridMode are set, EnableP2PHybridMode takes precedence.


Parameter NameDefault ValueIntroduced in VersionLast Updated in Version
EnableP2PHybridModefalse3434

Description:

Turns on both Relay and Peer-to-Peer (Hybrid) Networking. Enabling this setting also requires PublicAddress to be set.


Parameter NameDefault ValueIntroduced in VersionLast Updated in Version
P2PHybridNetAddressEmpty string3131

Description:

Sets the listen address used for P2P networking, if Hybrid Network is set.


Parameter NameDefault ValueIntroduced in VersionLast Updated in Version
EnableDHTProvidersfalse3434

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 NameDefault ValueIntroduced in VersionLast Updated in Version
P2PPersistPeerIDfalse2929

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 NameDefault ValueIntroduced in VersionLast Updated in Version
P2PPrivateKeyLocationEmpty string2929

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 NameDefault ValueIntroduced in VersionLast Updated in Version
MaxConnectionsPerIP83 (default was 30)35

Description:

Defines the maximum number of connections allowed per IP address.


Parameter NameDefault ValueIntroduced in VersionLast Updated in Version
IncomingConnectionsLimit24000 (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 NameDefault ValueIntroduced in VersionLast Updated in Version
P2PHybridIncomingConnectionsLimit12003434

Description:

Used as IncomingConnectionsLimit for P2P connections in Hybrid Network. For pure P2P Network the field IncomingConnectionsLimit is used instead.


Parameter NameDefault ValueIntroduced in VersionLast Updated in Version
BroadcastConnectionsLimit-144

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:

    1. Outgoing connections: Peers a node actively connects to.
    2. 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 NameDefault ValueIntroduced in VersionLast Updated in Version
AnnounceParticipationKeytrue44

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 NameDefault ValueIntroduced in VersionLast Updated in Version
PriorityPeersEmpty string44

Description:

Specifies peer IP addresses that should always get outgoing broadcast messages from
this node.


Parameter NameDefault ValueIntroduced in VersionLast Updated in Version
ConnectionsRateLimitingCount6044

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 NameDefault ValueIntroduced in VersionLast Updated in Version
ConnectionsRateLimitingWindowSeconds144

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 NameDefault ValueIntroduced in VersionLast Updated in Version
DisableOutgoingConnectionThrottlingfalse55

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 NameDefault ValueIntroduced in VersionLast Updated in Version
DisableLocalhostConnectionRateLimittrue1616

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 NameDefault ValueIntroduced in VersionLast Updated in Version
BaseLoggerDebugLevel40 (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 calls panic().

  • 1: Fatal: Logs and then calls os.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 NameDefault ValueIntroduced in VersionLast Updated in Version
CadaverSizeTarget00 (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 NameDefault ValueIntroduced in VersionLast Updated in Version
CadaverDirectoryEmpty string2727

Description:

If not set, MakeService will attempt to use ColdDataDir instead.


Parameter NameDefault ValueIntroduced in VersionLast Updated in Version
LogFileDirEmpty string3131

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 NameDefault ValueIntroduced in VersionLast Updated in Version
LogArchiveDirEmpty string3131

Description:

An optional directory to store the log archive. If not specified, the node will use the ColdDataDir.


Parameter NameDefault ValueIntroduced in VersionLast Updated in Version
LogSizeLimit107374182400

Description:

Sets the node log file size limit in bytes. When set to 0, logs will be written to stdout.


Parameter NameDefault ValueIntroduced in VersionLast Updated in Version
LogArchiveNamenode.archive.log44

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 NameDefault ValueIntroduced in VersionLast Updated in Version
LogArchiveMaxAgeEmpty string44

Description:

Specifies the maximum age for a node log. Valid units are s seconds, m minutes, h hours.


Parameter NameDefault ValueIntroduced in VersionLast Updated in Version
EnableRequestLoggerfalse44

Description:

Enables the logging of the incoming requests to the telemetry server.


Parameter NameDefault ValueIntroduced in VersionLast Updated in Version
TelemetryToLogtrue55

Description:

Configures whether to record messages to node.log that are usually only sent to remote event monitoring.


Parameter NameDefault ValueIntroduced in VersionLast Updated in Version
EnableVerbosedTransactionSyncLoggingfalse1717

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 NameDefault ValueIntroduced in VersionLast Updated in Version
NodeExporterListenAddress:910000

Description:

Sets the specific address for publishing metrics.


Parameter NameDefault ValueIntroduced in VersionLast Updated in Version
EnableMetricReportingfalse00

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 NameDefault ValueIntroduced in VersionLast Updated in Version
NodeExporterPath./node_exporter00

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 NameDefault ValueIntroduced in VersionLast Updated in Version
EnableAssembleStatsEmpty string33

Description:

Specifies whether to emit the AssembleBlockMetrics telemetry event.


Parameter NameDefault ValueIntroduced in VersionLast Updated in Version
EnableProcessBlockStatsEmpty string33

Description:

Specifies whether to emit the ProcessBlockMetrics telemetry event.


Parameter NameDefault ValueIntroduced in VersionLast Updated in Version
PeerConnectionsUpdateInterval360055

Description:

Defines the interval at which the peer connections’ information is sent to telemetry. Defined in seconds.


Parameter NameDefault ValueIntroduced in VersionLast Updated in Version
HeartbeatUpdateInterval6002727

Description:

Defines the interval at which the heartbeat information is sent to the telemetry (when enabled). Defined in seconds. Minimum value is 60.


Parameter NameDefault ValueIntroduced in VersionLast Updated in Version
EnableRuntimeMetricsfalse2222

Description:

Exposes Go runtime metrics in /metrics and via node_exporter.


Parameter NameDefault ValueIntroduced in VersionLast Updated in Version
EnableNetDevMetricsfalse3434

Description:

Exposes network interface total bytes sent/received metrics in /metrics


Parameter NameDefault ValueIntroduced in VersionLast Updated in Version
EnableAccountUpdatesStatsfalse1616

Description:

Specifies whether to emit the AccountUpdates telemetry event.


Parameter NameDefault ValueIntroduced in VersionLast Updated in Version
AccountUpdatesStatsInterval50000000001616

Description:

Time interval in nanoseconds between accountUpdates telemetry events.


Parameter NameDefault ValueIntroduced in VersionLast Updated in Version
EnableUsageLogfalse3131

Description:

Enables 10Hz log of CPU and RAM usage. Also adds algod_ram_usage (number of bytes in use) to /metrics.


🔗 API & Endpoints


Parameter NameDefault ValueIntroduced in VersionLast Updated in Version
EndpointAddress127.0.0.1:000

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 NameDefault ValueIntroduced in VersionLast Updated in Version
EnablePrivateNetworkAccessHeaderfalse3535

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 NameDefault ValueIntroduced in VersionLast Updated in Version
RestReadTimeoutSeconds1544

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 NameDefault ValueIntroduced in VersionLast Updated in Version
RestWriteTimeoutSeconds12044

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 NameDefault ValueIntroduced in VersionLast Updated in Version
BlockServiceCustomFallbackEndpointsEmpty string1616

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 NameDefault ValueIntroduced in VersionLast Updated in Version
RestConnectionsSoftLimit10242020

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 NameDefault ValueIntroduced in VersionLast Updated in Version
RestConnectionsHardLimit20482020

Description:

Defines the maximum number of active connections the API server will accept before closing requests with no response.


Parameter NameDefault ValueIntroduced in VersionLast Updated in Version
MaxAPIResourcesPerAccount1000002121

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 NameDefault ValueIntroduced in VersionLast Updated in Version
MaxAPIBoxPerApplication1000002525

Description:

Defines the maximum number of boxes per application that will be returned in GetApplicationBoxes REST API responses.


Parameter NameDefault ValueIntroduced in VersionLast Updated in Version
EnableExperimentalAPIfalse2626

Description:

Enables experimental API endpoint.

These endpoints have no guarantees in terms of functionality or future support.


Parameter NameDefault ValueIntroduced in VersionLast Updated in Version
DisableAPIAuthfalse3030

Description:

Disables authentication for public (non-admin) API endpoints.


🗳️ Consensus & Agreement


Parameter NameDefault ValueIntroduced in VersionLast Updated in Version
EnableAgreementReportingfalse33

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 NameDefault ValueIntroduced in VersionLast Updated in Version
EnableAgreementTimeMetricsfalse33

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 NameDefault ValueIntroduced in VersionLast Updated in Version
ProposalAssemblyTime50000000019 (default was 250000000)23

Description:

The maximum amount of time to spend on generating a proposal block. Expressed in nanoseconds.


Parameter NameDefault ValueIntroduced in VersionLast Updated in Version
AgreementIncomingVotesQueueLength2000021 (default was 10000)27

Description:

Sets the buffer size holding incoming votes.


Parameter NameDefault ValueIntroduced in VersionLast Updated in Version
AgreementIncomingProposalsQueueLength5021 (default was 25)27

Description:

Sets the buffer size holding incoming proposals.


Parameter NameDefault ValueIntroduced in VersionLast Updated in Version
AgreementIncomingBundlesQueueLength1521 (default was 7)27

Description:

Sets the buffer size holding incoming bundles.


🔄 Transaction Pool & Sync


Parameter NameDefault ValueIntroduced in VersionLast Updated in Version
TxPoolExponentialIncreaseFactor200

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 NameDefault ValueIntroduced in VersionLast Updated in Version
TxBacklogServiceRateWindowSeconds102727

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 NameDefault ValueIntroduced in VersionLast Updated in Version
TxBacklogReservedCapacityPerPeer202727

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 NameDefault ValueIntroduced in VersionLast Updated in Version
TxBacklogAppTxRateLimiterMaxSize10485763232

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 NameDefault ValueIntroduced in VersionLast Updated in Version
TxBacklogAppTxPerSecondRate1003232

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 NameDefault ValueIntroduced in VersionLast Updated in Version
TxBacklogRateLimitingCongestionPct503232

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 NameDefault ValueIntroduced in VersionLast Updated in Version
EnableTxBacklogAppRateLimitingtrue3232

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 NameDefault ValueIntroduced in VersionLast Updated in Version
TxBacklogAppRateLimitingCountERLDropsfalse3535

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 NameDefault ValueIntroduced in VersionLast Updated in Version
EnableTxBacklogRateLimitingtrue27 (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 NameDefault ValueIntroduced in VersionLast Updated in Version
TxBacklogSize260002727

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 NameDefault ValueIntroduced in VersionLast Updated in Version
TxPoolSize750000 (default was 50000)23

Description:

Defines the maximum number of transactions stored in the Transaction Pool buffer before being processed or discarded.


Parameter NameDefault ValueIntroduced in VersionLast Updated in Version
TxSyncTimeoutSeconds3000

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 NameDefault ValueIntroduced in VersionLast Updated in Version
TxSyncIntervalSeconds6000

Description:

Defines the number of seconds between transaction synchronizations.


Parameter NameDefault ValueIntroduced in VersionLast Updated in Version
TxSyncServeResponseSize100000033

Description:

Sets the maximum size, in bytes, that the synchronization server will return when serving transaction synchronization requests.


Parameter NameDefault ValueIntroduced in VersionLast Updated in Version
VerifiedTranscationsCacheSize15000014 (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 NameDefault ValueIntroduced in VersionLast Updated in Version
ForceFetchTransactionsfalse1717

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 NameDefault ValueIntroduced in VersionLast Updated in Version
TransactionSyncDataExchangeRate01717

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 NameDefault ValueIntroduced in VersionLast Updated in Version
TransactionSyncSignificantMessageThreshold01717

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 NameDefault ValueIntroduced in VersionLast Updated in Version
CatchupFailurePeerRefreshRate1000

Description:

Specifies the maximum number of consecutive attempts to Fast Catchup after which peer connections are replaced.


Parameter NameDefault ValueIntroduced in VersionLast Updated in Version
CatchupParallelBlocks163 (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 NameDefault ValueIntroduced in VersionLast Updated in Version
CatchpointInterval1000077

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 NameDefault ValueIntroduced in VersionLast Updated in Version
CatchpointFileHistoryLength36577

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 NameDefault ValueIntroduced in VersionLast Updated in Version
CatchupHTTPBlockFetchTimeoutSec499

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 NameDefault ValueIntroduced in VersionLast Updated in Version
CatchupGossipBlockFetchTimeoutSec499

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 NameDefault ValueIntroduced in VersionLast Updated in Version
CatchupLedgerDownloadRetryAttempts5099

Description:

Controls the number of attempts the Ledger fetcher would perform before giving up the Fast Catchup to the provided Catchpoint.


Parameter NameDefault ValueIntroduced in VersionLast Updated in Version
CatchupBlockDownloadRetryAttempts100099

Description:

Controls the number of attempts the Block fetcher would perform before giving up on a provided Catchpoint.


Parameter NameDefault ValueIntroduced in VersionLast Updated in Version
CatchpointTracking01111

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 as CatchpointInterval > 0.
  • 2: Track Catchpoints and always generate Catchpoint Files as long as CatchpointInterval > 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 as CatchpointInterval > 0.
  • Any other values of this field would behave as if the default value was provided.

Parameter NameDefault ValueIntroduced in VersionLast Updated in Version
MaxCatchpointDownloadDuration4320000000000013 (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 NameDefault ValueIntroduced in VersionLast Updated in Version
MinCatchpointFileDownloadBytesPerSecond204801313

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 NameDefault ValueIntroduced in VersionLast Updated in Version
CatchupBlockValidateMode00001616

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 NameDefault ValueIntroduced in VersionLast Updated in Version
IncomingMessageFilterBucketCount500

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 NameDefault ValueIntroduced in VersionLast Updated in Version
IncomingMessageFilterBucketSize51200

Description:

Defines the size of each incoming message hash bucket.


Parameter NameDefault ValueIntroduced in VersionLast Updated in Version
OutgoingMessageFilterBucketCount300

Description:

Defines the number of outgoing message hash buckets.


Parameter NameDefault ValueIntroduced in VersionLast Updated in Version
OutgoingMessageFilterBucketSize12800

Description:

Defines the size of each outgoing message hash bucket.


Parameter NameDefault ValueIntroduced in VersionLast Updated in Version
EnableOutgoingNetworkMessageFilteringtrue00

Description:

Enables the filtering of outgoing messages by comparing their hashes with those in the message hash buckets.


Parameter NameDefault ValueIntroduced in VersionLast Updated in Version
EnableIncomingMessageFilterfalse00

Description:

Enables the filtering of incoming messages.


Parameter NameDefault ValueIntroduced in VersionLast Updated in Version
TxIncomingFilteringFlags12626

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 NameDefault ValueIntroduced in VersionLast Updated in Version
TxIncomingFilterMaxSize5000002828

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 NameDefault ValueIntroduced in VersionLast Updated in Version
EnableDeveloperAPIfalse99

Description:

Enables teal/compile and teal/dryrun API endpoints.


Parameter NameDefault ValueIntroduced in VersionLast Updated in Version
DeadlockDetection011

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 NameDefault ValueIntroduced in VersionLast Updated in Version
DeadlockDetectionThreshold302020

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 NameDefault ValueIntroduced in VersionLast Updated in Version
EnableProfilerfalse00

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 NameDefault ValueIntroduced in VersionLast Updated in Version
NetworkMessageTraceServerEmpty string1313

Description:

A host:port address to report graph propagation trace info to.


Parameter NameDefault ValueIntroduced in VersionLast Updated in Version
EnableTxnEvalTracerfalse2727

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 NameDefault ValueIntroduced in VersionLast Updated in Version
ReservedFDs25622

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 NameDefault ValueIntroduced in VersionLast Updated in Version
DisableLedgerLRUCachefalse2727

Description:

Disables LRU caches in Ledger.


Parameter NameDefault ValueIntroduced in VersionLast Updated in Version
BlockServiceMemCap5000000002828

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 NameDefault ValueIntroduced in VersionLast Updated in Version
GoMemLimit03434

Description:

Provides the Go runtime with a soft memory limit. The default behavior is unlimited, unless the GOMEMLIMIT environment variable is set.


🚫 Deprecated


Parameter NameDefault ValueIntroduced in VersionLast Updated in Version
ReconnectTime600000000000 (default was 60)1

Description:

Deprecated and unused.


Parameter NameDefault ValueIntroduced in VersionLast Updated in Version
PeerPingPeriodSeconds000

Description:

Deprecated and unused.


Parameter NameDefault ValueIntroduced in VersionLast Updated in Version
EnableTopAccountsReportingfalse00

Description:

Deprecated and unused.


Parameter NameDefault ValueIntroduced in VersionLast Updated in Version
SuggestedFeeBlockHistory300

Description:

Deprecated and unused.


Parameter NameDefault ValueIntroduced in VersionLast Updated in Version
SuggestedFeeSlidingWindowSize5033

Description:

Deprecated and unused.


Parameter NameDefault ValueIntroduced in VersionLast Updated in Version
ParticipationKeysRefreshInterval600000000001616

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:

  1. First item
  2. Second item
  3. 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:

MonthSavings
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:

NameQuantitySize
Item A1S
Item B5M
Item C10XL

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:

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.