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