Fork resistance in Aeternity nodes
The Aeternity consensus and sync protocols resolve forks according to the specification in the aeternity Bitcoin-ng protocol. This means that 51% attacks are still possible, just as with other Proof-of-Work chains.
The Aeternity node implementation offers a few ways to withstand 51% attacks.
Fork resistance via gossip
The configuration variable
sync:
gossip_allowed_height_from_top : <height>
limits the height difference allowed for incoming blocks via gossip. This variable has a
hard-coded default of 5
, but can be changed via the aeternity_config.[yaml|json]
file (see the configuration instructions).
Any keyblock received via gossip will be rejected if its height is more than this
value below the current top.
Fork resistance via sync
The normal way to introduce a long competing fork would be via the sync protocol. That is, a node goes off-line and mines a long chain, then reconnects to the network and syncs against other nodes: if the competing fork has a higher difficulty, it will supersede the chain on the network.
The following configuration variable,
sync:
sync_allowed_height_from_top: <height>
will instruct the node to reject blocks received via sync whose height is more than <height>
blocks below the current top. The default value is 100
. A value of 0
disables the
protection. In practice, this should mean that once a transaction has at least <height>
keyblocks on top of it, the nodes will resist any competing fork trying to evict it.
The fork resistance is activated once the node has synced with at least one other node. It is possible to enable fork resistance immediately, using the following setting:
sync:
resist_forks_from_start: true`
Note that configuration variables can be set both via the config file and via OS environment variables. This means that it's possible to instruct the node to resist forks at a given node start in the following way:
AE__SYNC__RESIST_FORKS_FROM_START=true bin/aeternity start
(see the configuration documentation)
Finalized depth
When sync fork resistance (sync: sync_allowed_height_from_top
) is active, the node
will persist the height just below the fork resistance depth as finalized_height
.
If the node should restart and starts syncing with other nodes (recall that the dynamic
fork resistance is not activated until the node has successfully synced with one peer),
it will reject any chain that presents a key block hash at the finalized height which
differs from what the node already has on record.
This shores up the dynamic protection offered by sync_allowed_height_from_top
to also defend
against malicious nodes during node restarts. The function is automatic, once fork resistance
has been configured. If fork resistance is later turned off, finalized height will not be
enforced.
Discussion
When choosing a suitable fork resistance depth, it is important to consider some tradeoffs. Blockchains naturally rely on 'optimistic concurrency', that is, it is entirely possible that different nodes manage to produce keyblock candidates at roughly the same time. This means that keyblock forking can occur occasionally. The likelihood that it will happen repeatedly is extremely low, so such forks should resolve quickly, usually with the following keyblock.
Note that even with very long competing forks, the chain will converge. Absent malice and unless
the TTL
has been set very low for some transactions, transactions evicted for being on a
"losing fork" will be returned to the mempool and find their way back onto the main chain.
The problem is when this behavior is exploited to evict specific high-value transactions
(so-called "rollback", or "double-spend" attacks).
Even if all transactions get returned to the chain, miners who have mined several blocks on a losing fork may have reason to feel cheated, as they lose their mining rewards.
Network latency and network failures may increase the frequency and length of naturally competing forks, and setting the fork resistance too low might increase the risk of chain splits, where different nodes end up on different forks and cannot resolve the situation without manual intervention.
From a user experience perspective, fork resistance sets an upper bound on the confirmation
time needed to secure high-value transactions, since there is no need to wait longer than
the configured fork resistance depth. Once the generation that contains a transaction is
at or below the finalized depth, the transaction cannot be evicted, either accidentally
or through a malicious attack. Using the default setting of 100
, this would mean that
a block confirmation time of 100 key blocks (5 hours) will be sufficient to protect any transaction
amount.