Skip to main content
Version: 1.1

Godwoken Transaction

Godwoken is a layer 2 rollup framework for Nervos CKB. It provides scalability and an account-based programming model to CKB. For an overview of Godwoken see this post. This article dives into the innards of Godwoken, explains how it works, and how each individual piece fits together.

Rollup Layer 2 Chain

Godwoken is fundamentally a layer 2 solution to the layer 1 of Nervos CKB blockchain. Here is an example of a Godwoken deployment on CKB testnet. Layer 2 blocks are submitted to layer 1 in block submission transactions that generate CKB cells with cell data containing the current state of the Godwoken blockchain. For more information about block submission transactions, see the transaction. As of this layer 2 block, the chain state is as follows:

0xa0cf6037bfc238b179b74a30a9b12e15a4fbdd8881aebc8e5a66a8b5b5c95f0a4ca62a4c652697c96b7e02eea3a1bc21c5369bcb30626b9aee1cbb1babea11ff4f16000055a255f47f22b6f8ed1a025f971d60528f5407b7c71745c8c493a51a631bb4bfab6f000000000000000000000000000000000000000000000000000000000000000000000000000047ee34be61c51e026838c5348ab0ca3566eed50dc9b3dc676fe16a7f7d72f80fcfe4f3d680010000466f0000000000000001

This is actually a GlobalState data structure serialized in molecule format:

struct GlobalState {
rollup_config_hash: Byte32,
account: AccountMerkleState,
block: BlockMerkleState,
reverted_block_root: Byte32,
tip_block_hash: Byte32,
tip_block_timestamp: Uint64,
last_finalized_block_number: Uint64,
// 0: running, 1: halting
status: byte,
version: byte,
}

More definitions can be found in this file.

The block submission transaction also contains the actual layer 2 Godwoken block. The first witness of the transaction mentioned above contains the following data:

0x600300001000000069000000690000005500000055000000100000005500000055000000410000004082de966ef59f3777d5c6b447d7912a457cb6bffc6317f336b03f39930c4c0f474779d431f4832a4779a804880f0d1ff4e9a48554ca8e85b56511c982c2094400f302000000000000ef02000010000000e7020000eb020000d70200001c0000006c010000700100007401000078010000d3020000500100002c000000340000005400000074000000940000009c000000c0000000e4000000e80000000c010000aa6f0000000000001c0000000200000014000000715ab282b873b79a7be8b0e8c13c4e8966a52040c822d671a8097366aacd98746a00407cd378b54b2facb8ac8fe8970e447ced1ff245705db4fe72be953e4f9ee3808a1700a578341aa80a8b2349c236c4af64e5cfe4f3d6800100004ca62a4c652697c96b7e02eea3a1bc21c5369bcb30626b9aee1cbb1babea11ff4f1600004ca62a4c652697c96b7e02eea3a1bc21c5369bcb30626b9aee1cbb1babea11ff4f1600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000011b7aeea1224e8e80173fe93db2187d0251d4b41f00e61fe3c3433f0e6953800000000000000000004000000570100004c4f0150f4dad550e8b95696636f041140059724eacb55453d70a18072520c97411ae6b64f01501e5a5863aa3e7d66c1e19a26b1eaac8183aa59e9ce8d77db4e2cb30b65d95b534f01505677593a7fbb2a4ecf82f3b1f7bee953c0199d4c9d6552154a95d844635901e64f0150870913864cfddf50dc51a88b5ca5b8bdf95c476a068fbd2eaa19398318703af05060f8c8c082fafd59eca27762ccbebc580271e0bac92f7a381c739d1954f1c6c9508c4c5f7fb230bee4a9877f3039feb2675dd44d04e8ac0e41dd9f9e13f277119f503e6ee57e32132b778b0e14a9a06b185c73c4a4d01b6cab9370991c3897f90f7050dc2f0723654791da20e1dc7713e167d58c71ceac4839364a6e5033f07f00efc14f0150ad18935a95613207b9baec1fb60f35eab69f273e3423ddb58cb37d886205488650b32cd6babf0fd7a5506a00365d252bc7162f527f4123ec22dcb51ad9336797294ff1040000000000000000000000

This is actually the WitnessArgs data structure described here and is serialized in the molecule format. In the output_type field of WitnessArgs, the actual L2Block data structure is stored and serialized in the molecule format:

table L2Block {
raw: RawL2Block,
kv_state: KVPairVec,
kv_state_proof: Bytes,
transactions: L2TransactionVec,
block_proof: Bytes,
withdrawals: WithdrawalRequestVec,
}

Further details about L2Block, refer to this file.

An output cell containing the GlobalState is the rollup cell, as this cell contains all the latest information for a particular Godwoken rollup deployment. Godwoken has no restrictions on the number of the rollup cells permitted in CKB. Multiple Godwoken deployments can be setup in a single CKB blockchain for different purposes. Each Godwoken deployment is thus identified by a rollup cell.

In Godwoken, the rollup cell for each Godwoken deployment must use a script called the state validator, which is a Godwoken type script. This script is responsible for:

  • Enforce the type ID logic. This gives each rollup cell an ID, so that there is no need to refer to rollup cell's OutPoint, which changes each time the rollup cell is updated. The type script hash of the rollup cell is also known as rollup type hash of the current Godwoken deployment. A rollup type hash can also be considered as a chain ID that uniquely identifies each layer 2 Godwoken blockchain.
  • Enforce Godwoken's own checking logic. The state validator type script ensures that the GlobalState is indeed calculated from the current L2 block. (TODO: state validator validation rules).

The state validator type script is the backbone of Godwoken. It ensures all the security checks are performed on chain and all layer 2 funds remain secure in a decentralized environment.

Storage

Godwoken provides an account model to Nervos CKB:

  • A 256-bit key-value storage is provided for applications running on Godwoken.
  • Godwoken provides a sequential model conceptually: each layer 2 transaction is applied sequentially to the Godwoken storage. Smart contracts in layer 2 transactions are free to read from and write into the storage space.

Given the constant storage requirements in the above transactions, you may wonder where to store all the data. Godwoken leverages Sparse Merkle Tree to build the storage space. The rollup cell only contains the root hash of the Sparse Merkle Tree (SMT). State validator verifies that the sparse merkle tree is correctly updated in each layer 2 block. In fact, we have moved one step further here by building an optimized SMT, which can save a lot of space and calculation for CKB's use case. More details can be found here.

Optimistic Rollup

The workflow of Godwoken is as shown below:

  • Individual parties known as aggregators can collect layer 2 Godwoken transactions(with more details on layer 2 transactions explained below), then pack them into layer 2 blocks and submit them to CKB. Aggregators must stake a certain amount of CKB before they can submit a layer 2 block.
  • Each layer 2 block submitted to the chain is initially marked as unfinalized. An unfinalized layer 2 block may be challenged by others(known as challengers) if it appears malicious, .
    • A challenger starts a challenge request by marking a layer 2 transaction in the layer 2 block as invalid. The challenger must stake a certain amount of CKB in order to create a challenge. Once a challenge request is included in CKB, the challenge phase begins.
    • In the challenge phase(detailed below), the original aggregator that submitted the block must prove the marked layer 2 transaction is correct. The aggregator can submit a cancel challenge request to prove its correctness, which executes the marked layer 2 transaction on chain. CKB only accepts valid transactions on chain, which means the layer 2 transaction must succeed for the cancel challenge request to be committed on chain. In a cancel challenge request, the aggregator can claim staked tokens from the challenger.
    • If an aggregator fails to prove the layer 2 transaction is valid by canceling the challenge request, the challenger can then claim tokens staked by the aggregator and revert the chain to the state as it was before the invalid layer 2 block.
  • An unfinalized layer 2 block will be finalized over a certain period of time. Finalized layer 2 blocks will be frozen and cannot be challenged.
note

This is slightly different from the typical optimistic rollover: a transaction being executed here is simply to prove the transaction is valid, and a timeout indicates challenge success in this case.

In this design, an aggregator bears the liquidity costs for staking CKB, as well as layer 1 transaction fees for the layer 1 transactions containing layer 2 blocks. In exchange, the aggregator can charge layer 2 transaction fees from layer 2 users. As explained below, layer 2 transaction fees can be paid in CKB or any kind of sUDT. In fact, Godwoken treats CKB and sUDTs as indentical in layer 2.

Multiple Block Producers

Godwoken is designed based on the assumption that anyone can propose layer2 blocks. But in the current deployment, Godwoken only has one block producer to sequence transactions and propose new blocks. Supporting multiple block producers (or sequencers) is still an open question for different rollups. In the future, we may introduce a consensus through an upgrade to support multiple block producer coordination.

Actions

This sections contains an explanation of the actions that are available on Godwoken, along with detailed technical information on each action.

Deposit

To use Godwoken, you must first deposit some tokens (CKB or sUDT) from layer 1 to layer 2. This is called a deposit action. The deposit action is represented as a layer 1 transaction. It must create a special output cell called a deposit cell. Here is an example of a deposit action. This transaction deposits 10000 CKB to Godwoken. All that matters here is the lock script of output cell #0:

{
"code_hash": "0x50704b84ecb4c4b12b43c7acb260ddd69171c21b4c0ba15f3c469b7d143f6f18",
"args": "0x702359ea7f073558921eb50d8c1c77e92f760c8f8656bde4995f26b8963e2dd8a900000014000000340000009d000000a5000000e13f4a9c1642a6c59766eb5509580808bf3917bba104a616df0d207c93050e596900000010000000300000003100000007521d0aa8e66ef441ebc31204d86bb23fc83e9edc58c19dbb1b0ebe64336ec00134000000702359ea7f073558921eb50d8c1c77e92f760c8f8656bde4995f26b8963e2dd8e8ae579256c3b84efb76bbb69cb6bcbef1375f00813a0900000000c002000000",
"hash_type": "type"
}

code_hash and hash_type are pre-determined by each Godwoken deployment. args contains two parts:rollup type hash and DepositLockArgs. The first 32 bytes of args contain the rollup type hash of the current Godwoken deployment, which functions as a chain ID. The DepositLockArgs data structure is serialized in molecule format as follows:

table DepositLockArgs {
// layer1 lock hash
owner_lock_hash: Byte32,
layer2_lock: Script,
cancel_timeout: Uint64,
registry_id: Uint32,
}

The data structure above contains 2 parts of information:

  • layer2_lock specifies the lock script to be used when Godwoken transfers the tokens to layer 2.
  • If Godwoken ignores the request(e.g. when the network becomes bloated), owner_lock_hash and cancel_timeout provide a way to redeem the tokens. cancel_timeout specifies a timeout parameter in CKB's since format. When the timeout has expired, the user can create another request to cancel the deposit action and redeem the tokens for other uses. owner_lock_hash is used to provide the token owner's identity in case of a cancel deposit action.

Godwoken periodically collects all live deposit cells, and include them in layer 2 blocks. It transforms each deposit cell to custodian cell, creates (if one does not exist) a layer 2 account correspondingly based on the layer2_lock used in DepositLockArgs, and then put the newly deposited tokens in this account.

Custodian cells contain all the tokens that are managed internally in Godwoken. This transaction contains a custodian cell in its output cell #2. Like a deposit cell, a custodian cell is represented by its lock script:

{
"code_hash": "0x85ae4db0dd83f428a31deb342e4000af37ce2c9645d9e619df00096e3c50a2bb",
"args": "0x702359ea7f073558921eb50d8c1c77e92f760c8f8656bde4995f26b8963e2dd8e1000000100000003000000038000000595a1405676547c8ddc083f3413d58d65a1bb9275b5a923ec61dce4e572647cc4c6f000000000000a900000014000000340000009d000000a5000000e13f4a9c1642a6c59766eb5509580808bf3917bba104a616df0d207c93050e596900000010000000300000003100000007521d0aa8e66ef441ebc31204d86bb23fc83e9edc58c19dbb1b0ebe64336ec00134000000702359ea7f073558921eb50d8c1c77e92f760c8f8656bde4995f26b8963e2dd8e8ae579256c3b84efb76bbb69cb6bcbef1375f00813a0900000000c002000000",
"hash_type": "type"
}

Like deposit cells, custodian cells have pre-determined code_hash and hash_type based on Godwoken deployments. The first 32 bytes in args contain the rollup type hash as well. What's different here is that CustodianLockArgs is used to fill the remaining part of args:

table CustodianLockArgs {
deposit_block_hash: Byte32,
deposit_block_number: Uint64,
// used for revert this cell to deposit request cell
// after finalize, this lock is meaningless
deposit_lock_args: DepositLockArgs,
}

As noted, a custodian cell contains the original deposit information, as well the layer 2 block information in which the original deposit cell is processed.

Layer 2 Transfer

Once tokens have been deposited and processed by Godwoken, they can be used in layer 2 Godwoken blockchain. A layer 2 Godwoken transaction uses a totally different structure from a layer 1 CKB transaction:

table RawL2Transaction {
// chain id
chain_id: Uint64,
from_id: Uint32,
to_id: Uint32,
nonce: Uint32,
args: Bytes,
}

table L2Transaction {
raw: RawL2Transaction,
signature: Bytes,
}

Full defination and related types can be found in this file.

There are no cells in the layer 2 Godwoken transaction structure. There is only 32-bit integer value representing the accounts, a 32-bit nonce which is not used in layer 1 CKB, as well as a single variable length args part. This layer 2 transaction structure will be detailed piece by piece.

While all operations on Godwoken can be represented as a L2Transaction in the above format, let's start from a simple transfer operation:

{
"raw": {
"chain_id": "0x116e9",
"from_id": "0x2",
"to_id": "0x1",
"nonce": "0x3",
"args": "0x01000000040000009001000000000000000000000000000064000000000000000000000000000000"
},
"signature": "0x306d9240d6b18eaa29c83b5f4603c9cbd04402d09c3c38063a87c35b9bfe4ce16ffe64b9f20e306a59819cecfd9b72d3f62888305155bd3e48e40742a3cd8a8901"
}

This is the JSON representation of L2Transaction data structure. args contains a variable length, free formatted transaction argument data that is interpreted depending on the values of to_id. In this particular example, args contains SUDTTransfer data structure in the molecule serialization format:

table SUDTTransfer {
// Godwoken registry address: (registry_id (4 bytes) | address len(4 bytes) | address)
to_address: Bytes,
amount: Uint256,
// paid fee(ckb)
fee: Fee,
}

A JSON representation for this data structure is:

{
"type": "SUDTTransfer",
"value": {
"to_address": "0x0200000014000000bb1d13450cfa630728d0390c99957c6948bf7d19",
"amount": "0x190",
"fee": "0x64"
}
}

In this example, we are transferring 400 shannons(denoted by to_id 0x1) from account 0x2(denoted by from_id) to account 0x0200000014000000bb1d13450cfa630728d0390c99957c6948bf7d19(denoted by to_address in SUDTTransfer). In addition, we may need to pay 100 shannons as layer 2 transaction fees. The next 2 sections will discuss layer 2 transaction in more detail.

Account Locks

An account lock controls how a signature for a layer 2 transaction is validated. Recall that a deposit cell actually includes a layer 2 lock script in its DepositLockArgs:

table DepositLockArgs {
layer2_lock: Script,

// ...
// other fields are omitted
}

When Godwoken sees a layer2_lock in a DepositLockArgs data structure, it first queries its internal storage to locate an account using the particular layer 2 lock script. Godwoken will create a new account for this lock script if there is none, and assign a 32-bit integer account ID to it. Godwoken enforces a one-to-one mapping between a layer 2 lock script and account ID:

  • Given an account ID, one can look up for the layer 2 lock script in Godwoken.
  • Given a layer 2 lock script, there can be at most one account ID using that lock script in current Godwoken deployment

This provides some handy consequences for us:

  • Users do not need to remember their account IDs in a Godwoken deployment. As long as they know their wallet addresses, they will be able to query the correct account ID when translated to layer 2 lock script;
  • A layer 2 transaction can only include the account ID, which can be used to query the actual layer 2 lock script. This results in smaller layer 2 transaction and bigger throughput.

Users can choose any lock script to use in layer 2 while still minimizing layer 2 transaction size. This approach offers the advantage that layer 1 is as flexible as layer 2.

We developed Godwoken in conjunction with Polyjuice, our Ethereum-compatible solution. Furthermore, we provide an Ethereum compatible lock that enables you to use MetaMask in conjunction with Polyjuice. In practice, this feature has a broader application with different layer 2 locks. An example of this would be a lock that implements the EOS signature validation logic that enables an EOS wallet to call Ethereum dApps through Polyjuice. The entire interoperability power of Nervos CKB is preserved in Godwoken as well.

Now we can derive the signature validation rule for a layer 2 Godwoken transaction:

  • Godwoken locates the corresponding layer 2 lock script by using from_id in the layer 2 transaction.
  • The layer 2 lock script is executed to validate the layer 2 transaction.

The above flow has one quirk: the current version of Godwoken uses the optimistic rollup design. Due to the "optimistic" nature, the layer 2 lock script is not typically executed on chain. It is only executed when a challenger initiates a challenge request on chain, and an aggregator validates the validity of the layer 2 transaction through a cancel challenge request. Hence the way to build a layer 2 lock script is also slightly different. An example of such a script can be found here.

Backend

After the signature has been verigied, another problem arises: how does Godwoken calculate the next on-layer2-chain state? Backends in Godwoken handle this task.

Godwoken actually has 2 kinds of accounts:

  • Typical user accounts denoted by an account lock.
  • Contract accounts denoted by a backend script.

This design is quite similar to the EOA vs. contract account in Ethereum, which is appropriate because Godwoken was inspired by Ethereum.

A typical user account is a balance owned by the user, while a contract account provides storage for the on-chain smart contracts. The user account is generated from a general lock script with a user's public key hash. The smart contract for the contract account is generated from a backend with some special script args. In the Nervos ecosystem, smart contracts are also represented using the unified script data structure: the combination of code_hash and hash_type in a script identifies the backend, while args provides specific contract account arguments.

In the light of the above, the following rule can be introduced for executing backends:

  • Godwoken locates the corresponding backend script by using to_id in the layer 2 transaction;
  • The backend script is executed to calculate the state after applying the current transaction.

Similar to account locks, the above rule is more of a conceptual rule. The actual backend script is only executed on-chain in a cancel challenge request.

Here are a few examples of backends:

SUDT

sUDT is the most common backend in Godwoken and all tokens, whether they are CKB or layer 1 sUDTs, are represented as layer 2 sUDT types. A layer 2 sUDT backend script is defined by the following specification:

  • code_hash and hash_type are pre-determined by each Godwoken deployment.
  • args contains the script hash of layer 1 sUDT type script. In case CKB is used, args will be 0x0000000000000000000000000000000000000000000000000000000000000000.

In this way, we can correlate layer 1 sUDT type scrips with its corresponding layer 2 sUDT backend script.

The layer 2 sUDT backend provides ERC20 compatible interface. You may not using Solidity and EVM, but a similar API is available for you to use.

Layer 2 transaction using a sUDT backend must provided a SUDTArgs data structure serialized in molecule format in layer 2 transaction args. The two supported actions for sUDT backend is SUDTQuery and SUDTTransfer. Later versions may expand this list to include other ERC20 operations.

Note that Godwoken typically creates sUDT contract accounts when processing deposit cells. It doesn't create new contract accounts using sUDT as the backend script.

An implementation for the layer 2 sUDT backend can be found here.

MetaContract

MetaContract is a special backend in Godwoken:

  • It is reserved as account ID 0;
  • You cannot create a new contract account using MetaContract as the backend.

The sole purpose of MetaContract now is to create contract account given a particular backend.

Here is the JSON representation for a layer 2 transaction, which invokes MetaContract to create a Polyjuice root contract account:

{
"raw": {
"chain_id": "0x116e9",
"from_id": "0x2",
"to_id": "0x0",
"nonce": "0x4",
"args": "0x0000000041000000080000003900000010000000300000003100000020814f4f3ebaf8a297d452aa38dbf0f9cb0b2988a87cb6119c2497de817e7de9000400000001000000"
},
"signature": "0xc62d332f398323b972c5ee5c4481661ca9d17125af6f61e5220e2fbfe3bd325a0cc6c3ac174950dc1282d5e6059fc08838b9040ed7eca0ad13474af869f25a8701"
}

The args part in this transaction contains a MetaContractArgs data structure serialized in molecule format. A JSON representation for args is as follows:

{
"type": "CreateAccount",
"value": {
"script": {
"code_hash": "0x20814f4f3ebaf8a297d452aa38dbf0f9cb0b2988a87cb6119c2497de817e7de9",
"hash_type": "data",
"args": "0x01000000"
}
}
}

The details of this transaction will be explained in Life of a Polyjuice Transaction.

Polyjuice

Polyjuice is the main backend used now in Godwoken. It allows us to create a contract account using EVM bytecode. The resulting account will be able to execute smart contracts written for Ethereum. Polyjuice aims at 100% compatibility at EVM level, meaning all applications that runnable on Ethereum can be run on Godwoken powered by Polyjuice.

For more details on polyjuice, refer to Life of a Polyjuice Transaction.

Managing Account Locks & Backends

It is possible to create as many accounts as you like. However, Godwoken now only supports whitelisted account locks and backends for security reasons. The list now includes:

  • Ethereum compatible account lock
  • MetaContract backend
  • Layer 2 sUDT backend
  • ETH Registry backend
  • Polyjuice backend

If there is a particular account lock or backend that you like, please do not hesistate to let us know. We are interested in a future where an EOS wallet can control an Ethereum application, or a BTC wallet can control a Diem application. This is all possible with Godwoken thanks to the composability of account locks and backends.

Withdraw

The withdraw action enables you to withdraw tokens from layer 2 back to layer 1. A withdraw action uses the following data structure:

struct RawWithdrawalRequest {
nonce: Uint32,
// chain id
chain_id: Uint64,
// CKB amount
capacity: Uint64,
// SUDT amount
amount: Uint128,
sudt_script_hash: Byte32,
// layer2 account_script_hash
account_script_hash: Byte32,
// withdrawal registry ID
registry_id: Uint32,
// layer1 lock to withdraw after challenge period
owner_lock_hash: Byte32,
// withdrawal fee, paid to block producer
fee: Uint128,
}

vector WithdrawalRequestVec <WithdrawalRequest>;

struct WithdrawalRequest {
raw: RawWithdrawalRequest,
signature: Bytes,
}

The full definition and related types can be found in this file.

WithdrawalRequest uses account_script_hash as a key to locate the account lock and perform the same signature verification flow as layer 2 transaction. The Withdraw action can be used to withdraw CKB and a layer 1 sUDT type simutaneously. Due to the constraint of CKB's cell model, Godwoken currently requires a minimum of 400 CKBytes for each withdrawal.

WithdrawalRequest contains additional fields that are used to fulfill a special feature of Godwoken, the selling of withdrawn tokens. This is explained in the next section.

Here is the JSON representation of a WithdrawalRequest:

{
"raw": {
"nonce": "0x3",
"chain_id": "0x116e9",
"capacity": "0x9502f9000",
"amount": "0x0",
"registry_id": "0x2",
"sudt_script_hash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"account_script_hash": "0x167d7855f41c343e2064fc5b62f7d640d2d74a8255766bff62f1f5fc63ab4c58",
"owner_lock_hash": "0xa35eda3e71e86e4e22b7924012b6a6e90809dc7a68621d5f7a7c40eea01be45e",
},
"signature": "0xef1a36bb7cbd3884bf404811ed0e534b4ef3f87abe52f44838018b3af7c7a5534cd1b0b5c1cb55a32b122278ebc9fe37a92e2442ba83160e778163eafd444b9700"
}