Skip to main content

Root Rollup

The root rollup circuit is our top circuit, it applies the state changes passed through its children and the cross-chain messages. Essentially, it is the last step that allows us to prove that the state transition function T(S,B)S\mathcal{T}(S, B) \mapsto S' was applied correctly for a state SS and a block BB. Note, that the root rollup circuit's public inputs do not comprise the block entirely as it would be too costly to verify. Given a ProvenBlock and proof a node can derive the public inputs and validate the correctness of the state progression.

For rollup purposes, the node we want to convince of the correctness is the validating light node that we put on L1. We will cover it in more detail in the cross-chain communication section.

Squishers

This might practically happen through a series of "squisher" circuits that will wrap the proof in another proof that is cheaper to verify on-chain. For example, wrapping a ultra-plonk proof in a standard plonk proof.

Overview

Validity Conditions

def RootRollupCircuit(
l1_to_l2_roots: RootParityInput,
l1_to_l2_msgs_sibling_path: List[Fr],
parent: Header,
parent_sibling_path: List[Fr],
archive_sibling_path: List[Fr],
left: ChildRollupData,
right: ChildRollupData,
) -> RootRollupPublicInputs:
assert left.proof.is_valid(left.public_inputs)
assert right.proof.is_valid(right.public_inputs)
assert l1_to_l2_roots.proof.verify(l1_to_l2_roots.public_inputs)

assert left.public_inputs.constants == right.public_inputs.constants
assert left.public_inputs.end == right.public_inputs.start
assert left.public_inputs.type == right.public_inputs.type
assert left.public_inputs.height_in_block_tree == right.public_inputs.height_in_block_tree

assert parent.state.partial == left.public_inputs.start

# Check that the parent is a valid parent
assert merkle_inclusion(
parent.hash(),
parent_sibling_path,
left.public_inputs.constants.global_variables.block_number,
left.public_inputs.constants.last_archive.root
)

# Update the l1 to l2 msg tree
l1_to_l2_msg_tree = merkle_insertion(
parent.state.l1_to_l2_message_tree,
l1_to_l2_roots.public_inputs.converted_root,
l1_to_l2_msgs_sibling_path,
L1_TO_L2_MSG_SUBTREE_HEIGHT,
L1_TO_L2_MSG_TREE_HEIGHT
)

header = Header(
last_archive = left.public_inputs.constants.last_archive,
content_commitment: ContentCommitment(
tx_tree_height = left.public_inputs.height_in_block_tree + 1,
txs_hash = SHA256(left.public_inputs.txs_hash | right.public_inputs.txs_hash),
in_hash = l1_to_l2_roots.public_inputs.sha_root,
out_hash = SHA256(left.public_inputs.out_hash | right.public_inputs.out_hash),
),
state = StateReference(
l1_to_l2_message_tree = l1_to_l2_msg_tree,
partial = right.public_inputs.end,
),
global_variables = left.public_inputs.constants.global_variables,
)

archive = merkle_insertion(
header.last_archive
header.hash(),
archive_sibling_path,
0,
ARCHIVE_HEIGHT
)

return RootRollupPublicInputs(
aggregation_object =
left.public_inputs.aggregation_object +
right.public_inputs.aggregation_object,
archive = archive,
header: Header,
)

The RootRollupPublicInputs can then be used together with Body to build a ProvenBlock which can be used to convince the validating light node of state progression.