THIS IS A WORK IN PROGRESS!
The goal of this post is to provide a rough outline of what phase 2 might look like, to help make the discussion about state and execution more concrete as well as to give us an idea of the level and types of complexity that would be involved in implementing it. This section focuses on withdrawals from the beacon chain and cross-shard calls (which use the same mechanism); rent is unspecified but note that a hibernation can be implemented as a forced cross-shard call to the same shard.
Topics covered:
- Addresses
- Cross-shard receipt creation and inclusion
- Withdrawing from the beacon chain
Addresses
The Address type is a bytes32, with bytes used as follows:
[1 byte: version number] [2 bytes: shard] [29 bytes: address in shard]
There are many choices to make for address encoding when presented to users; one simple one is: version number - shard ID as number - address as mixed hex, eg. 0-572-0DF5283B84D83637e3E6AAC675cE922d558b296e8B11c43881b3f91484, but there are many options. Note that implementations may choose to treat an address as a struct:
{
"version": "uint8",
"shard": "uint16",
"address_in_shard": "bytes29"
}
Because SSZ encoding for basic tuples is just concatenation, this is equivalent to simply treating Address as a bytes32 with the interpretation given above.
Cross shard receipts
A CrossShardReceipt object, which contains the following fields:
{
"target": Address,
"wei_amount": uint128,
"index": uint64,
"slot": SlotNumber,
"calldata": bytes,
"init_data": InitiationData
}
InitiationData is the following:
{
'salt': bytes32,
'code': bytes,
'storage': bytes,
}
Note that in each shard there are a few “special addresses” relevant at this point:
-
CROSS_SHARD_MESSAGE_SENDER: 0x10 - has two functions:- Regular send: accepts as argument (i)
target: Address, (ii)calldata. Creates aCrossShardReceiptwith arguments:target=target,wei_amount=msg.value,index=self.storage.next_indices[target.shard](incrementingself.storage.next_indices[target.shard] += 1after doing this),slot=current_slot,calldata=calldata,init_data=None. - Yank: accepts as argument
target_shard: ShardNumber. Creates aCrossShardReceiptwithtarget=Address(0, target_shard, msg.sender),wei_amount=get_balance(msg.sender),index=self.storage.next_indices[target.shard](incrementingself.storage.next_indices[target.shard] += 1after doing this),slot=current_slot,calldata='',init_data=InitiationData(0, get_code(msg.sender), get_storage(msg.sender)). Deletes the existingmsg.senderaccount.
- Regular send: accepts as argument (i)
-
CROSS_SHARD_MESSAGE_RECEIVER: accepts as argument aCrossShardReceipt, asource_shardand a Merkle branch. Checks that the Merkle branch is valid and is rooted in a hash that the shard knows is legitimate for thesource_shard, and checks thatself.current_used_indices[source_shard][receipt.index] == 0. If theslotis too old, requires additional proofs to check that the proof was not already spent (see Cross-shard receipt and hibernation/waking anti-double-spending foe details). If checks pass, then executes the call specified; ifinit_datais nonempty and the target does not exist, instantiates it with the given code and storage.
Withdrawal from the beacon chain
A validator that is in the withdrawable state has the ability to withdraw. The block has a withdrawals field that contains a list of all withdrawals that happen in that block, and each withdrawal is a standardized CrossShardReceipt obect.
A CrossShardReceipt created by the beacon chain shard will always have the following arguments:
{
"address_to": Address(0,
dest_shard,
hash(salt + hash(init_storage) + hash(code))[3:]),
"wei_amount": deposit_value,
"index": state.next_indices[dest_shard],
"slot": state.slot,
"calldata": "",
"init_data": InitiationData(
"salt": 0,
"code": init_code,
"storage": init_storage
)
}
Where dest_shard, salt, init_storage, init_code are all chosen by the withdrawing validator. state.next_indices[dest_shard] is then incremented by 1. This receipt can then be processed by the CROSS_SHARD_MESSAGE_RECEIVER contract just like any other cross-shard receipt.
Transactions
A transaction object is as follows:
{
"version": "uint8",
"gas": "uint64",
"gas_max_basefee": "uint64",
"gas_tip": "uint64",
"call": CrossShardReceipt
}
Executing a transaction is simply processing the call, except a transaction (or generally, any call that it not directly an execution of an actual cross-shard receipt) can only create an account at some address if it has the salt such that target = hash(salt, hash(code) + hash(storage)). Note that a transaction simply specifies a call to an account; it’s up to the account to implement all account security logic.
Not covered:
- Specific protocol changes to facilitate abstraction
- The exact mechanics of the fee market (see https://github.com/ethereum/EIPs/issues/1559 for a rough idea though)
- The mechanics of the rent mechanism