State Machines & Event-Driven Architecture

⏱️ Duration: 1.5-2 hours 📊 Difficulty: Basic

Learning Objectives

1. What is a State Machine?

Definition

A state machine is a computational model that consists of:

Simple Example

Light Switch State Machine:
  States: {ON, OFF}
  Transitions:
    - If state is OFF and event is "flip" → state becomes ON
    - If state is ON and event is "flip" → state becomes OFF

Why State Machines?

2. The StateMachine Trait

Core Interface

In hyperscale-rs, everything implements the StateMachine trait:

trait StateMachine {
    fn handle(&mut self, event: Event) -> Vec<Action>;
    fn set_time(&mut self, now: Duration);
    fn now(&self) -> Duration;
}

Key Properties

Why No I/O? I/O (networking, storage, timers) is handled by "runners" that execute actions and feed results back as events. This separation makes the consensus logic testable and deterministic.

Exploring the Trait

Open crates/core/src/traits.rs and find the StateMachine trait. Notice:

3. Events: What Happens

What are Events?

Events are passive data structures that describe something that happened. They flow into state machines.

Event Types

Open crates/core/src/event.rs to see all event types. Examples:

enum Event {
    // Timers
    ProposalTimer,
    CleanupTimer,

    // Network Messages
    BlockHeaderReceived { header: BlockHeader, .. },
    BlockVoteReceived { vote: BlockVote },

    // Internal Events
    QuorumCertificateFormed { block_hash: Hash, qc: QuorumCertificate },
    BlockCommitted { block_hash: Hash, .. },

    // ... many more
}

Event Sources

Event Priority

Events at the same timestamp are processed by priority:

  1. Internal (0) - Consequences of prior processing
  2. Timer (1) - Scheduled timers
  3. Network (2) - External messages
  4. Client (3) - User submissions
Why Priority? Preserves causality — internal events (like QC formation; see Module 1.2 for steps (i)–(v) and hyperscale-rs crate/line refs) must be processed before new external inputs arrive.

4. Actions: What to Do

What are Actions?

Actions are commands that describe what the state machine wants to do. They flow out of state machines and are executed by runners.

Action Types

Open crates/core/src/action.rs to see all action types. Examples:

enum Action {
    // Network
    BroadcastToShard { shard: ShardGroupId, message: OutboundMessage },
    BroadcastStateVote { shard: ShardGroupId, vote: StateVoteBlock },

    // Timers
    SetTimer { id: TimerId, duration: Duration },

    // Internal
    EnqueueInternal { event: Event },

    // Storage
    CommitBlock { block: Block, qc: QuorumCertificate },

    // ... many more
}

Action Execution

Actions are executed by runners:

Key Insight: The same state machine code runs in both simulation and production. This means bugs found in simulation will also exist in production (and vice versa).

5. Event-Driven Flow

The Flow

1. Runner receives network message
2. Runner creates Event::BlockHeaderReceived
3. Runner calls state_machine.handle(event)
4. State machine processes event, updates state
5. State machine returns Vec<Action>
6. Runner executes actions (send network, store, etc.)
7. Actions may generate new events
8. Loop continues...

Example: Block Proposal Flow

  1. Timer firesEvent::ProposalTimer
  2. State machine handles → Checks if this node is proposer
  3. If proposer → Builds block, returns Action::BroadcastToShard
  4. Runner executes → Sends block header to network
  5. Other nodes receive → Create Event::BlockHeaderReceived
  6. They vote → Return Action::BroadcastToShard (vote)
  7. Votes collectedEvent::QuorumCertificateFormed
  8. QC processed → Chain state updated (latest_qc), commit rule checked (Module 1.2; bft crate)

6. Determinism: Why It Matters

What is Determinism?

Deterministic means: given the same initial state and same sequence of events, you always get the same final state and same sequence of actions.

Why Determinism Matters

What Breaks Determinism?

How Hyperscale-rs Maintains Determinism

7. Composing State Machines

NodeStateMachine

The NodeStateMachine composes multiple sub-state machines:

NodeStateMachine
├── BftState          (consensus)
├── ExecutionState    (transaction execution)
├── MempoolState      (transaction pool)
├── ProvisionCoordinator (cross-shard coordination)
└── LivelockState     (deadlock prevention)

How They Compose

  1. NodeStateMachine receives an event
  2. Routes event to appropriate sub-state machines
  3. Each sub-state machine processes the event
  4. Actions from all sub-machines are collected
  5. Sub-machines can generate internal events for each other

Exploring Composition

Open crates/node/src/state.rs and find the handle method. Notice how it:

8. Knowledge Check Quiz

9. Practical Assignment

Assignment: Trace an Event Through the System

Tasks:

  1. Choose an Event:
    • Pick Event::BlockHeaderReceived or Event::ProposalTimer
    • Read its definition in crates/core/src/event.rs
  2. Trace Through NodeStateMachine:
    • Open crates/node/src/state.rs
    • Find where your chosen event is handled
    • Trace which sub-state machines it goes to
    • Note what actions are generated
  3. Trace Through Sub-State Machine:
    • Open the relevant sub-state machine file (e.g., crates/bft/src/state.rs)
    • Find the handler for your event
    • Trace the logic step-by-step
    • Note what state changes occur
    • Note what actions are returned
  4. Create a Diagram:
    • Draw a flow diagram showing: Event → State Machine → Actions
    • Include state changes
    • Include any internal events generated
  5. Write a Summary:
    • Document the flow in your learning journal
    • Explain what happens at each step
    • List any questions you have

Success Criteria:

  • ✅ You can trace an event from entry to actions
  • ✅ You understand which state machines are involved
  • ✅ You can explain the flow to someone else
  • ✅ You have a diagram showing the flow