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
27 changes: 19 additions & 8 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[workspace.package]
version = "0.11.0"
edition = "2024"
description = "Surfpool is the best place to train before surfing Solana."
description = "Surfpool is where developers start their Solana journey."
license = "Apache-2.0"
readme = "README.md"
repository = "https://github.com/txtx/surfpool"
Expand All @@ -19,7 +19,7 @@ members = [
"crates/subgraph",
"crates/types",
]
exclude = ["examples/hello-geyser"]
exclude = ["examples/*"]
default-members = ["crates/cli"]
resolver = "2"

Expand All @@ -39,7 +39,10 @@ bincode = "1.3.3"
blake3 = { version = "1.8.2", default-features = false }
borsh = { version = "1.5.5", default-features = false }
bs58 = { version = "0.5.0", default-features = false }
chrono = { version = "0.4.42", features = ["alloc", "clock"], default-features = false }
chrono = { version = "0.4.42", features = [
"alloc",
"clock",
], default-features = false }
clap = { version = "4.5.48", default-features = false }
clap_complete = { version = "4.5.48", default-features = false }
convert_case = "0.8.0"
Expand All @@ -57,13 +60,17 @@ fern = { version = "0.7.1", default-features = false }
fork = "0.2.0"
futures = { version = "0.3.22", default-features = false }
hex = { version = "0.4.3", default-features = false }
hiro-system-kit = { version = "0.3.4", default-features = false, features = ["tokio_helpers"] }
hiro-system-kit = { version = "0.3.4", default-features = false, features = [
"tokio_helpers",
] }
include_dir = "0.7.4"
indicatif = { version = "0.18.0", default-features = false }
ipc-channel = "0.19.0"
itertools = { version = "0.14.0", default-features = false }
json5 = "0.4.1"
jsonrpc-core = { version = "18.0.0", features = ["futures"], default-features = false }
jsonrpc-core = { version = "18.0.0", features = [
"futures",
], default-features = false }
jsonrpc-core-client = { version = "18.0.0", default-features = false }
jsonrpc-derive = "18.0.0"
jsonrpc-http-server = "18.0.0"
Expand All @@ -83,12 +90,14 @@ mustache = "0.9.0"
notify = { version = "8.0.0", default-features = false }
npm_rs = "1.0.0"
once_cell = { version = "1.19.0", default-features = false }
ratatui = { version = "0.29.0", features = ["crossterm"], default-features = false }
ratatui = { version = "0.29.0", features = [
"crossterm",
], default-features = false }
reqwest = { version = "0.12.23", default-features = false }
rmcp = { git = "https://github.com/modelcontextprotocol/rust-sdk", rev = "ff71a526156e6c9409c450f71eccd6aced9bc339", package = "rmcp" }
rust-embed = "8.2.0"
serde = { version = "1.0.226", default-features = false }
serde_derive = { version = "1.0.226", default-features = false } # must match the serde version, see https://github.com/serde-rs/serde/issues/2584#issuecomment-1685252251
serde_derive = { version = "1.0.226", default-features = false } # must match the serde version, see https://github.com/serde-rs/serde/issues/2584#issuecomment-1685252251
serde_json = { version = "1.0.135", default-features = false }
serde_with = { version = "3.14.0", default-features = false }
solana-account = { version = "3.0.0", default-features = false }
Expand Down Expand Up @@ -118,7 +127,9 @@ solana-rpc-client = { version = "3.0.0", default-features = false }
solana-rpc-client-api = { version = "3.0.0", default-features = false }
solana-runtime = { version = "3.0.0", default-features = false }
solana-sdk-ids = { version = "3.0.0", default-features = false }
solana-signature = { version = "3.0.0", default-features = false, features = ["rand"] }
solana-signature = { version = "3.0.0", default-features = false, features = [
"rand",
] }
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 }
Expand Down
40 changes: 40 additions & 0 deletions crates/core/src/rpc/surfnet_cheatcodes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -749,6 +749,37 @@ pub trait SurfnetCheatcodes {
config: Option<ResetAccountConfig>,
) -> Result<RpcResponse<()>>;

/// A cheat code to reset a network.
///
/// ## Returns
/// An `RpcResponse<()>` indicating whether the network reset was successful.
///
/// ## Example Request
/// ```json
/// {
/// "jsonrpc": "2.0",
/// "id": 1,
/// "method": "surfnet_resetNetwork", /// }
/// ```
///
/// ## Example Response
/// ```json
/// {
/// "jsonrpc": "2.0",
/// "result": {
/// "context": {
/// "slot": 123456789,
/// "apiVersion": "2.3.8"
/// },
/// "value": null /// },
/// "id": 1
/// }
/// ```
///

#[rpc(meta, name = "surfnet_resetNetwork")]
fn reset_network(&self, meta: Self::Metadata) -> Result<RpcResponse<()>>;

/// A cheat code to export a snapshot of all accounts in the Surfnet SVM.
///
/// This method retrieves the current state of all accounts stored in the Surfnet Virtual Machine (SVM)
Expand Down Expand Up @@ -1422,6 +1453,15 @@ impl SurfnetCheatcodes for SurfnetCheatcodesRpc {
})
}

fn reset_network(&self, meta: Self::Metadata) -> Result<RpcResponse<()>> {
let svm_locker = meta.get_svm_locker()?;
svm_locker.reset_network()?;
Ok(RpcResponse {
context: RpcResponseContext::new(svm_locker.get_latest_absolute_slot()),
value: (),
})
}

fn stream_account(
&self,
meta: Self::Metadata,
Expand Down
9 changes: 9 additions & 0 deletions crates/core/src/surfnet/locker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1675,6 +1675,15 @@ impl SurfnetSvmLocker {
})
}

/// Resets SVM state
///
/// This function coordinates the reset of accounts by calling the SVM's reset_account method.
pub fn reset_network(&self) -> SurfpoolResult<()> {
let simnet_events_tx = self.simnet_events_tx();
let _ = simnet_events_tx.send(SimnetEvent::info("Resetting network..."));
self.with_svm_writer(move |svm_writer| svm_writer.reset_network())
}

/// Streams an account by its pubkey.
pub fn stream_account(
&self,
Expand Down
45 changes: 45 additions & 0 deletions crates/core/src/surfnet/svm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -677,6 +677,51 @@ impl SurfnetSvm {
}
}

pub fn reset_network(&mut self) -> SurfpoolResult<()> {
// pub inner: LiteSVM,
let mut inner = LiteSVM::new()
.with_feature_set(self.feature_set.clone())
.with_blockhash_check(false)
.with_sigverify(false);

// Add the native mint (SOL) to the SVM
create_native_mint(&mut inner);
let native_mint_account = inner
.get_account(&spl_token_interface::native_mint::ID)
.unwrap();
let parsed_mint_account = MintAccount::unpack(&native_mint_account.data).unwrap();

// Load native mint into owned account and token mint indexes
let accounts_by_owner = HashMap::from([(
native_mint_account.owner,
vec![spl_token_interface::native_mint::ID],
)]);
let token_mints =
HashMap::from([(spl_token_interface::native_mint::ID, parsed_mint_account)]);
Comment on lines +682 to +700
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Think it's worth putting this in a function that's used by both new/reset_network to DRY this up?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought about it, we already have a new and initialize functions, let's keep it in our visor and pull the trigger if it gets more complex?


self.inner = inner;
self.blocks.clear();
self.transactions.clear();
self.transactions_queued_for_confirmation.clear();
self.transactions_queued_for_finalization.clear();
self.perf_samples.clear();
self.transactions_processed = 0;
self.profile_tag_map.clear();
self.simulated_transaction_profiles.clear();
self.accounts_by_owner = accounts_by_owner;
self.account_associated_data.clear();
self.token_accounts.clear();
self.token_mints = token_mints;
self.token_accounts_by_owner.clear();
self.token_accounts_by_delegate.clear();
self.token_accounts_by_mint.clear();
self.non_circulating_accounts.clear();
self.registered_idls.clear();
self.runbook_executions.clear();
self.streamed_accounts.clear();
Ok(())
}

pub fn reset_account(
&mut self,
pubkey: &Pubkey,
Expand Down
49 changes: 49 additions & 0 deletions crates/core/src/tests/integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3962,3 +3962,52 @@ fn test_reset_streamed_account_cascade() {
assert!(svm_locker.get_account_local(&owner).inner.is_none());
assert!(svm_locker.get_account_local(&owned).inner.is_none());
}

#[test]
fn test_reset_network() {
let (svm_instance, _simnet_events_rx, _geyser_events_rx) = SurfnetSvm::new();
let svm_locker = SurfnetSvmLocker::new(svm_instance);

// Create owner account and owned account
let owner = Pubkey::new_unique();
let owned = Pubkey::new_unique();

let owner_account = Account {
lamports: 10 * LAMPORTS_PER_SOL,
data: vec![0x01, 0x02],
owner: solana_sdk_ids::system_program::id(),
executable: false,
rent_epoch: 0,
};

let owned_account = Account {
lamports: 5 * LAMPORTS_PER_SOL,
data: vec![0x03, 0x04],
owner, // Owned by the first account
executable: false,
rent_epoch: 0,
};

// Insert accounts
svm_locker
.with_svm_writer(|svm_writer| {
svm_writer.set_account(&owner, owner_account).unwrap();
svm_writer.set_account(&owned, owned_account).unwrap();
Ok::<(), SurfpoolError>(())
})
.unwrap();

// Verify accounts exist
assert!(!svm_locker.get_account_local(&owner).inner.is_none());
assert!(!svm_locker.get_account_local(&owned).inner.is_none());

// Reset with cascade=true (for regular accounts, doesn't cascade but tests the code path)
svm_locker.reset_network().unwrap();

// Owner is deleted, owned account is deleted
assert!(svm_locker.get_account_local(&owner).inner.is_none());
assert!(svm_locker.get_account_local(&owned).inner.is_none());

// Clean up
svm_locker.reset_account(owned, false).unwrap();
}
4 changes: 4 additions & 0 deletions crates/types/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -917,6 +917,10 @@ impl<K: std::hash::Hash + Eq, V> FifoMap<K, V> {
self.map.len()
}

pub fn clear(&mut self) {
self.map.clear();
}

pub fn is_empty(&self) -> bool {
self.map.is_empty()
}
Expand Down