Presenting ticking-optimism
Ticking-Optimism is a proof-of-concept implementation of a "ticking blockchain" built on top of the Optimism Bedrock rollup architecture.
In a ticking chain, a special smart contract called the "tick contract" is called automatically by the protocol, every block. This allows other smart contracts to be triggered at specific times or intervals automatically, without a user having to send a transaction.
Implementation
Optimism's new, modular rollup architecture, Optimism Bedrock, introduces a new type of transaction called the "Deposit Transaction". Unlike regular transactions, deposit transactions:
Are derived from Layer 1 blocks.
Do not require signature validation.
Buy their L2 gas on L1, so the L2 gas is not refundable.
In vanilla Bedrock, deposit transactions are used for two things:
Executing transactions sent directly to L1.
Setting L1 attributes (number, timestamp, hash, etc.) in a pre-deployed L2 contract every block.
In the latter case, the transaction is created by the rollup node. It doesn't pay for gas and the one it uses is not subtracted from the gas pool.
Ticking-Optimism modifies to rollup node to also create a "tick transaction” that works the same way, but instead of setting L1 attributes, it calls a tick()
function in a contract pre-deployed to the address 0x42000000000000000000000000000000000000A0
. This contract can be used to call another contract by setting its target
variable.
Motivation
To illustrate the power of ticking chains, imagine a game on a blockchain where multiple NPCs (non-player characters) move around a map. Without ticking chains, there are two main ways we could design this:
Lazy updating. The NPCs appear to move continuously in the client, but their positions are only updated on-chain when a user sends a transaction that interacts with them. The contract then calculates the NPC's new position based on its last on-chain update and the number of blocks that have passed since then.
Manual ticking. We define an update function that sets the position of every NPC on the map and have an external account call it periodically.
With a ticking chain, the solution would be similar to manual ticking, but the tick contract would automatically call the update function instead of it being called manually.
The advantages of using a ticking chain's "automatic ticking" over manual ticking are:
The update is guaranteed by the protocol.
The update will be executed before all transactions in the block and cannot be front-run because it is part of the protocol itself.
The update transaction does not participate in the regular gas market.
However, automatic ticking requires a custom blockchain. If the update rate is the same, both manual and automatic ticking are equally resource-intensive for nodes to compute. Lazy updating, on the other hand, is generally cheaper because on-chain updates are smaller and less frequent.
Additionally, as the state to be updated grows in size, the computational cost of tick transactions also increases. This puts extra pressure on developers to design their apps in a way that ensures the cost never exceeds what the chain can support.
Despite these great downsides, automatic ticking has properties that make it more suitable than lazy updating for certain types of applications.
1. The state is always explicitly on-chain and up-to-date
Ticking enables smart contracts to access a dynamic state updated with an open-form expression at a constant cost.
The state (in the example above, the NPCs’ positions) can always be read on-chain at a constant, relatively low, gas cost. This makes a big difference when the cost of computing the current state would otherwise increase with the number of blocks since the last update.
If we are updating the position of an entity moving at a constant velocity, we can compute where it should be at any given block from its last set position and the number of blocks since it was updated. The cost of this operation doesn’t grow with the number of blocks between the updates.
On the other hand, if the state we are updating is something like Conway’s Game of Life (or a three-body gravity simulation) the cost of an update grows linearly with the number of steps due since the previous one. This is a problem as it can grow over what users are willing to pay for or even over what the chain can support.
2. Clients are chain-imperative
With lazy updating, the update logic needs to be implemented in both a smart contract and the client. With ticking, it only needs to be implemented on the blockchain and the client can simply react to on-chain events.
3. The code is simpler and easier to audit
Lazy updating makes developers spread their update logic across many functions and smart contracts, and each of those is only triggered when certain transactions are executed. In contrast, the ticking approach only requires a single update function that is guaranteed to be triggered periodically. The latter makes it simpler to maintain the coherence and integrity of the state.
Additionally, every time a new lazily-updated state is added (e.g., a new type of NPC), all update functions might need to be modified to account for it. This makes the codebase more complex and issue-prone.
4. Users don’t pay for update costs
The cost of lazy updates is generally highly variable, and users can craft their transactions so most of the burden of paying for updates falls upon others. With ticking, the cost of all actions remains relatively stable and less prone to MEV exploits.
Conway’s game of life demo
I built a demo of a ticking chain that runs an interactive version of Conway’s Game of Life. The chain has been modified to include the cellular automata logic in the execution engine, making it more efficient and allowing for a larger game board than if it were implemented as smart contract bytecode.
Source code for the demo: https://github.com/therealbytes/ticking-conway