Skip to content
Merged
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,10 +1,12 @@
[workspace]
members = [
"accountsdb",
"keeper",
"ledger",
"nucleus",
"programs/magic-root-interface",
"programs/v42-calculator-interface",
"programs/v42-calculator-program",
"solana/account",
"solana/program-runtime",
"solana/svm",
Expand All @@ -24,6 +26,7 @@ version = "0.1.0"

[workspace.dependencies]
accountsdb = { path = "accountsdb", package = "magicblock-accountsdb" }
keeper = { path = "keeper", package = "magicblock-keeper" }
ledger = { path = "ledger", package = "magicblock-ledger" }
magic-root-interface = { path = "programs/magic-root-interface" }
magic-root-program = { path = "programs/magic-root-program" }
Expand Down Expand Up @@ -64,6 +67,7 @@ scc = "3.8.4"
serde = "1.0.228"
serde_bytes = "0.11.19"
serde_with = { version = "3.21.0", default-features = false }
smallvec = "1.15.2"
snedfile = "0.1"
tar = "0.4.45"
tempfile = "3"
Expand Down Expand Up @@ -138,7 +142,6 @@ solana-transaction-context = { path = "solana/transaction-context" }
missing_docs = "deny"
rust_2018_idioms = { level = "warn", priority = -1 }
unreachable_pub = "warn"
unsafe_op_in_unsafe_fn = "allow"
unused_lifetimes = "warn"
unused_macro_rules = "warn"
unused_qualifications = "warn"
Expand Down
66 changes: 66 additions & 0 deletions keeper/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
[package]
name = "magicblock-keeper"

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

[lib]
name = "keeper"

[features]
# Exposes `keeper::testkit`, the shared keeper-level test harness, as normal code
# so downstream crates can import it via a dev-dependency (no new crate needed).
testkit = ["accountsdb/testkit", "dep:solana-instruction", "ledger/testkit", "nucleus/testkit"]

[dependencies]
accountsdb = { workspace = true }
ledger = { workspace = true }
nucleus = { workspace = true, features = ["config", "notifier", "runtime"] }

ahash = { workspace = true }
arc-swap = { workspace = true }
derive_more = { workspace = true, features = ["from"] }
flume = { workspace = true }
oneshot = { workspace = true }
parking_lot = { workspace = true }
scc = { workspace = true }
serde = { workspace = true }
smallvec = { workspace = true }
tar = { workspace = true }
thiserror = { workspace = true }
tokio = { workspace = true, features = ["rt", "sync"] }
tracing = { workspace = true }
zstd = { workspace = true }

agave-feature-set = { workspace = true, features = ["agave-unstable-api"] }
solana-account = { workspace = true, features = ["bincode"] }
solana-feature-gate-interface = { workspace = true, features = ["bincode"] }
solana-hash = { workspace = true }
solana-instruction = { workspace = true, optional = true }
solana-keypair = { workspace = true }
solana-message = { workspace = true }
solana-program-runtime = { workspace = true }
solana-pubkey = { workspace = true }
solana-sdk-ids = { workspace = true }
solana-signature = { workspace = true }
solana-signer = { workspace = true }
solana-svm = { workspace = true }
solana-sysvar = { workspace = true }
solana-transaction-error = { workspace = true }

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

solana-instruction = { workspace = true }
solana-keypair = { workspace = true }
solana-signer = { workspace = true }
tokio = { workspace = true, features = ["macros"] }

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

Keeper opens accountsdb and the ledger as one durable state boundary. It also
owns startup account seeding, read-side caches, and live subscription fanout.
Account routing remains in accountsdb and ledger retention remains in ledger.

## Startup and recovery

`KeeperBuilder::build` opens both stores and seeds active feature accounts,
native builtins, configured loader-v4 programs, caller-provided accounts,
authority funding, and sysvars.

Accountsdb is restored from the newest retained snapshot when validation finds
corruption, its sealed superblock trails the ledger head, or its committed
transaction count trails the ledger's durable count. The accountsdb count is a
checkpoint high-water mark and may exceed the locally retained ledger count,
including after replication snapshot bootstrap. The original active tree is
saved until the restored snapshot validates. Engine replay is responsible for
advancing restored state from the successor of its sealed superblock through the
ledger tip and must finish with matching transaction counts when replay runs.

`nucleus::config::BlockstoreParams` supplies the expected block time and
non-zero superblock interval used by pacing and cache TTL calculation. The
shared accountsdb, blockstore, and ledger parameters are defined by nucleus;
keeper consumes them when opening its durable stores and caches.

## Authority

`nucleus::config::Authority::local` is the keypair used for locally signed
messages. When `Authority::remote` is set, `Keeper::authority` returns that
immediate upstream identity instead of the local pubkey, while `Keeper::signer`
continues to return the local signer. Replication followers retain both values
across restart.

The effective authority also identifies the engine's sponsor account. Keeper
creates this engine-local account only for an empty ledger, persists its spent
balance across restarts, and restores its initial balance on reset. Startup
rejects a non-empty deployment whose configured authority account is absent.

## Superblock finalization

`Keeper::finalize_superblock` snapshots accountsdb at the current ledger head,
computes the persisted-account checksum, appends the corresponding
`SuperblockSeal`, and archives the snapshot in the successor superblock
directory.

Finalization requires exclusive account-store access. Engine obtains that
exclusivity through the sequencer and simulator barriers before calling it.

## Synchronization

`Keeper::sync(false)` flushes queued appends and accountsdb while keeping ledger
workers available, as required by replay and replication. `Keeper::sync(true)`
is the irreversible shutdown fence: it closes every reader after earlier queued
requests, flushes and closes the appender, then flushes accountsdb.

## Caches and subscriptions

Signature and recent-block caches use slot-based TTLs with lazy eviction on
insertion. The account cache is an LRU that also coordinates concurrent loads of
missing accounts. Only non-authoritative modes enter the eviction LRU;
delegated, ephemeral, and unresolved transient state remains outside it.

Dedicated channels publish account and program updates, signature results, logs,
processed transactions, blocks, cache evictions, completed snapshots, and
service messages. Signatures have terminal oneshot fanout; persistent multicast
streams give each receiver a bounded queue and disconnect a receiver that falls
behind. Processed transactions, service messages, and cache evictions each have
one process-lifetime receiver and apply producer backpressure when full.
Append rejection notifies only its newest signature waiter, preserving older
waiters for an already accepted transaction; invalid-blockhash status is cached.

## `testkit`

The `testkit` feature exposes a keeper backed by throwaway directories plus v42
account and transaction helpers, including persisted-metadata fault injection.
When enabled, Keeper's build script builds the v42 SBF artifact consumed by the
harness. Downstream tests enable the feature on their dev-dependency instead of
duplicating the setup.
67 changes: 67 additions & 0 deletions keeper/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
//! Builds the v42 calculator SBF program for runtime tests.

use std::{env, io, path::PathBuf, process::Command};

const PROGRAM_DIR: &str = "programs/v42-calculator-program";
const PROGRAM: &str = "programs/v42-calculator-program/Cargo.toml";
const SO: &str = "v42_calculator_program.so";

fn main() -> Result<(), Box<dyn std::error::Error>> {
// The embedded program is only compiled by `keeper::testkit`.
if env::var_os("CARGO_FEATURE_TESTKIT").is_none() {
return Ok(());
}

let manifest_dir = PathBuf::from(
env::var_os("CARGO_MANIFEST_DIR")
.ok_or_else(|| io::Error::other("CARGO_MANIFEST_DIR is not set"))?,
);
let workspace = manifest_dir.parent().ok_or_else(|| {
io::Error::other(format!(
"CARGO_MANIFEST_DIR has no parent: {}",
manifest_dir.display()
))
})?;
let manifest = workspace.join(PROGRAM);
let artifact = workspace.join("target/deploy").join(SO);
println!(
"cargo:rerun-if-changed={}",
workspace.join(PROGRAM_DIR).display()
);

let output = Command::new("cargo")
.arg("build-sbf")
.arg("--manifest-path")
.arg(&manifest)
.arg("--arch")
.arg("v3")
.current_dir(workspace)
// `cargo clippy` exports these wrappers pointing at `clippy-driver`; left in
// place they hijack the SBF toolchain's rustc, which can't resolve the
// `sbpf*-solana` target. Strip them so `build-sbf` uses its own toolchain.
.env_remove("RUSTC_WRAPPER")
.env_remove("RUSTC_WORKSPACE_WRAPPER")
.output()
.map_err(|e| io::Error::other(format!("failed to run `cargo build-sbf`: {e}")))?;

if !output.status.success() {
return Err(io::Error::other(format!(
"`cargo build-sbf` failed with {}\nstdout:\n{}\nstderr:\n{}",
output.status,
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
))
.into());
}
if !artifact.is_file() {
Err(io::Error::other(format!(
"missing SBF artifact: {}",
artifact.display()
)))?;
}
println!(
"cargo:rustc-env=V42_CALCULATOR_PROGRAM_SO={}",
artifact.display()
);
Ok(())
}
Loading