Storage Layer

Storage Layer

The XE node uses a pluggable storage layer built around a core Store interface with optional capability interfaces composed on top. This design lets tests use a fast in-memory implementation while production nodes use BadgerDB for persistence.

Core Store interface

Every storage backend must implement the base Store interface:

Method

Description

GetBlock(hash)

Retrieve a block by its hash

PutBlock(block)

Store a block by its hash

GetAccountChain(addr)

Get the ordered list of block hashes for an account

PutAccountChain(addr, hashes)

Store the block hash list for an account

GetPendingSend(hash)

Retrieve a pending send by its block hash

PutPendingSend(send)

Store a pending send

DeletePendingSend(hash)

Remove a pending send after it has been received

GetPendingByDest(addr)

Get all pending sends addressed to an account

GetAllPending()

List all pending sends across all accounts

PutFrontier(addr, hash)

Set the frontier (latest block hash) for an account

GetFrontier(addr)

Get the frontier hash for an account

IsEmpty()

Check whether the store contains any data

Close()

Shut down the store and release resources

Optional interfaces

Additional capabilities are exposed through separate interfaces. The node checks at runtime whether the store implements each one (Go interface assertion).

ConflictStore

Manages conflict detection state during consensus.

Method

Description

SaveConflict(conflict)

Persist a detected conflict

GetConflict(id)

Retrieve a conflict by ID

DeleteConflict(id)

Remove a resolved conflict

GetConflictsForAccount(addr)

List conflicts involving an account

GetAllConflicts()

List all active conflicts

SaveStagedBlock(block)

Stage a block pending conflict resolution

GetStagedBlock(hash)

Retrieve a staged block

DeleteStagedBlock(hash)

Remove a staged block

VoteStore

Stores representative votes during conflict resolution.

Method

Description

PutVote(vote)

Store a vote

GetVotesByConflict(id)

Get all votes for a conflict

HasVoted(repAddr, conflictID)

Check if a representative already voted

QuorumStore

Tracks quorum outcomes and confirmation heights.

Method

Description

SetBlockStatus(hash, status)

Mark a block as confirmed or rejected

GetBlockStatus(hash)

Get the confirmation status of a block

SetConfirmationHeight(addr, height)

Set the confirmed chain height for an account

GetConfirmationHeight(addr)

Get the confirmed chain height

DeleteVotesForConflict(id)

Clean up votes after quorum is reached

DelegationStore

Manages voting weight delegation.

Method

Description

PutDelegation(addr, rep)

Set or update a delegation

DeleteDelegation(addr)

Remove a delegation

DelegationIterator

Method

Description

IterateDelegations(fn)

Iterate over all delegations with a callback

FrontierLister

Method

Description

AllFrontiers()

Return all account frontiers as a map

LeaseStore

Manages compute lease records.

Method

Description

PutLease(lease)

Store a lease

GetLease(hash)

Retrieve a lease by hash

GetLeasesByProvider(addr)

List leases for a provider

GetAllLeases()

List all leases

AtomicBlockStore

Provides atomic multi-part writes for block processing.

Method

Description

CommitBlock(BlockCommit)

Atomically write a block and all side effects

BlockCommit

The BlockCommit struct bundles all state changes that must be applied atomically when processing a block:

Field

Type

Description

Block

Block

The block to store

Account

string

Account address

Chain

[]string

Updated chain hash list

FrontierHash

string

New frontier hash

AddPending

*PendingSend

Pending send to create (for send blocks)

DeletePendingID

string

Pending send to remove (for receive blocks)

PutLease

*Lease

Lease to create or update

SettleLeaseHash

string

Lease to mark as settled

[!TIP] Why atomic writes matter Without CommitBlock, a crash between writing the block and updating the frontier could leave the store in an inconsistent state. The atomic commit ensures all-or-nothing semantics.

Implementations

MemStore

In-memory implementation using Go maps protected by sync.RWMutex. All reads return deep copies to prevent mutation of internal state.

  • Use case: Tests and short-lived nodes
  • Durability: None -- data is lost on process exit
  • Thread safety: Full read/write mutex protection
  • Copy semantics: Deep copies on both read and write

[!NOTE] Note MemStore implements all optional interfaces (ConflictStore, VoteStore, QuorumStore, DelegationStore, DelegationIterator, FrontierLister, LeaseStore, AtomicBlockStore).

BadgerStore

Persistent implementation backed by BadgerDB. Blocks and metadata are serialized as JSON. A background goroutine runs garbage collection every 5 minutes.

  • Use case: Production nodes
  • Durability: Full disk persistence with WAL
  • Serialization: JSON
  • GC: Background value log GC every 5 minutes
  • Shutdown: Close() stops GC and closes the database

Key prefix scheme

All keys in BadgerDB are prefixed with a single byte to partition the keyspace:

Prefix

Content

0x01

Block

0x02

Account chain (hash list)

0x03

Pending send

0x04

Pending by destination

0x05

Frontier

0x06

Delegation

0x07

Weight

0x08

Conflict

0x09

Staged block

0x0a

Vote

0x0b

Block status

0x0c

Confirmation height

0x0d

Lease

0x0e

State chain block

[!EXAMPLE] Key construction A block with hash abc123... is stored at key 0x01 + abc123... (prefix byte concatenated with the hash string). Account chains use 0x02 + account address, and so on.

See also

  • Binary Encoding -- how blocks are serialized to bytes
  • Consensus -- conflict detection and voting that use ConflictStore, VoteStore, QuorumStore
  • Compute Leasing -- lease lifecycle that uses LeaseStore
  • State Chain -- DAO state stored under prefix 0x0e