scp

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: 7 Imported by: 0

README

Synchronous Composability Protocol (SCP) — Minimal Spec Implementation

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

Publisher

The package provides the PublisherInstance interface with the core logic for the publisher role in SCP. It requires the following implementation dependency:

  • PublisherNetwork: to send StartInstance and Decided messages to all participants.

And provides the following methods:

  • Instance(): returns the compose.Instance metadata (ID, period, sequence, request).
  • DecisionState(): returns the current decision state (Pending, Accepted, Rejected).
  • Run(): starts the instance by broadcasting StartInstance.
  • ProcessVote(sender, vote): processes a vote from a participant chain.
    • Any false vote decides the instance as rejected immediately.
    • All true votes decide the instance as accepted.
    • Duplicated votes are rejected; non-participant votes are ignored.
  • Timeout(): decides the instance as rejected if still pending.
classDiagram
  direction TB

  class PublisherInstance {
    +Instance() Instance
    +DecisionState() DecisionState
    +Run()
    +ProcessVote(ChainID, bool) error
    +Timeout() error
  }

  class PublisherNetwork {
    <<interface>>
    +SendStartInstance(Instance)
    +SendDecided(InstanceID, bool)
  }

  class PublisherState {
    instance : Instance
    chains : []ChainID
    decisionState : DecisionState
    votes : map[ChainID]bool
  }

  PublisherInstance --> PublisherNetwork
  PublisherInstance --> PublisherState

Sequencer

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

  • ExecutionEngine: to simulate transactions with mailbox-aware tracing.
  • SequencerNetwork: to send mailbox messages to peers and votes to the publisher.

And provides the following methods:

  • DecisionState(): returns the current decision state.
  • Run(): starts the instance (upon the StartInstance message) and simulates the instance’s local transactions from a VM snapshot.
    • On success (no read miss, no error): sends Vote(true) and waits for Decided.
    • On read miss: stores the expected header and waits for inbox fulfillment, then re-simulates.
    • On other errors: sends Vote(false) and terminates.
  • ProcessMailboxMessage(msg): buffers incoming mailbox messages and, when any expected read is fulfilled, re-simulates.
  • ProcessDecidedMessage(decided): finalizes the instance as accepted/rejected.
  • Timeout(): if not already waiting for decision or done, sends Vote(false) and terminates.
classDiagram
  direction TB
  
  class SequencerInstance {
    +DecisionState() DecisionState
    +Run() error
    +ProcessMailboxMessage(MailboxMessage) error
    +ProcessDecidedMessage(bool) error
    +Timeout()
  }

  class ExecutionEngine {
    <<interface>>
    +ChainID() ChainID
    +Simulate(SimulationRequest) (*MailboxMessageHeader, []MailboxMessage, error)
  }

  class SequencerNetwork {
    <<interface>>
    +SendMailboxMessage(ChainID, MailboxMessage)
    +SendVote(bool)
  }

  class SequencerState {
    state : SequencerState
    decisionState : DecisionState
    txs : [][]byte
    expectedReadRequests : []MailboxMessageHeader
    pendingMessages : []MailboxMessage
    putInboxMessages : []MailboxMessage
    vmSnapshot : StateRoot
    writtenMessagesCache : []MailboxMessage
  }

  class SimulationRequest {
    PutInboxMessages : []MailboxMessage
    Transactions : [][]byte
    Snapshot : StateRoot
  }

  class MailboxMessageHeader {
    SourceChainID : ChainID
    DestChainID : ChainID
    Sender : EthAddress
    Receiver : EthAddress
    SessionID : SessionID
    Label : string
  }

  class MailboxMessage {
    MailboxMessageHeader
    Data : []byte
  }

  SequencerInstance --> ExecutionEngine
  SequencerInstance --> SequencerNetwork
  SequencerInstance --> SequencerState
  SequencerState --> MailboxMessage
  SequencerState --> MailboxMessageHeader
  ExecutionEngine ..> SimulationRequest

Notes:

  • The ExecutionEngine.Simulate returns at most one read miss header per run; the sequencer loops by re-running after inbox fulfillment.
  • writtenMessagesCache prevents duplicate mailbox sends when re-simulating.

Tests

To run the unit tests, use the following command:

go test ./...

Auxiliary Sequence Flows

1. Instance start and initial simulation
sequenceDiagram
  autonumber
  participant SP as Publisher
  participant SA as Sequencer A
  participant EA as ExecutionEngine A

  SP->>SA: StartInstance(id, period, seq, XTRequest)
  note over SA: Stop local txs and locks to a VM snapshot
  SA->>EA: Simulate({putInbox=[], txsA, snapshot})
  EA-->>SA: (readMiss? header) / (writeMessages) / (err?)
  alt success (no read miss, no error)
    SA->>SP: Vote(true)
    note over SA: Wait for Decided
  else read miss
    note over SA: Store expected header and wait inbox
  else error
    SA->>SP: Vote(false)
    note over SA: Terminate
  end
2. Mailbox exchange and re-simulation
sequenceDiagram
  autonumber
  participant SA as Sequencer A
  participant SB as Sequencer B
  participant EA as ExecutionEngine A
  participant EB as ExecutionEngine B

  note over SA: Has expected read for (B -> A : label)
  SB->>EB: Simulate(...)
  EB-->>SB: writeMessages = [msg(B->A,label,data)]
  SB->>SA: SendMailboxMessage(A, msg)
  SA->>SA: ProcessMailboxMessage(msg)
  SA->>EA: Simulate({putInbox=[msg], txsA, snapshot})
  EA-->>SA: success -> no read miss
  SA->>SP: Vote(true)
3. Positive decision (all-true votes)
sequenceDiagram
  autonumber
  participant SA as Sequencer A
  participant SB as Sequencer B
  participant SP as Publisher

  SA->>SP: Vote(true)
  SB->>SP: Vote(true)
  SP->>SP: All votes collected -> decide true
  SP-->>SA: Decided(true)
  SP-->>SB: Decided(true)
  SA->>SA: ProcessDecidedMessage(true)
  SB->>SB: ProcessDecidedMessage(true)
4. Negative decision (error or timeout)
sequenceDiagram
  autonumber
  participant SA as Sequencer A
  participant SP as Publisher

  note over SA: Simulation error OR local timeout
  SA->>SP: Vote(false)
  SP->>SP: Decide false immediately
  SP-->>SA: Decided(false)
  SA->>SA: ProcessDecidedMessage(false)

  %% Publisher-side timer path
  SP->>SP: Timeout() while pending
  SP-->>SA: Decided(false)

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrDuplicatedVote       = errors.New("duplicated vote")
	ErrSenderNotParticipant = errors.New("sender is not a participant")
)
View Source
var (
	ErrNoTransactions       = errors.New("no transactions to execute")
	ErrNotInSimulatingState = errors.New("sequencer not in simulating state")
)

Functions

This section is empty.

Types

type ExecutionEngine

type ExecutionEngine interface {
	ChainID() compose.ChainID
	// Simulate runs the VM with a tracer for the simulation request.
	// The simulation returns a list of written mailbox messages (writeMessages).
	// If there's a read miss, readRequest is populated with the expected header.
	// If there's a simulation error (not read miss), err is populated.
	// Else, err is nil and readRequest is nil (successful transaction execution).
	Simulate(request SimulationRequest) (readRequest *MailboxMessageHeader, writeMessages []MailboxMessage, err error)
}

ExecutionEngine represents the execution engine, such as the EVM.

type MailboxMessage

type MailboxMessage struct {
	MailboxMessageHeader

	Data []byte
}

MailboxMessage carries the data exchanged between sequencers for mailbox fulfillment.

func (MailboxMessage) Equal

func (a MailboxMessage) Equal(b MailboxMessage) bool

type MailboxMessageHeader

type MailboxMessageHeader struct {
	SessionID     compose.SessionID
	SourceChainID compose.ChainID
	DestChainID   compose.ChainID
	Sender        compose.EthAddress
	Receiver      compose.EthAddress
	Label         string
}

func (MailboxMessageHeader) Equal

type PublisherInstance

type PublisherInstance interface {
	Instance() compose.Instance
	DecisionState() compose.DecisionState
	Run()
	ProcessVote(sender compose.ChainID, vote bool) error
	Timeout() error
}

func NewPublisherInstance

func NewPublisherInstance(
	instance compose.Instance,
	network PublisherNetwork,
	logger zerolog.Logger,
) (PublisherInstance, error)

type PublisherNetwork

type PublisherNetwork interface {
	SendStartInstance(instance compose.Instance)
	SendDecided(instanceID compose.InstanceID, decided bool)
}

type SequencerInstance

type SequencerInstance interface {
	DecisionState() compose.DecisionState
	Run() error
	ProcessMailboxMessage(msg MailboxMessage) error
	ProcessDecidedMessage(decided bool) error
	Timeout()
}

SequencerInstance is an interface that represents the sequencer-side logic for an SCP instance.

func NewSequencerInstance

func NewSequencerInstance(
	instance compose.Instance,
	execution ExecutionEngine,
	network SequencerNetwork,
	vmSnapshot compose.StateRoot,
	logger zerolog.Logger,
) (SequencerInstance, error)

type SequencerNetwork

type SequencerNetwork interface {
	SendMailboxMessage(recipient compose.ChainID, msg MailboxMessage)
	SendVote(vote bool)
}

type SequencerState

type SequencerState int

SequencerState tracks the state machine for a sequencer in an SCP session.

const (
	SeqStateSimulating SequencerState = iota
	SeqStateWaitingDecided
	SeqStateDone
)

type SimulationRequest

type SimulationRequest struct {
	// mailbox.putInbox transactions
	PutInboxMessages []MailboxMessage
	Transactions     [][]byte
	Snapshot         compose.StateRoot
}

SimulationRequest represents the inputs for the mailbox-aware simulator.

Jump to

Keyboard shortcuts

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