From 92170602abd42bd0daf7b31b882cef75075300d8 Mon Sep 17 00:00:00 2001 From: ManojJiSharma Date: Wed, 25 Sep 2024 15:15:53 +0530 Subject: [PATCH 1/2] initial config --- Cargo.lock | 9 ++++ Cargo.toml | 2 +- chains/humanode/Cargo.toml | 14 ++++++ chains/humanode/res/humanode.scale | Bin 0 -> 89552 bytes chains/humanode/src/lib.rs | 66 +++++++++++++++++++++++++++++ 5 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 chains/humanode/Cargo.toml create mode 100644 chains/humanode/res/humanode.scale create mode 100644 chains/humanode/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 87868a51..a7e9cfcb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5636,6 +5636,15 @@ dependencies = [ "thiserror", ] +[[package]] +name = "rosetta-config-humanode" +version = "0.1.0" +dependencies = [ + "anyhow", + "rosetta-core", + "subxt", +] + [[package]] name = "rosetta-config-polkadot" version = "0.6.0" diff --git a/Cargo.toml b/Cargo.toml index df91943a..f5a1951b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,7 @@ members = [ "rosetta-utils", "chains/polygon/rosetta-testing-polygon", "chains/binance", - "chains/avalanche", + "chains/avalanche", "chains/humanode", ] resolver = "2" diff --git a/chains/humanode/Cargo.toml b/chains/humanode/Cargo.toml new file mode 100644 index 00000000..84b44dfb --- /dev/null +++ b/chains/humanode/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "rosetta-config-humanode" +version = "0.1.0" +edition = "2021" + + +[features] +default = ["humanode-metadata"] +humanode-metadata = ["subxt"] + +[dependencies] +anyhow = "1.0" +rosetta-core.workspace = true +subxt = { workspace = true, features = ["substrate-compat", "native"], optional = true } \ No newline at end of file diff --git a/chains/humanode/res/humanode.scale b/chains/humanode/res/humanode.scale new file mode 100644 index 0000000000000000000000000000000000000000..9520394e41264bd324bc042bd9ae9c8b58db9aa9 GIT binary patch literal 89552 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 literal 0 HcmV?d00001 diff --git a/chains/humanode/src/lib.rs b/chains/humanode/src/lib.rs new file mode 100644 index 00000000..145465fb --- /dev/null +++ b/chains/humanode/src/lib.rs @@ -0,0 +1,66 @@ +use anyhow::Result; +use rosetta_core::{ + crypto::{address::AddressFormat, Algorithm}, + BlockchainConfig, NodeUri, +}; +use std::sync::Arc; + + +// Generate an interface that we can use from the node's metadata. +#[cfg(feature = "humanode-metadata")] +pub mod metadata { + #[subxt::subxt(runtime_metadata_path = "res/humanode.scale")] + pub mod dev {} +} + + +/// Retrieve the [`BlockchainConfig`] from the provided `network` +/// +/// # Errors +/// Returns `Err` if the network is not supported +pub fn config(network: &str) -> Result { + // All available networks are listed here: + let (network, symbol) = match network { + "dev" => ("dev", "LOC"), + + "humanode" => ("humanode", "hmnd"), + + "shasta" => ("shasta", "hmnd"), + + "Nile" => ("Nile", "hmnd"), + + "Tronex" => ("Tronex", "hmnd"), + + _ => anyhow::bail!("unsupported network: {}", network), + }; + + Ok(BlockchainConfig { + blockchain: "humanode", + network, + algorithm: Algorithm::EcdsaRecoverableSecp256k1, + address_format: AddressFormat::Eip55, + coin: if network == "astar" { 592 } else { 5234 }, + bip44: true, + utxo: false, + currency_unit: "planck", + currency_symbol: symbol, + currency_decimals: 18, + node_uri: NodeUri::parse("ws://127.0.0.1:9944")?, + node_image: "staketechnologies/astar-collator:v5.28.0-rerun", + node_command: Arc::new(|network, port| { + let mut params = vec![ + format!("--chain={network}"), + "--rpc-cors=all".into(), + "--rpc-external".into(), + format!("--rpc-port={port}"), + ]; + if network.ends_with("dev") { + params.extend_from_slice(&["--alice".into(), "--tmp".into()]); + } + params + }), + node_additional_ports: &[], + connector_port: 8083, + testnet: network != "humanode", + }) +} From 40bdb34cb27acd1e7cc221c10680443977a9d5c8 Mon Sep 17 00:00:00 2001 From: ManojJiSharma Date: Thu, 26 Sep 2024 19:57:18 +0530 Subject: [PATCH 2/2] humanode server and testes added --- Cargo.lock | 32 ++ Cargo.toml | 7 +- chains/humanode/{ => config}/Cargo.toml | 6 +- .../humanode/{ => config}/res/humanode.scale | Bin chains/humanode/{ => config}/src/lib.rs | 10 +- chains/humanode/server/Cargo.toml | 36 ++ chains/humanode/server/src/lib.rs | 538 ++++++++++++++++++ rosetta-client/Cargo.toml | 1 + rosetta-client/src/client.rs | 43 ++ rosetta-client/src/lib.rs | 3 + rosetta-client/src/tx_builder.rs | 15 + rosetta-client/src/wallet.rs | 19 + 12 files changed, 700 insertions(+), 10 deletions(-) rename chains/humanode/{ => config}/Cargo.toml (65%) rename chains/humanode/{ => config}/res/humanode.scale (100%) rename chains/humanode/{ => config}/src/lib.rs (98%) create mode 100644 chains/humanode/server/Cargo.toml create mode 100644 chains/humanode/server/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 93a9778b..7019e982 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5605,6 +5605,7 @@ dependencies = [ "rosetta-core", "rosetta-server-astar", "rosetta-server-ethereum", + "rosetta-server-humanode", "rosetta-server-polkadot", "rosetta-tx-ethereum", "rosetta-tx-polkadot", @@ -5861,6 +5862,37 @@ dependencies = [ "url", ] +[[package]] +name = "rosetta-server-humanode" +version = "0.1.0" +dependencies = [ + "alloy-primitives 0.8.3", + "alloy-sol-types 0.8.3", + "anyhow", + "async-trait", + "ethers-solc", + "futures", + "futures-util", + "hex", + "hex-literal", + "log", + "parity-scale-codec", + "rosetta-chain-testing", + "rosetta-client", + "rosetta-config-ethereum", + "rosetta-config-humanode", + "rosetta-core", + "rosetta-docker", + "rosetta-server", + "rosetta-server-ethereum", + "serde", + "serde_json", + "sha3", + "sp-keyring", + "subxt", + "tokio", +] + [[package]] name = "rosetta-server-polkadot" version = "0.6.0" diff --git a/Cargo.toml b/Cargo.toml index 4b32fccf..f5d0f820 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,9 +20,10 @@ members = [ "rosetta-utils", "chains/polygon/rosetta-testing-polygon", "chains/binance", - "chains/avalanche", - "chains/humanode", + "chains/avalanche", "chains/rosetta-chain-testing", + "chains/humanode/config", + "chains/humanode/server", ] resolver = "2" @@ -37,6 +38,8 @@ rosetta-config-ethereum = { path = "chains/ethereum/config" } rosetta-server-ethereum = { path = "chains/ethereum/server" } rosetta-tx-ethereum = { path = "chains/ethereum/tx" } rosetta-ethereum-types = { path = "chains/ethereum/types", default-features = false } +rosetta-config-humanode = { path = "chains/humanode/config", default-features = false } +rosetta-server-humanode = { path = "chains/humanode/server" } rosetta-config-polkadot = { path = "chains/polkadot/config" } rosetta-server-polkadot = { path = "chains/polkadot/server" } rosetta-tx-polkadot = { path = "chains/polkadot/tx" } diff --git a/chains/humanode/Cargo.toml b/chains/humanode/config/Cargo.toml similarity index 65% rename from chains/humanode/Cargo.toml rename to chains/humanode/config/Cargo.toml index 84b44dfb..d3a9556e 100644 --- a/chains/humanode/Cargo.toml +++ b/chains/humanode/config/Cargo.toml @@ -2,7 +2,9 @@ name = "rosetta-config-humanode" version = "0.1.0" edition = "2021" - +license = "MIT" +repository = "https://github.com/analog-labs/chain-connectors" +description = "Humanode configuration" [features] default = ["humanode-metadata"] @@ -11,4 +13,4 @@ humanode-metadata = ["subxt"] [dependencies] anyhow = "1.0" rosetta-core.workspace = true -subxt = { workspace = true, features = ["substrate-compat", "native"], optional = true } \ No newline at end of file +subxt = { workspace = true, features = ["substrate-compat", "native"], optional = true } diff --git a/chains/humanode/res/humanode.scale b/chains/humanode/config/res/humanode.scale similarity index 100% rename from chains/humanode/res/humanode.scale rename to chains/humanode/config/res/humanode.scale diff --git a/chains/humanode/src/lib.rs b/chains/humanode/config/src/lib.rs similarity index 98% rename from chains/humanode/src/lib.rs rename to chains/humanode/config/src/lib.rs index 145465fb..dd0431ac 100644 --- a/chains/humanode/src/lib.rs +++ b/chains/humanode/config/src/lib.rs @@ -5,7 +5,6 @@ use rosetta_core::{ }; use std::sync::Arc; - // Generate an interface that we can use from the node's metadata. #[cfg(feature = "humanode-metadata")] pub mod metadata { @@ -13,7 +12,6 @@ pub mod metadata { pub mod dev {} } - /// Retrieve the [`BlockchainConfig`] from the provided `network` /// /// # Errors @@ -24,13 +22,13 @@ pub fn config(network: &str) -> Result { "dev" => ("dev", "LOC"), "humanode" => ("humanode", "hmnd"), - + "shasta" => ("shasta", "hmnd"), - + "Nile" => ("Nile", "hmnd"), - + "Tronex" => ("Tronex", "hmnd"), - + _ => anyhow::bail!("unsupported network: {}", network), }; diff --git a/chains/humanode/server/Cargo.toml b/chains/humanode/server/Cargo.toml new file mode 100644 index 00000000..538e3c82 --- /dev/null +++ b/chains/humanode/server/Cargo.toml @@ -0,0 +1,36 @@ +[package] +name = "rosetta-server-humanode" +version = "0.1.0" +edition = "2021" +license = "MIT" +repository = "https://github.com/analog-labs/chain-connectors" +description = "humanode rosetta server." + +[dependencies] +anyhow = "1.0" +async-trait = "0.1" +futures = { version = "0.3", default-features = false, features = ["std"] } +futures-util = "0.3" +hex = "0.4" +hex-literal = "0.4" +log = "0.4" +parity-scale-codec = { workspace = true, features = ["derive"] } +rosetta-chain-testing = { path = "../../rosetta-chain-testing" } +rosetta-config-ethereum.workspace = true +rosetta-config-humanode = { workspace = true, features = ["humanode-metadata"] } +rosetta-core.workspace = true +rosetta-server = { workspace = true, features = ["webpki-tls"] } +rosetta-server-ethereum.workspace = true +serde.workspace = true +serde_json.workspace = true +sp-keyring.workspace = true +subxt = { workspace = true, features = ["substrate-compat"] } +tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } + +[dev-dependencies] +alloy-primitives = { version = "0.8" } +alloy-sol-types = { version = "0.8" } +ethers-solc = "2.0" +rosetta-client.workspace = true +rosetta-docker = { workspace = true, features = ["tests"] } +sha3 = "0.10" diff --git a/chains/humanode/server/src/lib.rs b/chains/humanode/server/src/lib.rs new file mode 100644 index 00000000..4eaf0284 --- /dev/null +++ b/chains/humanode/server/src/lib.rs @@ -0,0 +1,538 @@ +use anyhow::{Context, Result}; +use parity_scale_codec::Decode; +use rosetta_config_ethereum::{ + ext::types::{H160, H256}, + EthereumMetadata, EthereumMetadataParams, Query as EthQuery, QueryResult as EthQueryResult, +}; +use rosetta_config_humanode::metadata::{ + dev as humanode_metadata, + dev::runtime_types::{frame_system::AccountInfo, pallet_balances::AccountData}, +}; +use rosetta_core::{ + crypto::{ + address::{Address, AddressFormat}, + PublicKey, + }, + types::{BlockIdentifier, PartialBlockIdentifier}, + BlockchainClient, BlockchainConfig, +}; +use rosetta_server::ws::default_client; +use rosetta_server_ethereum::MaybeWsEthereumClient; +use serde::{Deserialize, Serialize}; +use std::sync::Arc; +use subxt::{ + backend::{ + legacy::{rpc_methods::BlockNumber, LegacyBackendBuilder, LegacyRpcMethods}, + rpc::RpcClient, + BlockRef, + }, + config::substrate::U256, + dynamic::Value as SubtxValue, + ext::sp_core::{self, crypto::Ss58AddressFormat}, + tx::PairSigner, + utils::AccountId32, + OnlineClient, PolkadotConfig, +}; + +/// Re-exports libraries to not require any additional +/// dependencies to be explicitly added on the client side. +#[doc(hidden)] +pub mod ext { + pub use anyhow; + pub use rosetta_config_ethereum as ethereum_config; + pub use rosetta_config_humanode as humanode_config; + pub use rosetta_core as core; + pub use subxt; +} + +#[derive(Deserialize, Serialize)] +pub struct HumanodeMetadataParams(pub EthereumMetadataParams); + +#[derive(Deserialize, Serialize)] +pub struct HumanodeMetadata(pub EthereumMetadata); + +pub struct HumanodeClient { + client: MaybeWsEthereumClient, + ws_client: OnlineClient, + rpc_methods: LegacyRpcMethods, +} + +impl HumanodeClient { + /// Creates a new polkadot client, loading the config from `network` and connects to `addr` + /// + /// # Errors + /// Will return `Err` when the network is invalid, or when the provided `addr` is unreacheable. + pub async fn new(network: &str, url: &str) -> Result { + let config = rosetta_config_humanode::config(network)?; + Self::from_config(config, url).await + } + + /// Creates a new polkadot client using the provided `config` and connects to `addr` + /// + /// # Errors + /// Will return `Err` when the network is invalid, or when the provided `addr` is unreacheable. + pub async fn from_config(config: BlockchainConfig, url: &str) -> Result { + let ws_client = default_client(url, None).await?; + let rpc_client = RpcClient::new(ws_client.clone()); + let rpc_methods = LegacyRpcMethods::::new(rpc_client.clone()); + let backend = LegacyBackendBuilder::new().build(rpc_client); + let substrate_client = + OnlineClient::::from_backend(Arc::new(backend)).await?; + let ethereum_client = + MaybeWsEthereumClient::from_jsonrpsee(config, ws_client, None).await?; + Ok(Self { client: ethereum_client, ws_client: substrate_client, rpc_methods }) + } + + async fn account_info( + &self, + address: &Address, + maybe_block: Option<&PartialBlockIdentifier>, + ) -> Result>> { + let account: AccountId32 = address + .address() + .parse() + .map_err(|err| anyhow::anyhow!("{}", err)) + .context("invalid address")?; + + // Build a dynamic storage query to iterate account information. + let storage_query = + subxt::dynamic::storage("System", "Account", vec![SubtxValue::from_bytes(account)]); + + // TODO: Change the `PartialBlockIdentifier` for distinguish between ethereum blocks and + // substrate blocks. + let block_hash = match maybe_block { + Some(PartialBlockIdentifier { hash: Some(block_hash), .. }) => { + // If a hash if provided, we don't know if it's a ethereum block hash or substrate + // block hash. We try to fetch the block using ethereum first, and + // if it fails, we try to fetch it using substrate. + let ethereum_block = self + .client + .call(&EthQuery::GetBlockByHash(H256(*block_hash).into())) + .await + .map(|result| match result { + EthQueryResult::GetBlockByHash(block) => block, + _ => unreachable!(), + }); + + if let Ok(Some(ethereum_block)) = ethereum_block { + // Convert ethereum block to substrate block by fetching the block by number. + let substrate_block_number = + BlockNumber::Number(ethereum_block.header().number()); + let substrate_block_hash = self + .rpc_methods + .chain_get_block_hash(Some(substrate_block_number)) + .await? + .map(BlockRef::from_hash) + .ok_or_else(|| anyhow::anyhow!("no block hash found"))?; + + // Verify if the ethereum block belongs to this substrate block. + let query_current_eth_block = + humanode_metadata::storage().ethereum().current_block(); + + // Fetch ethereum block from `ethereum.current_block` state. + let Some(actual_eth_block) = self + .ws_client + .storage() + .at(substrate_block_hash.clone()) + .fetch(&query_current_eth_block) + .await? + else { + // This error should not happen, once all humanode blocks must have one + // ethereum block + anyhow::bail!("[report this bug!] no ethereum block found for humanode at block {substrate_block_hash:?}"); + }; + + // Verify if the ethereum block hash matches the provided ethereum block hash. + // TODO: compute the block hash + if U256(actual_eth_block.header.number.0) != + U256::from(ethereum_block.header().number()) + { + anyhow::bail!("ethereum block hash mismatch"); + } + if actual_eth_block.header.parent_hash.as_fixed_bytes() != + ðereum_block.header().header().parent_hash.0 + { + anyhow::bail!("ethereum block hash mismatch"); + } + substrate_block_hash + } else { + self.rpc_methods + .chain_get_block_hash(Some(BlockNumber::Hex(U256::from_big_endian( + block_hash, + )))) + .await? + .map(BlockRef::from_hash) + .ok_or_else(|| anyhow::anyhow!("no block hash found"))? + } + }, + Some(PartialBlockIdentifier { index: Some(block_number), .. }) => { + // If a block number is provided, the value is the same for ethereum blocks and + // substrate blocks. + self.rpc_methods + .chain_get_block_hash(Some(BlockNumber::Number(*block_number))) + .await? + .map(BlockRef::from_hash) + .ok_or_else(|| anyhow::anyhow!("no block hash found"))? + }, + Some(PartialBlockIdentifier { .. }) | None => self + .rpc_methods + .chain_get_block_hash(None) + .await? + .map(BlockRef::from_hash) + .ok_or_else(|| anyhow::anyhow!("no block hash found"))?, + }; + + let account_info = self.ws_client.storage().at(block_hash).fetch(&storage_query).await?; + account_info.map_or_else( + || { + Ok(AccountInfo::> { + nonce: 0, + consumers: 0, + providers: 0, + sufficients: 0, + data: AccountData { free: 0, reserved: 0, misc_frozen: 0, fee_frozen: 0 }, + }) + }, + |account_info| { + >>::decode(&mut account_info.encoded()) + .map_err(|_| anyhow::anyhow!("invalid format")) + }, + ) + } +} + +#[async_trait::async_trait] +impl BlockchainClient for HumanodeClient { + type MetadataParams = HumanodeMetadataParams; + type Metadata = HumanodeMetadata; + type EventStream<'a> = ::EventStream<'a>; + type Call = EthQuery; + type CallResult = EthQueryResult; + + type AtBlock = PartialBlockIdentifier; + type BlockIdentifier = BlockIdentifier; + + type Query = rosetta_config_ethereum::Query; + type Transaction = rosetta_config_ethereum::SignedTransaction; + type Subscription = ::Subscription; + type Event = ::Event; + type SubmitResult = ::SubmitResult; + + async fn query( + &self, + query: Self::Query, + ) -> Result<::Result> { + self.client.query(query).await + } + + fn config(&self) -> &BlockchainConfig { + self.client.config() + } + + fn genesis_block(&self) -> Self::BlockIdentifier { + self.client.genesis_block() + } + + async fn current_block(&self) -> Result { + self.client.current_block().await + } + + async fn finalized_block(&self) -> Result { + self.client.finalized_block().await + } + + async fn balance(&self, address: &Address, block: &Self::AtBlock) -> Result { + let balance = match address.format() { + AddressFormat::Ss58(_) => { + let account_info = self.account_info(address, Some(block)).await?; + account_info.data.free + }, + AddressFormat::Eip55 => { + // Frontier `eth_getBalance` returns the reducible_balance instead the free balance: + // https://github.com/paritytech/frontier/blob/polkadot-v0.9.43/frame/evm/src/lib.rs#L853-L855 + // using substrate to get the free balance + let address = address + .evm_to_ss58(Ss58AddressFormat::custom(42)) + .map_err(|err| anyhow::anyhow!("{}", err))?; + let account_info = self.account_info(&address, Some(block)).await?; + account_info.data.free + }, + AddressFormat::Bech32(_) => return Err(anyhow::anyhow!("invalid address format")), + }; + Ok(balance) + } + + async fn faucet( + &self, + address: &Address, + value: u128, + _high_gas_price: Option, + ) -> Result> { + // convert address + let dest = { + let address: H160 = address.address().parse()?; + let mut data = [0u8; 24]; + data[0..4].copy_from_slice(b"evm:"); + data[4..24].copy_from_slice(&address[..]); + let hash = sp_core::hashing::blake2_256(&data); + AccountId32::from(Into::<[u8; 32]>::into(hash)) + }; + + // Build the transfer transaction + let balance_transfer_tx = humanode_metadata::tx().balances().transfer(dest.into(), value); + let alice = sp_keyring::AccountKeyring::Alice.pair(); + let signer = PairSigner::::new(alice); + + let hash = self + .ws_client + .tx() + .sign_and_submit_then_watch_default(&balance_transfer_tx, &signer) + .await? + .wait_for_finalized_success() + .await? + .extrinsic_hash(); + Ok(hash.0.to_vec()) + } + + async fn metadata( + &self, + public_key: &PublicKey, + options: &Self::MetadataParams, + ) -> Result { + Ok(HumanodeMetadata(self.client.metadata(public_key, &options.0).await?)) + } + + async fn submit(&self, transaction: &[u8]) -> Result { + self.client.submit(transaction).await + } + + async fn call(&self, req: &EthQuery) -> Result { + self.client.call(req).await + } + + async fn listen<'a>(&'a self) -> Result>> { + self.client.listen().await + } + async fn subscribe(&self, _sub: &Self::Subscription) -> Result { + anyhow::bail!("not implemented"); + } +} + +#[allow(clippy::ignored_unit_patterns, clippy::pub_underscore_fields)] +#[cfg(test)] +mod tests { + use super::*; + use alloy_sol_types::{sol, SolCall}; + use ethers_solc::{artifacts::Source, CompilerInput, EvmVersion, Solc}; + use hex_literal::hex; + use rosetta_chain_testing::run_test; + use rosetta_client::Wallet; + use rosetta_config_ethereum::{AtBlock, CallResult}; + use sha3::Digest; + use std::{collections::BTreeMap, path::Path}; + + /// Humanode rpc url + const HUMANODE_RPC_WS_URL: &str = "ws://127.0.0.1:9944"; + + sol! { + interface TestContract { + event AnEvent(); + function emitEvent() external; + + function identity(bool a) external view returns (bool); + } + } + + #[tokio::test] + async fn test_network_status() { + // let config = rosetta_config_humanode::config("dev")?; + // rosetta_docker::tests::network_status::(client_from_config, + // config).await; + + run_test(async move { + let client = HumanodeClient::new("dev", HUMANODE_RPC_WS_URL) + .await + .expect("Error creating client"); + // Check if the genesis is consistent + let genesis_block = client.genesis_block(); + assert_eq!(genesis_block.index, 0); + + // Check if the current block is consistent + let current_block = client.current_block().await.unwrap(); + if current_block.index > 0 { + assert_ne!(current_block.hash, genesis_block.hash); + } else { + assert_eq!(current_block.hash, genesis_block.hash); + } + + // Check if the finalized block is consistent + let finalized_block = client.finalized_block().await.unwrap(); + assert!(finalized_block.index >= genesis_block.index); + }) + .await; + } + + #[tokio::test] + async fn test_account() { + run_test(async move { + let client = HumanodeClient::new("dev", HUMANODE_RPC_WS_URL) + .await + .expect("Error creating BinanceClient"); + let wallet = + Wallet::from_config(client.config().clone(), HUMANODE_RPC_WS_URL, None, None) + .await + .unwrap(); + let value = 10 * u128::pow(10, client.config().currency_decimals); + let _ = wallet.faucet(value, None).await; + let amount = wallet.balance().await.unwrap(); + assert_eq!(amount, value); + }) + .await; + } + + #[tokio::test] + async fn test_construction() { + run_test(async move { + let client = HumanodeClient::new("dev", HUMANODE_RPC_WS_URL) + .await + .expect("Error creating BinanceClient"); + let faucet = 100 * u128::pow(10, client.config().currency_decimals); + let value = u128::pow(10, client.config().currency_decimals); + let alice = + Wallet::from_config(client.config().clone(), HUMANODE_RPC_WS_URL, None, None) + .await + .unwrap(); + let bob = Wallet::from_config(client.config().clone(), HUMANODE_RPC_WS_URL, None, None) + .await + .unwrap(); + assert_ne!(alice.public_key(), bob.public_key()); + + // Alice and bob have no balance + let balance = alice.balance().await.unwrap(); + assert_eq!(balance, 0); + let balance = bob.balance().await.unwrap(); + assert_eq!(balance, 0); + + // Transfer faucets to alice + alice.faucet(faucet, None).await.unwrap(); + let balance = alice.balance().await.unwrap(); + assert_eq!(balance, faucet); + + // Alice transfers to bob + alice.transfer(bob.account(), value, None, None).await.unwrap(); + let amount = bob.balance().await.unwrap(); + assert_eq!(amount, value); + }) + .await; + } + + fn compile_snippet(source: &str) -> Result> { + let solc = Solc::default(); + let source = format!("contract Contract {{ {source} }}"); + let mut sources = BTreeMap::new(); + sources.insert(Path::new("contract.sol").into(), Source::new(source)); + let input = CompilerInput::with_sources(sources)[0] + .clone() + .evm_version(EvmVersion::Homestead); + let output = solc.compile_exact(&input)?; + let file = output.contracts.get("contract.sol").unwrap(); + let contract = file.get("Contract").unwrap(); + let bytecode = contract + .evm + .as_ref() + .unwrap() + .bytecode + .as_ref() + .unwrap() + .object + .as_bytes() + .unwrap() + .to_vec(); + Ok(bytecode) + } + + #[tokio::test] + async fn test_smart_contract() { + run_test(async move { + let client = HumanodeClient::new("dev", HUMANODE_RPC_WS_URL) + .await + .expect("Error creating BinanceClient"); + let faucet = 10 * u128::pow(10, client.config().currency_decimals); + let wallet = + Wallet::from_config(client.config().clone(), HUMANODE_RPC_WS_URL, None, None) + .await + .unwrap(); + wallet.faucet(faucet, None).await.unwrap(); + + let bytes = compile_snippet( + r" + event AnEvent(); + function emitEvent() public { + emit AnEvent(); + } + ", + ) + .unwrap(); + let tx_hash = wallet.eth_deploy_contract(bytes).await.unwrap().tx_hash().0; + let receipt = wallet.eth_transaction_receipt(tx_hash).await.unwrap().unwrap(); + let contract_address = receipt.contract_address.unwrap(); + let tx_hash = { + let call = TestContract::emitEventCall {}; + wallet + .eth_send_call(contract_address.0, call.abi_encode(), 0, None, None) + .await + .unwrap() + .tx_hash() + .0 + }; + let receipt = wallet.eth_transaction_receipt(tx_hash).await.unwrap().unwrap(); + assert_eq!(receipt.logs.len(), 1); + let topic = receipt.logs[0].topics[0]; + let expected = H256(sha3::Keccak256::digest("AnEvent()").into()); + assert_eq!(topic, expected); + }) + .await; + } + + #[tokio::test] + async fn test_smart_contract_view() { + run_test(async move { + let client = HumanodeClient::new("dev", HUMANODE_RPC_WS_URL) + .await + .expect("Error creating BinanceClient"); + let faucet = 10 * u128::pow(10, client.config().currency_decimals); + let wallet = + Wallet::from_config(client.config().clone(), HUMANODE_RPC_WS_URL, None, None) + .await + .unwrap(); + wallet.faucet(faucet, None).await.unwrap(); + let bytes = compile_snippet( + r" + function identity(bool a) public view returns (bool) { + return a; + } + ", + ) + .unwrap(); + let tx_hash = wallet.eth_deploy_contract(bytes).await.unwrap().tx_hash().0; + let receipt = wallet.eth_transaction_receipt(tx_hash).await.unwrap().unwrap(); + let contract_address = receipt.contract_address.unwrap(); + + let response = { + let call = TestContract::identityCall { a: true }; + wallet + .eth_view_call(contract_address.0, call.abi_encode(), AtBlock::Latest) + .await + .unwrap() + }; + assert_eq!( + response, + CallResult::Success( + hex!("0000000000000000000000000000000000000000000000000000000000000001") + .to_vec() + ) + ); + }) + .await; + } +} diff --git a/rosetta-client/Cargo.toml b/rosetta-client/Cargo.toml index dcdbfc2b..0de7a260 100644 --- a/rosetta-client/Cargo.toml +++ b/rosetta-client/Cargo.toml @@ -21,6 +21,7 @@ num-traits = "0.2" rosetta-core.workspace = true rosetta-server-astar.workspace = true rosetta-server-ethereum.workspace = true +rosetta-server-humanode.workspace = true rosetta-server-polkadot.workspace = true rosetta-tx-ethereum.workspace = true rosetta-tx-polkadot.workspace = true diff --git a/rosetta-client/src/client.rs b/rosetta-client/src/client.rs index 94adb73c..75e0d586 100644 --- a/rosetta-client/src/client.rs +++ b/rosetta-client/src/client.rs @@ -20,6 +20,7 @@ use rosetta_server_ethereum::{ EthereumMetadata, EthereumMetadataParams, MaybeWsEthereumClient as EthereumClient, SubmitResult, }; +use rosetta_server_humanode::{HumanodeClient, HumanodeMetadata, HumanodeMetadataParams}; use rosetta_server_polkadot::{PolkadotClient, PolkadotMetadata, PolkadotMetadataParams}; use serde::{Deserialize, Serialize}; use serde_json::Value; @@ -31,6 +32,7 @@ pub enum GenericClient { Ethereum(EthereumClient), Astar(AstarClient), Polkadot(PolkadotClient), + Humanode(HumanodeClient), } #[allow(clippy::missing_errors_doc)] @@ -66,6 +68,10 @@ impl GenericClient { let client = AstarClient::new(network, url).await?; Self::Astar(client) }, + Blockchain::Humanode => { + let client = HumanodeClient::new(network, url).await?; + Self::Humanode(client) + }, Blockchain::Polkadot | Blockchain::Rococo | Blockchain::Westend => { let client = PolkadotClient::new(network, url).await?; Self::Polkadot(client) @@ -95,6 +101,10 @@ impl GenericClient { let client = AstarClient::from_config(config, url).await?; Self::Astar(client) }, + Blockchain::Humanode => { + let client = HumanodeClient::from_config(config, url).await?; + Self::Humanode(client) + }, Blockchain::Polkadot | Blockchain::Rococo | Blockchain::Westend => { let client = PolkadotClient::from_config(config, url).await?; Self::Polkadot(client) @@ -111,6 +121,7 @@ impl GenericClient { pub enum GenericMetadataParams { Ethereum(EthereumMetadataParams), Astar(AstarMetadataParams), + Humanode(HumanodeMetadataParams), Polkadot(PolkadotMetadataParams), } @@ -119,6 +130,7 @@ pub enum GenericMetadataParams { pub enum GenericMetadata { Ethereum(EthereumMetadata), Astar(AstarMetadata), + Humanode(HumanodeMetadata), Polkadot(PolkadotMetadata), } @@ -144,6 +156,7 @@ macro_rules! dispatch { match $self { Self::Ethereum(client) => client$($method)*, Self::Astar(client) => client$($method)*, + Self::Humanode(client) => client$($method)*, Self::Polkadot(client) => client$($method)*, } }; @@ -182,6 +195,7 @@ impl BlockchainClient for GenericClient { match self { Self::Ethereum(client) => client.genesis_block(), Self::Astar(client) => client.genesis_block(), + Self::Humanode(client) => client.genesis_block(), Self::Polkadot(client) => client.genesis_block(), } } @@ -191,6 +205,7 @@ impl BlockchainClient for GenericClient { match self { Self::Ethereum(client) => client.current_block().await, Self::Astar(client) => client.current_block().await, + Self::Humanode(client) => client.current_block().await, Self::Polkadot(client) => client.current_block().await, } } @@ -200,6 +215,7 @@ impl BlockchainClient for GenericClient { match self { Self::Ethereum(client) => client.finalized_block().await, Self::Astar(client) => client.finalized_block().await, + Self::Humanode(client) => client.finalized_block().await, Self::Polkadot(client) => client.finalized_block().await, } } @@ -208,6 +224,7 @@ impl BlockchainClient for GenericClient { match self { Self::Ethereum(client) => client.balance(address, block).await, Self::Astar(client) => client.balance(address, block).await, + Self::Humanode(client) => client.balance(address, block).await, Self::Polkadot(client) => client.balance(address, block).await, } } @@ -233,6 +250,9 @@ impl BlockchainClient for GenericClient { (Self::Astar(client), GenericMetadataParams::Astar(params)) => { client.metadata(public_key, params).await?.into() }, + (Self::Humanode(client), GenericMetadataParams::Humanode(params)) => { + client.metadata(public_key, params).await?.into() + }, (Self::Polkadot(client), GenericMetadataParams::Polkadot(params)) => { client.metadata(public_key, params).await?.into() }, @@ -244,6 +264,7 @@ impl BlockchainClient for GenericClient { match self { Self::Ethereum(client) => client.submit(transaction).await, Self::Astar(client) => client.submit(transaction).await, + Self::Humanode(client) => client.submit(transaction).await, Self::Polkadot(client) => { // TODO: implement a custom receipt for Polkadot let result = client.submit(transaction).await?; @@ -274,6 +295,12 @@ impl BlockchainClient for GenericClient { }, GenericCall::Polkadot(_) => anyhow::bail!("invalid call"), }, + Self::Humanode(client) => match req { + GenericCall::Ethereum(args) => { + GenericCallResult::Ethereum(client.call(args).await?) + }, + GenericCall::Polkadot(_) => anyhow::bail!("invalid call"), + }, Self::Polkadot(client) => match req { GenericCall::Polkadot(args) => { GenericCallResult::Polkadot(client.call(args).await?) @@ -299,6 +326,12 @@ impl BlockchainClient for GenericClient { }; Ok(Some(GenericClientStream::Astar(stream))) }, + Self::Humanode(client) => { + let Some(stream) = client.listen().await? else { + return Ok(None); + }; + Ok(Some(GenericClientStream::Humanode(stream))) + }, Self::Polkadot(client) => { let Some(stream) = client.listen().await? else { return Ok(None); @@ -318,6 +351,10 @@ impl BlockchainClient for GenericClient { GenericClientSubscription::Astar(sub) => client.subscribe(sub).await, _ => anyhow::bail!("invalid subscription"), }, + Self::Humanode(client) => match sub { + GenericClientSubscription::Humanode(sub) => client.subscribe(sub).await, + _ => anyhow::bail!("invalid subscription"), + }, Self::Polkadot(client) => match sub { GenericClientSubscription::Polkadot(sub) => client.subscribe(sub).await, _ => anyhow::bail!("invalid subscription"), @@ -330,6 +367,7 @@ impl BlockchainClient for GenericClient { pub enum GenericClientSubscription { Ethereum(::Subscription), Astar(::Subscription), + Humanode(::Subscription), Polkadot(::Subscription), } @@ -337,12 +375,14 @@ pub enum GenericClientSubscription { pub enum GenericClientEvent { Ethereum(::Event), Astar(::Event), + Humanode(::Event), Polkadot(::Event), } pub enum GenericClientStream<'a> { Ethereum(::EventStream<'a>), Astar(::EventStream<'a>), + Humanode(::EventStream<'a>), Polkadot(::EventStream<'a>), } @@ -361,6 +401,9 @@ impl<'a> Stream for GenericClientStream<'a> { Self::Astar(stream) => stream .poll_next_unpin(cx) .map(|opt| opt.map(|event| event.map_event(GenericClientEvent::Astar))), + Self::Humanode(stream) => stream + .poll_next_unpin(cx) + .map(|opt| opt.map(|event| event.map_event(GenericClientEvent::Humanode))), Self::Polkadot(stream) => stream .poll_next_unpin(cx) .map(|opt| opt.map(|event| event.map_event(GenericClientEvent::Polkadot))), diff --git a/rosetta-client/src/lib.rs b/rosetta-client/src/lib.rs index 9a44d678..5893f1bd 100644 --- a/rosetta-client/src/lib.rs +++ b/rosetta-client/src/lib.rs @@ -33,6 +33,8 @@ pub enum Blockchain { Ethereum, /// Astar Astar, + /// Humanode + Humanode, /// Polkadot Polkadot, /// Kusama @@ -60,6 +62,7 @@ impl std::str::FromStr for Blockchain { Ok(match blockchain { "ethereum" => Self::Ethereum, "astar" => Self::Astar, + "humanode" => Self::Humanode, "polkadot" => Self::Polkadot, "kusama" => Self::Kusama, "rococo" => Self::Rococo, diff --git a/rosetta-client/src/tx_builder.rs b/rosetta-client/src/tx_builder.rs index bc9733ad..5fa3b616 100644 --- a/rosetta-client/src/tx_builder.rs +++ b/rosetta-client/src/tx_builder.rs @@ -6,10 +6,12 @@ use crate::{ use anyhow::Result; use rosetta_core::TransactionBuilder; use rosetta_server_astar::AstarMetadataParams; +use rosetta_server_humanode::HumanodeMetadataParams; pub enum GenericTransactionBuilder { Astar(rosetta_tx_ethereum::EthereumTransactionBuilder), Ethereum(rosetta_tx_ethereum::EthereumTransactionBuilder), + Humanode(rosetta_tx_ethereum::EthereumTransactionBuilder), Polkadot(rosetta_tx_polkadot::PolkadotTransactionBuilder), } @@ -20,6 +22,7 @@ impl GenericTransactionBuilder { "ethereum" | "polygon" | "arbitrum" | "binance" | "avalanche" => { Self::Ethereum(rosetta_tx_ethereum::EthereumTransactionBuilder) }, + "humanode" => Self::Humanode(rosetta_tx_ethereum::EthereumTransactionBuilder), "polkadot" | "westend" | "rococo" => { Self::Polkadot(rosetta_tx_polkadot::PolkadotTransactionBuilder) }, @@ -31,6 +34,7 @@ impl GenericTransactionBuilder { Ok(match self { Self::Astar(tx) => AstarMetadataParams(tx.transfer(address, amount)?).into(), Self::Ethereum(tx) => tx.transfer(address, amount)?.into(), + Self::Humanode(tx) => HumanodeMetadataParams(tx.transfer(address, amount)?).into(), Self::Polkadot(tx) => tx.transfer(address, amount)?.into(), }) } @@ -44,6 +48,9 @@ impl GenericTransactionBuilder { Ok(match self { Self::Astar(tx) => AstarMetadataParams(tx.method_call(contract, data, amount)?).into(), Self::Ethereum(tx) => tx.method_call(contract, data, amount)?.into(), + Self::Humanode(tx) => { + HumanodeMetadataParams(tx.method_call(contract, data, amount)?).into() + }, Self::Polkadot(tx) => tx.method_call(contract, data, amount)?.into(), }) } @@ -52,6 +59,9 @@ impl GenericTransactionBuilder { Ok(match self { Self::Astar(tx) => AstarMetadataParams(tx.deploy_contract(contract_binary)?).into(), Self::Ethereum(tx) => tx.deploy_contract(contract_binary)?.into(), + Self::Humanode(tx) => { + HumanodeMetadataParams(tx.deploy_contract(contract_binary)?).into() + }, Self::Polkadot(tx) => tx.deploy_contract(contract_binary)?.into(), }) } @@ -74,6 +84,11 @@ impl GenericTransactionBuilder { GenericMetadataParams::Ethereum(params), GenericMetadata::Ethereum(metadata), ) => tx.create_and_sign(config, params, metadata, secret_key), + ( + Self::Humanode(tx), + GenericMetadataParams::Humanode(params), + GenericMetadata::Humanode(metadata), + ) => tx.create_and_sign(config, ¶ms.0, &metadata.0, secret_key), ( Self::Polkadot(tx), GenericMetadataParams::Polkadot(params), diff --git a/rosetta-client/src/wallet.rs b/rosetta-client/src/wallet.rs index 94f0604f..f1ac365d 100644 --- a/rosetta-client/src/wallet.rs +++ b/rosetta-client/src/wallet.rs @@ -106,6 +106,7 @@ impl Wallet { match &self.client { GenericClient::Astar(client) => client.finalized_block().await, GenericClient::Ethereum(client) => client.finalized_block().await, + GenericClient::Humanode(client) => client.finalized_block().await, GenericClient::Polkadot(client) => client.finalized_block().await, } } @@ -123,6 +124,9 @@ impl Wallet { GenericClient::Ethereum(client) => { client.balance(&address, &PartialBlockIdentifier::from(block)).await? }, + GenericClient::Humanode(client) => { + client.balance(&address, &PartialBlockIdentifier::from(block)).await? + }, GenericClient::Polkadot(client) => { client.balance(&address, &PartialBlockIdentifier::from(block)).await? }, @@ -242,6 +246,7 @@ impl Wallet { match self.metadata(&metadata_params).await? { GenericMetadata::Ethereum(metadata) => metadata, GenericMetadata::Astar(metadata) => metadata.0, + GenericMetadata::Humanode(metadata) => metadata.0, GenericMetadata::Polkadot(_) => anyhow::bail!("unsupported op"), }; Ok(u128::from(metadata.gas_limit)) @@ -266,6 +271,7 @@ impl Wallet { let result = match &self.client { GenericClient::Ethereum(client) => client.call(&EthQuery::CallContract(call)).await?, GenericClient::Astar(client) => client.call(&EthQuery::CallContract(call)).await?, + GenericClient::Humanode(client) => client.call(&EthQuery::CallContract(call)).await?, GenericClient::Polkadot(_) => anyhow::bail!("polkadot doesn't support eth_view_call"), }; let EthQueryResult::CallContract(exit_reason) = result else { @@ -286,6 +292,7 @@ impl Wallet { let result = match &self.client { GenericClient::Ethereum(client) => client.call(&query).await?, GenericClient::Astar(client) => client.call(&query).await?, + GenericClient::Humanode(client) => client.call(&query).await?, GenericClient::Polkadot(_) => anyhow::bail!("polkadot doesn't support eth_view_call"), }; let result = ::parse_result(result)?; @@ -311,6 +318,9 @@ impl Wallet { GenericClient::Astar(client) => { client.call(&EthQuery::GetStorageAt(get_storage)).await? }, + GenericClient::Humanode(client) => { + client.call(&EthQuery::GetStorageAt(get_storage)).await? + }, GenericClient::Polkadot(_) => anyhow::bail!("polkadot doesn't support eth_storage"), }; let EthQueryResult::GetStorageAt(value) = result else { @@ -337,6 +347,7 @@ impl Wallet { let result = match &self.client { GenericClient::Ethereum(client) => client.call(&EthQuery::GetProof(get_proof)).await?, GenericClient::Astar(client) => client.call(&EthQuery::GetProof(get_proof)).await?, + GenericClient::Humanode(client) => client.call(&EthQuery::GetProof(get_proof)).await?, GenericClient::Polkadot(_) => anyhow::bail!("polkadot doesn't support eth_storage"), }; let EthQueryResult::GetProof(proof) = result else { @@ -360,6 +371,9 @@ impl Wallet { GenericClient::Astar(client) => { client.call(&EthQuery::GetTransactionReceipt(get_tx_receipt)).await? }, + GenericClient::Humanode(client) => { + client.call(&EthQuery::GetTransactionReceipt(get_tx_receipt)).await? + }, GenericClient::Polkadot(_) => anyhow::bail!("polkadot doesn't support eth_storage"), }; let EthQueryResult::GetTransactionReceipt(maybe_receipt) = result else { @@ -377,6 +391,7 @@ impl Wallet { let result = match &self.client { GenericClient::Ethereum(client) => client.call(&EthQuery::ChainId).await?, GenericClient::Astar(client) => client.call(&EthQuery::ChainId).await?, + GenericClient::Humanode(client) => client.call(&EthQuery::ChainId).await?, GenericClient::Polkadot(_) => anyhow::bail!("polkadot doesn't support eth_chainId"), }; let EthQueryResult::ChainId(value) = result else { @@ -401,6 +416,10 @@ fn update_metadata_params( params.0.nonce = nonce; params.0.gas_limit = gas_limit; }, + GenericMetadataParams::Humanode(params) => { + params.0.nonce = nonce; + params.0.gas_limit = gas_limit; + }, GenericMetadataParams::Polkadot(params) => { if let Some(nonce) = nonce { if let Ok(nonce) = u32::try_from(nonce) {