In general, games are based on a loop-based system. The game loop is a repetitive process that typically includes handling user input, updating the game state, and rendering the game world. This loop continues throughout the game runtime, usually running tens to hundreds of times per second to maintain the smoothness of the game world.
However, the architecture of blockchain is push-based. Blockchain is a distributed database that shares and stores information among nodes in the network. When a node generates a new transaction, such as a transfer or contract invocation, the transaction is pushed to the network, and other nodes receive and verify it before adding it to the blockchain. This is a passive process, where nodes do not actively search for new transactions but instead wait for them to be pushed by other nodes in the network. Therefore, the architecture of blockchain is referred to as push-based.
Therefore, it becomes crucial to implement a loop system with a clock cycle in a full-chain game. After all, in the so-called "autonomous world," we all hope that some NPCs or virtual environments can evolve automatically over time, rather than passively evolving based on transactions pushed to the blockchain.
@therealbytes has developed a concept validation of a ticking chain based on the OP Stack. It runs an automatic ticking implementation of Conway's Game of Life. Let's find out how he achieved this.
To keep the translation simple, we will translate "tick" directly as "滴答," which means "clock cycle."
Ticking-Optimism is a concept validation implementation of a "ticking blockchain" based on the Optimism Bedrock rollup architecture.
In the ticking chain, there is a special smart contract called the "ticking contract" that is automatically called for every block by the protocol. This allows other smart contracts to be triggered automatically at specific times or intervals without the need for user-initiated transactions.
How it works#
Optimism's new modular rollup architecture, Optimism Bedrock, introduces a new transaction type called "deposit transaction." Unlike regular transactions, deposit transactions:
- Come from Layer 1 blocks.
- Do not require signature verification.
- Purchase L2 gas on L1, so L2 gas is non-refundable.
In the original Bedrock, deposit transactions are used for two things:
- Executing transactions directly sent to L1.
- Setting L1 properties (number, timestamp, hash, etc.) for pre-deployed L2 contracts in each block.
In the latter case, the transactions are created by rollup nodes. They do not pay gas and the gas used is not deducted from the gas pool.
Ticking-Optimism modifies the rollup nodes and also creates a "tick transaction" that works similarly but instead of setting L1 properties, it calls the tick() function in a contract pre-deployed at address 0x42000000000000000000000000000000000000A0. This contract can call another contract by setting its target variable.
Motivation#
To illustrate the power of the ticking chain, let's imagine a game on a blockchain where multiple non-player characters (NPCs) move on a map. Without the ticking chain, we have two main design approaches:
-
Lazy updating: On the client side, NPCs appear to move continuously, but their positions are only updated on the chain when a user sends a transaction interacting with them. Then, the contract calculates the new positions of the NPCs based on their last on-chain update and the number of blocks passed since then.
-
Manual ticking: We define an update function that sets the positions of each NPC on the map and have an external account call it periodically.
With the ticking chain, the solution is similar to manual ticking, but the ticking contract automatically calls the update function instead of manual invocation.
The advantages of using the "automatic ticking" of the ticking chain instead of manual ticking are:
- Updates are guaranteed by the protocol.
- Updates are executed before all other transactions in a block and cannot be front-run because they are part of the protocol itself.
- Update transactions do not participate in the regular gas market.
However, automatic ticking requires a customized blockchain. If the update rate is the same, both manual and automatic ticking require similar computational resources from nodes. On the other hand, lazy updating is usually cheaper because on-chain updates are smaller and less frequent.
Additionally, as the state to be updated grows, the computational cost of tick transactions also increases. This puts additional pressure on developers to design their applications in a way that ensures costs never exceed what the chain can support.
Despite these significant drawbacks, automatic ticking is more suitable for certain types of applications than lazy updating.
- The state is always explicitly on-chain and up-to-date.
Ticking allows smart contracts to access a dynamic state with a constant cost, which is updated using open-form expressions.
The state (in the example above, the positions of NPCs) can always be read on-chain at a constant, relatively low gas cost. However, the cost of computing the current state increases significantly as the number of blocks since the last update grows.
If we are updating the position of an entity moving at a constant speed, we can calculate its position in any given block based on its last set position and the number of blocks passed since the update. The cost of this operation does not increase with the number of blocks between 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 updates grows linearly with the number of steps since the last update. This is a problem as it can grow beyond what users are willing to pay or what the chain can support.
- Different roles for the client
With lazy updating, the update logic needs to be implemented in both the 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.
- Simpler and easier-to-audit code
Lazy updating scatters the developer's update logic across many functions and smart contracts, with each function triggered only when certain transactions are executed. In contrast, the ticking approach only requires one update function that is guaranteed to be triggered periodically. The latter makes it easier to maintain consistency and integrity of the state.
Additionally, every time a new lazy updating state is added (e.g., a new type of NPC), all update functions may need to be modified to account for it. This makes the codebase more complex and prone to issues.
- Users do not pay for update costs
The cost of lazy updating can vary greatly, and users can structure their transactions to offload most of the burden of updates onto others. With ticking, the cost of all operations is relatively stable and not easily susceptible to MEV attacks.
Conway's Game of Life Demo
I built a demo of the ticking chain running an interactive version of Conway's Game of Life. The chain has been modified, including the cellular automaton logic in the execution engine to make it more efficient and allow for larger game boards than what can be achieved with smart contract bytecode implementation.
Source code for the demo: [GitHub link]
Demo video: [YouTube link]
Original article: [Link]
Original author: @therealbytes
Translated to the Chinese community by @hicaptainz