sbcp

package
v0.0.0-...-b89fbb9 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 18, 2025 License: GPL-3.0 Imports: 9 Imported by: 0

README

Superblock Construction Protocol (SBCP) — Minimal Spec Implementation

This package provides a minimal, testable implementation of the SBCP protocol.

Publisher

The package provides the Publisher interface with the core logic for the publisher role in SBCP. It requires the following implementation dependencies:

  • PublisherProver: to request network proofs from collected sequencer proofs.
  • PublisherMessenger: to broadcast period starts and rollbacks to sequencers.
  • L1: to publish network proofs to the L1 contract.

And provides the following methods:

  • StartPeriod(): should be called when a new period starts. Note that the implementation is responsible for period timers and for calling this method at the correct times, while the spec performs the transition logic.
  • StartInstance(XTRequest): attempts to start a new instance for the given XTRequest. Again, note the implementation is responsible for managing a queue of pending requests, and it should call the spec function to try to start a new instance.
  • DecideInstance(Instance): marks an instance as decided.
  • AdvanceSettledState(SuperblockNumber, SuperBlockHash): advances the settled state whenever an L1 event is received by the implementation.
  • ProofTimeout(): should be called by the implementation when the proof window expires. Again, note that the implementation is responsible for the timer management.
  • ReceiveProof(PeriodID, SuperblockNumber, []byte, ChainID): called by the implementation when a sequencer proof is received.
classDiagram
  direction TB

  class Publisher {
    +StartPeriod() error
    +StartInstance(XTRequest) (Instance, error)
    +DecideInstance(Instance) error
    +AdvanceSettledState(SuperblockNumber, SuperBlockHash) error
    +ProofTimeout()
    +ReceiveProof(PeriodID, SuperblockNumber, []byte, ChainID)
  }

  class PublisherState {
    PeriodID : PeriodID
    TargetSuperblockNumber : SuperblockNumber
    LastFinalizedSuperblockNumber : SuperblockNumber
    LastFinalizedSuperblockHash : SuperBlockHash
    Proofs : map[SuperblockNumber]map[ChainID][]byte
    Chains : set[ChainID]
    SequenceNumber : SequenceNumber
    ActiveChains : map[ChainID]bool
    ProofWindow : uint64
  }

  class PublisherProver {
    <<interface>>
    +RequestNetworkProof(SuperblockNumber, SuperBlockHash, [][]byte) ([]byte, error)
  }

  class PublisherMessenger {
    <<interface>>
    +BroadcastStartPeriod(PeriodID, SuperblockNumber)
    +BroadcastRollback(PeriodID, SuperblockNumber, SuperBlockHash)
  }

  class L1 {
    <<interface>>
    +PublishProof(SuperblockNumber, []byte)
  }

  Publisher --> PublisherState
  Publisher --> PublisherProver
  Publisher --> PublisherMessenger
  Publisher --> L1

Sequencer

The package also provides the Sequencer interface with the core logic for the sequencer role in SBCP. It requires the following implementation dependencies:

  • SequencerProver: to request proofs for sealed blocks.
  • SequencerMessenger: to forward XTRequests to the publisher and send proofs.

And provides the following methods:

  • StartPeriod(PeriodID, SuperblockNumber): called by the implementation when a StartPeriod message is received from the SP.
  • Rollback(SuperblockNumber, SuperBlockHash, PeriodID): called by the implementation when a Rollback message is received from the SP.
  • ReceiveXTRequest(XTRequest): called by the implementation when an XTRequest is received from a user.
  • AdvanceSettledState(SettledState): called by the implementation whenever an L1 event is received.

Furthermore, it adds a block building policy through the following methods:

  • BeginBlock(BlockNumber): should be called by the implementation whenever it wants to start a new block, returning an error if block creation is not currently allowed (e.g. during an instance).
  • CanIncludeLocalTx(): should be called by the implementation to check whether local transactions can be included in the current block.
  • EndBlock(BlockHeader): should be called by the implementation whenever it wants to seal the current block, returning an error if sealing can't be performed at the moment.
  • OnStartInstance(InstanceID, PeriodID, SequenceNumber): called by the implementation when a StartInstance message is received from the SP, returning an error if the instance can't be started.
  • OnDecidedInstance(InstanceID): called by the implementation when an instance gets decided, either due to a Decided message or due to a local Vote(0).
classDiagram
  direction TB
  
  class Sequencer {
    +StartPeriod(PeriodID, SuperblockNumber) error
    +Rollback(SuperblockNumber, SuperBlockHash, PeriodID) (BlockHeader, error)
    +ReceiveXTRequest(XTRequest)
    +AdvanceSettledState(SettledState)
    +BeginBlock(BlockNumber) error
    +CanIncludeLocalTx() (bool, error)
    +OnStartInstance(InstanceID, PeriodID, SequenceNumber) error
    +OnDecidedInstance(InstanceID) error
    +EndBlock(BlockHeader) error
  }

  class SequencerState {
    PeriodID : PeriodID
    TargetSuperblockNumber : SuperblockNumber
    PendingBlock : *PendingBlock
    ActiveInstanceID : *InstanceID
    LastSequenceNumber : *SequenceNumber
    Head : BlockNumber
    SealedBlockHead : map[PeriodID]SealedBlockHeader
    SettledState : SettledState
  }

  class SequencerProver {
    <<interface>>
    +RequestProofs(*BlockHeader, SuperblockNumber) []byte
  }

  class SequencerMessenger {
    <<interface>>
    +ForwardRequest(XTRequest)
    +SendProof(PeriodID, SuperblockNumber, []byte)
  }

  class PendingBlock {
    Number : BlockNumber
    PeriodID : PeriodID
    SuperblockNumber : SuperblockNumber
  }

  class BlockHeader {
    Number : BlockNumber
    BlockHash : BlockHash
    StateRoot : StateRoot
  }

  class SealedBlockHeader {
    BlockHeader : BlockHeader
    PeriodID : PeriodID
    SuperblockNumber : SuperblockNumber
  }

  class SettledState {
    BlockHeader : BlockHeader
    SuperblockNumber : SuperblockNumber
    SuperblockHash : SuperBlockHash
  }
  
  Sequencer --> SequencerState
  Sequencer --> SequencerProver
  Sequencer --> SequencerMessenger
  SequencerState --> PendingBlock
  SequencerState --> SettledState
  SequencerState --> SealedBlockHeader

Tests

To run the unit tests, use the following command:

go test ./...

Auxiliary Sequence Flows

1. Period start and sequencer settlement trigger
sequenceDiagram
  autonumber
  participant SP as Publisher (SP)
  participant S as Sequencer
  participant P as SequencerProver

  SP->>S: StartPeriod(period_id, target_superblock_number)
  Note over S: Update PeriodID and TargetSuperblockNumber
  S->>S: BeginBlock/EndBlock until last block of previous period sealed
  S->>P: RequestProofs(prevPeriodHead?, target_superblock_number-1)
  P-->>S: proof
  S->>SP: SendProof(prev_period_id, target_superblock_number-1, proof)
2. XTRequest forwarding and instance start/decision
sequenceDiagram
  autonumber
  participant U as User
  participant S as Sequencer
  participant SP as Publisher (SP)

  U->>S: XTRequest
  S->>SP: ForwardRequest(XTRequest)
  Note over SP: Queue + can_start_instance(policy)
  SP->>SP: StartInstance(XTRequest) -> Instance(period, seq, id)
  SP-->>S: StartInstance(id, period, seq, XTRequest) [SCP]
  S->>S: OnStartInstance(id, period, seq)
  Note over S: Lock local txs
  SP-->>S: Decided(id, decision) [SCP]
  S->>S: OnDecidedInstance(id)
  Note over S: Unlock local txs
3. Proof collection, network proof, and L1 publish
sequenceDiagram
  autonumber
  participant S1 as Sequencer A
  participant S2 as Sequencer B
  participant SP as Publisher (SP)
  participant NP as PublisherProver
  participant L1 as L1 Contract

  S1->>SP: Proof(period_x, S, proofA)
  S2->>SP: Proof(period_x, S, proofB)
  Note over SP: Wait until proofs from all chains
  SP->>NP: RequestNetworkProof(S, lastFinalizedHash, [proofA, proofB])
  NP-->>SP: networkProof
  SP->>L1: PublishProof(S, networkProof)
  L1-->>SP: Finalized(S, hash)
  SP->>SP: AdvanceSettledState(S, hash)

  L1-->>S2: Finalized(S, hash)
  S2->>S2: AdvanceSettledState(S, hash)
  L1-->>S1: Finalized(S, hash)
  S1->>S1: AdvanceSettledState(S, hash)
4. Rollback
sequenceDiagram
  autonumber
  participant SP as Publisher (SP)
  participant S as Sequencer

  SP->>SP: ProofTimeout() or pipeline failure
  SP->>S: BroadcastRollback(period_id, lastFinalizedNumber, lastFinalizedHash)
  S->>S: Rollback(lastFinalizedNumber, lastFinalizedHash, currentPeriodID)
  Note over S: Drop unfinalized, reset head/period/targetSuperblock

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrCannotStartInstance = errors.New("can not start any instance")
	ErrCannotStartPeriod   = errors.New("can not start period")
	ErrChainNotActive      = errors.New("chain not active")
	ErrOldSettledState     = errors.New("can not advance to older settled state")
	ErrInvalidRequest      = errors.New("invalid request")
)
View Source
var (
	ErrBlockSealMismatch = errors.New(
		"block number to be sealed does not match the current block number",
	)
	ErrBlockAlreadyOpen         = errors.New("there is already an open block")
	ErrBlockNotSequential       = errors.New("block number is not sequential")
	ErrNoPendingBlock           = errors.New("no pending block")
	ErrActiveInstanceExists     = errors.New("there is already an active instance")
	ErrNoActiveInstance         = errors.New("no active instance")
	ErrActiveInstanceMismatch   = errors.New("mismatched active instance ID")
	ErrMismatchedFinalizedState = errors.New("mismatched finalized state")
	ErrPeriodIDMismatch         = errors.New("instance period ID does not match current block period ID")
	ErrLowSequencerNumber       = errors.New("instance sequence number is not greater than last sequence number")
)

Functions

func GenerateInstanceID

func GenerateInstanceID(
	periodID compose.PeriodID,
	seq compose.SequenceNumber,
	xtRequest compose.XTRequest,
) compose.InstanceID

GenerateInstanceID returns SHA256(periodID || seq || tx1 || tx2 || ... || txn).

Types

type BlockHeader

type BlockHeader struct {
	Number    BlockNumber
	BlockHash compose.BlockHash
	StateRoot compose.StateRoot
}

type BlockNumber

type BlockNumber uint64

type L1

type L1 interface {
	PublishProof(superblockNumber compose.SuperblockNumber, proof []byte)
}

type PendingBlock

type PendingBlock struct {
	Number           BlockNumber
	PeriodID         compose.PeriodID
	SuperblockNumber compose.SuperblockNumber
}

type Publisher

type Publisher interface {
	// StartPeriod is called whenever a new period starts (i.e. CurrEthereumEpoch % 10 == 0).
	StartPeriod() error
	// StartInstance is called by the upper layer to try starting a new instance from the queued requests.
	StartInstance(req compose.XTRequest) (compose.Instance, error)
	// DecideInstance is called once an instance gets decided.
	DecideInstance(instance compose.Instance) error
	// AdvanceSettledState is called when L1 emits a new settled state event
	AdvanceSettledState(
		superblockNumber compose.SuperblockNumber,
		superblockHash compose.SuperblockHash,
	) error
	// ProofTimeout: Once a period starts, if the network ZK proof is not generated within 9 epochs,
	// the publisher must roll back to the last finalized superblock and discard any active settlement pipeline.
	ProofTimeout()
	// ReceiveProof is called whenever a proof is received from a sequencer.
	ReceiveProof(
		periodID compose.PeriodID,
		superblockNumber compose.SuperblockNumber,
		proof []byte,
		chainID compose.ChainID,
	)
}

func NewPublisher

func NewPublisher(
	prover PublisherProver,
	messenger PublisherMessenger,
	l1 L1,
	previousPeriodID compose.PeriodID,
	previousTargetSuperblockNumber compose.SuperblockNumber,
	lastFinalizedSuperblockNumber compose.SuperblockNumber,
	lastFinalizedSuperblockHash compose.SuperblockHash,
	proofWindow uint64,
	logger zerolog.Logger,
	chains map[compose.ChainID]struct{},
) (Publisher, error)

NewPublisher creates a new Publisher instance given a config, the immediate previous period ID, previous target superblock number, and the last settled state. The StartPeriod function needs to be called to start the first period, automatically incrementing PeriodID and TargetSuperblockNumber. Thus, if the current period is N and current superblock target is T, call NewPublisher with periodID = N-1 and target = T-1.

type PublisherMessenger

type PublisherMessenger interface {
	BroadcastStartPeriod(periodID compose.PeriodID, targetSuperblockNumber compose.SuperblockNumber)
	BroadcastRollback(
		periodID compose.PeriodID,
		superblockNumber compose.SuperblockNumber,
		superblockHash compose.SuperblockHash,
	)
}

type PublisherProver

type PublisherProver interface {
	// RequestSuperblockProof requests a proof for the given superblock number. It's called after all proofs from sequencers have been received.
	RequestSuperblockProof(
		superblockNumber compose.SuperblockNumber,
		lastSuperblockHash compose.SuperblockHash,
		proofs [][]byte,
	) ([]byte, error)
}

type PublisherState

type PublisherState struct {
	PeriodID               compose.PeriodID
	TargetSuperblockNumber compose.SuperblockNumber

	// Settlement state
	LastFinalizedSuperblockNumber compose.SuperblockNumber
	LastFinalizedSuperblockHash   compose.SuperblockHash
	Proofs                        map[compose.SuperblockNumber]map[compose.ChainID][]byte
	Chains                        map[compose.ChainID]struct{}

	// Instances scheduling
	SequenceNumber compose.SequenceNumber   // Per-period sequence counter (monotone)
	ActiveChains   map[compose.ChainID]bool // Chains with active instances

	// Proof window duration (in number of superblocks/periods) through which a pending superblock can be proven.
	// StartPeriods are rejected if the next superblock is bigger than LastFinalizedSuperblockNumber + ProofWindow.
	// 0 value means no window constrain.
	ProofWindow uint64
	// contains filtered or unexported fields
}

type SealedBlockHeader

type SealedBlockHeader struct {
	BlockHeader      BlockHeader
	PeriodID         compose.PeriodID
	SuperblockNumber compose.SuperblockNumber
}

SealedBlockHeader represents a block that has been sealed and included in the superblock chain.

type Sequencer

type Sequencer interface {
	// StartPeriod and Rollback are called when the publisher sends their respective messages.
	StartPeriod(ctx context.Context, periodID compose.PeriodID, targetSuperblockNumber compose.SuperblockNumber) error
	Rollback(
		superblockNumber compose.SuperblockNumber,
		superblockHash compose.SuperblockHash,
		currentPeriodID compose.PeriodID,
	) (BlockHeader, error)

	// ReceiveXTRequest is called whenever a request from a user is received.
	ReceiveXTRequest(ctx context.Context, request compose.XTRequest) error

	// AdvanceSettledState is called when the L1 settlement event has occurred.
	AdvanceSettledState(SettledState)

	// Block builder policy
	// BeginBlock is called at start of a new block
	BeginBlock(blockNumber BlockNumber) error
	// CanIncludeLocalTx return whether a local tx is admissible right now.
	CanIncludeLocalTx() (include bool, err error)
	// OnStartInstance is an SCP start-up hook. Locks local txs from being added (internal logic).
	OnStartInstance(id compose.InstanceID, periodID compose.PeriodID, sequenceNumber compose.SequenceNumber) error
	// OnDecidedInstance is an SCP decision hook. Unlocks local txs (internal logic).
	OnDecidedInstance(id compose.InstanceID) error
	// EndBlock: hook for when block ends
	EndBlock(ctx context.Context, b BlockHeader) error
}

func NewSequencer

func NewSequencer(
	prover SequencerProver,
	messenger SequencerMessenger,
	periodID compose.PeriodID,
	targetSuperblock compose.SuperblockNumber,
	settledState SettledState,
	logger zerolog.Logger,
) Sequencer

type SequencerMessenger

type SequencerMessenger interface {
	ForwardRequest(ctx context.Context, request compose.XTRequest) error
	SendProof(
		ctx context.Context,
		periodID compose.PeriodID,
		superblockNumber compose.SuperblockNumber,
		proof []byte,
	) error
}

type SequencerProver

type SequencerProver interface {
	// RequestProofs starts the settlement pipeline using the provided block header as head.
	// If nil, it means there's no sealed block for the period.
	RequestProofs(
		ctx context.Context,
		blockHeader *BlockHeader,
		superblockNumber compose.SuperblockNumber,
	) ([]byte, error)
}

type SequencerState

type SequencerState struct {
	PeriodID               compose.PeriodID
	TargetSuperblockNumber compose.SuperblockNumber // from StartPeriod.target_superblock_number

	// PendingBlock represents the block being built.
	PendingBlock       *PendingBlock
	ActiveInstanceID   *compose.InstanceID     // nil if no active instance
	LastSequenceNumber *compose.SequenceNumber // nil if no started instance in this period

	// Head represents the highest sealed block number.
	Head BlockNumber

	SealedBlockHead map[compose.PeriodID]SealedBlockHeader
	SettledState    SettledState
	// contains filtered or unexported fields
}

type SettledState

type SettledState struct {
	BlockHeader      BlockHeader
	SuperblockNumber compose.SuperblockNumber
	SuperblockHash   compose.SuperblockHash
}

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL