Skip to content

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

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

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

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