By Benjamin Samuels
Many security-critical off-chain applications use a simple block delay to determine finality: the point at which a transaction becomes immutable in a blockchain’s ledger (and is impossible to “undo” without extreme economic cost). But this is inadequate for most networks, and can become a single point of failure for the centralized exchanges, multi-chain bridges, and L2 scaling solutions that rely on transaction finality. Without proper consideration of a blockchain’s finality criteria, transactions that appear final can be expunged from the blockchain by a malicious actor in an event called a re-org, leading to double-spend attacks and value stolen from the application.
We researched several off-chain applications and L2 networks and discovered two L2 clients, Juno and Pathfinder, that either were not checking for finality or incorrectly used block delays to detect whether Ethereum blocks were finalized. We disclosed our findings to each product team, and fixes were published shortly after disclosure in version v0.4.0 for Juno and v0.6.2 for Pathfinder. This blog post gathers the knowledge and insights we gained from this research. It explains the dangers of reorgs, the differences between distinct finality mechanisms, and how to prevent double-spend attacks when writing applications that consume data from different kinds of blockchains.
Understanding re-orgs
When a user submits a transaction to a blockchain, it follows a lifecycle that is nearly identical across all blockchains. First, their transaction is gossiped across the blockchain’s peer-to-peer network to a block proposer. Once a block proposer receives the transaction and includes it in a block, the block is broadcast across the network.
Here is where the problems begin: some blockchains don’t explicitly define who the next block proposer should be, and the ones that do need a way to recover if that proposer is offline. These conditions lead to situations where there are two or more valid ways for the blockchain to proceed (a fork), and the network has to figure out which fork should be canonical.
Blockchains are designed with these issues in mind and define a fork choice rule to determine which fork should be considered canonical. Forks can sometimes last for multiple blocks, where different portions of the network consider a different chain to be canonical.
Figure 2: A PoW network where block candidates for slots 4, 5, and 6 were mined in quick succession and built on different parents, leading to a fork.
Assuming there is no bug in the network’s software, the fork will eventually be reconciled, leading to a single fork becoming canonical. The other forks, their blocks, and their transactions are expunged from the blockchain’s history, called a re-org.
When a transaction is expunged from the chain via a re-org, that transaction may either be re-queued for inclusion in a new block, or otherwise have its ordering or block number changed to whatever it is in the canonical chain. Attackers can leverage these changes to modify a transaction’s behavior or cancel the transaction entirely based on which fork it is included on.
Figure 3: A network after a three-block re-org. Transactions in blocks 4a, 5a, and 6a are no longer part of the canonical chain.
Re-orgs are a normal part of a blockchain’s lifecycle, and can happen regularly due to factors like block production speed, network latency, and network health. However, attackers can take advantage of (and even orchestrate!) re-orgs to perform double-spend attacks, a category of attack where an attacker submits a deposit transaction, waits for it to be included in a block, then orchestrates a re-org to expunge their transaction from the canonical chain while still receiving credit for their deposit on the off-chain application.
It is for this reason that finality considerations are important. If a centralized exchange or bridge indexes a deposit transaction that is not final, it is vulnerable to double-spend attacks by way of an attacker causing a re-org of the blockchain.
Blockchains use a variety of different consensus algorithms and thus have a variety of different finality conditions that should be considered for each chain.
Probabilistic finality
Examples: Bitcoin, Binance Smart Chain (pre-BEP-126), Polygon PoS, Avalanche – or generally any PoW-based blockchain
Chains using probabilistic finality are unique in that their blocks are never actually finalized—instead, they become probabilistically final, or more “final” as time goes on. Given enough time, the probability that a previous block will be re-orged off the chain approaches zero, and thus the block becomes final.
In most probabilistically final chains, the fork choice rule that determines the canonical chain is based on whichever fork has the most blocks built on top of it, called Nakamoto consensus. Under Nakamoto consensus, the chain may re-org if a longer chain is broadcast to the network, even if the longer chain excludes blocks/transactions that were already included in the shorter chain.
Double-spend attacks on probabilistic proof-of-work networks
The classic attack against proof-of-work networks is a 51% re-org attack. This attack requires an off-chain exchange, bridge, or other application that indexes deposit transactions very quickly, ideally indexing blocks as soon as they are produced or with an exceedingly short delay.
The attacker must accumulate, purchase, or rent enough computing resources so the attacker controls the majority of the hash power on the network. This means the attacker has enough resources to privately mine a chain that’s longer than the honest canonical chain. Note that this is a probabilistic attack; control over 51% of the network’s hash power makes the attack an eventual certainty. An attacker could theoretically perform double-spend attacks with much less than 51% of the network’s hash power, but it may require many attempts before the attack succeeds.
Once the mining resources are ready, the attacker submits a transaction on the public, canonical chain to deposit funds from their wallet to the exchange/bridge.
Immediately afterward, the attacker must create a second, conflicting transaction that transfers funds from their wallet to another attacker-controlled address. The attacker configures their mining resources to mine a new fork that includes the transfer transaction instead of the deposit transaction.
Figure 4: The attacker creates a private fork that includes their transfer transaction instead of the deposit transaction.
Given that the attacker controls the majority of the hash power on the network, eventually their private fork will have more blocks than the canonical fork. Once they have received credit for the deposit transaction and their private fork has more blocks than the canonical chain, the attacker instructs their network to publish the private chain’s blocks to the honest nodes following the canonical chain.
The honest nodes apply the “longest chain” fork choice rule, triggering a re-org around the attacker’s longer chain and excluding the blocks that contained the attacker’s deposit transaction.
Figure 5: Once the attacker publishes their private fork, the network reorgs and expunges the fork that includes their deposit transaction.
In effect, this allows the attacker to “double-spend” their coins: the exchange or bridge credits the attacker for the coins, while the coins are still present in an attacker-controlled wallet.
Measuring probabilistic finality
Since probabilistically final chains don’t define finality conditions, finality must be measured probabilistically based on the number of blocks that have elapsed since the target transaction/ancestor block. The more blocks that have been built on top of a given ancestor, the higher the cost of a re-org is for an attacker.
The correct block delay should be based on both historical factors and economic factors, selecting the greater of the two for the application’s indexing delay.
Among historical factors, off-chain application developers should consider how often the chain has reorgs and how large the reorgs typically are. If block production is probabilistic (as in Proof-of-work networks), then a larger delay should be factored in to compensate for the chance that many blocks are created in an unusually short period of time.
Among economic factors, one should consider the cost to execute a re-org attack for a transaction of a given economic value. Given the probabilistic nature of 51% attacks, engineers should build in a safety margin and consider the cost of a 25% attack instead of 51%. For example, if it costs a minimum of $500k USD to execute a 25% attack, six-block re-org attack against Bitcoin, then an off-chain application that receives a $500k deposit should wait for at least six blocks before considering the transaction final and indexing it.
Using Crypto51, we can calculate the block delays required for deposits and withdrawals of $75k with a 25% attack threshold (as of June 2023).
- Bitcoin: Two blocks for finality (~20 minutes)
- Litecoin/Dogecoin: 48 blocks for finality (~Two hours)
- Bitcoin Cash: 103 blocks for finality (~17 hours)
- Ethereum Classic: 3,031 blocks for finality (~11 hours)
- Ethereum PoW: 23,600 blocks for finality (~89 hours)
- Zcash: 1,881 blocks for finality (~40 hours)
These delays represent a single data point in time for a specific deposit amount. As the hash power on each network increases or decreases, the time-to-finality will change as well and must be updated accordingly. Mining hash power on each network correlates highly with the chain token price, so the amount of hash power can drop very quickly, requiring monitoring and fast response by integrating applications. Failure to adjust finality delays in a timely manner will lead to double-spending attacks.
Finality delays for proof-of-work chains may be reduced for certain exchange-like applications using on-chain monitoring, automated system halts, trading limits, and withdrawal delays. However, these mechanisms may overcomplicate the application’s logic and make it vulnerable to other forms of attack.
It should be noted that chains with extraordinarily low hash rates may easily be attacked with much more than 51% of the network’s hash rate. Existing services offer an easy-to-rent hashing capacity that may exceed a chain’s hash rate many times over. In cases like this, it is recommended to either avoid integration with the chain, or base finality calculations on the available-for-rent hashing capacity.
Probabilistic chains using proof-of-stake/proof-of-authority require slightly different considerations, since block proposers cannot freely enter the proposer set and may have different fork choice rules than proof-of-work networks.
- Binance Smart Chain: Blocks are considered final once the number of blocks that have passed is equal to two-thirds the number of validators in their validator set. Twenty blocks are required for finality (~60 seconds).
- Polygon PoS: Integrators should use L1 state root checkpoints as a measure of finality. When a state root checkpoint containing a given transaction is finalized on the L1, the Polygon transaction may be considered final. State root checkpoints occur roughly every 30 minutes.
Provable finality
Delayed finality examples: Ethereum PoS (Casper FFG), Polkadot (GRANDPA), Solana
Instant Finality Examples: Cosmos, Celestia, Algorand, Aptos
Systems using provable finality make special considerations for finality to ensure it happens more quickly and with better economic assurances than most probabilistically final chain constructions.
There are two types of provable finality: chains with instantly provable finality, and chains with delayed provable finality.
Chains with instant finality don’t need special finality considerations by off-chain applications. All blocks published by the network are immediately provably final by definition.
Chains with delayed finality have separate consensus mechanisms for newly produced vs. finalized blocks. These chains usually have superior liveness properties compared to instant finality chains, but at the cost of added complexity, vulnerability to re-orgs, and more complex integration considerations for off-chain applications.
Figure 6: A delayed-finality chain. Blocks to the right of the finality boundary may be re-orged and should not be indexed by exchanges or bridges.
Double-spend attacks on delayed finality chains
Historically, most blockchains haven’t had provable finality, so bridges, exchanges, and other off-chain applications would use a block delay for measuring the finality of any new chains they integrate with.
However, for chains with provable delayed finality, there are situations where the finality mechanism may stall or fail, as occurred in the May 2023 incident where Ethereum’s finality gadget, Casper FFG, stalled. When finality mechanisms fail, the chain may continue to produce blocks, creating long strings of unfinalized blocks that may be reorged by a bug or an attacker.
During the Ethereum incident, the chain’s finality mechanism was stalled by nine epochs—the equivalent of 139 blocks’ worth of confirmations (after controlling for missed slot proposals). At this time, most bridges/centralized exchanges used a block-delay rule to determine the finality of a transaction on Ethereum, with delays ranging from 14 blocks to 100 blocks.
Had the Ethereum finality incident been orchestrated by an attacker, the attacker may have been able to perform double-spend attacks against these bridges/exchanges by orchestrating exceedingly long re-orgs.
Checking for finality
For delayed-finality chains, as illustrated in the previous example, block delays are not an adequate way to “wait” for blocks to become final. Instead, applications must query the chain’s RPC for the exact finality condition to ensure the block being indexed is actually final.
Ethereum proof-of-stake
The Ethereum JSON RPC defines a “default block” parameter for various endpoints that should be set to “finalized” to query the most recent finalized block. To obtain the most recent finalized block, use eth_getBlockByNumber(“finalized”, ...)
. This parameter may be used for other endpoints, such as eth_call
, eth_getBalance
, and eth_getLogs
.
Polkadot
Call chain.getFinalizedHead()
to get the block hash of the latest finalized block, then use chain.getBlock()
to get the block associated with the hash.
Solana
Use the getBlocks()
RPC method with the commitment level set as finalized
.
When provable finality lies
One major caveat of provable finality/proof-of-stake systems is they have no way to provide strong subjectivity guarantees. A blockchain’s subjectivity refers to whether a node syncing from genesis will always arrive at the same chain head and whether an attacker can manipulate the end state of the syncing node.
In proof-of-work blockchains, the cost of creating an alternate chain for partially synced nodes to follow is equal to all of the work performed by miners from genesis to the canonical chain head, making any subjectivity attack impractical against proof-of-work networks.
However, in proof-of-stake networks, the cost of creating an alternate chain has only one requirement with an unknown, and possibly zero cost: the private keys of the chain’s historical validators. The keys for historical validators may be acquired by a number of means; private keys may be leaked or brute-forced, or validators who no longer use their keys may offer them up for sale.
This re-use of old validator keys creates the possibility for long-range sync attacks, in which a newly synced node may behave as though a specific transaction is submitted and finalized when in reality, it was never submitted to the canonical chain in the first place.
To protect against long-range sync attacks, operations teams should always begin node sync from weak subjectivity checkpoints. These checkpoints are essentially used as genesis blocks, providing a trusted starting point for nodes to sync from. Weak subjectivity checkpoints may be acquired from already-synced nodes or via social processes.
The special case of L2s
Examples: Arbitrum, Optimism, StarkNet, Scroll, ZKSync, Polygon zkEVM
L2 networks are unique in that they don’t have consensus mechanisms in the way a normal blockchain does. In a normal blockchain, the validator set must come to a consensus on the output of a state transition function. In an L2 network, it is the underlying L1 network that is responsible for verifying the state transition function. Ultimately, this means the finality condition for an L2 network is dependent on the finality condition of the underlying L1.
When an L2 sequencer or prover receives a transaction, it sequences/generates a proof for the transaction, then returns an L2 transaction receipt. Once the sequencer/prover has received enough transactions, it assembles the transactions into a batch that is submitted to the L1 network.
Figure 7: The flow of a user’s transaction on an L2 network. Notably, sequencers/provers provide users with transaction receipts far in advance of the transaction’s inclusion or finality on the L1.
For ZK-Rollups, the batch contains a proof representing the execution of every transaction in the batch. The L1 contract verifies the proof, and once the batch transaction is final, all of the L2 transactions included in the proof are final as well.
For Optimistic Rollups, the batch contains the calldata for every transaction in the batch. The L1 contract does not run any state transition function or verification that the calldata is valid. Instead, Optimistic Rollups use a challenge mechanism to allow L2 nodes to contest an L1 batch. This means a transaction submitted to an Optimistic Rollup may be considered final only once it’s been included in a batch on the L1, the batch and its parents are valid, and the L1 transaction is final.
Checking for finality
To determine the finality of an L2 transaction, one must verify that the commitment/proof transaction has both been included on and finalized by the L1. L2 providers often offer convenient RPC methods that off-chain integrators can use to determine the finality of a given L2 transaction.
Arbitrum Nitro/Optimism
Both Arbitrum and Optimism nodes implement the Ethereum JSON RPC, including the “finalized” block parameter. As a result, eth_getBlockByNumber(“finalized”, ...)
can be used to determine finality.
StarkNet
StarkNet’s sequencer provider has a getTransactionStatus()
function that reports the transaction’s status in the StarkNet transaction lifecycle. Transactions whose tx_status
is ACCEPTED_ON_L1
may be considered final.
ZkSync Classic
ZkSync’s v0.2 API has several endpoints that accept finalization parameters.
/accounts/{accountIdOrAddress}/{stateType}
may have thestateType
set tofinalized
./blocks/{blockNumber}
acceptslastFinalized
as theblockNumber
parameter./blocks/{blockNumber}/transactions{?from,limit,direction}
acceptslastFinalized
as theblockNumber
parameter.
Practicing safe finality
Like other recent innovations in the blockchain space, provable finality has drastically changed the kinds of security assurances a blockchain can provide. However, developers of off-chain or multi-chain applications must be cognizant of the specific finality requirements of different architectures and, where necessary, use the correct techniques to determine whether transactions are final.
Older techniques of determining finality, such as block delays, are not adequate for newer architectures, and using incorrect finality criteria may put applications at risk of double-spend attacks.
If you’re designing a new blockchain or off-chain application and have concerns about finality, please contact us.