Protocol is the centerpiece of every Protokit app-chain. Firstly it defines how state transitions generated by the rest of the framework are applied to the underlying state tree. Secondly it determines how proofs of state transitions are brought together with proofs of runtime execution to form a block, or at least a part of it.

Every protocol is composed of protocol modules, following the modular design of Protokit. The bare minimum protocol must always contain a StateTransitionProver and a BlockProver module.

Although modular, the protocol and sequencer have a certain level of coupling, therefore the need to always specify the state transition and block prover modules. The underlying coupling of the protocol and the sequencer comes from the orchestrational (block production) responsibilities of the sequencer.

State transitions

One of the core responsibilities of the protocol, is to define how state transitions coming from the rest of the framework are applied to the underlying state tree. State transition is defined by path, from and to properties, where from/to are optional values.

This enables state transitions not only to model how a state moves forward, but also to define how a state is meant to look like at a certain point in time. The ability to reason about the current state is reffered to as preconditions. The state transition prover implementation shipped with Protokit, is capable of both enforcing state transition preconditions and state writes.

Most importantly, state transition preconditions allow the sequencer to supply unchecked values during on-chain execution of runtime, and still be able to prove the correctness of the values supplied within the protocol.

Circuits for applying state transitions to the state tree are inifinitely recursive, in order to compensate for the current O1JS circuit size limits. This allows us to apply arbitrary amount of state transitions per e.g. each runtime method execution.

Block production

Blocks are created by either merging a runtime execution proof with a state transition proof, or by merging two block proofs. Applying a transaction to a block requires two things: a runtime proof and a state transition proof. Both of these proofs are merged together to generate a 'block proof' within the block prover. As part of this process, both proofs's commitments are cross checked to have proven their computation on top of the same underlying data. This means for instance checking if the state transition proof applied all the state transitions emitted by the runtime proof.

Extending the protocol with hooks

Protocol modules can extend tap into the lifecycle of the existing protocol, by specifying protocol hooks. Multiple different protocol hook injection points are available in the protocol out of the box: onTransaction, beforeBlock and afterBlock.

The onTransaction hooks have access to the state tree, and can emit state transitions as well. For instance account nonce tracking is implemented as an onTransaction hook. Hooks are executed from within the block prover, and have access to additional protocol context as well. (e.g. which transaction invoked the hook)

Protocol hooks also allow us to keep track of the last known network state, allowing for an implementation of historical state proofs.