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.
| Method | Description |
|---|---|
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
CommitUndoatomically.
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.
| Method | Description |
|---|---|
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.
| Method | Description |
|---|---|
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.
| Method | Description |
|---|---|
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.
| Method | Description |
|---|---|
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
| Method | Description |
|---|---|
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.
| Method | Description |
|---|---|
PutDelegation(account, representative) | Set or update a delegation |
DeleteDelegation(account) | 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(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.
| Method | Description |
|---|---|
PutCertificate(provider, data) | Store a provider's certificate JSON |
GetCertificate(provider) | Retrieve a provider's certificate |
AllCertificates() | Return every stored certificate |
ReputationStore
| Method | Description |
|---|---|
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
| Method | Description |
|---|---|
PutKeyset(account, keyset) | Store a multisig keyset |
GetKeyset(account) | Retrieve a keyset (nil on miss) |
StateChainStore
| Method | Description |
|---|---|
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.
| Method | Description |
|---|---|
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:
| Field | Type | Description |
|---|---|---|
Block | *Block | The block to store |
Account | string | Account address |
Chain | *AccountChain | Updated chain record |
FrontierHash | string | New frontier hash |
AddPending | *PendingSend | Pending send to create (send blocks) |
DeletePendingID | string | Pending send to remove (receive blocks) |
PutLease | *Lease | Lease to create or update |
SettleLeaseHash | string | Lease to mark settled |
CancelLeaseHash | string | Lease to mark cancelled |
UnfulfillLeaseHash | string | Lease to mark unfulfilled (force-settle) |
DelegationRep | string | Delegation to persist |
DeleteDelegation | bool | Delete the account's delegation |
PutKeyset | *Keyset | Keyset to store for a multisig account |
ExpectLeaseHash / ExpectLeaseState | string / LeaseState | Compare-and-swap guard: the commit fails unless the stored lease is in the expected state |
AssetCredit | *AssetDelta | Extra 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
MemStoreimplements 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, soSync()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:
| Prefix | Content |
|---|---|
0x01 | Block |
0x02 | Account chain |
0x03 | Pending send |
0x04 | Pending by destination |
0x05 | Frontier |
0x06 | Delegation |
0x07 | Representative weight |
0x08 | Conflict |
0x09 | Staged block |
0x0a | Vote |
0x0b | Block status |
0x0c | Final height |
0x0d | Lease |
0x0e | State chain block |
0x0f | Multisig keyset |
0x10 | Reputation aggregate |
0x11 | Final-vote commit-lock |
0x12 | Provider certificate |
[!EXAMPLE] Key construction A block with hash
abc123…is stored at key0x01+abc123…(prefix byte concatenated with the hash string). Account chains use0x02+ 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