diff --git a/Cargo.lock b/Cargo.lock index 4ac352c34016..71139a398b21 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11098,6 +11098,7 @@ name = "node-bench" version = "0.9.0-dev" dependencies = [ "array-bytes", + "async-trait", "clap 4.5.13", "derive_more 0.99.17", "fs_extra", @@ -23257,6 +23258,7 @@ version = "0.34.0" dependencies = [ "array-bytes", "assert_matches", + "async-trait", "futures", "futures-util", "hex", diff --git a/prdoc/pr_6111.prdoc b/prdoc/pr_6111.prdoc new file mode 100644 index 000000000000..4ada3031c805 --- /dev/null +++ b/prdoc/pr_6111.prdoc @@ -0,0 +1,17 @@ +title: "[pallet-revive] Update delegate_call to accept address and weight" + +doc: + - audience: Runtime Dev + description: | + Enhance the `delegate_call` function to accept an `address` target parameter instead of a `code_hash`. + This allows direct identification of the target contract using the provided address. + Additionally, introduce parameters for specifying a customizable `ref_time` limit and `proof_size` limit, + thereby improving flexibility and control during contract interactions. + +crates: + - name: pallet-revive + bump: major + - name: pallet-revive-fixtures + bump: patch + - name: pallet-revive-uapi + bump: major diff --git a/prdoc/pr_6220.prdoc b/prdoc/pr_6220.prdoc new file mode 100644 index 000000000000..6a5ee4fa59be --- /dev/null +++ b/prdoc/pr_6220.prdoc @@ -0,0 +1,10 @@ +title: Fix metrics not shutting down if there are open connections + +doc: + - audience: Runtime Dev + description: | + Fix prometheus metrics not shutting down if there are open connections + +crates: +- name: substrate-prometheus-endpoint + bump: patch diff --git a/prdoc/pr_6502.prdoc b/prdoc/pr_6502.prdoc new file mode 100644 index 000000000000..3e2467ed5524 --- /dev/null +++ b/prdoc/pr_6502.prdoc @@ -0,0 +1,10 @@ +title: "sp-trie: correctly avoid panicking when decoding bad compact proofs" + +doc: + - audience: "Runtime Dev" + description: | + "Fixed the check introduced in [PR #6486](https://github.com/paritytech/polkadot-sdk/pull/6486). Now `sp-trie` correctly avoids panicking when decoding bad compact proofs." + +crates: +- name: sp-trie + bump: patch diff --git a/prdoc/pr_6528.prdoc b/prdoc/pr_6528.prdoc new file mode 100644 index 000000000000..477ad76c947f --- /dev/null +++ b/prdoc/pr_6528.prdoc @@ -0,0 +1,18 @@ +title: 'TransactionPool API uses async_trait' +doc: +- audience: Node Dev + description: |- + This PR refactors `TransactionPool` API to use `async_trait`, replacing the` Pin>` pattern. This should improve readability and maintainability. + + The change is not altering any functionality. +crates: +- name: sc-rpc-spec-v2 + bump: minor +- name: sc-service + bump: minor +- name: sc-transaction-pool-api + bump: major +- name: sc-transaction-pool + bump: major +- name: sc-rpc + bump: minor diff --git a/substrate/bin/node/bench/Cargo.toml b/substrate/bin/node/bench/Cargo.toml index 8c6556da682c..447f947107c1 100644 --- a/substrate/bin/node/bench/Cargo.toml +++ b/substrate/bin/node/bench/Cargo.toml @@ -15,6 +15,7 @@ workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +async-trait = { workspace = true } array-bytes = { workspace = true, default-features = true } clap = { features = ["derive"], workspace = true } log = { workspace = true, default-features = true } diff --git a/substrate/bin/node/bench/src/construct.rs b/substrate/bin/node/bench/src/construct.rs index bed6e3d914c2..22129c6a1d69 100644 --- a/substrate/bin/node/bench/src/construct.rs +++ b/substrate/bin/node/bench/src/construct.rs @@ -24,14 +24,14 @@ //! DO NOT depend on user input). Thus transaction generation should be //! based on randomized data. -use futures::Future; use std::{borrow::Cow, collections::HashMap, pin::Pin, sync::Arc}; +use async_trait::async_trait; use node_primitives::Block; use node_testing::bench::{BenchDb, BlockType, DatabaseType, KeyTypes}; use sc_transaction_pool_api::{ - ImportNotificationStream, PoolFuture, PoolStatus, ReadyTransactions, TransactionFor, - TransactionSource, TransactionStatusStreamFor, TxHash, + ImportNotificationStream, PoolStatus, ReadyTransactions, TransactionFor, TransactionSource, + TransactionStatusStreamFor, TxHash, }; use sp_consensus::{Environment, Proposer}; use sp_inherents::InherentDataProvider; @@ -224,54 +224,47 @@ impl ReadyTransactions for TransactionsIterator { fn report_invalid(&mut self, _tx: &Self::Item) {} } +#[async_trait] impl sc_transaction_pool_api::TransactionPool for Transactions { type Block = Block; type Hash = node_primitives::Hash; type InPoolTransaction = PoolTransaction; type Error = sc_transaction_pool_api::error::Error; - /// Returns a future that imports a bunch of unverified transactions to the pool. - fn submit_at( + /// Asynchronously imports a bunch of unverified transactions to the pool. + async fn submit_at( &self, _at: Self::Hash, _source: TransactionSource, _xts: Vec>, - ) -> PoolFuture>, Self::Error> { + ) -> Result>, Self::Error> { unimplemented!() } - /// Returns a future that imports one unverified transaction to the pool. - fn submit_one( + /// Asynchronously imports one unverified transaction to the pool. + async fn submit_one( &self, _at: Self::Hash, _source: TransactionSource, _xt: TransactionFor, - ) -> PoolFuture, Self::Error> { + ) -> Result, Self::Error> { unimplemented!() } - fn submit_and_watch( + async fn submit_and_watch( &self, _at: Self::Hash, _source: TransactionSource, _xt: TransactionFor, - ) -> PoolFuture>>, Self::Error> { + ) -> Result>>, Self::Error> { unimplemented!() } - fn ready_at( + async fn ready_at( &self, _at: Self::Hash, - ) -> Pin< - Box< - dyn Future< - Output = Box> + Send>, - > + Send, - >, - > { - let iter: Box> + Send> = - Box::new(TransactionsIterator(self.0.clone().into_iter())); - Box::pin(futures::future::ready(iter)) + ) -> Box> + Send> { + Box::new(TransactionsIterator(self.0.clone().into_iter())) } fn ready(&self) -> Box> + Send> { @@ -306,18 +299,11 @@ impl sc_transaction_pool_api::TransactionPool for Transactions { unimplemented!() } - fn ready_at_with_timeout( + async fn ready_at_with_timeout( &self, _at: Self::Hash, _timeout: std::time::Duration, - ) -> Pin< - Box< - dyn Future< - Output = Box> + Send>, - > + Send - + '_, - >, - > { + ) -> Box> + Send> { unimplemented!() } } diff --git a/substrate/client/rpc-spec-v2/Cargo.toml b/substrate/client/rpc-spec-v2/Cargo.toml index 58dd8b830beb..daa805912fb9 100644 --- a/substrate/client/rpc-spec-v2/Cargo.toml +++ b/substrate/client/rpc-spec-v2/Cargo.toml @@ -44,6 +44,7 @@ rand = { workspace = true, default-features = true } schnellru = { workspace = true } [dev-dependencies] +async-trait = { workspace = true } jsonrpsee = { workspace = true, features = ["server", "ws-client"] } serde_json = { workspace = true, default-features = true } tokio = { features = ["macros"], workspace = true, default-features = true } diff --git a/substrate/client/rpc-spec-v2/src/transaction/tests/middleware_pool.rs b/substrate/client/rpc-spec-v2/src/transaction/tests/middleware_pool.rs index adcc987f9c39..a543969a89b8 100644 --- a/substrate/client/rpc-spec-v2/src/transaction/tests/middleware_pool.rs +++ b/substrate/client/rpc-spec-v2/src/transaction/tests/middleware_pool.rs @@ -16,16 +16,16 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +use async_trait::async_trait; use codec::Encode; -use futures::Future; use sc_transaction_pool::BasicPool; use sc_transaction_pool_api::{ - ImportNotificationStream, PoolFuture, PoolStatus, ReadyTransactions, TransactionFor, - TransactionPool, TransactionSource, TransactionStatusStreamFor, TxHash, + ImportNotificationStream, PoolStatus, ReadyTransactions, TransactionFor, TransactionPool, + TransactionSource, TransactionStatusStreamFor, TxHash, }; use crate::hex_string; -use futures::{FutureExt, StreamExt}; +use futures::StreamExt; use sp_runtime::traits::Block as BlockT; use std::{collections::HashMap, pin::Pin, sync::Arc}; @@ -77,67 +77,64 @@ impl MiddlewarePool { } } +#[async_trait] impl TransactionPool for MiddlewarePool { type Block = as TransactionPool>::Block; type Hash = as TransactionPool>::Hash; type InPoolTransaction = as TransactionPool>::InPoolTransaction; type Error = as TransactionPool>::Error; - fn submit_at( + async fn submit_at( &self, at: ::Hash, source: TransactionSource, xts: Vec>, - ) -> PoolFuture, Self::Error>>, Self::Error> { - self.inner_pool.submit_at(at, source, xts) + ) -> Result, Self::Error>>, Self::Error> { + self.inner_pool.submit_at(at, source, xts).await } - fn submit_one( + async fn submit_one( &self, at: ::Hash, source: TransactionSource, xt: TransactionFor, - ) -> PoolFuture, Self::Error> { - self.inner_pool.submit_one(at, source, xt) + ) -> Result, Self::Error> { + self.inner_pool.submit_one(at, source, xt).await } - fn submit_and_watch( + async fn submit_and_watch( &self, at: ::Hash, source: TransactionSource, xt: TransactionFor, - ) -> PoolFuture>>, Self::Error> { - let pool = self.inner_pool.clone(); - let sender = self.sender.clone(); + ) -> Result>>, Self::Error> { let transaction = hex_string(&xt.encode()); + let sender = self.sender.clone(); - async move { - let watcher = match pool.submit_and_watch(at, source, xt).await { - Ok(watcher) => watcher, - Err(err) => { - let _ = sender.send(MiddlewarePoolEvent::PoolError { - transaction: transaction.clone(), - err: err.to_string(), - }); - return Err(err); - }, - }; - - let watcher = watcher.map(move |status| { - let sender = sender.clone(); - let transaction = transaction.clone(); - - let _ = sender.send(MiddlewarePoolEvent::TransactionStatus { - transaction, - status: status.clone(), + let watcher = match self.inner_pool.submit_and_watch(at, source, xt).await { + Ok(watcher) => watcher, + Err(err) => { + let _ = sender.send(MiddlewarePoolEvent::PoolError { + transaction: transaction.clone(), + err: err.to_string(), }); + return Err(err); + }, + }; + + let watcher = watcher.map(move |status| { + let sender = sender.clone(); + let transaction = transaction.clone(); - status + let _ = sender.send(MiddlewarePoolEvent::TransactionStatus { + transaction, + status: status.clone(), }); - Ok(watcher.boxed()) - } - .boxed() + status + }); + + Ok(watcher.boxed()) } fn remove_invalid(&self, hashes: &[TxHash]) -> Vec> { @@ -164,17 +161,11 @@ impl TransactionPool for MiddlewarePool { self.inner_pool.ready_transaction(hash) } - fn ready_at( + async fn ready_at( &self, at: ::Hash, - ) -> Pin< - Box< - dyn Future< - Output = Box> + Send>, - > + Send, - >, - > { - self.inner_pool.ready_at(at) + ) -> Box> + Send> { + self.inner_pool.ready_at(at).await } fn ready(&self) -> Box> + Send> { @@ -185,18 +176,11 @@ impl TransactionPool for MiddlewarePool { self.inner_pool.futures() } - fn ready_at_with_timeout( + async fn ready_at_with_timeout( &self, at: ::Hash, _timeout: std::time::Duration, - ) -> Pin< - Box< - dyn Future< - Output = Box> + Send>, - > + Send - + '_, - >, - > { - self.inner_pool.ready_at(at) + ) -> Box> + Send> { + self.inner_pool.ready_at(at).await } } diff --git a/substrate/client/rpc/src/author/mod.rs b/substrate/client/rpc/src/author/mod.rs index 731f4df2f6f3..6afc871e565a 100644 --- a/substrate/client/rpc/src/author/mod.rs +++ b/substrate/client/rpc/src/author/mod.rs @@ -29,7 +29,6 @@ use crate::{ }; use codec::{Decode, Encode}; -use futures::TryFutureExt; use jsonrpsee::{core::async_trait, types::ErrorObject, Extensions, PendingSubscriptionSink}; use sc_rpc_api::check_if_safe; use sc_transaction_pool_api::{ @@ -191,14 +190,16 @@ where }, }; - let submit = self.pool.submit_and_watch(best_block_hash, TX_SOURCE, dxt).map_err(|e| { - e.into_pool_error() - .map(error::Error::from) - .unwrap_or_else(|e| error::Error::Verification(Box::new(e))) - }); - + let pool = self.pool.clone(); let fut = async move { - let stream = match submit.await { + let submit = + pool.submit_and_watch(best_block_hash, TX_SOURCE, dxt).await.map_err(|e| { + e.into_pool_error() + .map(error::Error::from) + .unwrap_or_else(|e| error::Error::Verification(Box::new(e))) + }); + + let stream = match submit { Ok(stream) => stream, Err(err) => { let _ = pending.reject(ErrorObject::from(err)).await; diff --git a/substrate/client/service/src/lib.rs b/substrate/client/service/src/lib.rs index 5cfd80cef910..9c01d7288a81 100644 --- a/substrate/client/service/src/lib.rs +++ b/substrate/client/service/src/lib.rs @@ -528,13 +528,17 @@ where }; let start = std::time::Instant::now(); - let import_future = self.pool.submit_one( - self.client.info().best_hash, - sc_transaction_pool_api::TransactionSource::External, - uxt, - ); + let pool = self.pool.clone(); + let client = self.client.clone(); Box::pin(async move { - match import_future.await { + match pool + .submit_one( + client.info().best_hash, + sc_transaction_pool_api::TransactionSource::External, + uxt, + ) + .await + { Ok(_) => { let elapsed = start.elapsed(); debug!(target: sc_transaction_pool::LOG_TARGET, "import transaction: {elapsed:?}"); diff --git a/substrate/client/transaction-pool/api/src/lib.rs b/substrate/client/transaction-pool/api/src/lib.rs index 3ac1a79a0c28..6f771e9479bd 100644 --- a/substrate/client/transaction-pool/api/src/lib.rs +++ b/substrate/client/transaction-pool/api/src/lib.rs @@ -23,7 +23,7 @@ pub mod error; use async_trait::async_trait; use codec::Codec; -use futures::{Future, Stream}; +use futures::Stream; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use sp_core::offchain::TransactionPoolExt; use sp_runtime::traits::{Block as BlockT, Member}; @@ -208,9 +208,6 @@ pub type LocalTransactionFor

= <

::Block as BlockT> /// Transaction's index within the block in which it was included. pub type TxIndex = usize; -/// Typical future type used in transaction pool api. -pub type PoolFuture = std::pin::Pin> + Send>>; - /// In-pool transaction interface. /// /// The pool is container of transactions that are implementing this trait. @@ -238,6 +235,7 @@ pub trait InPoolTransaction { } /// Transaction pool interface. +#[async_trait] pub trait TransactionPool: Send + Sync { /// Block type. type Block: BlockT; @@ -253,46 +251,40 @@ pub trait TransactionPool: Send + Sync { // *** RPC - /// Returns a future that imports a bunch of unverified transactions to the pool. - fn submit_at( + /// Asynchronously imports a bunch of unverified transactions to the pool. + async fn submit_at( &self, at: ::Hash, source: TransactionSource, xts: Vec>, - ) -> PoolFuture, Self::Error>>, Self::Error>; + ) -> Result, Self::Error>>, Self::Error>; - /// Returns a future that imports one unverified transaction to the pool. - fn submit_one( + /// Asynchronously imports one unverified transaction to the pool. + async fn submit_one( &self, at: ::Hash, source: TransactionSource, xt: TransactionFor, - ) -> PoolFuture, Self::Error>; + ) -> Result, Self::Error>; - /// Returns a future that imports a single transaction and starts to watch their progress in the + /// Asynchronously imports a single transaction and starts to watch their progress in the /// pool. - fn submit_and_watch( + async fn submit_and_watch( &self, at: ::Hash, source: TransactionSource, xt: TransactionFor, - ) -> PoolFuture>>, Self::Error>; + ) -> Result>>, Self::Error>; // *** Block production / Networking /// Get an iterator for ready transactions ordered by priority. /// - /// Guarantees to return only when transaction pool got updated at `at` block. - /// Guarantees to return immediately when `None` is passed. - fn ready_at( + /// Guaranteed to resolve only when transaction pool got updated at `at` block. + /// Guaranteed to resolve immediately when `None` is passed. + async fn ready_at( &self, at: ::Hash, - ) -> Pin< - Box< - dyn Future< - Output = Box> + Send>, - > + Send, - >, - >; + ) -> Box> + Send>; /// Get an iterator for ready transactions ordered by priority. fn ready(&self) -> Box> + Send>; @@ -322,22 +314,15 @@ pub trait TransactionPool: Send + Sync { /// Return specific ready transaction by hash, if there is one. fn ready_transaction(&self, hash: &TxHash) -> Option>; - /// Returns set of ready transaction at given block within given timeout. + /// Asynchronously returns a set of ready transaction at given block within given timeout. /// - /// If the timeout is hit during method execution then the best effort set of ready transactions - /// for given block, without executing full maintain process is returned. - fn ready_at_with_timeout( + /// If the timeout is hit during method execution, then the best effort (without executing full + /// maintain process) set of ready transactions for given block is returned. + async fn ready_at_with_timeout( &self, at: ::Hash, timeout: std::time::Duration, - ) -> Pin< - Box< - dyn Future< - Output = Box> + Send>, - > + Send - + '_, - >, - >; + ) -> Box> + Send>; } /// An iterator of ready transactions. diff --git a/substrate/client/transaction-pool/src/fork_aware_txpool/fork_aware_txpool.rs b/substrate/client/transaction-pool/src/fork_aware_txpool/fork_aware_txpool.rs index a342d35b2844..065d0cb3a274 100644 --- a/substrate/client/transaction-pool/src/fork_aware_txpool/fork_aware_txpool.rs +++ b/substrate/client/transaction-pool/src/fork_aware_txpool/fork_aware_txpool.rs @@ -33,7 +33,7 @@ use crate::{ enactment_state::{EnactmentAction, EnactmentState}, fork_aware_txpool::revalidation_worker, graph::{self, base_pool::Transaction, ExtrinsicFor, ExtrinsicHash, IsValidator, Options}, - PolledIterator, ReadyIteratorFor, LOG_TARGET, + ReadyIteratorFor, LOG_TARGET, }; use async_trait::async_trait; use futures::{ @@ -45,8 +45,8 @@ use futures::{ use parking_lot::Mutex; use prometheus_endpoint::Registry as PrometheusRegistry; use sc_transaction_pool_api::{ - ChainEvent, ImportNotificationStream, MaintainedTransactionPool, PoolFuture, PoolStatus, - TransactionFor, TransactionPool, TransactionSource, TransactionStatusStreamFor, TxHash, + ChainEvent, ImportNotificationStream, MaintainedTransactionPool, PoolStatus, TransactionFor, + TransactionPool, TransactionSource, TransactionStatusStreamFor, TxHash, }; use sp_blockchain::{HashAndNumber, TreeRoute}; use sp_core::traits::SpawnEssentialNamed; @@ -375,14 +375,13 @@ where /// /// Pruning is just rebuilding the underlying transactions graph, no validations are executed, /// so this process shall be fast. - pub fn ready_at_light(&self, at: Block::Hash) -> PolledIterator { + pub async fn ready_at_light(&self, at: Block::Hash) -> ReadyIteratorFor { let start = Instant::now(); let api = self.api.clone(); log::trace!(target: LOG_TARGET, "fatp::ready_at_light {:?}", at); let Ok(block_number) = self.api.resolve_block_number(at) else { - let empty: ReadyIteratorFor = Box::new(std::iter::empty()); - return Box::pin(async { empty }) + return Box::new(std::iter::empty()) }; let best_result = { @@ -401,57 +400,53 @@ where ) }; - Box::pin(async move { - if let Ok((Some(best_tree_route), Some(best_view))) = best_result { - let tmp_view: View = View::new_from_other( - &best_view, - &HashAndNumber { hash: at, number: block_number }, - ); - - let mut all_extrinsics = vec![]; + if let Ok((Some(best_tree_route), Some(best_view))) = best_result { + let tmp_view: View = + View::new_from_other(&best_view, &HashAndNumber { hash: at, number: block_number }); - for h in best_tree_route.enacted() { - let extrinsics = api - .block_body(h.hash) - .await - .unwrap_or_else(|e| { - log::warn!(target: LOG_TARGET, "Compute ready light transactions: error request: {}", e); - None - }) - .unwrap_or_default() - .into_iter() - .map(|t| api.hash_and_length(&t).0); - all_extrinsics.extend(extrinsics); - } + let mut all_extrinsics = vec![]; - let before_count = tmp_view.pool.validated_pool().status().ready; - let tags = tmp_view - .pool - .validated_pool() - .extrinsics_tags(&all_extrinsics) + for h in best_tree_route.enacted() { + let extrinsics = api + .block_body(h.hash) + .await + .unwrap_or_else(|e| { + log::warn!(target: LOG_TARGET, "Compute ready light transactions: error request: {}", e); + None + }) + .unwrap_or_default() .into_iter() - .flatten() - .flatten() - .collect::>(); - let _ = tmp_view.pool.validated_pool().prune_tags(tags); - - let after_count = tmp_view.pool.validated_pool().status().ready; - log::debug!(target: LOG_TARGET, - "fatp::ready_at_light {} from {} before: {} to be removed: {} after: {} took:{:?}", - at, - best_view.at.hash, - before_count, - all_extrinsics.len(), - after_count, - start.elapsed() - ); - Box::new(tmp_view.pool.validated_pool().ready()) - } else { - let empty: ReadyIteratorFor = Box::new(std::iter::empty()); - log::debug!(target: LOG_TARGET, "fatp::ready_at_light {} -> empty, took:{:?}", at, start.elapsed()); - empty + .map(|t| api.hash_and_length(&t).0); + all_extrinsics.extend(extrinsics); } - }) + + let before_count = tmp_view.pool.validated_pool().status().ready; + let tags = tmp_view + .pool + .validated_pool() + .extrinsics_tags(&all_extrinsics) + .into_iter() + .flatten() + .flatten() + .collect::>(); + let _ = tmp_view.pool.validated_pool().prune_tags(tags); + + let after_count = tmp_view.pool.validated_pool().status().ready; + log::debug!(target: LOG_TARGET, + "fatp::ready_at_light {} from {} before: {} to be removed: {} after: {} took:{:?}", + at, + best_view.at.hash, + before_count, + all_extrinsics.len(), + after_count, + start.elapsed() + ); + Box::new(tmp_view.pool.validated_pool().ready()) + } else { + let empty: ReadyIteratorFor = Box::new(std::iter::empty()); + log::debug!(target: LOG_TARGET, "fatp::ready_at_light {} -> empty, took:{:?}", at, start.elapsed()); + empty + } } /// Waits for the set of ready transactions for a given block up to a specified timeout. @@ -464,18 +459,18 @@ where /// maintain. /// /// Returns a future resolving to a ready iterator of transactions. - fn ready_at_with_timeout_internal( + async fn ready_at_with_timeout_internal( &self, at: Block::Hash, timeout: std::time::Duration, - ) -> PolledIterator { + ) -> ReadyIteratorFor { log::debug!(target: LOG_TARGET, "fatp::ready_at_with_timeout at {:?} allowed delay: {:?}", at, timeout); let timeout = futures_timer::Delay::new(timeout); let (view_already_exists, ready_at) = self.ready_at_internal(at); if view_already_exists { - return ready_at; + return ready_at.await; } let maybe_ready = async move { @@ -493,18 +488,19 @@ where }; let fall_back_ready = self.ready_at_light(at); - Box::pin(async { - let (maybe_ready, fall_back_ready) = - futures::future::join(maybe_ready.boxed(), fall_back_ready.boxed()).await; - maybe_ready.unwrap_or(fall_back_ready) - }) + let (maybe_ready, fall_back_ready) = + futures::future::join(maybe_ready, fall_back_ready).await; + maybe_ready.unwrap_or(fall_back_ready) } - fn ready_at_internal(&self, at: Block::Hash) -> (bool, PolledIterator) { + fn ready_at_internal( + &self, + at: Block::Hash, + ) -> (bool, Pin> + Send>>) { let mut ready_poll = self.ready_poll.lock(); if let Some((view, inactive)) = self.view_store.get_view_at(at, true) { - log::debug!(target: LOG_TARGET, "fatp::ready_at {at:?} (inactive:{inactive:?})"); + log::debug!(target: LOG_TARGET, "fatp::ready_at_internal {at:?} (inactive:{inactive:?})"); let iterator: ReadyIteratorFor = Box::new(view.pool.validated_pool().ready()); return (true, async move { iterator }.boxed()); } @@ -519,7 +515,7 @@ where }) .boxed(); log::debug!(target: LOG_TARGET, - "fatp::ready_at {at:?} pending keys: {:?}", + "fatp::ready_at_internal {at:?} pending keys: {:?}", ready_poll.pollers.keys() ); (false, pending) @@ -573,6 +569,7 @@ fn reduce_multiview_result(input: HashMap>>) -> Vec TransactionPool for ForkAwareTxPool where Block: BlockT, @@ -590,12 +587,12 @@ where /// /// The internal limits of the pool are checked. The results of submissions to individual views /// are reduced to single result. Refer to `reduce_multiview_result` for more details. - fn submit_at( + async fn submit_at( &self, _: ::Hash, source: TransactionSource, xts: Vec>, - ) -> PoolFuture, Self::Error>>, Self::Error> { + ) -> Result, Self::Error>>, Self::Error> { let view_store = self.view_store.clone(); log::debug!(target: LOG_TARGET, "fatp::submit_at count:{} views:{}", xts.len(), self.active_views_count()); log_xt_trace!(target: LOG_TARGET, xts.iter().map(|xt| self.tx_hash(xt)), "[{:?}] fatp::submit_at"); @@ -603,7 +600,7 @@ where let mempool_results = self.mempool.extend_unwatched(source, &xts); if view_store.is_empty() { - return future::ready(Ok(mempool_results)).boxed() + return Ok(mempool_results) } let to_be_submitted = mempool_results @@ -616,11 +613,10 @@ where .report(|metrics| metrics.submitted_transactions.inc_by(to_be_submitted.len() as _)); let mempool = self.mempool.clone(); - async move { - let results_map = view_store.submit(source, to_be_submitted.into_iter()).await; - let mut submission_results = reduce_multiview_result(results_map).into_iter(); + let results_map = view_store.submit(source, to_be_submitted.into_iter()).await; + let mut submission_results = reduce_multiview_result(results_map).into_iter(); - Ok(mempool_results + Ok(mempool_results .into_iter() .map(|result| { result.and_then(|xt_hash| { @@ -633,60 +629,50 @@ where }) }) .collect::>()) - } - .boxed() } /// Submits a single transaction and returns a future resolving to the submission results. /// /// Actual transaction submission process is delegated to the `submit_at` function. - fn submit_one( + async fn submit_one( &self, _at: ::Hash, source: TransactionSource, xt: TransactionFor, - ) -> PoolFuture, Self::Error> { + ) -> Result, Self::Error> { log::trace!(target: LOG_TARGET, "[{:?}] fatp::submit_one views:{}", self.tx_hash(&xt), self.active_views_count()); - let result_future = self.submit_at(_at, source, vec![xt]); - async move { - let result = result_future.await; - match result { - Ok(mut v) => - v.pop().expect("There is exactly one element in result of submit_at. qed."), - Err(e) => Err(e), - } + match self.submit_at(_at, source, vec![xt]).await { + Ok(mut v) => + v.pop().expect("There is exactly one element in result of submit_at. qed."), + Err(e) => Err(e), } - .boxed() } /// Submits a transaction and starts to watch its progress in the pool, returning a stream of /// status updates. /// /// Actual transaction submission process is delegated to the `ViewStore` internal instance. - fn submit_and_watch( + async fn submit_and_watch( &self, at: ::Hash, source: TransactionSource, xt: TransactionFor, - ) -> PoolFuture>>, Self::Error> { + ) -> Result>>, Self::Error> { log::trace!(target: LOG_TARGET, "[{:?}] fatp::submit_and_watch views:{}", self.tx_hash(&xt), self.active_views_count()); let xt = Arc::from(xt); let xt_hash = match self.mempool.push_watched(source, xt.clone()) { Ok(xt_hash) => xt_hash, - Err(e) => return future::ready(Err(e)).boxed(), + Err(e) => return Err(e), }; self.metrics.report(|metrics| metrics.submitted_transactions.inc()); let view_store = self.view_store.clone(); let mempool = self.mempool.clone(); - async move { - view_store - .submit_and_watch(at, source, xt) - .await - .inspect_err(|_| mempool.remove(xt_hash)) - } - .boxed() + view_store + .submit_and_watch(at, source, xt) + .await + .inspect_err(|_| mempool.remove(xt_hash)) } /// Intended to remove transactions identified by the given hashes, and any dependent @@ -757,9 +743,9 @@ where } /// Returns an iterator for ready transactions at a specific block, ordered by priority. - fn ready_at(&self, at: ::Hash) -> PolledIterator { + async fn ready_at(&self, at: ::Hash) -> ReadyIteratorFor { let (_, result) = self.ready_at_internal(at); - result + result.await } /// Returns an iterator for ready transactions, ordered by priority. @@ -782,12 +768,12 @@ where /// /// If the timeout expires before the maintain process is accomplished, a best-effort /// set of transactions is returned (refer to `ready_at_light`). - fn ready_at_with_timeout( + async fn ready_at_with_timeout( &self, at: ::Hash, timeout: std::time::Duration, - ) -> PolledIterator { - self.ready_at_with_timeout_internal(at, timeout) + ) -> ReadyIteratorFor { + self.ready_at_with_timeout_internal(at, timeout).await } } diff --git a/substrate/client/transaction-pool/src/fork_aware_txpool/mod.rs b/substrate/client/transaction-pool/src/fork_aware_txpool/mod.rs index 9f979e216b6d..5f7294a24fd7 100644 --- a/substrate/client/transaction-pool/src/fork_aware_txpool/mod.rs +++ b/substrate/client/transaction-pool/src/fork_aware_txpool/mod.rs @@ -201,12 +201,12 @@ //! required to accomplish it. //! //! ### Providing ready transactions: `ready_at` -//! The [`ready_at`] function returns a [future][`crate::PolledIterator`] that resolves to the -//! [ready transactions iterator][`ReadyTransactions`]. The block builder shall wait either for the -//! future to be resolved or for timeout to be hit. To avoid building empty blocks in case of -//! timeout, the waiting for timeout functionality was moved into the transaction pool, and new API -//! function was added: [`ready_at_with_timeout`]. This function also provides a fall back ready -//! iterator which is result of [light maintain](#light-maintain). +//! The asynchronous [`ready_at`] function resolves to the [ready transactions +//! iterator][`ReadyTransactions`]. The block builder shall wait either for the future to be +//! resolved or for timeout to be hit. To avoid building empty blocks in case of timeout, the +//! waiting for timeout functionality was moved into the transaction pool, and new API function was +//! added: [`ready_at_with_timeout`]. This function also provides a fall back ready iterator which +//! is result of [light maintain](#light-maintain). //! //! New function internally waits either for [maintain](#maintain) process triggered for requested //! block to be accomplished or for the timeout. If timeout hits then the result of [light diff --git a/substrate/client/transaction-pool/src/lib.rs b/substrate/client/transaction-pool/src/lib.rs index 888d25d3a0d2..3d3d596c291f 100644 --- a/substrate/client/transaction-pool/src/lib.rs +++ b/substrate/client/transaction-pool/src/lib.rs @@ -30,7 +30,7 @@ mod single_state_txpool; mod transaction_pool_wrapper; use common::{api, enactment_state}; -use std::{future::Future, pin::Pin, sync::Arc}; +use std::sync::Arc; pub use api::FullChainApi; pub use builder::{Builder, TransactionPoolHandle, TransactionPoolOptions, TransactionPoolType}; @@ -50,8 +50,6 @@ type BoxedReadyIterator = Box< type ReadyIteratorFor = BoxedReadyIterator, graph::ExtrinsicFor>; -type PolledIterator = Pin> + Send>>; - /// Log target for transaction pool. /// /// It can be used by other components for logging functionality strictly related to txpool (e.g. diff --git a/substrate/client/transaction-pool/src/single_state_txpool/single_state_txpool.rs b/substrate/client/transaction-pool/src/single_state_txpool/single_state_txpool.rs index 0826b95cf070..b29630b563bb 100644 --- a/substrate/client/transaction-pool/src/single_state_txpool/single_state_txpool.rs +++ b/substrate/client/transaction-pool/src/single_state_txpool/single_state_txpool.rs @@ -29,9 +29,8 @@ use crate::{ error, log_xt::log_xt_trace, }, - graph, - graph::{ExtrinsicHash, IsValidator}, - PolledIterator, ReadyIteratorFor, LOG_TARGET, + graph::{self, ExtrinsicHash, IsValidator}, + ReadyIteratorFor, LOG_TARGET, }; use async_trait::async_trait; use futures::{channel::oneshot, future, prelude::*, Future, FutureExt}; @@ -39,8 +38,8 @@ use parking_lot::Mutex; use prometheus_endpoint::Registry as PrometheusRegistry; use sc_transaction_pool_api::{ error::Error as TxPoolError, ChainEvent, ImportNotificationStream, MaintainedTransactionPool, - PoolFuture, PoolStatus, TransactionFor, TransactionPool, TransactionSource, - TransactionStatusStreamFor, TxHash, + PoolStatus, TransactionFor, TransactionPool, TransactionSource, TransactionStatusStreamFor, + TxHash, }; use sp_blockchain::{HashAndNumber, TreeRoute}; use sp_core::traits::SpawnEssentialNamed; @@ -224,26 +223,19 @@ where &self.api } - fn ready_at_with_timeout_internal( + async fn ready_at_with_timeout_internal( &self, at: Block::Hash, timeout: std::time::Duration, - ) -> PolledIterator { - let timeout = futures_timer::Delay::new(timeout); - let ready_maintained = self.ready_at(at); - let ready_current = self.ready(); - - let ready = async { - select! { - ready = ready_maintained => ready, - _ = timeout => ready_current - } - }; - - Box::pin(ready) + ) -> ReadyIteratorFor { + select! { + ready = self.ready_at(at)=> ready, + _ = futures_timer::Delay::new(timeout)=> self.ready() + } } } +#[async_trait] impl TransactionPool for BasicPool where Block: BlockT, @@ -255,12 +247,12 @@ where graph::base_pool::Transaction, graph::ExtrinsicFor>; type Error = PoolApi::Error; - fn submit_at( + async fn submit_at( &self, at: ::Hash, source: TransactionSource, xts: Vec>, - ) -> PoolFuture, Self::Error>>, Self::Error> { + ) -> Result, Self::Error>>, Self::Error> { let pool = self.pool.clone(); let xts = xts.into_iter().map(Arc::from).collect::>(); @@ -268,38 +260,32 @@ where .report(|metrics| metrics.submitted_transactions.inc_by(xts.len() as u64)); let number = self.api.resolve_block_number(at); - async move { - let at = HashAndNumber { hash: at, number: number? }; - Ok(pool.submit_at(&at, source, xts).await) - } - .boxed() + let at = HashAndNumber { hash: at, number: number? }; + Ok(pool.submit_at(&at, source, xts).await) } - fn submit_one( + async fn submit_one( &self, at: ::Hash, source: TransactionSource, xt: TransactionFor, - ) -> PoolFuture, Self::Error> { + ) -> Result, Self::Error> { let pool = self.pool.clone(); let xt = Arc::from(xt); self.metrics.report(|metrics| metrics.submitted_transactions.inc()); let number = self.api.resolve_block_number(at); - async move { - let at = HashAndNumber { hash: at, number: number? }; - pool.submit_one(&at, source, xt).await - } - .boxed() + let at = HashAndNumber { hash: at, number: number? }; + pool.submit_one(&at, source, xt).await } - fn submit_and_watch( + async fn submit_and_watch( &self, at: ::Hash, source: TransactionSource, xt: TransactionFor, - ) -> PoolFuture>>, Self::Error> { + ) -> Result>>, Self::Error> { let pool = self.pool.clone(); let xt = Arc::from(xt); @@ -307,13 +293,10 @@ where let number = self.api.resolve_block_number(at); - async move { - let at = HashAndNumber { hash: at, number: number? }; - let watcher = pool.submit_and_watch(&at, source, xt).await?; + let at = HashAndNumber { hash: at, number: number? }; + let watcher = pool.submit_and_watch(&at, source, xt).await?; - Ok(watcher.into_stream().boxed()) - } - .boxed() + Ok(watcher.into_stream().boxed()) } fn remove_invalid(&self, hashes: &[TxHash]) -> Vec> { @@ -343,9 +326,9 @@ where self.pool.validated_pool().ready_by_hash(hash) } - fn ready_at(&self, at: ::Hash) -> PolledIterator { + async fn ready_at(&self, at: ::Hash) -> ReadyIteratorFor { let Ok(at) = self.api.resolve_block_number(at) else { - return async { Box::new(std::iter::empty()) as Box<_> }.boxed() + return Box::new(std::iter::empty()) as Box<_> }; let status = self.status(); @@ -354,25 +337,23 @@ where // There could be transaction being added because of some re-org happening at the relevant // block, but this is relative unlikely. if status.ready == 0 && status.future == 0 { - return async { Box::new(std::iter::empty()) as Box<_> }.boxed() + return Box::new(std::iter::empty()) as Box<_> } if self.ready_poll.lock().updated_at() >= at { log::trace!(target: LOG_TARGET, "Transaction pool already processed block #{}", at); let iterator: ReadyIteratorFor = Box::new(self.pool.validated_pool().ready()); - return async move { iterator }.boxed() + return iterator } - self.ready_poll - .lock() - .add(at) - .map(|received| { - received.unwrap_or_else(|e| { - log::warn!(target: LOG_TARGET, "Error receiving pending set: {:?}", e); - Box::new(std::iter::empty()) - }) + let result = self.ready_poll.lock().add(at).map(|received| { + received.unwrap_or_else(|e| { + log::warn!(target: LOG_TARGET, "Error receiving pending set: {:?}", e); + Box::new(std::iter::empty()) }) - .boxed() + }); + + result.await } fn ready(&self) -> ReadyIteratorFor { @@ -384,12 +365,12 @@ where pool.futures().cloned().collect::>() } - fn ready_at_with_timeout( + async fn ready_at_with_timeout( &self, at: ::Hash, timeout: std::time::Duration, - ) -> PolledIterator { - self.ready_at_with_timeout_internal(at, timeout) + ) -> ReadyIteratorFor { + self.ready_at_with_timeout_internal(at, timeout).await } } diff --git a/substrate/client/transaction-pool/src/transaction_pool_wrapper.rs b/substrate/client/transaction-pool/src/transaction_pool_wrapper.rs index 4e1b53833b8f..e373c0278d80 100644 --- a/substrate/client/transaction-pool/src/transaction_pool_wrapper.rs +++ b/substrate/client/transaction-pool/src/transaction_pool_wrapper.rs @@ -22,16 +22,16 @@ use crate::{ builder::FullClientTransactionPool, graph::{base_pool::Transaction, ExtrinsicFor, ExtrinsicHash}, - ChainApi, FullChainApi, + ChainApi, FullChainApi, ReadyIteratorFor, }; use async_trait::async_trait; use sc_transaction_pool_api::{ ChainEvent, ImportNotificationStream, LocalTransactionFor, LocalTransactionPool, - MaintainedTransactionPool, PoolFuture, PoolStatus, ReadyTransactions, TransactionFor, - TransactionPool, TransactionSource, TransactionStatusStreamFor, TxHash, + MaintainedTransactionPool, PoolStatus, ReadyTransactions, TransactionFor, TransactionPool, + TransactionSource, TransactionStatusStreamFor, TxHash, }; use sp_runtime::traits::Block as BlockT; -use std::{collections::HashMap, future::Future, pin::Pin, sync::Arc}; +use std::{collections::HashMap, pin::Pin, sync::Arc}; /// The wrapper for actual object providing implementation of TransactionPool. /// @@ -49,6 +49,7 @@ where + 'static, Client::Api: sp_transaction_pool::runtime_api::TaggedTransactionQueue; +#[async_trait] impl TransactionPool for TransactionPoolWrapper where Block: BlockT, @@ -68,44 +69,38 @@ where >; type Error = as ChainApi>::Error; - fn submit_at( + async fn submit_at( &self, at: ::Hash, source: TransactionSource, xts: Vec>, - ) -> PoolFuture, Self::Error>>, Self::Error> { - self.0.submit_at(at, source, xts) + ) -> Result, Self::Error>>, Self::Error> { + self.0.submit_at(at, source, xts).await } - fn submit_one( + async fn submit_one( &self, at: ::Hash, source: TransactionSource, xt: TransactionFor, - ) -> PoolFuture, Self::Error> { - self.0.submit_one(at, source, xt) + ) -> Result, Self::Error> { + self.0.submit_one(at, source, xt).await } - fn submit_and_watch( + async fn submit_and_watch( &self, at: ::Hash, source: TransactionSource, xt: TransactionFor, - ) -> PoolFuture>>, Self::Error> { - self.0.submit_and_watch(at, source, xt) + ) -> Result>>, Self::Error> { + self.0.submit_and_watch(at, source, xt).await } - fn ready_at( + async fn ready_at( &self, at: ::Hash, - ) -> Pin< - Box< - dyn Future< - Output = Box> + Send>, - > + Send, - >, - > { - self.0.ready_at(at) + ) -> ReadyIteratorFor> { + self.0.ready_at(at).await } fn ready(&self) -> Box> + Send> { @@ -140,19 +135,12 @@ where self.0.ready_transaction(hash) } - fn ready_at_with_timeout( + async fn ready_at_with_timeout( &self, at: ::Hash, timeout: std::time::Duration, - ) -> Pin< - Box< - dyn Future< - Output = Box> + Send>, - > + Send - + '_, - >, - > { - self.0.ready_at_with_timeout(at, timeout) + ) -> ReadyIteratorFor> { + self.0.ready_at_with_timeout(at, timeout).await } } diff --git a/substrate/frame/revive/fixtures/contracts/delegate_call.rs b/substrate/frame/revive/fixtures/contracts/delegate_call.rs index 9fd155408af3..3cf74acf1321 100644 --- a/substrate/frame/revive/fixtures/contracts/delegate_call.rs +++ b/substrate/frame/revive/fixtures/contracts/delegate_call.rs @@ -28,7 +28,11 @@ pub extern "C" fn deploy() {} #[no_mangle] #[polkavm_derive::polkavm_export] pub extern "C" fn call() { - input!(code_hash: &[u8; 32],); + input!( + address: &[u8; 20], + ref_time: u64, + proof_size: u64, + ); let mut key = [0u8; 32]; key[0] = 1u8; @@ -42,7 +46,7 @@ pub extern "C" fn call() { assert!(value[0] == 2u8); let input = [0u8; 0]; - api::delegate_call(uapi::CallFlags::empty(), code_hash, &input, None).unwrap(); + api::delegate_call(uapi::CallFlags::empty(), address, ref_time, proof_size, None, &input, None).unwrap(); api::get_storage(StorageFlags::empty(), &key, value).unwrap(); assert!(value[0] == 1u8); diff --git a/substrate/frame/revive/fixtures/contracts/delegate_call_deposit_limit.rs b/substrate/frame/revive/fixtures/contracts/delegate_call_deposit_limit.rs new file mode 100644 index 000000000000..55203d534c9b --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/delegate_call_deposit_limit.rs @@ -0,0 +1,46 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![no_std] +#![no_main] + +use common::{input, u256_bytes}; +use uapi::{HostFn, HostFnImpl as api, StorageFlags}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + input!( + address: &[u8; 20], + deposit_limit: u64, + ); + + let input = [0u8; 0]; + api::delegate_call(uapi::CallFlags::empty(), address, 0, 0, Some(&u256_bytes(deposit_limit)), &input, None).unwrap(); + + let mut key = [0u8; 32]; + key[0] = 1u8; + + let mut value = [0u8; 32]; + + api::get_storage(StorageFlags::empty(), &key, &mut &mut value[..]).unwrap(); + assert!(value[0] == 1u8); +} diff --git a/substrate/frame/revive/fixtures/contracts/delegate_call_simple.rs b/substrate/frame/revive/fixtures/contracts/delegate_call_simple.rs index 20f8ec3364ee..a8501dad4692 100644 --- a/substrate/frame/revive/fixtures/contracts/delegate_call_simple.rs +++ b/substrate/frame/revive/fixtures/contracts/delegate_call_simple.rs @@ -28,9 +28,9 @@ pub extern "C" fn deploy() {} #[no_mangle] #[polkavm_derive::polkavm_export] pub extern "C" fn call() { - input!(code_hash: &[u8; 32],); + input!(address: &[u8; 20],); - // Delegate call into passed code hash. + // Delegate call into passed address. let input = [0u8; 0]; - api::delegate_call(uapi::CallFlags::empty(), code_hash, &input, None).unwrap(); + api::delegate_call(uapi::CallFlags::empty(), address, 0, 0, None, &input, None).unwrap(); } diff --git a/substrate/frame/revive/fixtures/contracts/locking_delegate_dependency.rs b/substrate/frame/revive/fixtures/contracts/locking_delegate_dependency.rs index 54c7c7f3d5e2..3d7702c6537a 100644 --- a/substrate/frame/revive/fixtures/contracts/locking_delegate_dependency.rs +++ b/substrate/frame/revive/fixtures/contracts/locking_delegate_dependency.rs @@ -30,6 +30,7 @@ const ALICE_FALLBACK: [u8; 20] = [1u8; 20]; fn load_input(delegate_call: bool) { input!( action: u32, + address: &[u8; 20], code_hash: &[u8; 32], ); @@ -51,7 +52,7 @@ fn load_input(delegate_call: bool) { } if delegate_call { - api::delegate_call(uapi::CallFlags::empty(), code_hash, &[], None).unwrap(); + api::delegate_call(uapi::CallFlags::empty(), address, 0, 0, None, &[], None).unwrap(); } } diff --git a/substrate/frame/revive/src/benchmarking/mod.rs b/substrate/frame/revive/src/benchmarking/mod.rs index 40ad3a3aed17..9c4d817a07de 100644 --- a/substrate/frame/revive/src/benchmarking/mod.rs +++ b/substrate/frame/revive/src/benchmarking/mod.rs @@ -1555,25 +1555,36 @@ mod benchmarks { #[benchmark(pov_mode = Measured)] fn seal_delegate_call() -> Result<(), BenchmarkError> { - let hash = Contract::::with_index(1, WasmModule::dummy(), vec![])?.info()?.code_hash; + let Contract { account_id: address, .. } = + Contract::::with_index(1, WasmModule::dummy(), vec![]).unwrap(); + + let address_bytes = address.encode(); + let address_len = address_bytes.len() as u32; + + let deposit: BalanceOf = (u32::MAX - 100).into(); + let deposit_bytes = Into::::into(deposit).encode(); let mut setup = CallSetup::::default(); + setup.set_storage_deposit_limit(deposit); setup.set_origin(Origin::from_account_id(setup.contract().account_id.clone())); let (mut ext, _) = setup.ext(); let mut runtime = crate::wasm::Runtime::<_, [u8]>::new(&mut ext, vec![]); - let mut memory = memory!(hash.encode(),); + let mut memory = memory!(address_bytes, deposit_bytes,); let result; #[block] { result = runtime.bench_delegate_call( memory.as_mut_slice(), - 0, // flags - 0, // code_hash_ptr - 0, // input_data_ptr - 0, // input_data_len - SENTINEL, // output_ptr + 0, // flags + 0, // address_ptr + 0, // ref_time_limit + 0, // proof_size_limit + address_len, // deposit_ptr + 0, // input_data_ptr + 0, // input_data_len + SENTINEL, // output_ptr 0, ); } diff --git a/substrate/frame/revive/src/exec.rs b/substrate/frame/revive/src/exec.rs index fdb45f045bbc..49c08166483e 100644 --- a/substrate/frame/revive/src/exec.rs +++ b/substrate/frame/revive/src/exec.rs @@ -210,7 +210,13 @@ pub trait Ext: sealing::Sealed { /// Execute code in the current frame. /// /// Returns the code size of the called contract. - fn delegate_call(&mut self, code: H256, input_data: Vec) -> Result<(), ExecError>; + fn delegate_call( + &mut self, + gas_limit: Weight, + deposit_limit: U256, + address: H160, + input_data: Vec, + ) -> Result<(), ExecError>; /// Instantiate a contract from the given code. /// @@ -581,18 +587,30 @@ struct Frame { allows_reentry: bool, /// If `true` subsequent calls cannot modify storage. read_only: bool, - /// The caller of the currently executing frame which was spawned by `delegate_call`. - delegate_caller: Option>, + /// The delegate call info of the currently executing frame which was spawned by + /// `delegate_call`. + delegate: Option>, /// The output of the last executed call frame. last_frame_output: ExecReturnValue, } +/// This structure is used to represent the arguments in a delegate call frame in order to +/// distinguish who delegated the call and where it was delegated to. +struct DelegateInfo { + /// The caller of the contract. + pub caller: Origin, + /// The address of the contract the call was delegated to. + pub callee: H160, +} + /// Used in a delegate call frame arguments in order to override the executable and caller. struct DelegatedCall { /// The executable which is run instead of the contracts own `executable`. executable: E, /// The caller of the contract. caller: Origin, + /// The address of the contract the call was delegated to. + callee: H160, } /// Parameter passed in when creating a new `Frame`. @@ -898,8 +916,7 @@ where read_only: bool, origin_is_caller: bool, ) -> Result, E)>, ExecError> { - let (account_id, contract_info, executable, delegate_caller, entry_point) = match frame_args - { + let (account_id, contract_info, executable, delegate, entry_point) = match frame_args { FrameArgs::Call { dest, cached_info, delegated_call } => { let contract = if let Some(contract) = cached_info { contract @@ -914,8 +931,8 @@ where }; let (executable, delegate_caller) = - if let Some(DelegatedCall { executable, caller }) = delegated_call { - (executable, Some(caller)) + if let Some(DelegatedCall { executable, caller, callee }) = delegated_call { + (executable, Some(DelegateInfo { caller, callee })) } else { (E::from_storage(contract.code_hash, gas_meter)?, None) }; @@ -931,8 +948,8 @@ where use sp_runtime::Saturating; address::create1( &deployer, - // the Nonce from the origin has been incremented pre-dispatch, so we need - // to subtract 1 to get the nonce at the time of the call. + // the Nonce from the origin has been incremented pre-dispatch, so we + // need to subtract 1 to get the nonce at the time of the call. if origin_is_caller { account_nonce.saturating_sub(1u32.into()).saturated_into() } else { @@ -956,7 +973,7 @@ where }; let frame = Frame { - delegate_caller, + delegate, value_transferred, contract_info: CachedContract::Cached(contract_info), account_id, @@ -1025,7 +1042,7 @@ where let frame = self.top_frame(); let entry_point = frame.entry_point; let delegated_code_hash = - if frame.delegate_caller.is_some() { Some(*executable.code_hash()) } else { None }; + if frame.delegate.is_some() { Some(*executable.code_hash()) } else { None }; // The output of the caller frame will be replaced by the output of this run. // It is also not accessible from nested frames. @@ -1103,7 +1120,13 @@ where frame.nested_storage.enforce_limit(contract)?; } - let frame = self.top_frame(); + let frame = self.top_frame_mut(); + + // If a special limit was set for the sub-call, we enforce it here. + // The sub-call will be rolled back in case the limit is exhausted. + let contract = frame.contract_info.as_contract(); + frame.nested_storage.enforce_subcall_limit(contract)?; + let account_id = T::AddressMapper::to_address(&frame.account_id); match (entry_point, delegated_code_hash) { (ExportedFunction::Constructor, _) => { @@ -1112,15 +1135,7 @@ where return Err(Error::::TerminatedInConstructor.into()); } - // If a special limit was set for the sub-call, we enforce it here. - // This is needed because contract constructor might write to storage. - // The sub-call will be rolled back in case the limit is exhausted. - let frame = self.top_frame_mut(); - let contract = frame.contract_info.as_contract(); - frame.nested_storage.enforce_subcall_limit(contract)?; - let caller = T::AddressMapper::to_address(self.caller().account_id()?); - // Deposit an instantiation event. Contracts::::deposit_event(Event::Instantiated { deployer: caller, @@ -1134,12 +1149,6 @@ where }); }, (ExportedFunction::Call, None) => { - // If a special limit was set for the sub-call, we enforce it here. - // The sub-call will be rolled back in case the limit is exhausted. - let frame = self.top_frame_mut(); - let contract = frame.contract_info.as_contract(); - frame.nested_storage.enforce_subcall_limit(contract)?; - let caller = self.caller(); Contracts::::deposit_event(Event::Called { caller: caller.clone(), @@ -1468,11 +1477,20 @@ where result } - fn delegate_call(&mut self, code_hash: H256, input_data: Vec) -> Result<(), ExecError> { + fn delegate_call( + &mut self, + gas_limit: Weight, + deposit_limit: U256, + address: H160, + input_data: Vec, + ) -> Result<(), ExecError> { // We reset the return data now, so it is cleared out even if no new frame was executed. // This is for example the case for unknown code hashes or creating the frame fails. *self.last_frame_output_mut() = Default::default(); + let code_hash = ContractInfoOf::::get(&address) + .ok_or(Error::::CodeNotFound) + .map(|c| c.code_hash)?; let executable = E::from_storage(code_hash, self.gas_meter_mut())?; let top_frame = self.top_frame_mut(); let contract_info = top_frame.contract_info().clone(); @@ -1482,11 +1500,15 @@ where FrameArgs::Call { dest: account_id, cached_info: Some(contract_info), - delegated_call: Some(DelegatedCall { executable, caller: self.caller().clone() }), + delegated_call: Some(DelegatedCall { + executable, + caller: self.caller().clone(), + callee: address, + }), }, value, - Weight::zero(), - BalanceOf::::zero(), + gas_limit, + deposit_limit.try_into().map_err(|_| Error::::BalanceConversionFailed)?, self.is_read_only(), )?; self.run(executable.expect(FRAME_ALWAYS_EXISTS_ON_INSTANTIATE), input_data) @@ -1601,7 +1623,7 @@ where } fn caller(&self) -> Origin { - if let Some(caller) = &self.top_frame().delegate_caller { + if let Some(DelegateInfo { caller, .. }) = &self.top_frame().delegate { caller.clone() } else { self.frames() @@ -1655,7 +1677,13 @@ where return Err(Error::::InvalidImmutableAccess.into()); } - let address = T::AddressMapper::to_address(self.account_id()); + // Immutable is read from contract code being executed + let address = self + .top_frame() + .delegate + .as_ref() + .map(|d| d.callee) + .unwrap_or(T::AddressMapper::to_address(self.account_id())); Ok(>::get(address).ok_or_else(|| Error::::InvalidImmutableAccess)?) } @@ -1917,7 +1945,7 @@ mod tests { AddressMapper, Error, }; use assert_matches::assert_matches; - use frame_support::{assert_err, assert_ok, parameter_types}; + use frame_support::{assert_err, assert_noop, assert_ok, parameter_types}; use frame_system::{AccountInfo, EventRecord, Phase}; use pallet_revive_uapi::ReturnFlags; use pretty_assertions::assert_eq; @@ -2185,18 +2213,20 @@ mod tests { let delegate_ch = MockLoader::insert(Call, move |ctx, _| { assert_eq!(ctx.ext.value_transferred(), U256::from(value)); - let _ = ctx.ext.delegate_call(success_ch, Vec::new())?; + let _ = + ctx.ext.delegate_call(Weight::zero(), U256::zero(), CHARLIE_ADDR, Vec::new())?; Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: Vec::new() }) }); ExtBuilder::default().build().execute_with(|| { place_contract(&BOB, delegate_ch); + place_contract(&CHARLIE, success_ch); set_balance(&ALICE, 100); let balance = get_balance(&BOB_FALLBACK); let origin = Origin::from_account_id(ALICE); let mut storage_meter = storage::meter::Meter::new(&origin, 0, 55).unwrap(); - let _ = MockStack::run_call( + assert_ok!(MockStack::run_call( origin, BOB_ADDR, &mut GasMeter::::new(GAS_LIMIT), @@ -2204,14 +2234,63 @@ mod tests { value.into(), vec![], None, - ) - .unwrap(); + )); assert_eq!(get_balance(&ALICE), 100 - value); assert_eq!(get_balance(&BOB_FALLBACK), balance + value); }); } + #[test] + fn delegate_call_missing_contract() { + let missing_ch = MockLoader::insert(Call, move |_ctx, _| { + Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: Vec::new() }) + }); + + let delegate_ch = MockLoader::insert(Call, move |ctx, _| { + let _ = + ctx.ext.delegate_call(Weight::zero(), U256::zero(), CHARLIE_ADDR, Vec::new())?; + Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: Vec::new() }) + }); + + ExtBuilder::default().build().execute_with(|| { + place_contract(&BOB, delegate_ch); + set_balance(&ALICE, 100); + + let origin = Origin::from_account_id(ALICE); + let mut storage_meter = storage::meter::Meter::new(&origin, 0, 55).unwrap(); + + // contract code missing + assert_noop!( + MockStack::run_call( + origin.clone(), + BOB_ADDR, + &mut GasMeter::::new(GAS_LIMIT), + &mut storage_meter, + U256::zero(), + vec![], + None, + ), + ExecError { + error: Error::::CodeNotFound.into(), + origin: ErrorOrigin::Callee, + } + ); + + // add missing contract code + place_contract(&CHARLIE, missing_ch); + assert_ok!(MockStack::run_call( + origin, + BOB_ADDR, + &mut GasMeter::::new(GAS_LIMIT), + &mut storage_meter, + U256::zero(), + vec![], + None, + )); + }); + } + #[test] fn changes_are_reverted_on_failing_call() { // This test verifies that changes are reverted on a call which fails (or equally, returns @@ -4615,7 +4694,12 @@ mod tests { // An unknown code hash to fail the delegate_call on the first condition. *ctx.ext.last_frame_output_mut() = output_revert(); assert_eq!( - ctx.ext.delegate_call(invalid_code_hash, Default::default()), + ctx.ext.delegate_call( + Weight::zero(), + U256::zero(), + H160([0xff; 20]), + Default::default() + ), Err(Error::::CodeNotFound.into()) ); assert_eq!(ctx.ext.last_frame_output(), &Default::default()); @@ -4732,14 +4816,12 @@ mod tests { Ok(vec![2]), ); - // In a delegate call, we should witness the caller immutable data + // Also in a delegate call, we should witness the callee immutable data assert_eq!( - ctx.ext.delegate_call(charlie_ch, Vec::new()).map(|_| ctx - .ext - .last_frame_output() - .data - .clone()), - Ok(vec![1]) + ctx.ext + .delegate_call(Weight::zero(), U256::zero(), CHARLIE_ADDR, Vec::new()) + .map(|_| ctx.ext.last_frame_output().data.clone()), + Ok(vec![2]) ); exec_success() diff --git a/substrate/frame/revive/src/tests.rs b/substrate/frame/revive/src/tests.rs index a35e4d908601..177b8dff706b 100644 --- a/substrate/frame/revive/src/tests.rs +++ b/substrate/frame/revive/src/tests.rs @@ -1127,7 +1127,7 @@ fn deploy_and_call_other_contract() { #[test] fn delegate_call() { let (caller_wasm, _caller_code_hash) = compile_module("delegate_call").unwrap(); - let (callee_wasm, callee_code_hash) = compile_module("delegate_call_lib").unwrap(); + let (callee_wasm, _callee_code_hash) = compile_module("delegate_call_lib").unwrap(); ExtBuilder::default().existential_deposit(500).build().execute_with(|| { let _ = ::Currency::set_balance(&ALICE, 1_000_000); @@ -1137,12 +1137,91 @@ fn delegate_call() { builder::bare_instantiate(Code::Upload(caller_wasm)) .value(300_000) .build_and_unwrap_contract(); - // Only upload 'callee' code - assert_ok!(Contracts::upload_code(RuntimeOrigin::signed(ALICE), callee_wasm, 100_000,)); + + // Instantiate the 'callee' + let Contract { addr: callee_addr, .. } = + builder::bare_instantiate(Code::Upload(callee_wasm)) + .value(100_000) + .build_and_unwrap_contract(); assert_ok!(builder::call(caller_addr) .value(1337) - .data(callee_code_hash.as_ref().to_vec()) + .data((callee_addr, 0u64, 0u64).encode()) + .build()); + }); +} + +#[test] +fn delegate_call_with_weight_limit() { + let (caller_wasm, _caller_code_hash) = compile_module("delegate_call").unwrap(); + let (callee_wasm, _callee_code_hash) = compile_module("delegate_call_lib").unwrap(); + + ExtBuilder::default().existential_deposit(500).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + // Instantiate the 'caller' + let Contract { addr: caller_addr, .. } = + builder::bare_instantiate(Code::Upload(caller_wasm)) + .value(300_000) + .build_and_unwrap_contract(); + + // Instantiate the 'callee' + let Contract { addr: callee_addr, .. } = + builder::bare_instantiate(Code::Upload(callee_wasm)) + .value(100_000) + .build_and_unwrap_contract(); + + // fails, not enough weight + assert_err!( + builder::bare_call(caller_addr) + .value(1337) + .data((callee_addr, 100u64, 100u64).encode()) + .build() + .result, + Error::::ContractTrapped, + ); + + assert_ok!(builder::call(caller_addr) + .value(1337) + .data((callee_addr, 500_000_000u64, 100_000u64).encode()) + .build()); + }); +} + +#[test] +fn delegate_call_with_deposit_limit() { + let (caller_pvm, _caller_code_hash) = compile_module("delegate_call_deposit_limit").unwrap(); + let (callee_pvm, _callee_code_hash) = compile_module("delegate_call_lib").unwrap(); + + ExtBuilder::default().existential_deposit(500).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + // Instantiate the 'caller' + let Contract { addr: caller_addr, .. } = + builder::bare_instantiate(Code::Upload(caller_pvm)) + .value(300_000) + .build_and_unwrap_contract(); + + // Instantiate the 'callee' + let Contract { addr: callee_addr, .. } = + builder::bare_instantiate(Code::Upload(callee_pvm)) + .value(100_000) + .build_and_unwrap_contract(); + + // Delegate call will write 1 storage and deposit of 2 (1 item) + 32 (bytes) is required. + // Fails, not enough deposit + assert_err!( + builder::bare_call(caller_addr) + .value(1337) + .data((callee_addr, 33u64).encode()) + .build() + .result, + Error::::StorageDepositLimitExhausted, + ); + + assert_ok!(builder::call(caller_addr) + .value(1337) + .data((callee_addr, 34u64).encode()) .build()); }); } @@ -3666,6 +3745,12 @@ fn locking_delegate_dependency_works() { .map(|c| sp_core::H256(sp_io::hashing::keccak_256(c))) .collect(); + let hash2addr = |code_hash: &H256| { + let mut addr = H160::zero(); + addr.as_bytes_mut().copy_from_slice(&code_hash.as_ref()[..20]); + addr + }; + // Define inputs with various actions to test locking / unlocking delegate_dependencies. // See the contract for more details. let noop_input = (0u32, callee_hashes[0]); @@ -3675,17 +3760,19 @@ fn locking_delegate_dependency_works() { // Instantiate the caller contract with the given input. let instantiate = |input: &(u32, H256)| { + let (action, code_hash) = input; builder::bare_instantiate(Code::Upload(wasm_caller.clone())) .origin(RuntimeOrigin::signed(ALICE_FALLBACK)) - .data(input.encode()) + .data((action, hash2addr(code_hash), code_hash).encode()) .build() }; // Call contract with the given input. let call = |addr_caller: &H160, input: &(u32, H256)| { + let (action, code_hash) = input; builder::bare_call(*addr_caller) .origin(RuntimeOrigin::signed(ALICE_FALLBACK)) - .data(input.encode()) + .data((action, hash2addr(code_hash), code_hash).encode()) .build() }; const ED: u64 = 2000; @@ -3702,7 +3789,7 @@ fn locking_delegate_dependency_works() { // Upload all the delegated codes (they all have the same size) let mut deposit = Default::default(); for code in callee_codes.iter() { - let CodeUploadReturnValue { deposit: deposit_per_code, .. } = + let CodeUploadReturnValue { deposit: deposit_per_code, code_hash } = Contracts::bare_upload_code( RuntimeOrigin::signed(ALICE_FALLBACK), code.clone(), @@ -3710,6 +3797,9 @@ fn locking_delegate_dependency_works() { ) .unwrap(); deposit = deposit_per_code; + // Mock contract info by using first 20 bytes of code_hash as address. + let addr = hash2addr(&code_hash); + ContractInfoOf::::set(&addr, ContractInfo::new(&addr, 0, code_hash).ok()); } // Instantiate should now work. @@ -3746,7 +3836,11 @@ fn locking_delegate_dependency_works() { // Locking self should fail. assert_err!( - call(&addr_caller, &(1u32, self_code_hash)).result, + builder::bare_call(addr_caller) + .origin(RuntimeOrigin::signed(ALICE_FALLBACK)) + .data((1u32, &addr_caller, self_code_hash).encode()) + .build() + .result, Error::::CannotAddSelfAsDelegateDependency ); @@ -3785,7 +3879,7 @@ fn locking_delegate_dependency_works() { assert_err!( builder::bare_call(addr_caller) .storage_deposit_limit(dependency_deposit - 1) - .data(lock_delegate_dependency_input.encode()) + .data((1u32, hash2addr(&callee_hashes[0]), callee_hashes[0]).encode()) .build() .result, Error::::StorageDepositLimitExhausted diff --git a/substrate/frame/revive/src/wasm/runtime.rs b/substrate/frame/revive/src/wasm/runtime.rs index 8310fe701013..3e2c83db1ebd 100644 --- a/substrate/frame/revive/src/wasm/runtime.rs +++ b/substrate/frame/revive/src/wasm/runtime.rs @@ -536,16 +536,17 @@ macro_rules! charge_gas { /// The kind of call that should be performed. enum CallType { /// Execute another instantiated contract - Call { callee_ptr: u32, value_ptr: u32, deposit_ptr: u32, weight: Weight }, - /// Execute deployed code in the context (storage, account ID, value) of the caller contract - DelegateCall { code_hash_ptr: u32 }, + Call { value_ptr: u32 }, + /// Execute another contract code in the context (storage, account ID, value) of the caller + /// contract + DelegateCall, } impl CallType { fn cost(&self) -> RuntimeCosts { match self { CallType::Call { .. } => RuntimeCosts::CallBase, - CallType::DelegateCall { .. } => RuntimeCosts::DelegateCallBase, + CallType::DelegateCall => RuntimeCosts::DelegateCallBase, } } } @@ -987,6 +988,9 @@ impl<'a, E: Ext, M: ?Sized + Memory> Runtime<'a, E, M> { memory: &mut M, flags: CallFlags, call_type: CallType, + callee_ptr: u32, + deposit_ptr: u32, + weight: Weight, input_data_ptr: u32, input_data_len: u32, output_ptr: u32, @@ -994,6 +998,10 @@ impl<'a, E: Ext, M: ?Sized + Memory> Runtime<'a, E, M> { ) -> Result { self.charge_gas(call_type.cost())?; + let callee = memory.read_h160(callee_ptr)?; + let deposit_limit = + if deposit_ptr == SENTINEL { U256::zero() } else { memory.read_u256(deposit_ptr)? }; + let input_data = if flags.contains(CallFlags::CLONE_INPUT) { let input = self.input_data.as_ref().ok_or(Error::::InputForwarded)?; charge_gas!(self, RuntimeCosts::CallInputCloned(input.len() as u32))?; @@ -1006,13 +1014,7 @@ impl<'a, E: Ext, M: ?Sized + Memory> Runtime<'a, E, M> { }; let call_outcome = match call_type { - CallType::Call { callee_ptr, value_ptr, deposit_ptr, weight } => { - let callee = memory.read_h160(callee_ptr)?; - let deposit_limit = if deposit_ptr == SENTINEL { - U256::zero() - } else { - memory.read_u256(deposit_ptr)? - }; + CallType::Call { value_ptr } => { let read_only = flags.contains(CallFlags::READ_ONLY); let value = memory.read_u256(value_ptr)?; if value > 0u32.into() { @@ -1033,13 +1035,11 @@ impl<'a, E: Ext, M: ?Sized + Memory> Runtime<'a, E, M> { read_only, ) }, - CallType::DelegateCall { code_hash_ptr } => { + CallType::DelegateCall => { if flags.intersects(CallFlags::ALLOW_REENTRY | CallFlags::READ_ONLY) { return Err(Error::::InvalidCallFlags.into()); } - - let code_hash = memory.read_h256(code_hash_ptr)?; - self.ext.delegate_call(code_hash, input_data) + self.ext.delegate_call(weight, deposit_limit, callee, input_data) }, }; @@ -1252,12 +1252,10 @@ pub mod env { self.call( memory, CallFlags::from_bits(flags).ok_or(Error::::InvalidCallFlags)?, - CallType::Call { - callee_ptr, - value_ptr, - deposit_ptr, - weight: Weight::from_parts(ref_time_limit, proof_size_limit), - }, + CallType::Call { value_ptr }, + callee_ptr, + deposit_ptr, + Weight::from_parts(ref_time_limit, proof_size_limit), input_data_ptr, input_data_len, output_ptr, @@ -1272,7 +1270,10 @@ pub mod env { &mut self, memory: &mut M, flags: u32, - code_hash_ptr: u32, + address_ptr: u32, + ref_time_limit: u64, + proof_size_limit: u64, + deposit_ptr: u32, input_data_ptr: u32, input_data_len: u32, output_ptr: u32, @@ -1281,7 +1282,10 @@ pub mod env { self.call( memory, CallFlags::from_bits(flags).ok_or(Error::::InvalidCallFlags)?, - CallType::DelegateCall { code_hash_ptr }, + CallType::DelegateCall, + address_ptr, + deposit_ptr, + Weight::from_parts(ref_time_limit, proof_size_limit), input_data_ptr, input_data_len, output_ptr, diff --git a/substrate/frame/revive/uapi/src/host.rs b/substrate/frame/revive/uapi/src/host.rs index cb52cf93540b..6b3a8b07f040 100644 --- a/substrate/frame/revive/uapi/src/host.rs +++ b/substrate/frame/revive/uapi/src/host.rs @@ -323,7 +323,13 @@ pub trait HostFn: private::Sealed { /// # Parameters /// /// - `flags`: See [`CallFlags`] for a documentation of the supported flags. - /// - `code_hash`: The hash of the code to be executed. + /// - `address`: The address of the code to be executed. Should be decodable as an + /// `T::AccountId`. Traps otherwise. + /// - `ref_time_limit`: how much *ref_time* Weight to devote to the execution. + /// - `proof_size_limit`: how much *proof_size* Weight to devote to the execution. + /// - `deposit_limit`: The storage deposit limit for delegate call. Passing `None` means setting + /// no specific limit for the call, which implies storage usage up to the limit of the parent + /// call. /// - `input`: The input data buffer used to call the contract. /// - `output`: A reference to the output data buffer to write the call output buffer. If `None` /// is provided then the output buffer is not copied. @@ -338,7 +344,10 @@ pub trait HostFn: private::Sealed { /// - [CodeNotFound][`crate::ReturnErrorCode::CodeNotFound] fn delegate_call( flags: CallFlags, - code_hash: &[u8; 32], + address: &[u8; 20], + ref_time_limit: u64, + proof_size_limit: u64, + deposit_limit: Option<&[u8; 32]>, input_data: &[u8], output: Option<&mut &mut [u8]>, ) -> Result; diff --git a/substrate/frame/revive/uapi/src/host/riscv32.rs b/substrate/frame/revive/uapi/src/host/riscv32.rs index 199a0abc3ddc..e8b27057ed18 100644 --- a/substrate/frame/revive/uapi/src/host/riscv32.rs +++ b/substrate/frame/revive/uapi/src/host/riscv32.rs @@ -59,14 +59,7 @@ mod sys { out_len_ptr: *mut u32, ) -> ReturnCode; pub fn call(ptr: *const u8) -> ReturnCode; - pub fn delegate_call( - flags: u32, - code_hash_ptr: *const u8, - input_data_ptr: *const u8, - input_data_len: u32, - out_ptr: *mut u8, - out_len_ptr: *mut u32, - ) -> ReturnCode; + pub fn delegate_call(ptr: *const u8) -> ReturnCode; pub fn instantiate(ptr: *const u8) -> ReturnCode; pub fn terminate(beneficiary_ptr: *const u8); pub fn input(out_ptr: *mut u8, out_len_ptr: *mut u32); @@ -306,24 +299,42 @@ impl HostFn for HostFnImpl { fn delegate_call( flags: CallFlags, - code_hash: &[u8; 32], + address: &[u8; 20], + ref_time_limit: u64, + proof_size_limit: u64, + deposit_limit: Option<&[u8; 32]>, input: &[u8], mut output: Option<&mut &mut [u8]>, ) -> Result { let (output_ptr, mut output_len) = ptr_len_or_sentinel(&mut output); - let ret_code = { - unsafe { - sys::delegate_call( - flags.bits(), - code_hash.as_ptr(), - input.as_ptr(), - input.len() as u32, - output_ptr, - &mut output_len, - ) - } + let deposit_limit_ptr = ptr_or_sentinel(&deposit_limit); + #[repr(packed)] + #[allow(dead_code)] + struct Args { + flags: u32, + address: *const u8, + ref_time_limit: u64, + proof_size_limit: u64, + deposit_limit: *const u8, + input: *const u8, + input_len: u32, + output: *mut u8, + output_len: *mut u32, + } + let args = Args { + flags: flags.bits(), + address: address.as_ptr(), + ref_time_limit, + proof_size_limit, + deposit_limit: deposit_limit_ptr, + input: input.as_ptr(), + input_len: input.len() as _, + output: output_ptr, + output_len: &mut output_len as *mut _, }; + let ret_code = { unsafe { sys::delegate_call(&args as *const Args as *const _) } }; + if let Some(ref mut output) = output { extract_from_slice(output, output_len as usize); } diff --git a/substrate/primitives/trie/src/node_codec.rs b/substrate/primitives/trie/src/node_codec.rs index 27da0c6334a2..400f57f3b1bf 100644 --- a/substrate/primitives/trie/src/node_codec.rs +++ b/substrate/primitives/trie/src/node_codec.rs @@ -110,8 +110,8 @@ where NodeHeader::Null => Ok(NodePlan::Empty), NodeHeader::HashedValueBranch(nibble_count) | NodeHeader::Branch(_, nibble_count) => { let padding = nibble_count % nibble_ops::NIBBLE_PER_BYTE != 0; - // data should be at least the size of the offset - if data.len() < input.offset { + // data should be at least of size offset + 1 + if data.len() < input.offset + 1 { return Err(Error::BadFormat) } // check that the padding is valid (if any) @@ -158,8 +158,8 @@ where }, NodeHeader::HashedValueLeaf(nibble_count) | NodeHeader::Leaf(nibble_count) => { let padding = nibble_count % nibble_ops::NIBBLE_PER_BYTE != 0; - // data should be at least the size of the offset - if data.len() < input.offset { + // data should be at least of size offset + 1 + if data.len() < input.offset + 1 { return Err(Error::BadFormat) } // check that the padding is valid (if any) diff --git a/substrate/primitives/trie/src/storage_proof.rs b/substrate/primitives/trie/src/storage_proof.rs index a9f6298742f6..bf0dc72e650b 100644 --- a/substrate/primitives/trie/src/storage_proof.rs +++ b/substrate/primitives/trie/src/storage_proof.rs @@ -232,7 +232,8 @@ pub mod tests { use super::*; use crate::{tests::create_storage_proof, StorageProof}; - type Layout = crate::LayoutV1; + type Hasher = sp_core::Blake2Hasher; + type Layout = crate::LayoutV1; const TEST_DATA: &[(&[u8], &[u8])] = &[(b"key1", &[1; 64]), (b"key2", &[2; 64]), (b"key3", &[3; 64]), (b"key11", &[4; 64])]; @@ -245,4 +246,11 @@ pub mod tests { Err(StorageProofError::DuplicateNodes) )); } + + #[test] + fn invalid_compact_proof_does_not_panic_when_decoding() { + let invalid_proof = CompactProof { encoded_nodes: vec![vec![135]] }; + let result = invalid_proof.to_memory_db::(None); + assert!(result.is_err()); + } } diff --git a/substrate/utils/prometheus/Cargo.toml b/substrate/utils/prometheus/Cargo.toml index 9bdec3cb8183..b8dfd6fb2bee 100644 --- a/substrate/utils/prometheus/Cargo.toml +++ b/substrate/utils/prometheus/Cargo.toml @@ -18,7 +18,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] http-body-util = { workspace = true } hyper = { features = ["http1", "server"], workspace = true } -hyper-util = { features = ["server-auto", "tokio"], workspace = true } +hyper-util = { features = ["server-auto", "server-graceful", "tokio"], workspace = true } log = { workspace = true, default-features = true } prometheus = { workspace = true } thiserror = { workspace = true } diff --git a/substrate/utils/prometheus/src/lib.rs b/substrate/utils/prometheus/src/lib.rs index 35597cad03d8..5edac2e6650f 100644 --- a/substrate/utils/prometheus/src/lib.rs +++ b/substrate/utils/prometheus/src/lib.rs @@ -102,6 +102,7 @@ async fn init_prometheus_with_listener( log::info!(target: "prometheus", "〽️ Prometheus exporter started at {}", listener.local_addr()?); let server = hyper_util::server::conn::auto::Builder::new(hyper_util::rt::TokioExecutor::new()); + let graceful = hyper_util::server::graceful::GracefulShutdown::new(); loop { let io = match listener.accept().await { @@ -120,6 +121,7 @@ async fn init_prometheus_with_listener( hyper::service::service_fn(move |req| request_metrics(req, registry.clone())), ) .into_owned(); + let conn = graceful.watch(conn); tokio::spawn(async move { if let Err(err) = conn.await {