diff --git a/Cargo.lock b/Cargo.lock index 4dbcde4..56d7e57 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -37,6 +37,55 @@ dependencies = [ "memchr", ] +[[package]] +name = "anstream" +version = "0.6.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" + +[[package]] +name = "anstyle-parse" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" +dependencies = [ + "anstyle", + "windows-sys 0.52.0", +] + [[package]] name = "anyhow" version = "1.0.86" @@ -613,6 +662,39 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "clap" +version = "4.5.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed6719fffa43d0d87e5fd8caeab59be1554fb028cd30edc88fc4369b17971019" +dependencies = [ + "clap_builder", +] + +[[package]] +name = "clap_builder" +version = "4.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "216aec2b177652e3846684cbfe25c9964d18ec45234f0f5da5157b207ed1aab6" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_lex" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" + +[[package]] +name = "colorchoice" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" + [[package]] name = "convert_case" version = "0.4.0" @@ -1305,6 +1387,12 @@ version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + [[package]] name = "itoa" version = "1.0.11" @@ -2517,7 +2605,7 @@ dependencies = [ ] [[package]] -name = "ssri-server" +name = "ssri-runner" version = "0.1.0" dependencies = [ "anyhow", @@ -2526,6 +2614,7 @@ dependencies = [ "ckb-sdk", "ckb-types", "ckb-vm", + "clap", "hex", "jsonrpc-core", "jsonrpsee", @@ -2535,6 +2624,12 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "subtle" version = "2.6.1" @@ -2901,6 +2996,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + [[package]] name = "valuable" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 732215e..ae0e587 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,12 @@ [package] -name = "ssri-server" +name = "ssri-runner" version = "0.1.0" edition = "2021" +[[bin]] +name = "ssri-cli" +path = "src/cli.rs" + [dependencies] anyhow = "1.0.86" ckb-jsonrpc-types = "0.116.1" @@ -17,3 +21,4 @@ reqwest = { version = "0.12.5", features = ["json"] } serde = "1.0.204" tokio = { version = "1.38.1", features = ["signal"] } tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } +clap = { version = "4.5.16", features = ["env"] } diff --git a/README.md b/README.md index e2eefaf..be8f003 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,28 @@ -# SSRI Server +# SSRI Runner + +This repo will compile to a binary that can run SSRI scripts and also start a RPC server to run scripts remotely. + +Run a script with + +```sh +RUST_LOG=ssri_cli=debug cargo run -- run --tx-hash 0x900afcf79235e88f7bdf8a5d320365b7912f8074f4489a68405f43586fc51e5c --index 0 0x58f02409de9de7b1 0x0000000000000000 0x0a00000000000000 +``` Start server with ```sh -RUST_LOG=ssri_server=debug cargo run +RUST_LOG=ssri_cli=debug cargo run -- server --ckb-rpc https://testnet.ckbapp.dev/ --server-addr localhost:9090 ``` -Run a script with +Send request to server with ```sh echo '{ "id": 2, "jsonrpc": "2.0", - "method": "run_script", + "method": "run_script_level_code", "params": ["0x900afcf79235e88f7bdf8a5d320365b7912f8074f4489a68405f43586fc51e5c", 0, ["0x58f02409de9de7b1", "0x0000000000000000", "0x0a00000000000000"]] }' \ | curl -H 'content-type: application/json' -d @- \ -http://localhost:8090 -``` \ No newline at end of file +http://localhost:9090 +``` diff --git a/src/cli.rs b/src/cli.rs new file mode 100644 index 0000000..f7b1804 --- /dev/null +++ b/src/cli.rs @@ -0,0 +1,137 @@ +// a CLI that can run SSRI scripts and also start a RPC server to run scripts remotely. + +use ckb_types::H256; +use ssri_runner::{types::Hex, SSRIRunner}; +use std::str::FromStr; + +mod server; + +use clap::{Arg, ArgAction, Command}; + +fn main() { + let matches = Command::new("SSRI CLI") + .version("1.0") + .about("CLI for executing SSRI scripts") + .subcommand( + Command::new("run") + .about("Run a script") + .arg( + Arg::new("tx_hash") + .long("tx-hash") + .required(true) + .help("Transaction hash"), + ) + .arg( + Arg::new("index") + .long("index") + .required(true) + .help("Cell index"), + ) + .arg( + Arg::new("ckb_rpc") + .long("ckb-rpc") + .help("CKB RPC URL") + .default_value("https://testnet.ckbapp.dev/"), + ) + .arg( + Arg::new("args") + .action(ArgAction::Append) + .help("Script arguments"), + ), + ) + .subcommand( + Command::new("server") + .about("Start the RPC server") + .arg( + Arg::new("ckb_rpc") + .long("ckb-rpc") + .help("CKB RPC URL") + .default_value("https://testnet.ckbapp.dev/"), + ) + .arg( + Arg::new("server_addr") + .long("server-addr") + .help("Server address to listen on") + .default_value("localhost:9090"), + ), + ) + .get_matches(); + + match matches.subcommand() { + Some(("run", matches)) => { + let tx_hash = matches + .get_one::("tx_hash") + .expect("Transaction hash is required"); + let tx_hash = if let Some(stripped) = tx_hash.strip_prefix("0x") { + H256::from_str(stripped) + } else { + H256::from_str(tx_hash) + } + .expect("Invalid transaction hash"); + let index = matches + .get_one::("index") + .unwrap() + .parse::() + .expect("Invalid index"); + let args: Vec = matches + .get_many::("args") + .map(|values| values.map(|v| Hex::from(v.as_str())).collect()) + .unwrap_or_default(); + let ckb_rpc = matches + .get_one::("ckb_rpc") + .unwrap_or(&"https://testnet.ckbapp.dev/".to_string()) + .to_string(); + + tokio::runtime::Builder::new_multi_thread() + .enable_all() + .build() + .unwrap() + .block_on(async { + let runner = SSRIRunner::new(&ckb_rpc); + match runner + .run_script(tx_hash, index, args, None, None, None) + .await + { + Ok(result) => match result { + Some(hex) => println!("Script execution result: {:?}", hex.clone()), + None => println!("Script execution completed without a return value"), + }, + Err(e) => eprintln!("Error executing script: {}", e), + } + }); + } + + Some(("server", matches)) => { + let ckb_rpc = matches + .get_one::("ckb_rpc") + .unwrap_or(&"https://testnet.ckbapp.dev/".to_string()) + .to_string(); + let server_addr = matches + .get_one::("server_addr") + .unwrap_or(&"0.0.0.0:9090".to_string()) + .to_string(); + + tracing_subscriber::FmtSubscriber::builder() + .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) + .try_init() + .expect("setting default subscriber failed"); + + tokio::runtime::Builder::new_multi_thread() + .enable_all() + .build() + .unwrap() + .block_on(async { + println!( + "Starting server with CKB RPC: {}, Server address: {}", + ckb_rpc, server_addr + ); + match server::run_server(&ckb_rpc, &server_addr).await { + Ok(_) => println!("Server execution completed"), + Err(e) => eprintln!("Server error: {}", e), + } + }); + } + + _ => unreachable!(), + } +} diff --git a/src/main.rs b/src/lib.rs similarity index 73% rename from src/main.rs rename to src/lib.rs index 14cd922..098e952 100644 --- a/src/main.rs +++ b/src/lib.rs @@ -1,25 +1,21 @@ use ckb_jsonrpc_types::{OutPoint, Script, TransactionView}; use ckb_types::H256; use jsonrpsee::core::async_trait; -use jsonrpsee::proc_macros::rpc; -use jsonrpsee::server::Server; use jsonrpsee::tracing; use jsonrpsee::types::ErrorObjectOwned; -mod error; -mod rpc_client; -mod ssri_vm; -mod types; +pub mod error; +pub mod rpc_client; +pub mod ssri_vm; +pub mod types; use error::Error; use rpc_client::RpcClient; -use types::{CellOutputWithData, Hex}; - use ssri_vm::execute_riscv_binary; +use types::{CellOutputWithData, Hex}; -#[rpc(server)] -pub trait Rpc { - #[method(name = "run_script_level_code")] +#[async_trait] +pub trait SSRILevel { async fn run_script_level_code( &self, tx_hash: H256, @@ -27,7 +23,6 @@ pub trait Rpc { args: Vec, ) -> Result, ErrorObjectOwned>; - #[method(name = "run_script_level_script")] async fn run_script_level_script( &self, tx_hash: H256, @@ -36,7 +31,6 @@ pub trait Rpc { script: Script, ) -> Result, ErrorObjectOwned>; - #[method(name = "run_script_level_cell")] async fn run_script_level_cell( &self, tx_hash: H256, @@ -45,7 +39,6 @@ pub trait Rpc { cell: CellOutputWithData, ) -> Result, ErrorObjectOwned>; - #[method(name = "run_script_level_tx")] async fn run_script_level_tx( &self, tx_hash: H256, @@ -55,18 +48,18 @@ pub trait Rpc { ) -> Result, ErrorObjectOwned>; } -pub struct RpcServerImpl { +pub struct SSRIRunner { rpc: RpcClient, } -impl RpcServerImpl { +impl SSRIRunner { pub fn new(rpc: &str) -> Self { Self { rpc: RpcClient::new(rpc), } } - async fn run_script( + pub async fn run_script( &self, tx_hash: H256, index: u32, @@ -109,7 +102,7 @@ impl RpcServerImpl { } #[async_trait] -impl RpcServer for RpcServerImpl { +impl SSRILevel for SSRIRunner { async fn run_script_level_code( &self, tx_hash: H256, @@ -153,32 +146,3 @@ impl RpcServer for RpcServerImpl { .await } } - -#[tokio::main] -async fn main() -> anyhow::Result<()> { - tracing_subscriber::FmtSubscriber::builder() - .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) - .try_init() - .expect("setting default subscriber failed"); - - let ckb_rpc = std::env::args() - .nth(1) - .unwrap_or_else(|| "https://testnet.ckbapp.dev/".to_string()); - let server_addr = std::env::args() - .nth(2) - .unwrap_or_else(|| "0.0.0.0:9090".to_string()); - - run_server(&ckb_rpc, &server_addr).await?; - Ok(()) -} - -async fn run_server(ckb_rpc: &str, server_addr: &str) -> anyhow::Result<()> { - let server = Server::builder().build(server_addr).await?; - - let handle = server.start(RpcServerImpl::new(ckb_rpc).into_rpc()); - - tokio::signal::ctrl_c().await.unwrap(); - handle.stop().unwrap(); - - Ok(()) -} diff --git a/src/server.rs b/src/server.rs new file mode 100644 index 0000000..bf88ce5 --- /dev/null +++ b/src/server.rs @@ -0,0 +1,134 @@ +use ckb_jsonrpc_types::{Script, TransactionView}; +use ckb_types::H256; +use jsonrpsee::core::async_trait; +use jsonrpsee::proc_macros::rpc; +use jsonrpsee::server::Server; +use jsonrpsee::types::ErrorObjectOwned; + +use ssri_runner::types::{CellOutputWithData, Hex}; +use ssri_runner::{SSRILevel, SSRIRunner}; + +#[rpc(server)] +pub trait Rpc { + #[method(name = "run_script_level_code")] + async fn run_script_level_code( + &self, + tx_hash: H256, + index: u32, + args: Vec, + ) -> Result, ErrorObjectOwned>; + + #[method(name = "run_script_level_script")] + async fn run_script_level_script( + &self, + tx_hash: H256, + index: u32, + args: Vec, + script: Script, + ) -> Result, ErrorObjectOwned>; + + #[method(name = "run_script_level_cell")] + async fn run_script_level_cell( + &self, + tx_hash: H256, + index: u32, + args: Vec, + cell: CellOutputWithData, + ) -> Result, ErrorObjectOwned>; + + #[method(name = "run_script_level_tx")] + async fn run_script_level_tx( + &self, + tx_hash: H256, + index: u32, + args: Vec, + tx: TransactionView, + ) -> Result, ErrorObjectOwned>; +} + +pub struct RpcServerImpl { + runner: SSRIRunner, +} + +impl RpcServerImpl { + pub fn new(rpc: &str) -> Self { + Self { + runner: SSRIRunner::new(rpc), + } + } +} + +#[async_trait] +impl RpcServer for RpcServerImpl { + async fn run_script_level_code( + &self, + tx_hash: H256, + index: u32, + args: Vec, + ) -> Result, ErrorObjectOwned> { + println!("Received run_script_level_code request:"); + println!(" tx_hash: {:?}", tx_hash); + println!(" index: {}", index); + println!(" args: {:?}", args); + + self.runner + .run_script(tx_hash, index, args, None, None, None) + .await + } + + async fn run_script_level_script( + &self, + tx_hash: H256, + index: u32, + args: Vec, + script: Script, + ) -> Result, ErrorObjectOwned> { + self.runner + .run_script_level_script(tx_hash, index, args, script) + .await + } + + async fn run_script_level_cell( + &self, + tx_hash: H256, + index: u32, + args: Vec, + cell: CellOutputWithData, + ) -> Result, ErrorObjectOwned> { + self.runner + .run_script_level_cell(tx_hash, index, args, cell) + .await + } + + async fn run_script_level_tx( + &self, + tx_hash: H256, + index: u32, + args: Vec, + tx: TransactionView, + ) -> Result, ErrorObjectOwned> { + self.runner + .run_script_level_tx(tx_hash, index, args, tx) + .await + } +} + +pub async fn run_server(ckb_rpc: &str, server_addr: &str) -> anyhow::Result<()> { + println!("Initializing server..."); + let server = Server::builder().build(server_addr).await?; + println!("Server built successfully"); + + let rpc_impl = RpcServerImpl::new(ckb_rpc); + println!("RPC implementation created"); + + let handle = server.start(rpc_impl.into_rpc()); + println!("Server started on {}", server_addr); + + println!("Waiting for Ctrl+C signal..."); + tokio::signal::ctrl_c().await.unwrap(); + println!("Ctrl+C received, stopping server..."); + handle.stop().unwrap(); + println!("Server stopped"); + + Ok(()) +} diff --git a/src/types.rs b/src/types.rs index b1f373e..8ac4657 100644 --- a/src/types.rs +++ b/src/types.rs @@ -85,6 +85,17 @@ impl From for Hex { } } +impl From<&str> for Hex { + fn from(value: &str) -> Self { + if !value.starts_with("0x") { + panic!("expected a 0x-prefixed hex encoded string"); + } + let hex_str = &value[2..]; + let hex_bytes = Vec::from_hex(hex_str).unwrap(); + Self { hex: hex_bytes } + } +} + #[derive(Serialize, Deserialize, Clone)] pub struct CellOutputWithData { pub cell_output: CellOutput,