A Journey Through Phase 2 of Ethereum 2.0

Moving 1.0 ether into the 2.0 chain

In order to move your ether from the old Ethereum 1.0 chain and into the Ethereum 2.0 chain, you’ll need to burn your ether by depositing it into a contract on the old chain. The new chain has a protocol and voting period to recognize this deposit and surface your ether into the beacon chain. Other than deposit into the contract on the old chain, you do not need to do anything else. The current protocol’s voting/inclusion system will bring your funds into the beacon chain automatically. Over time, this system may be phased out for another one as the legacy chain becomes less active. However, discussions are still happening on what this transition could look like.

Beacon Chain Contracts

The beacon chain now stores smart contracts (recently called Threads) known as beacon chain contracts. These contracts are not analogous to regular smart contracts you would deploy for your application on Ethereum 1.0 (or smart contracts such as multisig wallets that you would set up to represent your account in the eth2 account abstraction scheme). Those would live within the shard chains. In contrast, beacon chain contracts will represent execution environments or transaction frameworks as a whole. For example, you could have a different beacon chain contract representing a different implementation or framework around Ethereum. One may represent an account-based Ethereum transaction/storage model, while another could represent a UTXO-based Ethereum model. In practice, there should not be a plethora of beacon chain contracts. There should only be a few — especially at first.

Buying into a framework

Now that we have a little background, lets continue our journey. We’ve decided we want to bring our eth into the accepted account-based Ethereum model. Lets say this model is defined within beacon chain contract 0 (or lives in the 0th index of the list of contracts). Also, we’ve already brought our ether from eth 1.0 into the beacon chain. It lives in a validator/staking account, but we are choosing to not become an active validator. Instead, we want our ether to surface into a particular shard. In this case, lets say we’re interested in buying into shard 5 (my favorite cryptokitties application lives there and I’ve been dying to play :)).

{
# Your account in the beacon chain holding the eth
"validator_index": uint64,
"data": {
shard: 5,
address: address
},
"pubkey": BLSPubkey,
"signature": BLSSignature
}

Shard Chain Contract and State Execution

As discussed above, shard chains do not have a concept of native ether. Instead, you lock your ether into a beacon chain contract and then use it on the shard chain. To understand this process, it’s good to visualize and understand the connection between the shard chain and beacon chain contracts. In essence, the shard chains and their state execution functions will be a reflection and integration of the framework defined in the beacon chain contracts.

{
# What we think of as the actual "state"
"objects": [[StateObject, 2**256], 2**64],
# Receipts
"receipts": [Receipt],
"next_receipt_index": uint64,
# Current slot
"slot": uint64,
...
}
{
# Version number for future compatibility
"version": uint64,
# Contents
"storage": bytes,
# StateObject can be removed if it expires (ie. now > ttl)
"ttl": uint64
}

Moving Ether into the Shard Chain

The beacon chain contract has a function called depositToShard.

def depositToShard(state: BeaconState,
receipt: WithdrawalReceipt,
proof: WithdrawalReceiptRootProof):
# Verify Merkle proof of the withdrawal receipt
assert verify_withdrawal_receipt_root_proof(
get_recent_beacon_state_root(proof.root_slot),
receipt,
proof
)
# Interpret receipt data as an object in our own format
receipt_data = deserialize(receipt.withdrawal.data, FormattedReceiptData)
# Check that this function is being executed on the right shard
assert receipt_data.shard_id == getShard()
# Check that the account does not exist yet
assert getStorageValue(hash(receipt_data.pubkey)) == b''
# Set its storage
setStorage(hash(receipt_data.pubkey), serialize(EthAccount(
pubkey=receipt_data.pubkey,
nonce=0,
value=receipt.amount
)))
EthAccount {
pubkey: BLSPubkey,
nonce: 0,
value: 32eth
}

Transferring funds

Now that we have an account on the shard chain, we should be able to transfer some of our funds to another account. This is simple and only a matter of adding another function to the beacon chain contract:

def transfer(sender: bytes32,
nonce: uint64,
target: bytes32,
amount: uint64,
signature: BLSSignature):
sender_account = deserialize(getStorageValue(sender), EthAccount)
target_account = deserialize(getStorageValue(target), EthAccount)
assert nonce == sender_account.nonce
assert sender_account.value >= amount
assert bls_verify(
pubkey=sender_account.pubkey,
message_hash=hash(nonce, target, amount),
signature=signature
)
setStorage(sender, EthAccount(
pubkey=sender_account.pubkey,
nonce=sender_account.nonce + 1,
value=sender_account.value - amount
))
setStorage(target, EthAccount(
pubkey=target_account.pubkey,
nonce=target_account.nonce,
value=target_account.value + amount
))

Recap on current journey so far

So far, we’ve taken our eth and moved it into the Ethereum 2.0 beacon chain from the Ethereum 1.0 chain. We also locked the eth into a beacon chain contract which allowed us to use it on shard 5. Finally, we transferred some of our eth to another account on that shard. So far, we’ve started simple. The current framework only represents a basic account model with a balance. We could definitely make it a bit more complex and begin including smart contracts and sophisticated state executions, but lets hold off for a bit. I want to add just a little more of a foundation, then we’ll begin talking about a more sophisticated transaction model.

Fee Markets

In the current journey, we’ve stated that shard chains do not have any concept of native ether. In reality, your ether will surface into a shard chain differently based on the framework or beacon chain contract you buy into. This may bring a couple questions to mind. For example, if there is no native currency, how does a block producer get paid? In our example, this framework pegs a 1:1 with beacon chain ether. Therefore, the block producer probably won’t mind accepting the ether or currencies defined in a beacon chain contract. However, this brings forward even more questions.

{
'to': address,
'from': address,
'amount': uint64,
'gas_price': uint64,
'signature': bytes
}
{
'transactions': [Transaction],
'signature': bytes,
'fee': uint64
}
def process_block(block: BlockProposal):
assert verify_signature(block, block.signature)
for transaction in transactions:
process_transfer(transaction)
def proccess_transfer(tx):
assert verify_signature(tx, tx.signature)

to = get_storage(tx.to)
to.balance += tx.amount
gas_fee = TRANSFER_GAS * tx.gas_price
from = get_storage(tx.from)
from.balance -= (tx.amount + gas_fee)

relayer = get_storage(get_relayer())
relayer.balance += gas_fee

set_storage(tx.to, to)
set_storage(tx.from, from)
set_storage(get_relayer(), relayer)

Shard Chains Don’t Need State

To get a deeper dive into this concept, take a look at Vitalik’s proposal here. A couple ideas are introduced in his writeup:

  1. State or the objects field we described earlier do not need to be maintained within a shard node. The concept of it may still exist, but it exists at the application layer and not at the consensus layer; nodes that are not interested in “plugging in” to this specific execution environment need not be aware of it.
  2. Checkins or crosslinks on the beacon chain get compressed state checkins (ie Merkle hash).
[
...,
EthAccount{
nonce: 3,
value: 1232,
pub_key: BLSPubkey
},
EthAccount{
nonce: 12,
value: 22,
pub_key: BLSPubkey
},
...
]
  1. Reduced shuffling period on the shard chain gives us better security and simplicity (no need to spend days to sync a full node and stakers have less time to collude)
  2. Longer shuffling period on crosslink/beacon chain committees gives us more network stability and reduces the load on syncing a new light client every 6 minutes

Cross Shard Transactions

Cross shard transactions are largely out of the scope of this blog post. We are writing a number of POCs right now and contributing in active research within this area. Expect to receive write-ups and further blog posts on this topic soon. However, the following are good write-ups on the current discussion:

Expanding to Full State Execution

If you’ve made it this far, congratulations! There has been a lot of information to absorb. At this point, you really should just dive deeper into Vitalik’s original proposal (which gives an overview of full state execution) and followup discussion. If you are trying to engage in a lighter read, you may want to skip forward to the conclusion which covers general pros and cons of this proposal. However, I’ll give a brief explanation.

EthAccount {
pubkey: BLSPubkey,
nonce: 0,
value: uint64,
code: bytes
}
executeCode(code: bytes, data: bytes) -> bytes

Double Spending Protection via Bitfields

Part of the phase 2 proposal adds a receipts list to both the beacon chain and shard chains. These receipts are extremely important. For example, the depositToShard function we called earlier utilizes a receipt from the beacon chain. Additionally, shard chains also store receipts in their blocks. The shard chain receipts are used for a few purposes:

  1. Verification in cross shard transactions via merkle proof
{
# Unique nonce
"receipt_index": uint64,
# Execution Script (beacon chain contract index) that the receipt is created by
"executor": uint64,
# Address that it is intended for
"target": bytes32,
# Data
"data": bytes
}
check_and_set_bitfield_bit(bitfield_id: uint64, bit: uint64):

Conclusion

This is definitely a dense article. I hope it helps in your journey of understanding the current and evolving specification around Ethereum 2.0, phase 2. I’m also hoping it helps as a secondary resource if you are wanting to read through current proposals.

  1. Less risk of consensus forks
  2. Faster time to deployments (updates to execution/beacon contracts vs. having to update the core protocol and client code)
  3. Easier path to upgrade the environment in the future without hard fork governance politics
  4. Less code that needs to be written/repeated in different client implementations
  5. Ability to test different approaches in parallel on the same base layer
  1. The execution environments/beacon chain contracts would need to be audited meticulously to ensure there are no bugs/issues
  2. Less hard fork politics, but more standardization politics
  3. Risk that the total complexity of the consensus layer, layer 2, relay networks and secondary environments is greater

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Will Villanueva

Will Villanueva

@wjvill CEO & Co-Founder of Element Finance