diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs index 0ed24db97df8..bbd686b5cf50 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs @@ -124,7 +124,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("westmint"), impl_name: create_runtime_str!("westmint"), authoring_version: 1, - spec_version: 1_016_002, + spec_version: 1_016_004, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 16, @@ -968,6 +968,7 @@ impl pallet_revive::Config for Runtime { type Debug = (); type Xcm = pallet_xcm::Pallet; type ChainId = ConstU64<420_420_421>; + type NativeToEthRatio = ConstU32<1_000_000>; // 10^(18 - 12) Eth is 10^18, Native is 10^12. } impl TryFrom for pallet_revive::Call { diff --git a/prdoc/pr_6317.prdoc b/prdoc/pr_6317.prdoc new file mode 100644 index 000000000000..4034ab3f3012 --- /dev/null +++ b/prdoc/pr_6317.prdoc @@ -0,0 +1,12 @@ +title: eth-rpc fixes +doc: +- audience: Runtime Dev + description: | + Various fixes for the release of eth-rpc & ah-westend-runtime: + - Bump asset-hub westend spec version + - Fix the status of the Receipt to properly report failed transactions + - Fix value conversion between native and eth decimal representation + +crates: +- name: asset-hub-westend-runtime + bump: patch diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index 12e8dc3e5077..dd16967aa7d7 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -1426,6 +1426,7 @@ impl pallet_revive::Config for Runtime { type Debug = (); type Xcm = (); type ChainId = ConstU64<420_420_420>; + type NativeToEthRatio = ConstU32<1_000_000>; // 10^(18 - 12) Eth is 10^18, Native is 10^12. } impl pallet_sudo::Config for Runtime { diff --git a/substrate/client/service/src/lib.rs b/substrate/client/service/src/lib.rs index 54e847791cff..3df9020b0418 100644 --- a/substrate/client/service/src/lib.rs +++ b/substrate/client/service/src/lib.rs @@ -98,7 +98,9 @@ pub use sc_transaction_pool::TransactionPoolOptions; pub use sc_transaction_pool_api::{error::IntoPoolError, InPoolTransaction, TransactionPool}; #[doc(hidden)] pub use std::{ops::Deref, result::Result, sync::Arc}; -pub use task_manager::{SpawnTaskHandle, Task, TaskManager, TaskRegistry, DEFAULT_GROUP_NAME}; +pub use task_manager::{ + SpawnEssentialTaskHandle, SpawnTaskHandle, Task, TaskManager, TaskRegistry, DEFAULT_GROUP_NAME, +}; use tokio::runtime::Handle; const DEFAULT_PROTOCOL_ID: &str = "sup"; diff --git a/substrate/frame/revive/rpc/Cargo.toml b/substrate/frame/revive/rpc/Cargo.toml index e6d8c38c04f0..56db91f920fa 100644 --- a/substrate/frame/revive/rpc/Cargo.toml +++ b/substrate/frame/revive/rpc/Cargo.toml @@ -77,3 +77,4 @@ example = ["hex", "hex-literal", "rlp", "secp256k1", "subxt-signer"] hex-literal = { workspace = true } pallet-revive-fixtures = { workspace = true } substrate-cli-test-utils = { workspace = true } +subxt-signer = { workspace = true, features = ["unstable-eth"] } diff --git a/substrate/frame/revive/rpc/examples/rust/transfer.rs b/substrate/frame/revive/rpc/examples/rust/transfer.rs index 185ad808e787..b99d48a2f78e 100644 --- a/substrate/frame/revive/rpc/examples/rust/transfer.rs +++ b/substrate/frame/revive/rpc/examples/rust/transfer.rs @@ -26,14 +26,14 @@ async fn main() -> anyhow::Result<()> { let alith = Account::default(); let client = HttpClientBuilder::default().build("http://localhost:8545")?; - let baltathar = Account::from(subxt_signer::eth::dev::baltathar()); - let value = 1_000_000_000_000_000_000u128.into(); // 1 ETH + let ethan = Account::from(subxt_signer::eth::dev::ethan()); + let value = 1_000_000_000_000_000_000_000u128.into(); let print_balance = || async { let balance = client.get_balance(alith.address(), BlockTag::Latest.into()).await?; println!("Alith {:?} balance: {balance:?}", alith.address()); - let balance = client.get_balance(baltathar.address(), BlockTag::Latest.into()).await?; - println!("Baltathar {:?} balance: {balance:?}", baltathar.address()); + let balance = client.get_balance(ethan.address(), BlockTag::Latest.into()).await?; + println!("ethan {:?} balance: {balance:?}", ethan.address()); anyhow::Result::<()>::Ok(()) }; @@ -41,14 +41,15 @@ async fn main() -> anyhow::Result<()> { println!("\n\n=== Transferring ===\n\n"); let hash = - send_transaction(&alith, &client, value, Bytes::default(), Some(baltathar.address())) - .await?; + send_transaction(&alith, &client, value, Bytes::default(), Some(ethan.address())).await?; println!("Transaction hash: {hash:?}"); - let ReceiptInfo { block_number, gas_used, .. } = wait_for_receipt(&client, hash).await?; + let ReceiptInfo { block_number, gas_used, status, .. } = + wait_for_receipt(&client, hash).await?; println!("Receipt: "); println!("- Block number: {block_number}"); println!("- Gas used: {gas_used}"); + println!("- Success: {status:?}"); print_balance().await?; Ok(()) diff --git a/substrate/frame/revive/rpc/revive_chain.metadata b/substrate/frame/revive/rpc/revive_chain.metadata index eef6dd2a0aea..305d079f9bd8 100644 Binary files a/substrate/frame/revive/rpc/revive_chain.metadata and b/substrate/frame/revive/rpc/revive_chain.metadata differ diff --git a/substrate/frame/revive/rpc/src/cli.rs b/substrate/frame/revive/rpc/src/cli.rs index b95fa78bf3ee..fcb84e6b54b0 100644 --- a/substrate/frame/revive/rpc/src/cli.rs +++ b/substrate/frame/revive/rpc/src/cli.rs @@ -109,11 +109,11 @@ pub fn run(cmd: CliCommand) -> anyhow::Result<()> { let tokio_handle = tokio_runtime.handle(); let signals = tokio_runtime.block_on(async { Signals::capture() })?; let mut task_manager = TaskManager::new(tokio_handle.clone(), prometheus_registry)?; - let spawn_handle = task_manager.spawn_handle(); + let essential_spawn_handle = task_manager.spawn_essential_handle(); let gen_rpc_module = || { let signals = tokio_runtime.block_on(async { Signals::capture() })?; - let fut = Client::from_url(&node_rpc_url, &spawn_handle).fuse(); + let fut = Client::from_url(&node_rpc_url, &essential_spawn_handle).fuse(); pin_mut!(fut); match tokio_handle.block_on(signals.try_until_signal(fut)) { @@ -125,7 +125,7 @@ pub fn run(cmd: CliCommand) -> anyhow::Result<()> { // Prometheus metrics. if let Some(PrometheusConfig { port, registry }) = prometheus_config.clone() { - spawn_handle.spawn( + task_manager.spawn_handle().spawn( "prometheus-endpoint", None, prometheus_endpoint::init_prometheus(port, registry).map(drop), diff --git a/substrate/frame/revive/rpc/src/client.rs b/substrate/frame/revive/rpc/src/client.rs index 64f7f2a61617..ba93d0af62ac 100644 --- a/substrate/frame/revive/rpc/src/client.rs +++ b/substrate/frame/revive/rpc/src/client.rs @@ -35,7 +35,6 @@ use pallet_revive::{ }, EthContractResult, }; -use sc_service::SpawnTaskHandle; use sp_runtime::traits::{BlakeTwo256, Hash}; use sp_weights::Weight; use std::{ @@ -145,9 +144,6 @@ pub enum ClientError { /// The transaction fee could not be found #[error("TransactionFeePaid event not found")] TxFeeNotFound, - /// The token decimals property was not found - #[error("tokenDecimals not found in properties")] - TokenDecimalsNotFound, /// The cache is empty. #[error("Cache is empty")] CacheEmpty, @@ -165,7 +161,7 @@ impl From for ErrorObjectOwned { /// The number of recent blocks maintained by the cache. /// For each block in the cache, we also store the EVM transaction receipts. -pub const CACHE_SIZE: usize = 10; +pub const CACHE_SIZE: usize = 256; impl BlockCache { fn latest_block(&self) -> Option<&Arc> { @@ -228,7 +224,7 @@ impl ClientInner { let rpc = LegacyRpcMethods::::new(RpcClient::new(rpc_client.clone())); let (native_to_evm_ratio, chain_id, max_block_weight) = - tokio::try_join!(native_to_evm_ratio(&rpc), chain_id(&api), max_block_weight(&api))?; + tokio::try_join!(native_to_evm_ratio(&api), chain_id(&api), max_block_weight(&api))?; Ok(Self { api, rpc_client, rpc, cache, chain_id, max_block_weight, native_to_evm_ratio }) } @@ -238,6 +234,11 @@ impl ClientInner { value.saturating_mul(self.native_to_evm_ratio) } + /// Convert an evm balance to a native balance. + fn evm_to_native_decimals(&self, value: U256) -> U256 { + value / self.native_to_evm_ratio + } + /// Get the receipt infos from the extrinsics in a block. async fn receipt_infos( &self, @@ -274,7 +275,7 @@ impl ClientInner { .checked_div(gas_price.as_u128()) .unwrap_or_default(); - let success = events.find_first::().is_ok(); + let success = events.has::()?; let transaction_index = ext.index(); let transaction_hash = BlakeTwo256::hash(&Vec::from(ext.bytes()).encode()); let block_hash = block.hash(); @@ -319,16 +320,10 @@ async fn max_block_weight(api: &OnlineClient) -> Result) -> Result { - let props = rpc.system_properties().await?; - let eth_decimals = U256::from(18u32); - let native_decimals: U256 = props - .get("tokenDecimals") - .and_then(|v| v.as_number()?.as_u64()) - .ok_or(ClientError::TokenDecimalsNotFound)? - .into(); - - Ok(U256::from(10u32).pow(eth_decimals - native_decimals)) +async fn native_to_evm_ratio(api: &OnlineClient) -> Result { + let query = subxt_client::constants().revive().native_to_eth_ratio(); + let ratio = api.constants().at(&query)?; + Ok(U256::from(ratio)) } /// Extract the block timestamp. @@ -344,7 +339,10 @@ async fn extract_block_timestamp(block: &SubstrateBlock) -> Option { impl Client { /// Create a new client instance. /// The client will subscribe to new blocks and maintain a cache of [`CACHE_SIZE`] blocks. - pub async fn from_url(url: &str, spawn_handle: &SpawnTaskHandle) -> Result { + pub async fn from_url( + url: &str, + spawn_handle: &sc_service::SpawnEssentialTaskHandle, + ) -> Result { log::info!(target: LOG_TARGET, "Connecting to node at: {url} ..."); let inner: Arc = Arc::new(ClientInner::from_url(url).await?); log::info!(target: LOG_TARGET, "Connected to node at: {url}"); @@ -619,9 +617,10 @@ impl Client { block: BlockNumberOrTagOrHash, ) -> Result, ClientError> { let runtime_api = self.runtime_api(&block).await?; - let value = tx - .value - .unwrap_or_default() + + let value = self + .inner + .evm_to_native_decimals(tx.value.unwrap_or_default()) .try_into() .map_err(|_| ClientError::ConversionFailed)?; diff --git a/substrate/frame/revive/rpc/src/tests.rs b/substrate/frame/revive/rpc/src/tests.rs index 5d84e06e9e04..01fcb6ae3bd2 100644 --- a/substrate/frame/revive/rpc/src/tests.rs +++ b/substrate/frame/revive/rpc/src/tests.rs @@ -50,16 +50,14 @@ async fn ws_client_with_retry(url: &str) -> WsClient { async fn test_jsonrpsee_server() -> anyhow::Result<()> { // Start the node. let _ = thread::spawn(move || { - match start_node_inline(vec![ + if let Err(e) = start_node_inline(vec![ "--dev", "--rpc-port=45789", "--no-telemetry", "--no-prometheus", + "-lerror,evm=debug,sc_rpc_server=info,runtime::revive=debug", ]) { - Ok(_) => {}, - Err(e) => { - panic!("Node exited with error: {}", e); - }, + panic!("Node exited with error: {e:?}"); } }); @@ -69,17 +67,36 @@ async fn test_jsonrpsee_server() -> anyhow::Result<()> { "--rpc-port=45788", "--node-rpc-url=ws://localhost:45789", "--no-prometheus", + "-linfo,eth-rpc=debug", ]); - let _ = thread::spawn(move || match cli::run(args) { - Ok(_) => {}, - Err(e) => { - panic!("eth-rpc exited with error: {}", e); - }, + let _ = thread::spawn(move || { + if let Err(e) = cli::run(args) { + panic!("eth-rpc exited with error: {e:?}"); + } }); let client = ws_client_with_retry("ws://localhost:45788").await; let account = Account::default(); + // Balance transfer + let ethan = Account::from(subxt_signer::eth::dev::ethan()); + let ethan_balance = client.get_balance(ethan.address(), BlockTag::Latest.into()).await?; + assert_eq!(U256::zero(), ethan_balance); + + let value = 1_000_000_000_000_000_000_000u128.into(); + let hash = + send_transaction(&account, &client, value, Bytes::default(), Some(ethan.address())).await?; + + let receipt = wait_for_receipt(&client, hash).await?; + assert_eq!( + Some(ethan.address()), + receipt.to, + "Receipt should have the correct contract address." + ); + + let ethan_balance = client.get_balance(ethan.address(), BlockTag::Latest.into()).await?; + assert_eq!(value, ethan_balance, "ethan's balance should be the same as the value sent."); + // Deploy contract let data = b"hello world".to_vec(); let value = U256::from(5_000_000_000_000u128); @@ -96,11 +113,7 @@ async fn test_jsonrpsee_server() -> anyhow::Result<()> { ); let balance = client.get_balance(contract_address, BlockTag::Latest.into()).await?; - assert_eq!( - value * 1_000_000, - balance, - "Contract balance should be the same as the value sent." - ); + assert_eq!(value, balance, "Contract balance should be the same as the value sent."); // Call contract let hash = diff --git a/substrate/frame/revive/src/evm/runtime.rs b/substrate/frame/revive/src/evm/runtime.rs index 3acd67b32aab..9b360c7de712 100644 --- a/substrate/frame/revive/src/evm/runtime.rs +++ b/substrate/frame/revive/src/evm/runtime.rs @@ -313,10 +313,14 @@ pub trait EthExtra { return Err(InvalidTransaction::Call); } + let value = (value / U256::from(::NativeToEthRatio::get())) + .try_into() + .map_err(|_| InvalidTransaction::Call)?; + let call = if let Some(dest) = to { crate::Call::call:: { dest, - value: value.try_into().map_err(|_| InvalidTransaction::Call)?, + value, gas_limit, storage_deposit_limit, data: input.0, @@ -336,7 +340,7 @@ pub trait EthExtra { }; crate::Call::instantiate_with_code:: { - value: value.try_into().map_err(|_| InvalidTransaction::Call)?, + value, gas_limit, storage_deposit_limit, code: code.to_vec(), @@ -370,7 +374,7 @@ pub trait EthExtra { Default::default(), ) .into(); - log::debug!(target: LOG_TARGET, "try_into_checked_extrinsic: encoded_len: {encoded_len:?} actual_fee: {actual_fee:?} eth_fee: {eth_fee:?}"); + log::trace!(target: LOG_TARGET, "try_into_checked_extrinsic: encoded_len: {encoded_len:?} actual_fee: {actual_fee:?} eth_fee: {eth_fee:?}"); if eth_fee < actual_fee { log::debug!(target: LOG_TARGET, "fees {eth_fee:?} too low for the extrinsic {actual_fee:?}"); @@ -381,10 +385,10 @@ pub trait EthExtra { let max = actual_fee.max(eth_fee_no_tip); let diff = Percent::from_rational(max - min, min); if diff > Percent::from_percent(10) { - log::debug!(target: LOG_TARGET, "Difference between the extrinsic fees {actual_fee:?} and the Ethereum gas fees {eth_fee_no_tip:?} should be no more than 10% got {diff:?}"); + log::trace!(target: LOG_TARGET, "Difference between the extrinsic fees {actual_fee:?} and the Ethereum gas fees {eth_fee_no_tip:?} should be no more than 10% got {diff:?}"); return Err(InvalidTransaction::Call.into()) } else { - log::debug!(target: LOG_TARGET, "Difference between the extrinsic fees {actual_fee:?} and the Ethereum gas fees {eth_fee_no_tip:?}: {diff:?}"); + log::trace!(target: LOG_TARGET, "Difference between the extrinsic fees {actual_fee:?} and the Ethereum gas fees {eth_fee_no_tip:?}: {diff:?}"); } let tip = eth_fee.saturating_sub(eth_fee_no_tip); diff --git a/substrate/frame/revive/src/exec.rs b/substrate/frame/revive/src/exec.rs index 8629a21c4fda..943c377e504d 100644 --- a/substrate/frame/revive/src/exec.rs +++ b/substrate/frame/revive/src/exec.rs @@ -1268,8 +1268,10 @@ where fn transfer(from: &T::AccountId, to: &T::AccountId, value: BalanceOf) -> ExecResult { // this avoids events to be emitted for zero balance transfers if !value.is_zero() { - T::Currency::transfer(from, to, value, Preservation::Preserve) - .map_err(|_| Error::::TransferFailed)?; + T::Currency::transfer(from, to, value, Preservation::Preserve).map_err(|err| { + log::debug!(target: LOG_TARGET, "Transfer of {value:?} from {from:?} to {to:?} failed: {err:?}"); + Error::::TransferFailed + })?; } Ok(Default::default()) } diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index 51e9a8fa3f90..5038ae44afad 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -303,6 +303,10 @@ pub mod pallet { /// preventing replay attacks. #[pallet::constant] type ChainId: Get; + + /// The ratio between the decimal representation of the native token and the ETH token. + #[pallet::constant] + type NativeToEthRatio: Get; } /// Container for different types that implement [`DefaultConfig`]` of this pallet. @@ -374,7 +378,8 @@ pub mod pallet { type Xcm = (); type RuntimeMemory = ConstU32<{ 128 * 1024 * 1024 }>; type PVFMemory = ConstU32<{ 512 * 1024 * 1024 }>; - type ChainId = ConstU64<{ 0 }>; + type ChainId = ConstU64<0>; + type NativeToEthRatio = ConstU32<1_000_000>; } } @@ -1239,6 +1244,7 @@ where T::Nonce: Into, T::Hash: frame_support::traits::IsType, { + log::debug!(target: LOG_TARGET, "bare_eth_transact: dest: {dest:?} value: {value:?} gas_limit: {gas_limit:?} storage_deposit_limit: {storage_deposit_limit:?}"); // Get the nonce to encode in the tx. let nonce: T::Nonce = >::account_nonce(&origin); @@ -1264,7 +1270,7 @@ where // Get the encoded size of the transaction. let tx = TransactionLegacyUnsigned { - value: value.into(), + value: value.into().saturating_mul(T::NativeToEthRatio::get().into()), input: input.into(), nonce: nonce.into(), chain_id: Some(T::ChainId::get().into()), @@ -1300,7 +1306,7 @@ where ) .into(); - log::debug!(target: LOG_TARGET, "bare_eth_call: len: {encoded_len:?} fee: {fee:?}"); + log::trace!(target: LOG_TARGET, "bare_eth_call: len: {encoded_len:?} fee: {fee:?}"); EthContractResult { gas_required: result.gas_required, storage_deposit: result.storage_deposit.charge_or_zero(), @@ -1339,7 +1345,7 @@ where let tx = TransactionLegacyUnsigned { gas: max_gas_fee.into(), nonce: nonce.into(), - value: value.into(), + value: value.into().saturating_mul(T::NativeToEthRatio::get().into()), input: input.clone().into(), gas_price: GAS_PRICE.into(), chain_id: Some(T::ChainId::get().into()), @@ -1373,7 +1379,7 @@ where ) .into(); - log::debug!(target: LOG_TARGET, "Call dry run Result: dispatch_info: {dispatch_info:?} len: {encoded_len:?} fee: {fee:?}"); + log::trace!(target: LOG_TARGET, "bare_eth_call: len: {encoded_len:?} fee: {fee:?}"); EthContractResult { gas_required: result.gas_required, storage_deposit: result.storage_deposit.charge_or_zero(),