Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
304f41e
initial definition of `Storage` trait
MicaiahReid Dec 16, 2025
893c1f3
add implementation of `Storage` crate for sqlite storage
MicaiahReid Dec 16, 2025
82c813f
update dependencies to have sqlite/postgres features
MicaiahReid Dec 16, 2025
c7648c2
publicize Storage mod; add error conversion
MicaiahReid Dec 16, 2025
ff4c82e
replace `HashMap` with `dyn Storage` for `blocks` store in svm
MicaiahReid Dec 16, 2025
ffe5c8c
implement conversion from LiteSVMError to SurfpoolError
MicaiahReid Dec 19, 2025
4e6ca32
refactor error handling in Storage and Sqlite modules; enhance error …
MicaiahReid Dec 19, 2025
f730cf1
refactor logging level from debug to trace in SqliteStorage methods
MicaiahReid Dec 19, 2025
0c9b647
refactor StorageConstructor connect method to require a non-optional …
MicaiahReid Dec 19, 2025
1aa70b6
implement Storage trait for HashMap
MicaiahReid Dec 19, 2025
b834a8a
create SurfnetLiteSvm struct with database integration
MicaiahReid Dec 19, 2025
7971d87
add `db` flag to CLI and use to pass db url to surfnet instantiation
MicaiahReid Dec 19, 2025
c0ea22e
implement db storage for `blocks` + `chain_tip`
MicaiahReid Dec 19, 2025
6baf027
update `svm.rs` to use `SurfnetLiteSvm` for `inner`
MicaiahReid Dec 19, 2025
3b200ca
propagate changes in `inner` in `svm.rs` to all tests by adding unwra…
MicaiahReid Dec 19, 2025
38cf26a
add test helpers to storage mod to assist in augmenting existing test…
MicaiahReid Dec 19, 2025
059d8b2
implement svm `get_all_accounts` to get vec of all accounts from db
MicaiahReid Jan 2, 2026
9ee5017
sort results for and document `get_all_accounts` method
MicaiahReid Jan 2, 2026
801013b
clean up tests
MicaiahReid Jan 2, 2026
17ddb31
upgrade svm/integration tests to use all db types for tests
MicaiahReid Jan 2, 2026
129338b
chore: make `remove_from_indexes` return result
MicaiahReid Jan 6, 2026
284025c
fix account registry update by propagating deletions to database
MicaiahReid Jan 6, 2026
1ff7911
add delete_account fn to SurfnetLiteSvm; propagate account purging to…
MicaiahReid Jan 6, 2026
2ca8793
fix: skip db when getting post-execution accounts
MicaiahReid Jan 6, 2026
af93268
fix: sqlite connection strings to add read-write-create mode
MicaiahReid Jan 6, 2026
54000cd
add initial postgres storage impl
MicaiahReid Jan 6, 2026
09d8592
create/impl storage instantiation helper fn
MicaiahReid Jan 6, 2026
01ee88c
feat: add surfnet_id to isolate database storage for multiple surfnets
MicaiahReid Jan 6, 2026
7b97307
add test utils for postgres storage tests
MicaiahReid Jan 6, 2026
1178a73
feat: implement shared connection pool for PostgreSQL storage to opti…
MicaiahReid Jan 6, 2026
72517f8
add postgres db test_cases
MicaiahReid Jan 6, 2026
8dd0ed0
feat: add SQLite pragmas for performance and reliability in storage
MicaiahReid Jan 6, 2026
0a1f85c
upgrade `SurfnetSvm` transactions to use Storage rather than HashMap
MicaiahReid Jan 6, 2026
185eeb1
fix sqlite db file naming and cleanup files on shutdown
MicaiahReid Jan 8, 2026
4da71ea
upgrade `token_accounts` index to use Storage trait
MicaiahReid Jan 9, 2026
4ba7dee
refactor: update token_mints to use Storage trait and improve seriali…
MicaiahReid Jan 9, 2026
94fae08
combine TokenAccount/TokenMint serde discriminant logic into one enum
MicaiahReid Jan 9, 2026
65c79e5
refactor: replace HashMap with Storage trait for token account indexing
MicaiahReid Jan 9, 2026
f979d3d
refactor: replace HashMap with Storage trait for accounts_by_owner in…
MicaiahReid Jan 9, 2026
7d01f75
refactor: update SurfnetSvm to use Storage trait for registered_idls …
MicaiahReid Jan 9, 2026
f61cf92
refactor HashMap to Storage for profile_tag_map
MicaiahReid Jan 9, 2026
2cf0d2b
feat: implement Storage trait for FifoMap
MicaiahReid Jan 12, 2026
7bc1b19
refactor: enhance new_kv_store functions to support additional constr…
MicaiahReid Jan 12, 2026
1ca867b
refactor: update SurfnetSvm to use Storage trait for transaction prof…
MicaiahReid Jan 12, 2026
45c6639
feat: add count method to Storage trait and implement for FifoMap, Ha…
MicaiahReid Jan 12, 2026
5cd7d35
fix: set svm's initial `transactions_processed` to come from db count
MicaiahReid Jan 12, 2026
5f85660
fix: display correct successful tx count in tui on startup (from db d…
MicaiahReid Jan 12, 2026
1187a92
Merge branch 'main' into feat/persistent-surfnets
MicaiahReid Jan 12, 2026
8f8c956
implement storage for account_associated_data
MicaiahReid Jan 12, 2026
14b4ebd
set native mint associated data on reset network
MicaiahReid Jan 13, 2026
3c707db
add overlay storage to prevent db mutations when profiling
MicaiahReid Jan 13, 2026
d091a49
fix timeouts on flaky tests
MicaiahReid Jan 13, 2026
c404766
add test to assert tx profiles don't update svm state
MicaiahReid Jan 13, 2026
4451753
add test to assert instruction profiling doesn't mutate db
MicaiahReid Jan 13, 2026
23770ab
fmt
MicaiahReid Jan 13, 2026
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
4 changes: 2 additions & 2 deletions .cargo/config.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[alias]
surfpool-install = "install --path crates/cli --locked --force --features supervisor_ui --features version_check"
surfpool-install-dev = "install --path crates/cli --locked --force --features supervisor_ui"
surfpool-install = "install --path crates/cli --locked --force --features supervisor_ui --features version_check --features sqlite"
surfpool-install-dev = "install --path crates/cli --locked --force --features supervisor_ui --features sqlite"
# useful for local builds that point to local txtx crates - prevents conflicts with the supervisor_ui feature
surfpool-install-minimal = "install --path crates/cli --locked --force"
6 changes: 6 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ solana-signer = { version = "3.0.0", default-features = false }
solana-slot-hashes = { version = "3.0.0", default-features = false }
solana-system-interface = { version = "2.0.0", default-features = false }
solana-sysvar = { version = "3.0.0", default-features = false }
solana-sysvar-id = { version = "3.0.0", default-features = false }
solana-transaction = { version = "3.0.0", default-features = false }
solana-transaction-context = { version = "3.0.0", default-features = false }
solana-transaction-error = { version = "3.0.0", default-features = false }
Expand All @@ -147,7 +148,9 @@ solana-version = { version = "3.0.0", default-features = false }
spl-associated-token-account-interface = { version = "2.0.0", default-features = false }
spl-token-2022-interface = { version = "2.0.0", default-features = false }
spl-token-interface = { version = "2.0.0", default-features = false }
tempfile = "3.23.0"
test-case = "^3.3.1"
thiserror = "2.0"
tokio = { version = "1.43.0", default-features = false }
tokio-tungstenite = { version = "=0.20.1", default-features = false }
toml = { version = "0.8.23", default-features = false }
Expand Down
4 changes: 2 additions & 2 deletions crates/cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,8 @@ cli = ["clap/derive", "clap/env", "clap_complete", "toml", "ctrlc", "hiro-system
supervisor_ui = ["txtx-supervisor-ui/crates_build"]
explorer = []
geyser_plugin = ["surfpool-core/geyser_plugin"]
sqlite = ["surfpool-gql/sqlite"]
postgres = ["surfpool-gql/postgres"]
sqlite = ["surfpool-gql/sqlite", "surfpool-core/sqlite"]
postgres = ["surfpool-gql/postgres", "surfpool-core/postgres"]
version_check = []
subgraph = ["surfpool-core/subgraph"]

Expand Down
7 changes: 7 additions & 0 deletions crates/cli/src/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,12 @@ pub struct StartSimnet {
/// A set of inputs to use for the runbook (eg. surfpool start --runbook-input myInputs.json)
#[arg(long = "runbook-input", short = 'i')]
pub runbook_input: Vec<String>,
/// Surfnet database connection URL for persistent Surfnets. For an in-memory sqlite database, use ":memory:". For an on-disk sqlite database, use a filename ending in '.sqlite'.
#[arg(long = "db")]
pub db: Option<String>,
/// Unique identifier for this surfnet instance. Used to isolate database storage when multiple surfnets share the same database. Defaults to 0.
#[arg(long = "surfnet-id", default_value_t = 0)]
pub surfnet_id: u32,
/// Path to JSON snapshot file(s) to preload accounts from. Can be specified multiple times.
/// (eg. surfpool start --snapshot ./snapshot1.json --snapshot ./snapshot2.json)
/// The snapshot format matches the output of surfnet_exportSnapshot RPC method.
Expand Down Expand Up @@ -403,6 +409,7 @@ impl StartSimnet {
},
feature_config: self.feature_config(),
skip_signature_verification: false,
surfnet_id: self.surfnet_id,
snapshot,
}
}
Expand Down
24 changes: 18 additions & 6 deletions crates/cli/src/cli/simnet/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,9 @@ pub async fn handle_start_local_surfnet_command(
}

// We start the simnet as soon as possible, as it needs to be ready for deployments
let (mut surfnet_svm, simnet_events_rx, geyser_events_rx) = SurfnetSvm::new();
let (mut surfnet_svm, simnet_events_rx, geyser_events_rx) =
SurfnetSvm::new_with_db(cmd.db.as_deref(), cmd.surfnet_id)
.map_err(|e| format!("Failed to initialize Surfnet SVM: {}", e))?;

// Apply feature configuration from CLI flags
let feature_config = cmd.feature_config();
Expand Down Expand Up @@ -168,7 +170,7 @@ pub async fn handle_start_local_surfnet_command(
let config_copy = config.clone();

let simnet_events_tx_for_thread = simnet_events_tx.clone();
let _handle = hiro_system_kit::thread_named("simnet")
let simnet_handle = hiro_system_kit::thread_named("simnet")
.spawn(move || {
let future = start_local_surfnet(
surfnet_svm,
Expand All @@ -188,18 +190,18 @@ pub async fn handle_start_local_surfnet_command(

// Collect events that occur before Ready so we can re-send them to the TUI
let mut early_events = Vec::new();
loop {
let initial_transactions = loop {
match simnet_events_rx.recv() {
Ok(SimnetEvent::Aborted(error)) => {
eprintln!("Error: {}", error);
return Err(error);
}
Ok(SimnetEvent::Shutdown) => return Ok(()),
Ok(SimnetEvent::Ready) => break,
Ok(SimnetEvent::Ready(initial_transactions)) => break initial_transactions,
Ok(other) => early_events.push(other),
Err(_) => continue,
}
}
};

// Re-send early events (like snapshot loading messages) so the TUI receives them
for event in early_events {
Expand Down Expand Up @@ -262,9 +264,13 @@ pub async fn handle_start_local_surfnet_command(
explorer_handle,
ctx_cc,
Some(runloop_terminator),
initial_transactions,
)
.await;

// Wait for the simnet thread to finish cleanup (including Drop/checkpoint)
let _ = simnet_handle.join();

Ok(())
}

Expand All @@ -280,6 +286,7 @@ async fn start_service(
explorer_handle: Option<ServerHandle>,
_ctx: Context,
runloop_terminator: Option<Arc<AtomicBool>>,
initial_transactions: u64,
) -> Result<(), String> {
let displayed_url = if cmd.no_studio {
DisplayedUrl::Datasource(sanitized_config)
Expand All @@ -306,12 +313,14 @@ async fn start_service(
deploy_progress_rx,
displayed_url,
breaker,
initial_transactions,
)
.map_err(|e| format!("{}", e))?;
}
if let Some(explorer_handle) = explorer_handle {
let _ = explorer_handle.stop(true).await;
}

Ok(())
}

Expand All @@ -325,8 +334,11 @@ fn log_events(
) -> Result<(), String> {
let mut deployment_completed = false;
let do_stop_loop = runloop_terminator.clone();
let terminate_tx = simnet_commands_tx.clone();
ctrlc::set_handler(move || {
do_stop_loop.store(true, Ordering::Relaxed);
// Send terminate command to allow graceful shutdown (Drop to run)
let _ = terminate_tx.send(SimnetCommand::Terminate(None));
})
.expect("Error setting Ctrl-C handler");

Expand Down Expand Up @@ -399,7 +411,7 @@ fn log_events(
error!("{}", error);
return Err(error);
}
SimnetEvent::Ready => {}
SimnetEvent::Ready(_) => {}
SimnetEvent::Connected(_rpc_url) => {}
SimnetEvent::Shutdown => {
break;
Expand Down
15 changes: 12 additions & 3 deletions crates/cli/src/tui/simnet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,7 @@ impl App {
deploy_progress_rx: Vec<Receiver<BlockEvent>>,
displayed_url: DisplayedUrl,
breaker: Option<Keypair>,
initial_transactions: u64,
) -> App {
let theme = Theme::detect();
let palette = theme.palette();
Expand Down Expand Up @@ -328,7 +329,7 @@ impl App {
block_height: 0,
transaction_count: None,
},
successful_transactions: 0,
successful_transactions: initial_transactions as u32,
events,
include_debug_logs,
deploy_progress_rx,
Expand Down Expand Up @@ -403,6 +404,7 @@ pub fn start_app(
deploy_progress_rx: Vec<Receiver<BlockEvent>>,
displayed_url: DisplayedUrl,
breaker: Option<Keypair>,
initial_transactions: u64,
) -> Result<(), Box<dyn Error>> {
// setup terminal
enable_raw_mode()?;
Expand All @@ -419,6 +421,7 @@ pub fn start_app(
deploy_progress_rx,
displayed_url,
breaker,
initial_transactions,
);
let res = run_app(&mut terminal, app);

Expand Down Expand Up @@ -553,7 +556,7 @@ fn run_app<B: Backend>(terminal: &mut Terminal<B>, mut app: App) -> io::Result<(
SimnetEvent::Aborted(_error) => {
break;
}
SimnetEvent::Ready => {}
SimnetEvent::Ready(_) => {}
SimnetEvent::Connected(_) => {}
SimnetEvent::Shutdown => {
break;
Expand Down Expand Up @@ -705,10 +708,16 @@ fn run_app<B: Backend>(terminal: &mut Terminal<B>, mut app: App) -> io::Result<(
if key_event.kind == KeyEventKind::Press {
use KeyCode::*;
if key_event.modifiers == KeyModifiers::CONTROL && key_event.code == Char('c') {
// Send terminate command to allow graceful shutdown (Drop to run)
let _ = app.simnet_commands_tx.send(SimnetCommand::Terminate(None));
return Ok(());
}
match key_event.code {
Char('q') | Esc => return Ok(()),
Char('q') | Esc => {
// Send terminate command to allow graceful shutdown (Drop to run)
let _ = app.simnet_commands_tx.send(SimnetCommand::Terminate(None));
return Ok(());
}
Down => app.next(),
Up => app.previous(),
Char('f') | Char('j') => {
Expand Down
9 changes: 9 additions & 0 deletions crates/core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ base64 = { workspace = true }
bincode = { workspace = true }
blake3 = { workspace = true }
borsh = { workspace = true }
bytemuck = "1.21"
bs58 = { workspace = true }
chrono = { workspace = true }
convert_case = { workspace = true }
Expand Down Expand Up @@ -77,16 +78,20 @@ solana-signer = { workspace = true }
solana-slot-hashes = { workspace = true }
solana-system-interface = { workspace = true }
solana-sysvar = { workspace = true }
solana-sysvar-id = { workspace = true }
solana-transaction = { workspace = true }
solana-transaction-context = { workspace = true }
solana-transaction-error = { workspace = true }
solana-transaction-status = { workspace = true }
solana-version = { workspace = true }
spl-associated-token-account-interface = { workspace = true }
spl-token-interface = { workspace = true }
spl-token-2022-interface = { workspace = true }
thiserror = { workspace = true }
tokio = { workspace = true }
uuid = { workspace = true }

surfpool-db = { workspace = true }
surfpool-subgraph = { workspace = true, optional = true }
surfpool-types = { workspace = true }

Expand All @@ -99,8 +104,12 @@ txtx-addon-network-svm = { workspace = true }
[dev-dependencies]
test-case = { workspace = true }
env_logger = "*"
tempfile = { workspace = true }

[features]
default = ["sqlite"]
sqlite = ["surfpool-db/sqlite"]
postgres = ["surfpool-db/postgres"]
ignore_tests_ci = []
geyser_plugin = ["solana-geyser-plugin-manager"]
subgraph = ["surfpool-subgraph"]
19 changes: 19 additions & 0 deletions crates/core/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@ use std::{fmt::Display, future::Future, pin::Pin};

use crossbeam_channel::TrySendError;
use jsonrpc_core::{Error, Result};
use litesvm::error::LiteSVMError;
use serde::Serialize;
use serde_json::json;
use solana_client::{client_error::ClientError, rpc_request::TokenAccountsFilter};
use solana_clock::Slot;
use solana_pubkey::Pubkey;
use solana_transaction_status::EncodeError;

use crate::storage::StorageError;

pub type SurfpoolResult<T> = std::result::Result<T, SurfpoolError>;

#[derive(Debug, Clone)]
Expand Down Expand Up @@ -447,3 +450,19 @@ impl SurfpoolError {
Self(error)
}
}

impl From<StorageError> for SurfpoolError {
fn from(e: StorageError) -> Self {
let mut error = Error::internal_error();
error.data = Some(json!(format!("Storage error: {}", e.to_string())));
SurfpoolError(error)
}
}

impl From<LiteSVMError> for SurfpoolError {
fn from(e: LiteSVMError) -> Self {
let mut error = Error::internal_error();
error.data = Some(json!(format!("LiteSVM error: {}", e.to_string())));
SurfpoolError(error)
}
}
1 change: 1 addition & 0 deletions crates/core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ pub mod helpers;
pub mod rpc;
pub mod runloops;
pub mod scenarios;
pub mod storage;
pub mod surfnet;
pub mod types;

Expand Down
Loading