Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
[workspace]
members = [
"accountsdb",
"ledger",
"nucleus",
"programs/magic-root-interface",
"programs/magic-root-program",
Expand All @@ -23,7 +24,8 @@ rust-version = "1.94.1"
version = "0.1.0"

[workspace.dependencies]
accountsdb = { path = "accountsdb" }
accountsdb = { path = "accountsdb", package = "magicblock-accountsdb" }
ledger = { path = "ledger", package = "magicblock-ledger" }
magic-root-interface = { path = "programs/magic-root-interface" }
magic-root-program = { path = "programs/magic-root-program" }
nucleus = { path = "nucleus", package = "magicblock-engine-nucleus" }
Expand All @@ -46,6 +48,7 @@ clonetree = "0.0.2"
criterion = "0.7.0"
derive_more = "2.1.1"
env_logger = "0.11.8"
flume = { version = "0.12" }
futures = { version = "0.3.32", default-features = false }
heed = { version = "0.22.1", default-features = false }
itertools = "0.14.0"
Expand Down
45 changes: 45 additions & 0 deletions ledger/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
[package]
name = "magicblock-ledger"

authors.workspace = true
edition.workspace = true
homepage.workspace = true
license.workspace = true
repository.workspace = true
rust-version.workspace = true
version.workspace = true

[lib]
name = "ledger"

[dependencies]
nucleus = { workspace = true, features = ["heed", "ledger-schema", "metrics", "shutdown"] }

bitcode = { workspace = true }
bytemuck = { workspace = true }
derive_more = { workspace = true, features = ["deref", "from"] }
flume = { workspace = true }
heed = { workspace = true }
memmap2 = { workspace = true }
num_cpus = { workspace = true }
oneshot = { workspace = true, features = ["std"] }
parking_lot = { workspace = true }
rustix = { workspace = true, features = ["fs"] }
thiserror = { workspace = true }
tokio = { workspace = true, features = ["sync"] }
tracing = { workspace = true }
wincode = { workspace = true }
zstd = { workspace = true }

agave-transaction-view = { workspace = true, features = ["agave-unstable-api"] }
solana-hash = { workspace = true }
solana-pubkey = { workspace = true }
solana-signature = { workspace = true, features = ["wincode"] }
solana-transaction-error = { workspace = true, features = ["wincode"] }

[dev-dependencies]
nucleus = { workspace = true, features = ["testkit"] }
solana-hash = { workspace = true }

[lints]
workspace = true
44 changes: 44 additions & 0 deletions ledger/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# `magicblock-ledger`

The ledger is the engine's durable record of what executed: transactions, block
boundaries, and the execution metadata that goes with them. The design problem it
solves is retention. A ledger grows without bound, and at some point old history
has to be dropped or archived — cheaply, and without disturbing the part still
being written.

The answer is the **superblock**. Each superblock is a self-contained directory:
its own data files, its own LMDB index, everything it needs to be read in
isolation. Because nothing is shared across superblocks, retention and archival
become a filesystem operation — drop or move a whole directory — instead of a
delicate compaction over a single monolithic store.

```text
ledger.meta
superblock-000000001/
superblock.meta
blockstore.db # wincode stream of txns, block boundaries, superblock seals
executions.db # fixed execution header + compressed bitcode payload
index/
```

## Reads and writes are split

A **single writer** owns the ordered append path, and the ordering rules matter:
a transaction stays *pending* until its execution metadata arrives (so a record is
never half-written); a block boundary syncs the data files, commits the indexes,
publishes the durable cursors, and only then updates `ledger.meta`; a superblock
seal rotates to the next segment. That strict sequence is what lets a reader trust
any published cursor.

Reads are served by a **pool of reader workers** through `LedgerHandle::reader`.
Each worker keeps its own decode buffers, so no mutable decoder state is ever
shared across threads — reads scale out without contending on the writer or each
other.

## Retention caveat

`Ledger::truncate` drops the oldest *sealed* superblock when the ledger
filesystem hits its size limit; the active head is never removed. One thing to
know: the size check assumes the ledger owns its filesystem outright. Any
unrelated files sharing that filesystem count toward "used space" and will make
truncation trigger earlier than you'd expect.
Loading