Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions msm/encoding.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,18 +44,21 @@ type SimplexEpochInfo struct {
// of the sealing block of the previous epoch.
EpochNumber uint64 `canoto:"uint,2"`
// PrevSealingBlockHash is the hash of the sealing block of the previous epoch.
// It is empty for the first epoch, and the second epoch has the PrevSealingBlockHash set to be
// the hash of the first ever block built by the StateMachine.
// It is set to the hash of the zero block in the first epoch, and in subsequent epochs it is set to be
// the hash of the sealing block of the previous epoch.
// This is used to be able to quickly fetch and verify the sealing blocks without having to retrieve the interleaving blocks,
// which allows to bootstrap the BLS keys of the validator set for each epoch before fully syncing the interleaving blocks.
PrevSealingBlockHash [32]byte `canoto:"fixed bytes,3"`

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this be a simplex.Digest?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes but it's unrelated to this PR. I can change it in the PR where I will flatten the protocol metadata

// NextPChainReferenceHeight is the P-Chain height that the StateMachine uses as a reference for the next epoch.
// When the NextPChainReferenceHeight is > 0, it means the StateMachine is on its way to transition to a new epoch
// in which the validator set will be based on the given P-chain height.
// It sets the PChainReferenceHeight for the next epoch.
NextPChainReferenceHeight uint64 `canoto:"uint,4"`
// PrevVMBlockSeq is the block sequence of the previous block that has a VM block (inner block).
// This is used to know on which VM block to build the next block.
PrevVMBlockSeq uint64 `canoto:"uint,5"`
// BlockValidationDescriptor is the metadata that describes the validator set of the next epoch.
// It is only set in the sealing block, and nil in all other blocks.
// It is only set in the sealing block and zero block, and nil in all other blocks.
BlockValidationDescriptor *BlockValidationDescriptor `canoto:"pointer,6"`
// NextEpochApprovals is the metadata that contains the approvals from validators for the next epoch.
// It is set only in the sealing block and the blocks preceding it starting from a block that has a NextPChainReferenceHeight set.
Expand Down
102 changes: 102 additions & 0 deletions msm/msm.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,43 @@ import (
"go.uber.org/zap"
)

// state encodes the different stages of the epoch transition process, which determines how we build and verify blocks.
//
// SimplexEpochInfo.NextState() inspects the parent block's metadata to perform the following state transitions:
//
// (initial state: No Simplex blocks yet)
// │
// ▼
// ┌───────────────────────────────────┐
// │ stateFirstSimplexBlock │ builds the zero block (no inner block);
// │ │ creates epoch 1 with the initial validator set
// └─────────────────┬─────────────────┘
// │
// ▼
// ┌───────────────────────────────────┐ ◀── validator set unchanged ──┐
// │ stateBuildBlockNormalOp │ │
// │ builds inner blocks within the │ ──────────────────────────────┘
// │ current epoch │ ◀────────────────────────────────────────────┐
// └─────────────────┬─────────────────┘ │
// │ validator set changed │
// │ (sets NextPChainReferenceHeight > 0) │
// ▼ │
// ┌───────────────────────────────────┐ ◀── not enough approvals ─────┐ │
// │ stateBuildCollectingApprovals │ │ │
// │ aggregates approvals from │ ──────────────────────────────┘ │
// │ the next epoch's validator set │ │
// └─────────────────┬─────────────────┘ │
// │ quorum reached: emit sealing block │
// │ (BlockValidationDescriptor set) │
// ▼ │
// ┌───────────────────────────────────┐ ◀── sealing block ────────────┐ │
// │ stateBuildBlockEpochSealed │ not finalized yet │ │
// │ emits Telock (no inner block) │ ──────────────────────────────┘ │
// │ until the sealing block is │ │
// │ finalized; then opens the new │ ─── new epoch (EpochNumber advanced) ─────────┘
// │ epoch │
// └───────────────────────────────────┘

var (
errLastNonSimplexInnerBlockNil = errors.New("failed constructing zero block: last non-Simplex inner block is nil")
errInvalidProtocolMetadataSeq = errors.New("invalid ProtocolMetadata sequence number: should be > 0")
Expand Down Expand Up @@ -314,6 +351,17 @@ func (sm *StateMachine) verifyEpochNumber(block *StateMachineBlock) error {
}

// buildBlockNormalOp builds a block while potentially also transitioning to a new epoch, depending on the P-chain.
//
// Relevant SimplexEpochInfo fields (PCH = PChainReferenceHeight,
// EN = EpochNumber, NPCH = NextPChainReferenceHeight):
//
// parent (NormalOp) validator set unchanged validator set changed at p'
// ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
// │ PCH = p │ ───► │ PCH = p (copy) │ │ PCH = p (copy) │
// │ EN = e │ OR │ EN = e (copy) │ OR │ EN = e (copy) │
// │ NPCH = 0 │ │ NPCH = 0 │ │ NPCH = p' (> 0) │
// └─────────────────┘ └─────────────────┘ └─────────────────┘
// → stays NormalOp → CollectingApprovals
func (sm *StateMachine) buildBlockNormalOp(ctx context.Context, parentBlock StateMachineBlock, simplexMetadata, simplexBlacklist []byte, prevBlockSeq uint64) (*StateMachineBlock, error) {
// Since in the previous block, we were not transitioning to a new epoch,
// the P-chain reference height and epoch of the new block should remain the same.
Expand Down Expand Up @@ -569,6 +617,29 @@ func (sm *StateMachine) createBlockBuildingDecider(pChainReferenceHeight uint64)

// buildBlockZero builds the first ever block for Simplex,
// which is a special block that introduces the first validator set and starts the first epoch.
//
// How EpochNumber (EN), PrevSealingBlockHash (PSH), and SealingBlockSeq (SBS)
// evolve along the block chain (Seq = block sequence number; h(n) = digest of
// the block at sequence n):
//
// ────────────────── Epoch 1 ────────────────────────────────────│─── Epoch s ────
// │
// Seq: z ... s s+1 ... s+x │ s+1 (Telocks get pruned) ...
// ┌──────┐ ┌────────┐ ┌──────┐ ┌──────┐ │ ┌────────────┐
// │ Zero │ ... │Sealing │ │Telock│ ... │Telock│ │ │first block │ ...
// │ block│ │ block │ │ │ │ │ │ │ of epoch s │
// └──────┘ └────────┘ └──────┘ └──────┘ │ └────────────┘
// EN = 1 EN = 1 EN = 1 EN = 1 │ EN = s
// SBS = 0 SBS = 0 SBS = s SBS = s │ SBS = 0
// PSH = 0 PSH = h(z) PSH = 0 PSH = 0 │ PSH = 0
//
// - EN : copied within an epoch; on the first block of a new epoch, EN
// equals the sequence number of the previous epoch's sealing block.
// The first epoch number is set to the sequence number of that block.
// - PSH : only set on a sealing block. In the first epoch it points to the zero block;
// otherwise it points to the previous epoch's sealing block.
// - SBS : 0 except on Telocks of a sealed-but-not-yet-finalized epoch, where
// it equals the sequence number of that epoch's sealing block.
func (sm *StateMachine) buildBlockZero(parentBlock StateMachineBlock, simplexMetadata, simplexBlacklist []byte) (*StateMachineBlock, error) {
pChainHeight := sm.LastNonSimplexBlockPChainHeight

Expand Down Expand Up @@ -664,6 +735,22 @@ func (sm *StateMachine) verifyBlockZero(block *StateMachineBlock, prevBlock Stat
return nil
}

// buildBlockCollectingApprovals builds either another collecting-approvals block (if not enough approvals yet)
// or a sealing block (if quorum is reached).
//
// Relevant SimplexEpochInfo fields (EN = EpochNumber, NPCH = NextPChainReferenceHeight,
// NEA = NextEpochApprovals, BVD = BlockValidationDescriptor, PSH = PrevSealingBlockHash):
//
// parent (Collecting) not enough approvals yet quorum of approvals reached: sealing block
// ┌──────────────────┐ ┌────────────────────┐ ┌────────────────────────────┐
// │ EN = e │ │ EN = e │ │ EN = e │
// │ NPCH = p' │ ────► │ NPCH = p' │ │ NPCH = p' │
// │ NEA = A_old │ │ NEA = A_old ∪ new │ OR │ NEA = A_old ∪ new │
// │ BVD = nil │ │ BVD = nil │ │ BVD = validator set at p' │
// │ │ │ │ │ PSH = h(prev epoch's │
// │ │ │ │ │ sealing block) │
// └──────────────────┘ └────────────────────┘ └────────────────────────────┘
// → stays Collecting → BuildBlockEpochSealed
func (sm *StateMachine) buildBlockCollectingApprovals(ctx context.Context, parentBlock StateMachineBlock, simplexMetadata, simplexBlacklist []byte, prevBlockSeq uint64) (*StateMachineBlock, error) {
newApprovals, err := sm.computeNewApprovals(parentBlock)
if err != nil {
Expand Down Expand Up @@ -943,6 +1030,21 @@ func (sm *StateMachine) areWeReadyToTransitionEpoch(parentBlock StateMachineBloc
}

// buildBlockEpochSealed builds a block where the epoch is being sealed due to a sealing block already created in this epoch.
//
// Relevant SimplexEpochInfo fields (PCH = PChainReferenceHeight, EN = EpochNumber,
// NPCH = NextPChainReferenceHeight, SBS = SealingBlockSeq, BVD = BlockValidationDescriptor):
//
// parent (sealing block) sealing block NOT finalized sealing block IS finalized
// → emit Telock (no inner block) → first block of new epoch
// ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────────────┐
// │ Seq = s │ │ Seq = s+1 │ │ Seq = s+1 │
// │ PCH = p │ │ PCH = p (copy) │ │ PCH = p' (was NPCH) │
// │ EN = e │ ──► │ EN = e (copy) │ OR │ EN = s │
// │ NPCH = p' │ OR │ NPCH = p' (copy) │ │ NPCH = 0 (reset) │
// │ SBS = 0 │ │ SBS = s │ │ SBS = 0 │
// │ BVD = vset@p' │ │ BVD = nil │ │ BVD = nil │
// └──────────────────┘ └──────────────────┘ └──────────────────────────┘
// → stays EpochSealed → NormalOp (new epoch)
func (sm *StateMachine) buildBlockEpochSealed(ctx context.Context, parentBlock StateMachineBlock, simplexMetadata, simplexBlacklist []byte, prevBlockSeq uint64) (*StateMachineBlock, error) {
// We check if the sealing block has already been finalized.
// If not, we build a Telock block.
Expand Down
Loading