-
Notifications
You must be signed in to change notification settings - Fork 126
Vara‐eth consensus
Vara-eth is a protocol that allows programs deployed on the Ethereum network to be executed by off-chain executors that live outside the main (parent) Ethereum chain. A transaction sent by a user on Ethereum is executed by a Vara-eth executor, and the result of this execution is later committed back to Ethereum in subsequent blocks.
This significantly expands what Ethereum programs can do and can drastically reduce gas consumption. However, even just sending a transaction on Ethereum is expensive, even if we ignore the cost of the main smart contract logic. Moreover, if transactions can only be sent via Ethereum L1, that also limits the throughput of Vara-eth programs.
To reduce both the cost of sending transactions and increase throughput, Vara-eth introduces so-called injected transactions. These are transactions that are not sent via the main Ethereum network; instead, they are sent directly to Vara-eth executors via p2p.
While this has obvious advantages, it also introduces a classical blockchain problem: how do executors reach consensus on:
- which injected transactions to execute,
- when to execute them, and
- in what order to execute them?
Previously, when all transactions came only from Ethereum, all executors simply followed Ethereum data. All honest executors had the same program state up to the last finalized Ethereum block (in the worst case), and usually converged to the same state within a few seconds after receiving the next Ethereum chain head. In other words, without having its own consensus algorithm, Vara-eth inherited consensus from the Ethereum network.
Once we add injected transactions, the situation changes radically: we now need a way for Vara-eth nodes to agree on the ordering and inclusion of these injected transactions.
The natural question is:
Can we avoid implementing a full-blown consensus algorithm inside Vara-eth?
It turns out that Ethereum’s consensus still helps us achieve consensus between Vara-eth nodes even in this extended setting. The rest of this article explains how.
To explain how the Vara-eth consensus algorithm works, we first need the concept of an Announce.
When we introduce injected transactions, we partially borrow ideas from classic L1 blockchains.
For every Ethereum slot, Vara-eth executor nodes take turns acting as a block producer. A block producer is allowed to pick an ordered set of known valid injected transactions and broadcast this set to other validator nodes. This “block of transactions” is what we call an Announce.
Besides the injected transactions themselves, an Announce contains the following fields:
struct Announce {
// Hash of the Ethereum block this announce is attached to.
//
// In addition to knowing which injected_transactions should be executed,
// nodes must know which Ethereum transactions to execute, and with which
// block number and timestamp.
//
// Adding this field makes an Announce dependent on Ethereum blocks:
// each announce is tied to a specific Ethereum block and can only be
// finalized together with that block.
block_hash: H256,
// Hash of the parent announce. We must know on top of which program state
// to execute both injected and Ethereum transactions.
//
// The block_hash alone is not enough: it only tells us which Ethereum
// transactions were executed earlier, but not which injected transactions
// were executed.
parent_announce_hash: H256,
// Gas allowance (for advancing actor queues).
//
// Like Vara, Vara-eth uses an actor model based on message passing.
// Transactions themselves don’t execute immediately; they only enqueue
// messages into program queues. Actual execution of these messages happens
// in a dedicated function.
//
// The analog in Vara is `gear::run`, which takes as input the amount of gas
// allowed for computation. Vara-eth has a similar function, and (with some
// constraints) the block producer in the current slot chooses this value.
//
// If this is `None`, no queue advancement happens at all.
gas_allowance: Option<u64>,
// Finally, the injected transactions along with the sender signatures.
transactions: Vec<SignedInjectedTransaction>,
}This gives us an analog of a block in a classic L1 blockchain, but:
- greatly simplified, and
- fully dependent on the parent Ethereum network.
A special case is when:
-
gas_allowance == None, and -
transactions.is_empty() == true.
Such an Announce is called base (a base announce). Any Announce that does not match this pattern is called non-base (or not-base).
Announces eventually affect program state on Ethereum. After computing new program states, each block producer tries to commit the corresponding state changes to Ethereum. Sometimes this succeeds quickly; sometimes the commit is delayed, and the changes are applied only after several blocks, possibly by a different block producer.
Sometimes failures or edge cases occur, and these changes never make it to Ethereum at all and are eventually forgotten by executors (for example, if an Ethereum reorg happens and the Ethereum blocks that the announces are attached to get reverted). In such cases, Vara-eth nodes simply discard the associated announces because they are no longer needed.
Every Announce references its parent announce hash. This naturally creates chains of announces, much like chains of blocks in a classic blockchain.
Vara-eth defines a genesis block: the Ethereum block in which the Router contract was deployed (the main Ethereum contract responsible for creating programs, validating incoming state changes, etc.).
For this genesis Ethereum block, we create a base announce:
let genesis_announce = Announce {
// Attached to the Vara-eth genesis Ethereum block
block_hash: genesis_block_hash,
// Since there were no announces before, the parent is zero
parent_announce_hash: H256::zero(),
// No gas allowance
gas_allowance: None,
// No injected transactions
transactions: vec![],
};For a specific Vara-eth network, this Announce is the ancestor of all other announces in that network.
Announces form chains like a typical blockchain, which leads to the next question:
Can announce chains fork?
The answer is yes.
Forks arise naturally due to network imperfections. For example:
- Validator
V1creates an Announce and broadcasts it. - Some validators receive it; some do not.
- As a result, some validators build their announce chains including
V1’s Announce, while others build chains without it.
These divergent chains are announce branches, and resolving such divergence is exactly what the Vara-eth consensus algorithm is designed to handle.
We can describe the consensus problem in general terms as follows.
There are $begin:math:text$N$end:math:text$ actors (nodes) communicating via a fixed protocol. Each actor has its own internal state $begin:math:text$S_i$end:math:text$. This state changes over time based on incoming data (e.g. transactions).
For honest actors, the communication and state-transition protocol should satisfy the following property:
After applying a given set of inputs, the states of all honest actors converge to the same state
$$S = S_1 = S_2 = \dots = S_N$$ within some finite, constant time$T > 0$ .
If we did not have injected transactions, it would be enough for the Vara-eth protocol to be deterministic. Ethereum already satisfies the above property with
and there would be no other input source except Ethereum transactions.
But what to do with injected transactions? To explain the solution, we’ll move step by step.
Consider the following simple rule for not-base announces:
- If a not-base Announce is created for Ethereum block
block, it may only be applied on Ethereum in the direct child block ofblock. - If it is not applied in that child block, the Announce becomes stale, and is replaced by a base Announce for the same
block, with the sameparent_announce_hash.
This gives us:
- At the start of each next Ethereum block, every executor node knows exactly which Announce it must build on top of.
- Program states are finalized at the parent block, up to the latest Ethereum chain head. If an Ethereum reorg occurs, those states may become outdated, but for finalized Ethereum blocks we get a strong guarantee:
- For any finalized Ethereum block that is a descendant of the Vara-eth genesis block, the program state for its parent block is fully determined (or at least fully computable, because all transaction orderings are finalized):
So, we achieve consensus in the worst case in time
or, more conservatively, if block production on Ethereum stalls for a while:
This simple rule is already enough to get convergence without a separate consensus algorithm inside Vara-eth. But it has one critical disadvantage - it's very very difficult to be able to commit announces right in the next block. So, it's not a very nice solution and let's move to the better one below.
Now let’s complicate the rule to gain more flexibility.
We introduce a parameter
- If a not-base Announce is created for block
b, it may be applied on Ethereum within the nextndescendant blocks ofb. - If a not-base Announce is not committed within these
nblocks, it is considered stale, and so are all announce branches that contain this Announce. - If any Announce (base or not-base) is committed to Ethereum, then all branches that do not contain this Announce are considered stale.
In this extended rule, unlike the simple one:
- We can no longer know exactly which Announce to build on top of for the next block, because a previous Announce might still be committed later within the next
nblocks. - As a result, announce branches naturally exist and need to be managed.
- But, the good point is that Vara-eth nodes have n-blocks to commit changes.
We now need to specify how validators should:
- build branches,
- create announces, and
- accept or reject announces.
Whenever a node receives a new Ethereum chain head, it learns:
- which announces have been committed, and
- which announces have not been committed (by exclusion).
For each parent_announce attached to a parent_block of the new chain head, the node creates a base announce for the new chain head, if parent_announce is not stale with respect to the new chain head only:
let new_base_announce = Announce {
block_hash: chain_head,
parent_announce_hash: parent_announce,
gas_allowance: None,
transactions: vec![],
};In this way, the node immediately extends its announce chains (at least one chain) all the way up to the new chain head.
If the node is the block producer for the current slot, it must pick a parent announce and create a not-base announce.
-
The node selects the best
parent_announce. Currently, each node tries to choose aparent_announcethat lies on a branch with the largest number of not-base announces – intuitively, this corresponds to the branch with the most work/compute done. This is not a strict requirement, however; the only hard requirement is that the chosenparent_announceis not stale. -
The node then creates a new Announce, choosing:
-
gas_allowanceaccording to its scheduling/execution policy, and - a set of
transactionsconsisting of valid injected transactions, within network limits.
-
let new_announce_from_producer = Announce {
block_hash: chain_head,
parent_announce_hash: chosen_alive_parent_announce_hash,
gas_allowance: chosen_gas_allowance,
transactions: chosen_valid_injected_transactions,
};This Announce is then broadcast to other validators.
If the node is not the block producer:
- It waits for an Announce from the current producer.
- If no Announce is received, it does nothing – it already has at least one extended announce branch ending at the chain head (via base announces).
- If an Announce is received, the node validates it by:
- checking that all transactions are valid, and
- ensuring that
parent_announce_hashrefers to a known, non-stale Announce.
If the validate step fails (e.g. the parent branch is unknown or considered stale), the node rejects the Announce.
Network issues can lead to situations where:
- One node has a particular announce branch that another node does not know about.
- This can cause some nodes to accept an Announce while others reject it.
This is expected behavior where two main scenarios can be:
-
Majority accepts an Announce.
If a majority of nodes accept a given Announce and it later gets committed to Ethereum, then any node that initially rejected it will be forced to align with Ethereum: that node must include it in its own branches. So, soon or lately this node reaches the same state as others.
-
Majority rejects an Announce
If a majority does not accept an Announce, it will not be committed to Ethereum. After some time (more than
nblocks), it becomes stale, along with all branches built on top of it. Again, the network converges to a consistent state.
Thus, even with temporary divergence, the combination of:
- Ethereum’s finality, and
- the stale/valid rules for announces
brings the network back to consensus.
By following the rules above, the Vara-eth network achieves robust consensus over injected transactions with a convergence time roughly bounded by:
and, in the worst case,
Crucially:
- We do not need to implement a full stand-alone consensus algorithm within Vara-eth, which would often be complex and introduce significant latency.
- Ethereum remains the primary source of truth for Vara-eth:
- Ethereum, being a decentralized network, acts as a centralized truth anchor for Vara-eth nodes.
- In any ambiguous situation, nodes can always “look back” to Ethereum’s finalized state and correct their local state accordingly.
In summary, Vara-eth consensus leverages Ethereum’s consensus to order and finalize injected transactions, achieving:
- higher throughput and cheaper transaction submission,
- while still inheriting Ethereum’s security and finality guarantees,
- and avoiding the need for a separate heavyweight consensus protocol inside Vara-eth itself.