Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SVM: add a sample stand-alone application based on SVM #2217

Merged
merged 17 commits into from
Oct 4, 2024
34 changes: 34 additions & 0 deletions Cargo.lock

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

5 changes: 4 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,10 @@ members = [
"zk-token-sdk",
]

exclude = ["programs/sbf", "svm/tests/example-programs"]
exclude = [
"programs/sbf",
"svm/tests/example-programs",
]

resolver = "2"

Expand Down
2 changes: 1 addition & 1 deletion accounts-db/src/accounts_db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3170,7 +3170,7 @@ impl AccountsDb {
.map(|dirty_store_chunk| {
let mut oldest_dirty_slot = max_slot_inclusive.saturating_add(1);
dirty_store_chunk.iter().for_each(|(slot, store)| {
if slot < &oldest_non_ancient_slot {
if *slot < oldest_non_ancient_slot {
dirty_ancient_stores.fetch_add(1, Ordering::Relaxed);
}
oldest_dirty_slot = oldest_dirty_slot.min(*slot);
Expand Down
41 changes: 41 additions & 0 deletions svm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,20 +46,46 @@ name = "solana_svm"

[dev-dependencies]
assert_matches = { workspace = true }
base64 = { workspace = true }
bincode = { workspace = true }
borsh = { version = "1.5.1", features = ["derive"] }
bs58 = { workspace = true }
clap = { workspace = true }
crossbeam-channel = { workspace = true }
env_logger = { workspace = true }
home = "0.5"
jsonrpc-core = { workspace = true }
jsonrpc-core-client = { workspace = true }
jsonrpc-derive = { workspace = true }
jsonrpc-http-server = { workspace = true }
lazy_static = { workspace = true }
libsecp256k1 = { workspace = true }
prost = { workspace = true }
rand = { workspace = true }
serde_derive = { workspace = true }
serde_json = { workspace = true }
shuttle = { workspace = true }
solana-account-decoder = { workspace = true }
solana-accounts-db = { workspace = true }
solana-bpf-loader-program = { workspace = true }
solana-client = { workspace = true }
solana-compute-budget-program = { workspace = true }
solana-logger = { workspace = true }
solana-perf = { workspace = true }
solana-program = { workspace = true }
solana-rpc-client = { workspace = true }
solana-rpc-client-api = { workspace = true }
solana-sdk = { workspace = true, features = ["dev-context-only-utils"] }
# See order-crates-for-publishing.py for using this unusual `path = "."`
solana-svm = { path = ".", features = ["dev-context-only-utils"] }
solana-svm-conformance = { workspace = true }
solana-transaction-status = { workspace = true }
solana-version = { workspace = true }
spl-token-2022 = { workspace = true, features = ["no-entrypoint"] }
test-case = { workspace = true }
tokio = { workspace = true, features = ["full"] }
tokio-util = { workspace = true, features = ["codec", "compat"] }
yaml-rust = "0.4"

[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
Expand All @@ -80,5 +106,20 @@ shuttle-test = [
"solana-loader-v4-program/shuttle-test",
]

[[example]]
name = "json-rpc-server"
path = "examples/json-rpc/server/src/main.rs"
crate-type = ["bin"]

[[example]]
name = "json-rpc-client"
path = "examples/json-rpc/client/src/main.rs"
crate-type = ["bin"]

[[example]]
name = "json-rpc-example-program"
path = "examples/json-rpc/program/src/lib.rs"
crate-type = ["cdylib", "lib"]

[lints]
workspace = true
31 changes: 31 additions & 0 deletions svm/examples/json-rpc/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
This is an example application using SVM to implement a tiny subset of
Solana RPC protocol for the purpose of simulating transaction
execution without having to use the entire Solana Runtime.

The exmample consists of two host applications
- json-rpc-server -- the RPC server that accepts incoming RPC requests
and performs transaction simulation sending back the results,
- json-rpc-client -- the RPC client program that sends transactions to
json-rpc-server for simulation,

and

- json-rpc-program is the source code of on-chain program that is
executed in a transaction sent by json-rpc-client.

To run the example, compile the json-rpc-program with `cargo
build-sbf` command. Using solana-test-validator create a ledger, or
use an existing one, and deploy the compiled program to store it in
the ledger. Using agave-ledger-tool dump ledger accounts to a file,
e.g. `accounts.out`. Now start the json-rpc-server, e.g.
```
cargo run --manifest-path json-rpc-server/Cargo.toml -- -l test-ledger -a accounts.json
```

Finally, run the client program.
```
cargo run --manifest-path json-rpc-client/Cargo.toml -- -C config.yml -k json-rpc-program/target/deploy/helloworld-keypair.json -u localhost
```

The client will communicate with the server and print the responses it
recieves from the server.
79 changes: 79 additions & 0 deletions svm/examples/json-rpc/client/src/client.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
use {
crate::utils,
solana_client::rpc_client::RpcClient,
solana_sdk::{
commitment_config::CommitmentConfig,
instruction::{AccountMeta, Instruction},
message::Message,
signature::Signer,
signer::keypair::{read_keypair_file, Keypair},
transaction::Transaction,
},
};

/// Establishes a RPC connection with the Simulation server.
/// Information about the server is gleened from the config file `config.yml`.
pub fn establish_connection(url: &Option<&str>, config: &Option<&str>) -> utils::Result<RpcClient> {
let rpc_url = match url {
Some(x) => {
if *x == "localhost" {
"http://localhost:8899".to_string()
} else {
String::from(*x)
}
}
None => utils::get_rpc_url(config)?,
};
Ok(RpcClient::new_with_commitment(
rpc_url,
CommitmentConfig::confirmed(),
))
}

/// Loads keypair information from the file located at KEYPAIR_PATH
/// and then verifies that the loaded keypair information corresponds
/// to an executable account via CONNECTION. Failure to read the
/// keypair or the loaded keypair corresponding to an executable
/// account will result in an error being returned.
pub fn get_program(keypair_path: &str, connection: &RpcClient) -> utils::Result<Keypair> {
let program_keypair = read_keypair_file(keypair_path).map_err(|e| {
utils::Error::InvalidConfig(format!(
"failed to read program keypair file ({}): ({})",
keypair_path, e
))
})?;

let program_info = connection.get_account(&program_keypair.pubkey())?;
if !program_info.executable {
return Err(utils::Error::InvalidConfig(format!(
"program with keypair ({}) is not executable",
keypair_path
)));
}

Ok(program_keypair)
}

pub fn say_hello(player: &Keypair, program: &Keypair, connection: &RpcClient) -> utils::Result<()> {
let greeting_pubkey = utils::get_greeting_public_key(&player.pubkey(), &program.pubkey())?;
println!("greeting pubkey {greeting_pubkey:?}");

// Submit an instruction to the chain which tells the program to
// run. We pass the account that we want the results to be stored
// in as one of the account arguments which the program will
// handle.

let data = [1u8];
let instruction = Instruction::new_with_bytes(
program.pubkey(),
&data,
vec![AccountMeta::new(greeting_pubkey, false)],
);
let message = Message::new(&[instruction], Some(&player.pubkey()));
let transaction = Transaction::new(&[player], message, connection.get_latest_blockhash()?);

let response = connection.simulate_transaction(&transaction)?;
println!("{:?}", response);

Ok(())
}
48 changes: 48 additions & 0 deletions svm/examples/json-rpc/client/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
use clap::{crate_description, crate_name, crate_version, App, Arg};

mod client;
mod utils;

fn main() {
let version = crate_version!().to_string();
let args = std::env::args().collect::<Vec<_>>();
let matches = App::new(crate_name!())
.about(crate_description!())
.version(version.as_str())
.arg(
Arg::with_name("config")
.long("config")
.short("C")
.takes_value(true)
.value_name("CONFIG")
.help("Config filepath"),
)
.arg(
Arg::with_name("keypair")
.long("keypair")
.short("k")
.takes_value(true)
.value_name("KEYPAIR")
.help("Filepath or URL to a keypair"),
)
.arg(
Arg::with_name("url")
.long("url")
.short("u")
.takes_value(true)
.value_name("URL_OR_MONIKER")
.help("URL for JSON RPC Server"),
)
.get_matches_from(args);
let config = matches.value_of("config");
let keypair = matches.value_of("keypair").unwrap();
let url = matches.value_of("url");
let connection = client::establish_connection(&url, &config).unwrap();
println!(
"Connected to Simulation server running version ({}).",
connection.get_version().unwrap()
);
let player = utils::get_player(&config).unwrap();
let program = client::get_program(keypair, &connection).unwrap();
client::say_hello(&player, &program, &connection).unwrap();
}
Loading
Loading