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. All Get methods return (nil, nil) on a miss rather than an error.

MethodDescription
GetBlock(hash)Retrieve a block by its hash
PutBlock(block)Store a block by its hash
GetAccountChain(account)Get the account's chain record
PutAccountChain(account, chain)Store the account's chain record
GetPendingSend(sendHash)Retrieve a pending send by its send-block hash
PutPendingSend(ps)Store a pending send
DeletePendingSend(sendHash)Remove a pending send after it has been received
GetPendingByDest(account)Get all pending sends addressed to an account
GetAllPending()List all pending sends across all accounts
PutFrontier(account, blockHash)Set the frontier (latest block hash) for an account
GetFrontier(account)Get the frontier hash for an account (empty string on miss)
CommitUndo(undo)Apply a full block rollback in one atomic transaction
IsEmpty()Check whether the store contains any blocks
Close()Shut down the store and release resources

[!IMPORTANT] CommitUndo is not optional Rolling a block back touches the chain, the frontier, pending sends, block status and delegation. Applying those as separate writes reopens a crash window that can double-credit a receive, so there is deliberately no non-atomic fallback -- every backend must implement CommitUndo atomically.

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. Conflicts are keyed by the chain position -- the account plus the previous hash the competing blocks share.

MethodDescription
SaveConflict(c)Persist a detected conflict
GetConflict(account, previousHash)Retrieve the conflict at a position
DeleteConflict(account, previousHash)Remove a resolved conflict
GetConflictsForAccount(account)List conflicts involving an account
GetAllConflicts()List all active conflicts
SaveStagedBlock(b)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. One evolving slot per representative per position: a converge vote may be replaced or promoted to a final vote.

MethodDescription
PutVote(vote)Store a vote
GetVotesByConflict(account, previous)Get all votes at a position
HasVoted(account, previous, repPubKey)Check whether a representative already voted
GetVote(account, previous, repPubKey)Get a representative's current vote at a position

QuorumStore

Tracks finalization status and per-account final heights.

MethodDescription
SetBlockStatus(hash, status)Mark a block finalized or rejected
GetBlockStatus(hash)Get a block's status (pending if unrecorded)
SetFinalHeight(account, height)Set the finalized chain height for an account
GetFinalHeight(account)Get the finalized chain height (0 if unrecorded)
DeleteVotesForConflict(account, previous)Clean up votes after a position resolves

FinalVoteStore

The write-once finalization commit-lock: at most one finalized block hash per chain position, ever. A record is written before a representative signs a final vote and is never deleted -- persistence across rollback and restart is what makes a final vote irrevocable.

MethodDescription
GetFinalVote(account, previous)Return the locked hash for a position, if any
PutFinalVoteIfAbsent(account, previous, hash)Atomically record the hash iff no record exists

Syncer

MethodDescription
Sync()Flush pending writes to stable storage

BadgerDB is opened with SyncWrites=false for throughput, so a caller that must guarantee durability before an irrevocable, externally-visible side effect -- gossiping a final vote -- calls Sync() first.

DelegationStore

Manages voting weight delegation.

MethodDescription
PutDelegation(account, representative)Set or update a delegation
DeleteDelegation(account)Remove a delegation

DelegationIterator

MethodDescription
IterateDelegations(fn)Iterate over all delegations with a callback

FrontierLister

MethodDescription
AllFrontiers()Return all account frontiers as a map

LeaseStore

Manages compute lease records.

MethodDescription
PutLease(lease)Store a lease
GetLease(leaseHash)Retrieve a lease by hash
GetLeasesByProvider(provider)List leases for a provider
GetLeasesByState(state)List leases in a given lifecycle state
GetAllLeases()List all leases

CertificateStore

Provider performance certificates are chain data -- a lease block's hash binds its certificate hash -- so a node must be able to serve them to cold-syncing peers long after the original gossip stopped.

MethodDescription
PutCertificate(provider, data)Store a provider's certificate JSON
GetCertificate(provider)Retrieve a provider's certificate
AllCertificates()Return every stored certificate

ReputationStore

MethodDescription
PutReputation(account, agg)Store an account's reputation aggregate
GetReputation(account)Retrieve an aggregate (nil on miss)
GetAllReputations()Return every stored aggregate
DeleteReputation(account)Drop a record reverted to no counters

KeysetStore

MethodDescription
PutKeyset(account, keyset)Store a multisig keyset
GetKeyset(account)Retrieve a keyset (nil on miss)

StateChainStore

MethodDescription
PutStateBlock(block)Store a state chain block
GetStateBlock(index)Retrieve a state chain block by index
GetStateTip()Return the highest state chain block
GetStateBlockRange(start, count)Return a contiguous range of state chain blocks
StateBlockCount()Count stored state chain blocks

AtomicBlockStore

Provides atomic multi-part writes for block processing.

MethodDescription
CommitBlock(c)Atomically write a block and all its side effects
CommitCascade(undos, commit)Atomically apply a set of undos followed by a winner commit

CommitCascade is the crash-atomicity boundary for conflict promotion: the on-chain loser and its cross-account cascade are unwound and the winner re-applied in one transaction. The cascade is never silently split -- a transaction that is too large surfaces as an error rather than degrading to per-block writes.

BlockCommit

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

FieldTypeDescription
Block*BlockThe block to store
AccountstringAccount address
Chain*AccountChainUpdated chain record
FrontierHashstringNew frontier hash
AddPending*PendingSendPending send to create (send blocks)
DeletePendingIDstringPending send to remove (receive blocks)
PutLease*LeaseLease to create or update
SettleLeaseHashstringLease to mark settled
CancelLeaseHashstringLease to mark cancelled
UnfulfillLeaseHashstringLease to mark unfulfilled (force-settle)
DelegationRepstringDelegation to persist
DeleteDelegationboolDelete the account's delegation
PutKeyset*KeysetKeyset to store for a multisig account
ExpectLeaseHash / ExpectLeaseStatestring / LeaseStateCompare-and-swap guard: the commit fails unless the stored lease is in the expected state
AssetCredit*AssetDeltaExtra asset-balance credit applied with the commit (the provider's XUSD stake return on settle)

[!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.

BlockUndo is the mirror image: the truncated chain and new frontier, the pending send to delete or re-create, the rejected-status write, the delegation restore, and the inverse asset delta -- all applied in a single transaction by CommitUndo.

Implementations

MemStore

In-memory implementation using Go maps protected by sync.RWMutex. Reads return 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: Copies on both read and write

[!NOTE] Note MemStore implements every optional interface listed above, so test ledgers exercise the same code paths as BadgerStore-backed ledgers.

BadgerStore

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

  • Use case: Production nodes
  • Durability: Disk persistence; opened with SyncWrites=false, so Sync() is called explicitly before irrevocable side effects
  • 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:

PrefixContent
0x01Block
0x02Account chain
0x03Pending send
0x04Pending by destination
0x05Frontier
0x06Delegation
0x07Representative weight
0x08Conflict
0x09Staged block
0x0aVote
0x0bBlock status
0x0cFinal height
0x0dLease
0x0eState chain block
0x0fMultisig keyset
0x10Reputation aggregate
0x11Final-vote commit-lock
0x12Provider certificate

[!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. Conflicts and final-vote locks are keyed by position, so their keys concatenate the account and previous hash.

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