Skip to content

System Design & Architecture

JamForge is a functional-first implementation of the JAM (Join-Accumulate Machine) protocol using Scala 3. The implementation prioritizes type safety, immutability, and clear separation of concerns through a modular architecture.

  • Immutable data structures throughout
  • Pure functions for core protocol logic
  • Type classes for polymorphic behavior
  • Algebraic data types (ADTs) for protocol types
  • Scala 3 GADTs for instruction encoding
  • Opaque types for domain primitives
  • Compile-time validation where possible
  • Explicit error handling via Either/Option
  • Leverage Scala 3 features to reduce boilerplate
  • ~70% fewer lines of code vs. imperative implementations
  • Clear mapping between code and Gray Paper specification
  • Minimal abstraction layers

The codebase is organized into five main modules with clear dependencies:

┌─────────────────────────────────────────────────────┐
│ jam-conformance │
│ (Conformance Server & Testing) │
└─────────────────┬───────────────────────────────────┘
│
┌─────────────────▼───────────────────────────────────┐
│ jam-protocol │
│ (State Transition Functions & Block Pipeline) │
└─────────┬───────────────┬───────────────────────────┘
│ │
┌─────────▼──────┐ ┌────▼────────────────────────────┐
│ jam-crypto │ │ jam-pvm │
│ (Cryptography)│ │ (PolkaVM Execution) │
└────────┬───────┘ └────┬────────────────────────────┘
│ │
└───────┬───────┘
│
┌───────▼──────────────────────────────────┐
│ jam-core │
│ (Types, Encoding, Constants) │
└──────────────────────────────────────────┘

Purpose: Foundation module providing core protocol types and serialization.

Key Components:

  • Types: Block, Header, Extrinsic, WorkPackage, WorkReport, etc.
  • Codec System: Codec type classes for binary serialization
  • Primitives: Hash types, unsigned integers (via Spire)
  • Constants: Protocol constants from Gray Paper
  • Configuration: Chain configuration loading

Package: io.forge.jam.core

Dependencies:

  • Spire (unsigned integers)
  • Circe (JSON parsing)
  • scodec (binary encoding)

Purpose: Cryptographic operations required by the JAM protocol.

Key Components:

  • Bandersnatch VRF: Ring VRF signatures via native Rust library
  • Ed25519: Signature operations via native ed25519-zebra library
  • Erasure Coding: Reed-Solomon coding via native Rust library
  • Hashing: Blake2b, Keccak implementations

Package: io.forge.jam.crypto

Dependencies:

  • jam-core
  • Native libraries (Rust via JNI):
    • libbandersnatch_vrfs_wrapper
    • libed25519_zebra_wrapper
    • liberasure_coding_wrapper

Native Library Build: Native libraries are automatically built by sbt during compilation using Cargo.

Purpose: PolkaVM virtual machine for executing work package code.

Key Components:

  • Opcode System: RISC-V instruction set using Scala 3 GADTs
  • Memory Model: Page-based memory with access control
  • Execution Engine: Instruction handlers and VM state management
  • Program Loading: Bytecode parsing from program binary format
  • Gas Metering: Resource accounting for execution

Package: io.forge.jam.pvm

Dependencies:

  • jam-core
  • Cats (functional abstractions)

Purpose: State transition functions (STFs) and block import pipeline.

Key Components:

  • State Transition Functions:

    • Safrole (block production)
    • Assurances (availability attestation)
    • Authorizations (service authorization)
    • Reports (work report processing)
    • Disputes (validator disagreements)
    • History (block ancestry tracking)
    • Preimages (data storage)
    • Statistics (validator metrics)
    • Accumulation (work result finalization)
  • Block Pipeline: End-to-end block import and validation

  • State Management: Full JAM state representation and merklization

Package: io.forge.jam.protocol

Dependencies:

  • jam-core
  • jam-crypto
  • jam-pvm
  • Monocle (lens-based state updates)

Purpose: Protocol conformance testing and validation server.

Key Components:

  • Conformance Server: Unix domain socket server for protocol testing
  • Block Importer: Processes test vectors and validates state transitions
  • State Codec: Encoding/decoding of full JAM state
  • Fuzzing Support: Integration with jam-conformance fuzzing tools

Package: io.forge.jam.conformance

Dependencies:

  • jam-core
  • jam-crypto
  • jam-protocol
  • FS2 (streaming and Unix socket IO)
  • Cats Effect (async/effect management)
  1. Block Receipt

    • Receive block from network or test vector
    • Deserialize using codec type class
  2. Header Validation

    • Verify seal signature
    • Check block ancestry
    • Validate VRF proofs (Safrole)
  3. State Transition Execution

    Initial State
    ↓
    Safrole STF (block production)
    ↓
    Disputes STF (validator disputes)
    ↓
    Assurances STF (availability)
    ↓
    Reports STF (work reports)
    ↓
    Accumulation STF (finalization)
    ↓
    History STF (ancestry)
    ↓
    Authorizations STF (service auth)
    ↓
    Preimages STF (data storage)
    ↓
    Statistics STF (metrics)
    ↓
    Final State
  4. State Root Verification

    • Compute state merklization
    • Compare with block header
  5. Block Finalization

    • Update chain state
    • Persist to storage
  1. Work Package Receipt

    • Extract from block extrinsic
    • Validate authorization
  2. PVM Execution

    • Load work item bytecode into PolkaVM
    • Execute with gas metering
    • Handle host calls (service interactions)
  3. Work Report Generation

    • Collect execution results
    • Generate availability chunks (erasure coding)
    • Produce work report with signatures
  4. Accumulation

    • Process work reports in Accumulation STF
    • Finalize service state updates
    • Update validator statistics

The codec system provides type-safe binary serialization conforming to the JAM specification using scodec, a combinator library for working with binary data.

scodec uses Codec[A] type class for bidirectional encoding/decoding:

import scodec.Codec
import scodec.codecs._
// Codec handles both encoding and decoding
trait Codec[A]:
def encode(value: A): Attempt[BitVector]
def decode(bits: BitVector): Attempt[DecodeResult[A]]

Codecs compose declaratively using scodec combinators:

case class Block(header: Header, extrinsic: Extrinsic)
// Codecs compose with :: operator
given Codec[Block] = (
Codec[Header] ::
Codec[Extrinsic]
).as[Block]

JAM-specific encodings are implemented as custom scodec codecs:

// Compact integer encoding
val compactCodec: Codec[UInt] = new Codec[UInt]:
def encode(n: UInt) =
val bytes = encodeCompact(n)
Attempt.successful(BitVector(bytes))
def decode(bits: BitVector) =
decodeCompact(bits.bytes.toArray) match
case Right(value) => Attempt.successful(DecodeResult(value, bits))
case Left(err) => Attempt.failure(Err(err))

JAM state is represented as an immutable case class (FullJamState) containing:

  • Block chain state (best block, finalized block)
  • Validator epoch state
  • Service accounts
  • Authorization pool
  • Preimage storage
  • Pending reports and disputes
  • Historical MMR

State transitions use lenses (Monocle) for type-safe updates:

val updatedState = SafroleTransition.transition(
currentState,
newBlock,
config
)

State is merklized into a single root hash for verification:

  • Each state component is hashed independently
  • Hashes are combined into a Merkle tree
  • Root hash included in block header

Operations that can fail return Either[Error, Result]:

def transition(state: State, block: Block): Either[PipelineError, State]
  • Codec Errors: Deserialization failures
  • Validation Errors: Invalid block/transaction data
  • Execution Errors: PVM runtime failures
  • State Errors: Invalid state transitions

Errors propagate through monadic composition:

for
header <- decodeHeader(bytes)
valid <- validateHeader(header)
newState <- applyBlock(state, valid)
yield newState
  • JIT compilation for hot paths
  • Efficient collection usage (Vector, LazyList)
  • Minimal allocations in tight loops
  • Cryptographic operations in native code
  • Zero-copy data passing where possible
  • Batch operations for VRF and erasure coding
  • Parallel STF execution where independent
  • Incremental state merklization
  • Caching of frequently accessed state
  • GADTs: Perfect for instruction encoding with type-safe width parameters
  • Opaque Types: Zero-cost abstractions for domain types
  • Type Classes: Extensible encoding/decoding without runtime overhead
  • Inline Functions: Compile-time code generation for performance
  • Functional Core: Immutability and pure functions match protocol specification
  • Avoid reimplementation bugs in complex cryptography
  • Leverage existing audited Rust implementations
  • Performance benefits of native code
  • Thread safety without locks
  • Easier reasoning about state transitions
  • Natural fit with functional programming
  • Simplified testing and debugging
  1. Parallel STF Execution: Execute independent STFs concurrently
  2. Incremental Merklization: Only recompute changed state components
  3. Storage Abstraction: Pluggable storage backends (RocksDB)
  4. Network Layer: P2P networking for full node operation
  5. RPC Interface: JSON-RPC API for client applications