diff --git a/Cargo.lock b/Cargo.lock index 2a3dfb4..060b678 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -20,7 +20,7 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "alloy-json-rpc" version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy.git?rev=098ad56#098ad5657d55bbc5fe9469ede2a9ca79def738f2" +source = "git+https://github.com/alloy-rs/alloy.git?rev=6f8ebb4#6f8ebb45afca1a201a11d421ec46db0f7a1d8d08" dependencies = [ "alloy-primitives", "serde", @@ -30,9 +30,9 @@ dependencies = [ [[package]] name = "alloy-primitives" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4b6fb2b432ff223d513db7f908937f63c252bee0af9b82bfd25b0a5dd1eb0d8" +checksum = "ef197eb250c64962003cb08b90b17f0882c192f4a6f2f544809d424fd7cb0e7d" dependencies = [ "alloy-rlp", "bytes", @@ -75,7 +75,7 @@ dependencies = [ [[package]] name = "alloy-rpc-types" version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy.git?rev=098ad56#098ad5657d55bbc5fe9469ede2a9ca79def738f2" +source = "git+https://github.com/alloy-rs/alloy.git?rev=6f8ebb4#6f8ebb45afca1a201a11d421ec46db0f7a1d8d08" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -88,10 +88,11 @@ dependencies = [ [[package]] name = "alloy-transport" version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy.git?rev=098ad56#098ad5657d55bbc5fe9469ede2a9ca79def738f2" +source = "git+https://github.com/alloy-rs/alloy.git?rev=6f8ebb4#6f8ebb45afca1a201a11d421ec46db0f7a1d8d08" dependencies = [ "alloy-json-rpc", "base64", + "futures-util", "serde", "serde_json", "thiserror", @@ -618,6 +619,17 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" +[[package]] +name = "futures-macro" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + [[package]] name = "futures-task" version = "0.3.30" @@ -631,9 +643,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" dependencies = [ "futures-core", + "futures-macro", "futures-task", "pin-project-lite", "pin-utils", + "slab", ] [[package]] @@ -1386,6 +1400,15 @@ dependencies = [ "rand_core", ] +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + [[package]] name = "smallvec" version = "1.13.1" @@ -1757,15 +1780,6 @@ version = "0.2.90" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d91413b1c31d7539ba5ef2451af3f0b833a005eb27a631cec32bc0635a8602b" -[[package]] -name = "wasm-encoder" -version = "0.38.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ad2b51884de9c7f4fe2fd1043fccb8dcad4b1e29558146ee57a144d15779f3f" -dependencies = [ - "leb128", -] - [[package]] name = "wasm-encoder" version = "0.41.0" @@ -1787,18 +1801,8 @@ dependencies = [ "serde_derive", "serde_json", "spdx", - "wasm-encoder 0.41.0", - "wasmparser 0.121.0", -] - -[[package]] -name = "wasmparser" -version = "0.118.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95ee9723b928e735d53000dec9eae7b07a60e490c85ab54abb66659fc61bfcd9" -dependencies = [ - "indexmap", - "semver 1.0.21", + "wasm-encoder", + "wasmparser", ] [[package]] @@ -1899,8 +1903,8 @@ dependencies = [ [[package]] name = "wit-bindgen" -version = "0.16.0" -source = "git+https://github.com/bytecodealliance/wit-bindgen?rev=efcc759#efcc7592cf3277bcb9be1034e48569c6d822b322" +version = "0.17.0" +source = "git+https://github.com/bytecodealliance/wit-bindgen?rev=21a46c7#21a46c774532da99384f7a1877c1fcfb7a4c72d3" dependencies = [ "bitflags", "wit-bindgen-rust-macro", @@ -1908,8 +1912,8 @@ dependencies = [ [[package]] name = "wit-bindgen-core" -version = "0.16.0" -source = "git+https://github.com/bytecodealliance/wit-bindgen?rev=efcc759#efcc7592cf3277bcb9be1034e48569c6d822b322" +version = "0.17.0" +source = "git+https://github.com/bytecodealliance/wit-bindgen?rev=21a46c7#21a46c774532da99384f7a1877c1fcfb7a4c72d3" dependencies = [ "anyhow", "wit-component", @@ -1918,8 +1922,8 @@ dependencies = [ [[package]] name = "wit-bindgen-rust" -version = "0.16.0" -source = "git+https://github.com/bytecodealliance/wit-bindgen?rev=efcc759#efcc7592cf3277bcb9be1034e48569c6d822b322" +version = "0.17.0" +source = "git+https://github.com/bytecodealliance/wit-bindgen?rev=21a46c7#21a46c774532da99384f7a1877c1fcfb7a4c72d3" dependencies = [ "anyhow", "heck", @@ -1930,8 +1934,8 @@ dependencies = [ [[package]] name = "wit-bindgen-rust-macro" -version = "0.16.0" -source = "git+https://github.com/bytecodealliance/wit-bindgen?rev=efcc759#efcc7592cf3277bcb9be1034e48569c6d822b322" +version = "0.17.0" +source = "git+https://github.com/bytecodealliance/wit-bindgen?rev=21a46c7#21a46c774532da99384f7a1877c1fcfb7a4c72d3" dependencies = [ "anyhow", "proc-macro2", @@ -1944,9 +1948,9 @@ dependencies = [ [[package]] name = "wit-component" -version = "0.18.2" +version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b8a35a2a9992898c9d27f1664001860595a4bc99d32dd3599d547412e17d7e2" +checksum = "331de496d439010797c17637d8002712b9b69110f1669164c09dfa226ad277bb" dependencies = [ "anyhow", "bitflags", @@ -1955,9 +1959,9 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "wasm-encoder 0.38.1", + "wasm-encoder", "wasm-metadata", - "wasmparser 0.118.1", + "wasmparser", "wit-parser", ] diff --git a/Cargo.toml b/Cargo.toml index 2a8a97f..ed4c7e6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,10 +8,10 @@ homepage = "https://kinode.org" repository = "https://github.com/kinode-dao/process_lib" [dependencies] -alloy-rpc-types = { git = "https://github.com/alloy-rs/alloy", rev = "098ad56" } -alloy-primitives = "0.6.2" -alloy-transport = { git = "https://github.com/alloy-rs/alloy.git", rev = "098ad56" } -alloy-json-rpc = { git = "https://github.com/alloy-rs/alloy.git", rev = "098ad56" } +alloy-rpc-types = { git = "https://github.com/alloy-rs/alloy", rev = "6f8ebb4" } +alloy-primitives = "0.6.3" +alloy-transport = { git = "https://github.com/alloy-rs/alloy.git", rev = "6f8ebb4" } +alloy-json-rpc = { git = "https://github.com/alloy-rs/alloy.git", rev = "6f8ebb4" } anyhow = "1.0" bincode = "1.3.3" http = "1.0.0" diff --git a/src/eth.rs b/src/eth.rs index 7fb89db..07f27fb 100644 --- a/src/eth.rs +++ b/src/eth.rs @@ -7,15 +7,23 @@ pub use alloy_rpc_types::{ TransactionReceipt, }; use serde::{Deserialize, Serialize}; +use std::collections::HashSet; -/// The Action and Request type that can be made to eth:distro:sys. +// +// types mirrored from runtime module +// + +/// The Action and Request type that can be made to eth:distro:sys. Any process with messaging +/// capabilities can send this action to the eth provider. +/// /// Will be serialized and deserialized using `serde_json::to_vec` and `serde_json::from_slice`. -#[derive(Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub enum EthAction { /// Subscribe to logs with a custom filter. ID is to be used to unsubscribe. /// Logs come in as alloy_rpc_types::pubsub::SubscriptionResults SubscribeLogs { sub_id: u64, + chain_id: u64, kind: SubscriptionKind, params: Params, }, @@ -23,22 +31,25 @@ pub enum EthAction { UnsubscribeLogs(u64), /// Raw request. Used by kinode_process_lib. Request { + chain_id: u64, method: String, params: serde_json::Value, }, } -/// Incoming Result type for subscription updates or errors that processes will receive. +/// Incoming `Request` containing subscription updates or errors that processes will receive. +/// Can deserialize all incoming requests from eth:distro:sys to this type. +/// +/// Will be serialized and deserialized using `serde_json::to_vec` and `serde_json::from_slice`. pub type EthSubResult = Result; -/// Incoming Request type for subscription updates. +/// Incoming type for successful subscription updates. #[derive(Debug, Serialize, Deserialize)] pub struct EthSub { pub id: u64, pub result: SubscriptionResult, } -/// Incoming Request for subscription errors that processes will receive. /// If your subscription is closed unexpectedly, you will receive this. #[derive(Debug, Serialize, Deserialize)] pub struct EthSubError { @@ -47,8 +58,11 @@ pub struct EthSubError { } /// The Response type which a process will get from requesting with an [`EthAction`] will be -/// of the form `Result<(), EthError>`, serialized and deserialized using `serde_json::to_vec` +/// of this type, serialized and deserialized using `serde_json::to_vec` /// and `serde_json::from_slice`. +/// +/// In the case of an [`EthAction::SubscribeLogs`] request, the response will indicate if +/// the subscription was successfully created or not. #[derive(Debug, Serialize, Deserialize)] pub enum EthResponse { Ok, @@ -56,375 +70,564 @@ pub enum EthResponse { Err(EthError), } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, PartialEq)] pub enum EthError { - /// Underlying transport error - TransportError(String), + /// provider module cannot parse message + MalformedRequest, + /// No RPC provider for the chain + NoRpcForChain, /// Subscription closed SubscriptionClosed(u64), - /// The subscription ID was not found, so we couldn't unsubscribe. - SubscriptionNotFound, /// Invalid method InvalidMethod(String), + /// Invalid parameters + InvalidParams, /// Permission denied - PermissionDenied(String), - /// Internal RPC error - RpcError(String), + PermissionDenied, + /// RPC timed out + RpcTimeout, + /// RPC gave garbage back + RpcMalformedResponse, } -/// Sends a request based on the specified `EthAction` and parses the response. +/// The action type used for configuring eth:distro:sys. Only processes which have the "root" +/// capability from eth:distro:sys can successfully send this action. /// -/// This function constructs a request targeting the Ethereum distribution system, serializes the provided `EthAction`, -/// and sends it. It awaits a response with a specified timeout, then attempts to parse the response into the expected -/// type `T`. This method is generic and can be used for various Ethereum actions by specifying the appropriate `EthAction` -/// and return type `T`. -/// Note the timeout of 5s. -pub fn send_request_and_parse_response( - action: EthAction, -) -> anyhow::Result { - let resp = KiRequest::new() - .target(("our", "eth", "distro", "sys")) - .body(serde_json::to_vec(&action)?) - .send_and_await_response(10)??; - - match resp { - Message::Response { body, .. } => { - let response = serde_json::from_slice::(&body)?; - match response { - EthResponse::Response { value } => serde_json::from_value::(value) - .map_err(|e| anyhow::anyhow!("failed to parse response: {}", e)), - _ => Err(anyhow::anyhow!("unexpected response: {:?}", response)), - } - } - _ => Err(anyhow::anyhow!("unexpected message type: {:?}", resp)), - } +/// NOTE: changes to config will not be persisted between boots, they must be saved in .env +/// to be reflected between boots. TODO: can change this +#[derive(Debug, Serialize, Deserialize)] +pub enum EthConfigAction { + /// Add a new provider to the list of providers. + AddProvider(ProviderConfig), + /// Remove a provider from the list of providers. + /// The tuple is (chain_id, node_id/rpc_url). + RemoveProvider((u64, String)), + /// make our provider public + SetPublic, + /// make our provider not-public + SetPrivate, + /// add node to whitelist on a provider + AllowNode(String), + /// remove node from whitelist on a provider + UnallowNode(String), + /// add node to blacklist on a provider + DenyNode(String), + /// remove node from blacklist on a provider + UndenyNode(String), + /// Set the list of providers to a new list. + /// Replaces all existing saved provider configs. + SetProviders(SavedConfigs), + /// Get the list of current providers as a [`SavedConfigs`] object. + GetProviders, + /// Get the current access settings. + GetAccessSettings, } -/// Retrieves the current block number. -/// -/// # Returns -/// An `anyhow::Result` representing the current block number. -pub fn get_block_number() -> anyhow::Result { - let action = EthAction::Request { - method: "eth_blockNumber".to_string(), - params: ().into(), - }; - - let res = send_request_and_parse_response::(action)?; - Ok(res.to::()) +/// Response type from an [`EthConfigAction`] request. +#[derive(Debug, Serialize, Deserialize)] +pub enum EthConfigResponse { + Ok, + /// Response from a GetProviders request. + /// Note the [`crate::kernel_types::KnsUpdate`] will only have the correct `name` field. + /// The rest of the Update is not saved in this module. + Providers(SavedConfigs), + /// Response from a GetAccessSettings request. + AccessSettings(AccessSettings), + /// Permission denied due to missing capability + PermissionDenied, } -/// Retrieves the balance of the given address at the specified block. -/// -/// # Parameters -/// - `address`: The address to query the balance for. -/// - `tag`: Optional block ID to specify the block at which the balance is queried. -/// -/// # Returns -/// An `anyhow::Result` representing the balance of the address. -pub fn get_balance(address: Address, tag: Option) -> anyhow::Result { - let params = serde_json::to_value(( - address, - tag.unwrap_or(BlockId::Number(BlockNumberOrTag::Latest)), - ))?; - let action = EthAction::Request { - method: "eth_getBalance".to_string(), - params, - }; - - send_request_and_parse_response::(action) +/// Settings for our ETH provider +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct AccessSettings { + pub public: bool, // whether or not other nodes can access through us + pub allow: HashSet, // whitelist for access (only used if public == false) + pub deny: HashSet, // blacklist for access (always used) } -/// Retrieves logs based on a filter. -/// -/// # Parameters -/// - `filter`: The filter criteria for the logs. -/// -/// # Returns -/// An `anyhow::Result>` containing the logs that match the filter. -pub fn get_logs(filter: &Filter) -> anyhow::Result> { - let action = EthAction::Request { - method: "eth_getLogs".to_string(), - params: serde_json::to_value((filter,))?, - }; - - send_request_and_parse_response::>(action) -} +pub type SavedConfigs = Vec; -/// Retrieves the current gas price. -/// -/// # Returns -/// An `anyhow::Result` representing the current gas price. -pub fn get_gas_price() -> anyhow::Result { - let action = EthAction::Request { - method: "eth_gasPrice".to_string(), - params: ().into(), - }; - - send_request_and_parse_response::(action) +/// Provider config. Can currently be a node or a ws provider instance. +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct ProviderConfig { + pub chain_id: u64, + pub trusted: bool, + pub provider: NodeOrRpcUrl, } -/// Retrieves the chain ID. -/// -/// # Returns -/// An `anyhow::Result` representing the chain ID. -pub fn get_chain_id() -> anyhow::Result { - let action = EthAction::Request { - method: "eth_chainId".to_string(), - params: ().into(), - }; - - send_request_and_parse_response::(action) +#[derive(Clone, Debug, Deserialize, Serialize)] +pub enum NodeOrRpcUrl { + Node { + kns_update: crate::kernel_types::KnsUpdate, + use_as_provider: bool, // for routers inside saved config + }, + RpcUrl(String), } -/// Retrieves the number of transactions sent from the given address. -/// -/// # Parameters -/// - `address`: The address to query the transaction count for. -/// - `tag`: Optional block ID to specify the block at which the count is queried. -/// -/// # Returns -/// An `anyhow::Result` representing the number of transactions sent from the address. -pub fn get_transaction_count(address: Address, tag: Option) -> anyhow::Result { - let params = serde_json::to_value((address, tag.unwrap_or_default()))?; - let action = EthAction::Request { - method: "eth_getTransactionCount".to_string(), - params, - }; - - send_request_and_parse_response::(action) +impl std::cmp::PartialEq for NodeOrRpcUrl { + fn eq(&self, other: &str) -> bool { + match self { + NodeOrRpcUrl::Node { kns_update, .. } => kns_update.name == other, + NodeOrRpcUrl::RpcUrl(url) => url == other, + } + } } -/// Retrieves a block by its hash. -/// -/// # Parameters -/// - `hash`: The hash of the block to retrieve. -/// - `full_tx`: Whether to return full transaction objects or just their hashes. -/// -/// # Returns -/// An `anyhow::Result>` representing the block, if found. -pub fn get_block_by_hash(hash: BlockHash, full_tx: bool) -> anyhow::Result> { - let params = serde_json::to_value((hash, full_tx))?; - let action = EthAction::Request { - method: "eth_getBlockByHash".to_string(), - params, - }; - - send_request_and_parse_response::>(action) -} -/// Retrieves a block by its number or tag. -/// -/// # Parameters -/// - `number`: The number or tag of the block to retrieve. -/// - `full_tx`: Whether to return full transaction objects or just their hashes. -/// -/// # Returns -/// An `anyhow::Result>` representing the block, if found. -pub fn get_block_by_number( - number: BlockNumberOrTag, - full_tx: bool, -) -> anyhow::Result> { - let params = serde_json::to_value((number, full_tx))?; - let action = EthAction::Request { - method: "eth_getBlockByNumber".to_string(), - params, - }; - - send_request_and_parse_response::>(action) +/// An EVM chain provider. Create this object to start making RPC calls. +/// Set the chain_id to determine which chain to call: requests will fail +/// unless the node this process is running on has access to a provider +/// for that chain. +pub struct Provider { + chain_id: u64, + request_timeout: u64, } -/// Retrieves the storage at a given address and key. -/// -/// # Parameters -/// - `address`: The address of the storage to query. -/// - `key`: The key of the storage slot to retrieve. -/// - `tag`: Optional block ID to specify the block at which the storage is queried. -/// -/// # Returns -/// An `anyhow::Result` representing the data stored at the given address and key. -pub fn get_storage_at(address: Address, key: U256, tag: Option) -> anyhow::Result { - let params = serde_json::to_value((address, key, tag.unwrap_or_default()))?; - let action = EthAction::Request { - method: "eth_getStorageAt".to_string(), - params, - }; - - send_request_and_parse_response::(action) -} +impl Provider { + /// Instantiate a new provider. + pub fn new(chain_id: u64, request_timeout: u64) -> Self { + Self { + chain_id, + request_timeout, + } + } + /// Sends a request based on the specified `EthAction` and parses the response. + /// + /// This function constructs a request targeting the Ethereum distribution system, serializes the provided `EthAction`, + /// and sends it. It awaits a response with a specified timeout, then attempts to parse the response into the expected + /// type `T`. This method is generic and can be used for various Ethereum actions by specifying the appropriate `EthAction` + /// and return type `T`. + pub fn send_request_and_parse_response( + &self, + action: EthAction, + ) -> Result { + let resp = KiRequest::new() + .target(("our", "eth", "distro", "sys")) + .body(serde_json::to_vec(&action).unwrap()) + .send_and_await_response(self.request_timeout) + .unwrap() + .map_err(|_| EthError::RpcTimeout)?; + + match resp { + Message::Response { body, .. } => match serde_json::from_slice::(&body) { + Ok(EthResponse::Response { value }) => { + serde_json::from_value::(value).map_err(|_| EthError::RpcMalformedResponse) + } + Ok(EthResponse::Err(e)) => Err(e), + _ => Err(EthError::RpcMalformedResponse), + }, + _ => Err(EthError::RpcMalformedResponse), + } + } -/// Retrieves the code at a given address. -/// -/// # Parameters -/// - `address`: The address of the code to query. -/// - `tag`: The block ID to specify the block at which the code is queried. -/// -/// # Returns -/// An `anyhow::Result` representing the code stored at the given address. -pub fn get_code_at(address: Address, tag: BlockId) -> anyhow::Result { - let params = serde_json::to_value((address, tag))?; - let action = EthAction::Request { - method: "eth_getCode".to_string(), - params, - }; - - send_request_and_parse_response::(action) -} + /// Retrieves the current block number. + /// + /// # Returns + /// A `Result` representing the current block number. + pub fn get_block_number(&self) -> Result { + let action = EthAction::Request { + chain_id: self.chain_id, + method: "eth_blockNumber".to_string(), + params: ().into(), + }; + + let res = self.send_request_and_parse_response::(action)?; + Ok(res.to::()) + } -/// Retrieves a transaction by its hash. -/// -/// # Parameters -/// - `hash`: The hash of the transaction to retrieve. -/// -/// # Returns -/// An `anyhow::Result>` representing the transaction, if found. -pub fn get_transaction_by_hash(hash: TxHash) -> anyhow::Result> { - let params = serde_json::to_value((hash,))?; - let action = EthAction::Request { - method: "eth_getTransactionByHash".to_string(), - params, - }; - - send_request_and_parse_response::>(action) -} + /// Retrieves the balance of the given address at the specified block. + /// + /// # Parameters + /// - `address`: The address to query the balance for. + /// - `tag`: Optional block ID to specify the block at which the balance is queried. + /// + /// # Returns + /// A `Result` representing the balance of the address. + pub fn get_balance(&self, address: Address, tag: Option) -> Result { + let params = serde_json::to_value(( + address, + tag.unwrap_or(BlockId::Number(BlockNumberOrTag::Latest)), + )) + .unwrap(); + let action = EthAction::Request { + chain_id: self.chain_id, + method: "eth_getBalance".to_string(), + params, + }; + + self.send_request_and_parse_response::(action) + } -/// Retrieves the receipt of a transaction by its hash. -/// -/// # Parameters -/// - `hash`: The hash of the transaction for which the receipt is requested. -/// -/// # Returns -/// An `anyhow::Result>` representing the transaction receipt, if found. -pub fn get_transaction_receipt(hash: TxHash) -> anyhow::Result> { - let params = serde_json::to_value((hash,))?; - let action = EthAction::Request { - method: "eth_getTransactionReceipt".to_string(), - params, - }; - - send_request_and_parse_response::>(action) -} + /// Retrieves logs based on a filter. + /// + /// # Parameters + /// - `filter`: The filter criteria for the logs. + /// + /// # Returns + /// A `Result, EthError>` containing the logs that match the filter. + pub fn get_logs(&self, filter: &Filter) -> Result, EthError> { + // NOTE: filter must be encased by a tuple to be serialized correctly + let Ok(params) = serde_json::to_value((filter,)) else { + return Err(EthError::InvalidParams); + }; + let action = EthAction::Request { + chain_id: self.chain_id, + method: "eth_getLogs".to_string(), + params, + }; + + self.send_request_and_parse_response::>(action) + } -/// Estimates the amount of gas that a transaction will consume. -/// -/// # Parameters -/// - `tx`: The transaction request object containing the details of the transaction to estimate gas for. -/// - `block`: Optional block ID to specify the block at which the gas estimate should be made. -/// -/// # Returns -/// An `anyhow::Result` representing the estimated gas amount. -pub fn estimate_gas(tx: TransactionRequest, block: Option) -> anyhow::Result { - let params = serde_json::to_value((tx, block.unwrap_or_default()))?; - let action = EthAction::Request { - method: "eth_estimateGas".to_string(), - params, - }; - - send_request_and_parse_response::(action) -} + /// Retrieves the current gas price. + /// + /// # Returns + /// A `Result` representing the current gas price. + pub fn get_gas_price(&self) -> Result { + let action = EthAction::Request { + chain_id: self.chain_id, + method: "eth_gasPrice".to_string(), + params: ().into(), + }; + + self.send_request_and_parse_response::(action) + } -/// Retrieves the list of accounts controlled by the node. -/// -/// # Returns -/// An `anyhow::Result>` representing the list of accounts. -/// Note: This function may return an empty list depending on the node's configuration and capabilities. -pub fn get_accounts() -> anyhow::Result> { - let action = EthAction::Request { - method: "eth_accounts".to_string(), - params: serde_json::Value::Array(vec![]), - }; - - send_request_and_parse_response::>(action) -} + /// Retrieves the number of transactions sent from the given address. + /// + /// # Parameters + /// - `address`: The address to query the transaction count for. + /// - `tag`: Optional block ID to specify the block at which the count is queried. + /// + /// # Returns + /// A `Result` representing the number of transactions sent from the address. + pub fn get_transaction_count( + &self, + address: Address, + tag: Option, + ) -> Result { + let Ok(params) = serde_json::to_value((address, tag.unwrap_or_default())) else { + return Err(EthError::InvalidParams); + }; + let action = EthAction::Request { + chain_id: self.chain_id, + method: "eth_getTransactionCount".to_string(), + params, + }; + + self.send_request_and_parse_response::(action) + } -/// Retrieves the fee history for a given range of blocks. -/// -/// # Parameters -/// - `block_count`: The number of blocks to include in the history. -/// - `last_block`: The ending block number or tag for the history range. -/// - `reward_percentiles`: A list of percentiles to report fee rewards for. -/// -/// # Returns -/// An `anyhow::Result` representing the fee history for the specified range. -pub fn get_fee_history( - block_count: U256, - last_block: BlockNumberOrTag, - reward_percentiles: Vec, -) -> anyhow::Result { - let params = serde_json::to_value((block_count, last_block, reward_percentiles))?; - let action = EthAction::Request { - method: "eth_feeHistory".to_string(), - params, - }; - - send_request_and_parse_response::(action) -} + /// Retrieves a block by its hash. + /// + /// # Parameters + /// - `hash`: The hash of the block to retrieve. + /// - `full_tx`: Whether to return full transaction objects or just their hashes. + /// + /// # Returns + /// A `Result, EthError>` representing the block, if found. + pub fn get_block_by_hash( + &self, + hash: BlockHash, + full_tx: bool, + ) -> Result, EthError> { + let Ok(params) = serde_json::to_value((hash, full_tx)) else { + return Err(EthError::InvalidParams); + }; + let action = EthAction::Request { + chain_id: self.chain_id, + method: "eth_getBlockByHash".to_string(), + params, + }; + + self.send_request_and_parse_response::>(action) + } + /// Retrieves a block by its number or tag. + /// + /// # Parameters + /// - `number`: The number or tag of the block to retrieve. + /// - `full_tx`: Whether to return full transaction objects or just their hashes. + /// + /// # Returns + /// A `Result, EthError>` representing the block, if found. + pub fn get_block_by_number( + &self, + number: BlockNumberOrTag, + full_tx: bool, + ) -> Result, EthError> { + let Ok(params) = serde_json::to_value((number, full_tx)) else { + return Err(EthError::InvalidParams); + }; + let action = EthAction::Request { + chain_id: self.chain_id, + method: "eth_getBlockByNumber".to_string(), + params, + }; + + self.send_request_and_parse_response::>(action) + } -/// Executes a call transaction, which is directly executed in the VM of the node, but never mined into the blockchain. -/// -/// # Parameters -/// - `tx`: The transaction request object containing the details of the call. -/// - `block`: Optional block ID to specify the block at which the call should be made. -/// -/// # Returns -/// An `anyhow::Result` representing the result of the call. -pub fn call(tx: TransactionRequest, block: Option) -> anyhow::Result { - let params = serde_json::to_value((tx, block.unwrap_or_default()))?; - let action = EthAction::Request { - method: "eth_call".to_string(), - params, - }; - - send_request_and_parse_response::(action) -} + /// Retrieves the storage at a given address and key. + /// + /// # Parameters + /// - `address`: The address of the storage to query. + /// - `key`: The key of the storage slot to retrieve. + /// - `tag`: Optional block ID to specify the block at which the storage is queried. + /// + /// # Returns + /// A `Result` representing the data stored at the given address and key. + pub fn get_storage_at( + &self, + address: Address, + key: U256, + tag: Option, + ) -> Result { + let Ok(params) = serde_json::to_value((address, key, tag.unwrap_or_default())) else { + return Err(EthError::InvalidParams); + }; + let action = EthAction::Request { + chain_id: self.chain_id, + method: "eth_getStorageAt".to_string(), + params, + }; + + self.send_request_and_parse_response::(action) + } -/// Sends a raw transaction to the network. -/// -/// # Parameters -/// - `tx`: The raw transaction data. -/// -/// # Returns -/// An `anyhow::Result` representing the hash of the transaction once it has been sent. -pub fn send_raw_transaction(tx: Bytes) -> anyhow::Result { - let action = EthAction::Request { - method: "eth_sendRawTransaction".to_string(), - params: serde_json::to_value((tx,))?, - }; - - send_request_and_parse_response::(action) -} + /// Retrieves the code at a given address. + /// + /// # Parameters + /// - `address`: The address of the code to query. + /// - `tag`: The block ID to specify the block at which the code is queried. + /// + /// # Returns + /// A `Result` representing the code stored at the given address. + pub fn get_code_at(&self, address: Address, tag: BlockId) -> Result { + let Ok(params) = serde_json::to_value((address, tag)) else { + return Err(EthError::InvalidParams); + }; + let action = EthAction::Request { + chain_id: self.chain_id, + method: "eth_getCode".to_string(), + params, + }; + + self.send_request_and_parse_response::(action) + } -/// Subscribes to logs without waiting for a confirmation. -/// -/// # Parameters -/// - `sub_id`: The subscription ID to be used for unsubscribing. -/// - `filter`: The filter criteria for the logs. -/// -/// # Returns -/// An `anyhow::Result<()>` indicating the operation was dispatched. -pub fn subscribe(sub_id: u64, filter: Filter) -> anyhow::Result<()> { - let action = EthAction::SubscribeLogs { - sub_id, - kind: SubscriptionKind::Logs, - params: Params::Logs(Box::new(filter)), - }; - - KiRequest::new() - .target(("our", "eth", "distro", "sys")) - .body(serde_json::to_vec(&action)?) - .send() -} -/// Unsubscribes from a previously created subscription. -/// -/// # Parameters -/// - `sub_id`: The subscription ID to unsubscribe from. -/// -/// # Returns -/// An `anyhow::Result<()>` indicating the success or failure of the unsubscription request. -pub fn unsubscribe(sub_id: u64) -> anyhow::Result<()> { - let action = EthAction::UnsubscribeLogs(sub_id); - - KiRequest::new() - .target(("our", "eth", "distro", "sys")) - .body(serde_json::to_vec(&action)?) - .send() + /// Retrieves a transaction by its hash. + /// + /// # Parameters + /// - `hash`: The hash of the transaction to retrieve. + /// + /// # Returns + /// A `Result, EthError>` representing the transaction, if found. + pub fn get_transaction_by_hash(&self, hash: TxHash) -> Result, EthError> { + // NOTE: hash must be encased by a tuple to be serialized correctly + let Ok(params) = serde_json::to_value((hash,)) else { + return Err(EthError::InvalidParams); + }; + let action = EthAction::Request { + chain_id: self.chain_id, + method: "eth_getTransactionByHash".to_string(), + params, + }; + + self.send_request_and_parse_response::>(action) + } + + /// Retrieves the receipt of a transaction by its hash. + /// + /// # Parameters + /// - `hash`: The hash of the transaction for which the receipt is requested. + /// + /// # Returns + /// A `Result, EthError>` representing the transaction receipt, if found. + pub fn get_transaction_receipt( + &self, + hash: TxHash, + ) -> Result, EthError> { + // NOTE: hash must be encased by a tuple to be serialized correctly + let Ok(params) = serde_json::to_value((hash,)) else { + return Err(EthError::InvalidParams); + }; + let action = EthAction::Request { + chain_id: self.chain_id, + method: "eth_getTransactionReceipt".to_string(), + params, + }; + + self.send_request_and_parse_response::>(action) + } + + /// Estimates the amount of gas that a transaction will consume. + /// + /// # Parameters + /// - `tx`: The transaction request object containing the details of the transaction to estimate gas for. + /// - `block`: Optional block ID to specify the block at which the gas estimate should be made. + /// + /// # Returns + /// A `Result` representing the estimated gas amount. + pub fn estimate_gas( + &self, + tx: TransactionRequest, + block: Option, + ) -> Result { + let Ok(params) = serde_json::to_value((tx, block.unwrap_or_default())) else { + return Err(EthError::InvalidParams); + }; + let action = EthAction::Request { + chain_id: self.chain_id, + method: "eth_estimateGas".to_string(), + params, + }; + + self.send_request_and_parse_response::(action) + } + + /// Retrieves the list of accounts controlled by the node. + /// + /// # Returns + /// A `Result, EthError>` representing the list of accounts. + /// Note: This function may return an empty list depending on the node's configuration and capabilities. + pub fn get_accounts(&self) -> Result, EthError> { + let action = EthAction::Request { + chain_id: self.chain_id, + method: "eth_accounts".to_string(), + params: serde_json::Value::Array(vec![]), + }; + + self.send_request_and_parse_response::>(action) + } + + /// Retrieves the fee history for a given range of blocks. + /// + /// # Parameters + /// - `block_count`: The number of blocks to include in the history. + /// - `last_block`: The ending block number or tag for the history range. + /// - `reward_percentiles`: A list of percentiles to report fee rewards for. + /// + /// # Returns + /// A `Result` representing the fee history for the specified range. + pub fn get_fee_history( + &self, + block_count: U256, + last_block: BlockNumberOrTag, + reward_percentiles: Vec, + ) -> Result { + let Ok(params) = serde_json::to_value((block_count, last_block, reward_percentiles)) else { + return Err(EthError::InvalidParams); + }; + let action = EthAction::Request { + chain_id: self.chain_id, + method: "eth_feeHistory".to_string(), + params, + }; + + self.send_request_and_parse_response::(action) + } + + /// Executes a call transaction, which is directly executed in the VM of the node, but never mined into the blockchain. + /// + /// # Parameters + /// - `tx`: The transaction request object containing the details of the call. + /// - `block`: Optional block ID to specify the block at which the call should be made. + /// + /// # Returns + /// A `Result` representing the result of the call. + pub fn call(&self, tx: TransactionRequest, block: Option) -> Result { + let Ok(params) = serde_json::to_value((tx, block.unwrap_or_default())) else { + return Err(EthError::InvalidParams); + }; + let action = EthAction::Request { + chain_id: self.chain_id, + method: "eth_call".to_string(), + params, + }; + + self.send_request_and_parse_response::(action) + } + + /// Sends a raw transaction to the network. + /// + /// # Parameters + /// - `tx`: The raw transaction data. + /// + /// # Returns + /// A `Result` representing the hash of the transaction once it has been sent. + pub fn send_raw_transaction(&self, tx: Bytes) -> Result { + let action = EthAction::Request { + chain_id: self.chain_id, + method: "eth_sendRawTransaction".to_string(), + // NOTE: tx must be encased by a tuple to be serialized correctly + params: serde_json::to_value((tx,)).unwrap(), + }; + + self.send_request_and_parse_response::(action) + } + + /// Subscribes to logs without waiting for a confirmation. + /// + /// # Parameters + /// - `sub_id`: The subscription ID to be used for unsubscribing. + /// - `filter`: The filter criteria for the logs. + /// + /// # Returns + /// A `Result<(), EthError>` indicating whether the subscription was created. + pub fn subscribe(&self, sub_id: u64, filter: Filter) -> Result<(), EthError> { + let action = EthAction::SubscribeLogs { + sub_id, + chain_id: self.chain_id, + kind: SubscriptionKind::Logs, + params: Params::Logs(Box::new(filter)), + }; + + let Ok(body) = serde_json::to_vec(&action) else { + return Err(EthError::InvalidParams); + }; + + let resp = KiRequest::new() + .target(("our", "eth", "distro", "sys")) + .body(body) + .send_and_await_response(self.request_timeout) + .unwrap() + .map_err(|_| EthError::RpcTimeout)?; + + match resp { + Message::Response { body, .. } => { + let response = serde_json::from_slice::(&body); + match response { + Ok(EthResponse::Ok) => Ok(()), + Ok(EthResponse::Err(e)) => Err(e), + _ => Err(EthError::RpcMalformedResponse), + } + } + _ => Err(EthError::RpcMalformedResponse), + } + } + + /// Unsubscribes from a previously created subscription. + /// + /// # Parameters + /// - `sub_id`: The subscription ID to unsubscribe from. + /// + /// # Returns + /// A `Result<(), EthError>` indicating whether the subscription was cancelled. + pub fn unsubscribe(&self, sub_id: u64) -> Result<(), EthError> { + let action = EthAction::UnsubscribeLogs(sub_id); + + let resp = KiRequest::new() + .target(("our", "eth", "distro", "sys")) + .body(serde_json::to_vec(&action).map_err(|_| EthError::MalformedRequest)?) + .send_and_await_response(self.request_timeout) + .unwrap() + .map_err(|_| EthError::RpcTimeout)?; + + match resp { + Message::Response { body, .. } => match serde_json::from_slice::(&body) { + Ok(EthResponse::Ok) => Ok(()), + _ => Err(EthError::RpcMalformedResponse), + }, + _ => Err(EthError::RpcMalformedResponse), + } + } } diff --git a/src/kernel_types.rs b/src/kernel_types.rs index e79aef6..258e9f0 100644 --- a/src/kernel_types.rs +++ b/src/kernel_types.rs @@ -432,3 +432,14 @@ pub enum MessageType { Request, Response, } + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct KnsUpdate { + pub name: String, // actual username / domain name + pub owner: String, + pub node: String, // hex namehash of node + pub public_key: String, + pub ip: String, + pub port: u16, + pub routers: Vec, +}