From 20d7c50f44fb1b81642f35c1307653031f451723 Mon Sep 17 00:00:00 2001 From: David Lutterkort Date: Sat, 24 May 2025 16:27:58 -0700 Subject: [PATCH 1/3] all: Update wasmtime to version 33 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 2620237e28e..023abb4633c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -95,7 +95,7 @@ tonic = { version = "0.12.3", features = ["tls-roots", "gzip"] } tonic-build = { version = "0.12.3", features = ["prost"] } tower-http = { version = "0.6.6", features = ["cors"] } wasmparser = "0.118.1" -wasmtime = "33.0.2" +wasmtime = { version = "33.0.2" } substreams = "=0.6.0" substreams-entity-change = "2" substreams-near-core = "=0.10.2" From d540584412411887fad688e8e67f78d79629cd19 Mon Sep 17 00:00:00 2001 From: David Lutterkort Date: Mon, 29 Sep 2025 18:05:15 -0700 Subject: [PATCH 2/3] runtime: Use RwLock in AscHeapCtx We need AscHeapCtx to be Send --- runtime/wasm/src/module/mod.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/runtime/wasm/src/module/mod.rs b/runtime/wasm/src/module/mod.rs index 262b083ab71..b59ec61456c 100644 --- a/runtime/wasm/src/module/mod.rs +++ b/runtime/wasm/src/module/mod.rs @@ -1,4 +1,3 @@ -use std::cell::RefCell; use std::convert::TryFrom; use std::mem::MaybeUninit; @@ -6,6 +5,7 @@ use anyhow::anyhow; use anyhow::Error; use graph::blockchain::Blockchain; use graph::data_source::subgraph; +use graph::parking_lot::RwLock; use graph::util::mem::init_slice; use semver::Version; use wasmtime::AsContext; @@ -168,7 +168,7 @@ pub struct AscHeapCtx { // is zeroed when initialized or grown. memory: Memory, - arena: RefCell, + arena: RwLock, } impl AscHeapCtx { @@ -207,28 +207,28 @@ impl AscHeapCtx { Ok(Arc::new(AscHeapCtx { memory_allocate, memory, - arena: RefCell::new(Arena::new()), + arena: RwLock::new(Arena::new()), api_version, id_of_type, })) } fn arena_start_ptr(&self) -> i32 { - self.arena.borrow().start + self.arena.read().start } fn arena_free_size(&self) -> i32 { - self.arena.borrow().size + self.arena.read().size } fn set_arena(&self, start_ptr: i32, size: i32) { - let mut arena = self.arena.borrow_mut(); + let mut arena = self.arena.write(); arena.start = start_ptr; arena.size = size; } fn allocated(&self, size: i32) { - let mut arena = self.arena.borrow_mut(); + let mut arena = self.arena.write(); arena.start += size; arena.size -= size; } From fc8865f2277542fefddf37b2111a88a8fe37d65b Mon Sep 17 00:00:00 2001 From: David Lutterkort Date: Sat, 24 May 2025 17:47:51 -0700 Subject: [PATCH 3/3] all: Use async wasmtime In particular, that makes the host function stubs in runtime/wasm/src/module/context.rs async which in turn will make it possible to use async implementations in lower levels of the stack, for example, in the store. --- Cargo.toml | 2 +- chain/ethereum/src/runtime/abi.rs | 411 +++++++-------- chain/ethereum/src/runtime/runtime_adapter.rs | 109 ++-- chain/ethereum/src/trigger.rs | 33 +- chain/near/src/runtime/abi.rs | 374 ++++++++------ chain/near/src/trigger.rs | 30 +- chain/substreams/src/trigger.rs | 3 +- graph/src/blockchain/mod.rs | 8 +- graph/src/runtime/asc_heap.rs | 46 +- graph/src/runtime/asc_ptr.rs | 13 +- graph/src/runtime/mod.rs | 5 +- runtime/test/src/test.rs | 472 ++++++++++-------- runtime/test/src/test/abi.rs | 129 +++-- runtime/test/src/test_padding.rs | 24 +- runtime/wasm/src/asc_abi/class.rs | 26 +- runtime/wasm/src/asc_abi/v0_0_4.rs | 8 +- runtime/wasm/src/asc_abi/v0_0_5.rs | 8 +- runtime/wasm/src/host_exports.rs | 54 +- runtime/wasm/src/mapping.rs | 51 +- runtime/wasm/src/module/context.rs | 220 ++++---- runtime/wasm/src/module/instance.rs | 317 ++++++++---- runtime/wasm/src/module/mod.rs | 51 +- runtime/wasm/src/to_from/external.rs | 160 +++--- runtime/wasm/src/to_from/mod.rs | 54 +- 24 files changed, 1518 insertions(+), 1090 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 023abb4633c..99791a52402 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -95,7 +95,7 @@ tonic = { version = "0.12.3", features = ["tls-roots", "gzip"] } tonic-build = { version = "0.12.3", features = ["prost"] } tower-http = { version = "0.6.6", features = ["cors"] } wasmparser = "0.118.1" -wasmtime = { version = "33.0.2" } +wasmtime = { version = "33.0.2", features = ["async"] } substreams = "=0.6.0" substreams-entity-change = "2" substreams-near-core = "=0.10.2" diff --git a/chain/ethereum/src/runtime/abi.rs b/chain/ethereum/src/runtime/abi.rs index 1a4af0663cb..a88e482bc0c 100644 --- a/chain/ethereum/src/runtime/abi.rs +++ b/chain/ethereum/src/runtime/abi.rs @@ -4,12 +4,15 @@ use crate::trigger::{ }; use graph::{ prelude::{ - ethabi, - web3::types::{Log, TransactionReceipt, H256}, + async_trait, ethabi, + web3::{ + self, + types::{Log, TransactionReceipt, H256}, + }, BigInt, }, runtime::{ - asc_get, asc_new, gas::GasCounter, AscHeap, AscIndexId, AscPtr, AscType, + asc_get, asc_new, asc_new_or_null, gas::GasCounter, AscHeap, AscIndexId, AscPtr, AscType, DeterministicHostError, FromAscObj, HostExportError, IndexForAscTypeId, ToAscObj, }, }; @@ -37,15 +40,18 @@ impl AscType for AscLogParamArray { } } +#[async_trait] impl ToAscObj for &[ethabi::LogParam] { - fn to_asc_obj( + async fn to_asc_obj( &self, heap: &mut H, gas: &GasCounter, ) -> Result { - let content: Result, _> = self.iter().map(|x| asc_new(heap, x, gas)).collect(); - let content = content?; - Ok(AscLogParamArray(Array::new(&content, heap, gas)?)) + let mut content = Vec::with_capacity(self.len()); + for x in *self { + content.push(asc_new(heap, x, gas).await?); + } + Ok(AscLogParamArray(Array::new(&content, heap, gas).await?)) } } @@ -68,17 +74,18 @@ impl AscType for AscTopicArray { } } +#[async_trait] impl ToAscObj for Vec { - fn to_asc_obj( + async fn to_asc_obj( &self, heap: &mut H, gas: &GasCounter, ) -> Result { - let topics = self - .iter() - .map(|topic| asc_new(heap, topic, gas)) - .collect::, _>>()?; - Ok(AscTopicArray(Array::new(&topics, heap, gas)?)) + let mut topics = Vec::with_capacity(self.len()); + for topic in self { + topics.push(asc_new(heap, topic, gas).await?); + } + Ok(AscTopicArray(Array::new(&topics, heap, gas).await?)) } } @@ -101,17 +108,19 @@ impl AscType for AscLogArray { } } +#[async_trait] impl ToAscObj for Vec { - fn to_asc_obj( + async fn to_asc_obj( &self, heap: &mut H, gas: &GasCounter, ) -> Result { - let logs = self - .iter() - .map(|log| asc_new(heap, &log, gas)) - .collect::, _>>()?; - Ok(AscLogArray(Array::new(&logs, heap, gas)?)) + let mut logs = Vec::with_capacity(self.len()); + for log in self { + logs.push(asc_new(heap, log, gas).await?); + } + + Ok(AscLogArray(Array::new(&logs, heap, gas).await?)) } } @@ -420,181 +429,184 @@ where const INDEX_ASC_TYPE_ID: IndexForAscTypeId = IndexForAscTypeId::EthereumCall; } +#[async_trait] impl<'a> ToAscObj for EthereumBlockData<'a> { - fn to_asc_obj( + async fn to_asc_obj( &self, heap: &mut H, gas: &GasCounter, ) -> Result { + let size = match self.size() { + Some(size) => asc_new(heap, &BigInt::from_unsigned_u256(&size), gas).await?, + None => AscPtr::null(), + }; + Ok(AscEthereumBlock { - hash: asc_new(heap, self.hash(), gas)?, - parent_hash: asc_new(heap, self.parent_hash(), gas)?, - uncles_hash: asc_new(heap, self.uncles_hash(), gas)?, - author: asc_new(heap, self.author(), gas)?, - state_root: asc_new(heap, self.state_root(), gas)?, - transactions_root: asc_new(heap, self.transactions_root(), gas)?, - receipts_root: asc_new(heap, self.receipts_root(), gas)?, - number: asc_new(heap, &BigInt::from(self.number()), gas)?, - gas_used: asc_new(heap, &BigInt::from_unsigned_u256(self.gas_used()), gas)?, - gas_limit: asc_new(heap, &BigInt::from_unsigned_u256(self.gas_limit()), gas)?, - timestamp: asc_new(heap, &BigInt::from_unsigned_u256(self.timestamp()), gas)?, - difficulty: asc_new(heap, &BigInt::from_unsigned_u256(self.difficulty()), gas)?, + hash: asc_new(heap, self.hash(), gas).await?, + parent_hash: asc_new(heap, self.parent_hash(), gas).await?, + uncles_hash: asc_new(heap, self.uncles_hash(), gas).await?, + author: asc_new(heap, self.author(), gas).await?, + state_root: asc_new(heap, self.state_root(), gas).await?, + transactions_root: asc_new(heap, self.transactions_root(), gas).await?, + receipts_root: asc_new(heap, self.receipts_root(), gas).await?, + number: asc_new(heap, &BigInt::from(self.number()), gas).await?, + gas_used: asc_new(heap, &BigInt::from_unsigned_u256(self.gas_used()), gas).await?, + gas_limit: asc_new(heap, &BigInt::from_unsigned_u256(self.gas_limit()), gas).await?, + timestamp: asc_new(heap, &BigInt::from_unsigned_u256(self.timestamp()), gas).await?, + difficulty: asc_new(heap, &BigInt::from_unsigned_u256(self.difficulty()), gas).await?, total_difficulty: asc_new( heap, &BigInt::from_unsigned_u256(self.total_difficulty()), gas, - )?, - size: self - .size() - .map(|size| asc_new(heap, &BigInt::from_unsigned_u256(&size), gas)) - .unwrap_or(Ok(AscPtr::null()))?, + ) + .await?, + size, }) } } +#[async_trait] impl<'a> ToAscObj for EthereumBlockData<'a> { - fn to_asc_obj( + async fn to_asc_obj( &self, heap: &mut H, gas: &GasCounter, ) -> Result { + let size = match self.size() { + Some(size) => asc_new(heap, &BigInt::from_unsigned_u256(&size), gas).await?, + None => AscPtr::null(), + }; + let base_fee_per_block = match self.base_fee_per_gas() { + Some(base_fee) => asc_new(heap, &BigInt::from_unsigned_u256(&base_fee), gas).await?, + None => AscPtr::null(), + }; + Ok(AscEthereumBlock_0_0_6 { - hash: asc_new(heap, self.hash(), gas)?, - parent_hash: asc_new(heap, self.parent_hash(), gas)?, - uncles_hash: asc_new(heap, self.uncles_hash(), gas)?, - author: asc_new(heap, self.author(), gas)?, - state_root: asc_new(heap, self.state_root(), gas)?, - transactions_root: asc_new(heap, self.transactions_root(), gas)?, - receipts_root: asc_new(heap, self.receipts_root(), gas)?, - number: asc_new(heap, &BigInt::from(self.number()), gas)?, - gas_used: asc_new(heap, &BigInt::from_unsigned_u256(self.gas_used()), gas)?, - gas_limit: asc_new(heap, &BigInt::from_unsigned_u256(self.gas_limit()), gas)?, - timestamp: asc_new(heap, &BigInt::from_unsigned_u256(self.timestamp()), gas)?, - difficulty: asc_new(heap, &BigInt::from_unsigned_u256(self.difficulty()), gas)?, + hash: asc_new(heap, self.hash(), gas).await?, + parent_hash: asc_new(heap, self.parent_hash(), gas).await?, + uncles_hash: asc_new(heap, self.uncles_hash(), gas).await?, + author: asc_new(heap, self.author(), gas).await?, + state_root: asc_new(heap, self.state_root(), gas).await?, + transactions_root: asc_new(heap, self.transactions_root(), gas).await?, + receipts_root: asc_new(heap, self.receipts_root(), gas).await?, + number: asc_new(heap, &BigInt::from(self.number()), gas).await?, + gas_used: asc_new(heap, &BigInt::from_unsigned_u256(self.gas_used()), gas).await?, + gas_limit: asc_new(heap, &BigInt::from_unsigned_u256(self.gas_limit()), gas).await?, + timestamp: asc_new(heap, &BigInt::from_unsigned_u256(self.timestamp()), gas).await?, + difficulty: asc_new(heap, &BigInt::from_unsigned_u256(self.difficulty()), gas).await?, total_difficulty: asc_new( heap, &BigInt::from_unsigned_u256(self.total_difficulty()), gas, - )?, - size: self - .size() - .map(|size| asc_new(heap, &BigInt::from_unsigned_u256(&size), gas)) - .unwrap_or(Ok(AscPtr::null()))?, - base_fee_per_block: self - .base_fee_per_gas() - .map(|base_fee| asc_new(heap, &BigInt::from_unsigned_u256(&base_fee), gas)) - .unwrap_or(Ok(AscPtr::null()))?, + ) + .await?, + size, + base_fee_per_block, }) } } +#[async_trait] impl<'a> ToAscObj for EthereumTransactionData<'a> { - fn to_asc_obj( + async fn to_asc_obj( &self, heap: &mut H, gas: &GasCounter, ) -> Result { Ok(AscEthereumTransaction_0_0_1 { - hash: asc_new(heap, self.hash(), gas)?, - index: asc_new(heap, &BigInt::from_unsigned_u128(self.index()), gas)?, - from: asc_new(heap, self.from(), gas)?, - to: self - .to() - .map(|to| asc_new(heap, &to, gas)) - .unwrap_or(Ok(AscPtr::null()))?, - value: asc_new(heap, &BigInt::from_unsigned_u256(self.value()), gas)?, - gas_limit: asc_new(heap, &BigInt::from_unsigned_u256(self.gas_limit()), gas)?, - gas_price: asc_new(heap, &BigInt::from_unsigned_u256(self.gas_price()), gas)?, + hash: asc_new(heap, self.hash(), gas).await?, + index: asc_new(heap, &BigInt::from_unsigned_u128(self.index()), gas).await?, + from: asc_new(heap, self.from(), gas).await?, + to: asc_new_or_null(heap, self.to(), gas).await?, + value: asc_new(heap, &BigInt::from_unsigned_u256(self.value()), gas).await?, + gas_limit: asc_new(heap, &BigInt::from_unsigned_u256(self.gas_limit()), gas).await?, + gas_price: asc_new(heap, &BigInt::from_unsigned_u256(self.gas_price()), gas).await?, }) } } +#[async_trait] impl<'a> ToAscObj for EthereumTransactionData<'a> { - fn to_asc_obj( + async fn to_asc_obj( &self, heap: &mut H, gas: &GasCounter, ) -> Result { Ok(AscEthereumTransaction_0_0_2 { - hash: asc_new(heap, self.hash(), gas)?, - index: asc_new(heap, &BigInt::from_unsigned_u128(self.index()), gas)?, - from: asc_new(heap, self.from(), gas)?, - to: self - .to() - .map(|to| asc_new(heap, &to, gas)) - .unwrap_or(Ok(AscPtr::null()))?, - value: asc_new(heap, &BigInt::from_unsigned_u256(self.value()), gas)?, - gas_limit: asc_new(heap, &BigInt::from_unsigned_u256(self.gas_limit()), gas)?, - gas_price: asc_new(heap, &BigInt::from_unsigned_u256(self.gas_price()), gas)?, - input: asc_new(heap, self.input(), gas)?, + hash: asc_new(heap, self.hash(), gas).await?, + index: asc_new(heap, &BigInt::from_unsigned_u128(self.index()), gas).await?, + from: asc_new(heap, self.from(), gas).await?, + to: asc_new_or_null(heap, self.to(), gas).await?, + value: asc_new(heap, &BigInt::from_unsigned_u256(self.value()), gas).await?, + gas_limit: asc_new(heap, &BigInt::from_unsigned_u256(self.gas_limit()), gas).await?, + gas_price: asc_new(heap, &BigInt::from_unsigned_u256(self.gas_price()), gas).await?, + input: asc_new(heap, self.input(), gas).await?, }) } } +#[async_trait] impl<'a> ToAscObj for EthereumTransactionData<'a> { - fn to_asc_obj( + async fn to_asc_obj( &self, heap: &mut H, gas: &GasCounter, ) -> Result { Ok(AscEthereumTransaction_0_0_6 { - hash: asc_new(heap, self.hash(), gas)?, - index: asc_new(heap, &BigInt::from_unsigned_u128(self.index()), gas)?, - from: asc_new(heap, self.from(), gas)?, - to: self - .to() - .map(|to| asc_new(heap, &to, gas)) - .unwrap_or(Ok(AscPtr::null()))?, - value: asc_new(heap, &BigInt::from_unsigned_u256(self.value()), gas)?, - gas_limit: asc_new(heap, &BigInt::from_unsigned_u256(self.gas_limit()), gas)?, - gas_price: asc_new(heap, &BigInt::from_unsigned_u256(self.gas_price()), gas)?, - input: asc_new(heap, self.input(), gas)?, - nonce: asc_new(heap, &BigInt::from_unsigned_u256(self.nonce()), gas)?, + hash: asc_new(heap, self.hash(), gas).await?, + index: asc_new(heap, &BigInt::from_unsigned_u128(self.index()), gas).await?, + from: asc_new(heap, self.from(), gas).await?, + to: asc_new_or_null(heap, self.to(), gas).await?, + value: asc_new(heap, &BigInt::from_unsigned_u256(self.value()), gas).await?, + gas_limit: asc_new(heap, &BigInt::from_unsigned_u256(self.gas_limit()), gas).await?, + gas_price: asc_new(heap, &BigInt::from_unsigned_u256(self.gas_price()), gas).await?, + input: asc_new(heap, self.input(), gas).await?, + nonce: asc_new(heap, &BigInt::from_unsigned_u256(self.nonce()), gas).await?, }) } } +#[async_trait] impl<'a, T, B> ToAscObj> for EthereumEventData<'a> where - T: AscType + AscIndexId, - B: AscType + AscIndexId, + T: AscType + AscIndexId + Send, + B: AscType + AscIndexId + Send, EthereumTransactionData<'a>: ToAscObj, EthereumBlockData<'a>: ToAscObj, { - fn to_asc_obj( + async fn to_asc_obj( &self, heap: &mut H, gas: &GasCounter, ) -> Result, HostExportError> { Ok(AscEthereumEvent { - address: asc_new(heap, self.address(), gas)?, - log_index: asc_new(heap, &BigInt::from_unsigned_u256(self.log_index()), gas)?, + address: asc_new(heap, self.address(), gas).await?, + log_index: asc_new(heap, &BigInt::from_unsigned_u256(self.log_index()), gas).await?, transaction_log_index: asc_new( heap, &BigInt::from_unsigned_u256(self.transaction_log_index()), gas, - )?, - log_type: self - .log_type() - .as_ref() - .map(|log_type| asc_new(heap, log_type, gas)) - .unwrap_or(Ok(AscPtr::null()))?, - block: asc_new::(heap, &self.block, gas)?, - transaction: asc_new::(heap, &self.transaction, gas)?, - params: asc_new(heap, &self.params, gas)?, + ) + .await?, + log_type: asc_new_or_null(heap, self.log_type(), gas).await?, + block: asc_new::(heap, &self.block, gas).await?, + transaction: asc_new::(heap, &self.transaction, gas) + .await?, + params: asc_new(heap, &self.params, gas).await?, }) } } +#[async_trait] impl<'a, T, B> ToAscObj> for (EthereumEventData<'a>, Option<&TransactionReceipt>) where - T: AscType + AscIndexId, - B: AscType + AscIndexId, + T: AscType + AscIndexId + Send, + B: AscType + AscIndexId + Send, EthereumTransactionData<'a>: ToAscObj, EthereumBlockData<'a>: ToAscObj, { - fn to_asc_obj( + async fn to_asc_obj( &self, heap: &mut H, gas: &GasCounter, @@ -608,9 +620,9 @@ where block, transaction, params, - } = event_data.to_asc_obj(heap, gas)?; + } = event_data.to_asc_obj(heap, gas).await?; let receipt = if let Some(receipt_data) = optional_receipt { - asc_new(heap, receipt_data, gas)? + asc_new(heap, receipt_data, gas).await? } else { AscPtr::null() }; @@ -627,117 +639,106 @@ where } } +async fn asc_new_or_null_u256( + heap: &mut H, + value: &Option, + gas: &GasCounter, +) -> Result, HostExportError> { + match value { + Some(value) => asc_new(heap, &BigInt::from_unsigned_u256(value), gas).await, + None => Ok(AscPtr::null()), + } +} + +async fn asc_new_or_null_u64( + heap: &mut H, + value: &Option, + gas: &GasCounter, +) -> Result, HostExportError> { + match value { + Some(value) => asc_new(heap, &BigInt::from(*value), gas).await, + None => Ok(AscPtr::null()), + } +} + +#[async_trait] impl ToAscObj for Log { - fn to_asc_obj( + async fn to_asc_obj( &self, heap: &mut H, gas: &GasCounter, ) -> Result { + let removed = match self.removed { + Some(removed) => asc_new(heap, &AscWrapped { inner: removed }, gas).await?, + None => AscPtr::null(), + }; Ok(AscEthereumLog { - address: asc_new(heap, &self.address, gas)?, - topics: asc_new(heap, &self.topics, gas)?, - data: asc_new(heap, self.data.0.as_slice(), gas)?, - block_hash: self - .block_hash - .map(|block_hash| asc_new(heap, &block_hash, gas)) - .unwrap_or(Ok(AscPtr::null()))?, - block_number: self - .block_number - .map(|block_number| asc_new(heap, &BigInt::from(block_number), gas)) - .unwrap_or(Ok(AscPtr::null()))?, - transaction_hash: self - .transaction_hash - .map(|txn_hash| asc_new(heap, &txn_hash, gas)) - .unwrap_or(Ok(AscPtr::null()))?, - transaction_index: self - .transaction_index - .map(|txn_index| asc_new(heap, &BigInt::from(txn_index), gas)) - .unwrap_or(Ok(AscPtr::null()))?, - log_index: self - .log_index - .map(|log_index| asc_new(heap, &BigInt::from_unsigned_u256(&log_index), gas)) - .unwrap_or(Ok(AscPtr::null()))?, - transaction_log_index: self - .transaction_log_index - .map(|index| asc_new(heap, &BigInt::from_unsigned_u256(&index), gas)) - .unwrap_or(Ok(AscPtr::null()))?, - log_type: self - .log_type - .as_ref() - .map(|log_type| asc_new(heap, &log_type, gas)) - .unwrap_or(Ok(AscPtr::null()))?, - removed: self - .removed - .map(|removed| asc_new(heap, &AscWrapped { inner: removed }, gas)) - .unwrap_or(Ok(AscPtr::null()))?, + address: asc_new(heap, &self.address, gas).await?, + topics: asc_new(heap, &self.topics, gas).await?, + data: asc_new(heap, self.data.0.as_slice(), gas).await?, + block_hash: asc_new_or_null(heap, &self.block_hash, gas).await?, + block_number: asc_new_or_null_u64(heap, &self.block_number, gas).await?, + transaction_hash: asc_new_or_null(heap, &self.transaction_hash, gas).await?, + transaction_index: asc_new_or_null_u64(heap, &self.transaction_index, gas).await?, + log_index: asc_new_or_null_u256(heap, &self.log_index, gas).await?, + transaction_log_index: asc_new_or_null_u256(heap, &self.transaction_log_index, gas) + .await?, + log_type: asc_new_or_null(heap, &self.log_type, gas).await?, + removed, }) } } +#[async_trait] impl ToAscObj for &TransactionReceipt { - fn to_asc_obj( + async fn to_asc_obj( &self, heap: &mut H, gas: &GasCounter, ) -> Result { Ok(AscEthereumTransactionReceipt { - transaction_hash: asc_new(heap, &self.transaction_hash, gas)?, - transaction_index: asc_new(heap, &BigInt::from(self.transaction_index), gas)?, - block_hash: self - .block_hash - .map(|block_hash| asc_new(heap, &block_hash, gas)) - .unwrap_or(Ok(AscPtr::null()))?, - block_number: self - .block_number - .map(|block_number| asc_new(heap, &BigInt::from(block_number), gas)) - .unwrap_or(Ok(AscPtr::null()))?, + transaction_hash: asc_new(heap, &self.transaction_hash, gas).await?, + transaction_index: asc_new(heap, &BigInt::from(self.transaction_index), gas).await?, + block_hash: asc_new_or_null(heap, &self.block_hash, gas).await?, + block_number: asc_new_or_null_u64(heap, &self.block_number, gas).await?, cumulative_gas_used: asc_new( heap, &BigInt::from_unsigned_u256(&self.cumulative_gas_used), gas, - )?, - gas_used: self - .gas_used - .map(|gas_used| asc_new(heap, &BigInt::from_unsigned_u256(&gas_used), gas)) - .unwrap_or(Ok(AscPtr::null()))?, - contract_address: self - .contract_address - .map(|contract_address| asc_new(heap, &contract_address, gas)) - .unwrap_or(Ok(AscPtr::null()))?, - logs: asc_new(heap, &self.logs, gas)?, - status: self - .status - .map(|status| asc_new(heap, &BigInt::from(status), gas)) - .unwrap_or(Ok(AscPtr::null()))?, - root: self - .root - .map(|root| asc_new(heap, &root, gas)) - .unwrap_or(Ok(AscPtr::null()))?, - logs_bloom: asc_new(heap, self.logs_bloom.as_bytes(), gas)?, + ) + .await?, + gas_used: asc_new_or_null_u256(heap, &self.gas_used, gas).await?, + contract_address: asc_new_or_null(heap, &self.contract_address, gas).await?, + logs: asc_new(heap, &self.logs, gas).await?, + status: asc_new_or_null_u64(heap, &self.status, gas).await?, + root: asc_new_or_null(heap, &self.root, gas).await?, + logs_bloom: asc_new(heap, self.logs_bloom.as_bytes(), gas).await?, }) } } +#[async_trait] impl<'a> ToAscObj for EthereumCallData<'a> { - fn to_asc_obj( + async fn to_asc_obj( &self, heap: &mut H, gas: &GasCounter, ) -> Result { Ok(AscEthereumCall { - address: asc_new(heap, self.to(), gas)?, - block: asc_new(heap, &self.block, gas)?, - transaction: asc_new(heap, &self.transaction, gas)?, - inputs: asc_new(heap, &self.inputs, gas)?, - outputs: asc_new(heap, &self.outputs, gas)?, + address: asc_new(heap, self.to(), gas).await?, + block: asc_new(heap, &self.block, gas).await?, + transaction: asc_new(heap, &self.transaction, gas).await?, + inputs: asc_new(heap, &self.inputs, gas).await?, + outputs: asc_new(heap, &self.outputs, gas).await?, }) } } +#[async_trait] impl<'a> ToAscObj> for EthereumCallData<'a> { - fn to_asc_obj( + async fn to_asc_obj( &self, heap: &mut H, gas: &GasCounter, @@ -746,20 +747,21 @@ impl<'a> ToAscObj { Ok(AscEthereumCall_0_0_3 { - to: asc_new(heap, self.to(), gas)?, - from: asc_new(heap, self.from(), gas)?, - block: asc_new(heap, &self.block, gas)?, - transaction: asc_new(heap, &self.transaction, gas)?, - inputs: asc_new(heap, &self.inputs, gas)?, - outputs: asc_new(heap, &self.outputs, gas)?, + to: asc_new(heap, self.to(), gas).await?, + from: asc_new(heap, self.from(), gas).await?, + block: asc_new(heap, &self.block, gas).await?, + transaction: asc_new(heap, &self.transaction, gas).await?, + inputs: asc_new(heap, &self.inputs, gas).await?, + outputs: asc_new(heap, &self.outputs, gas).await?, }) } } +#[async_trait] impl<'a> ToAscObj> for EthereumCallData<'a> { - fn to_asc_obj( + async fn to_asc_obj( &self, heap: &mut H, gas: &GasCounter, @@ -768,25 +770,26 @@ impl<'a> ToAscObj { Ok(AscEthereumCall_0_0_3 { - to: asc_new(heap, self.to(), gas)?, - from: asc_new(heap, self.from(), gas)?, - block: asc_new(heap, &self.block, gas)?, - transaction: asc_new(heap, &self.transaction, gas)?, - inputs: asc_new(heap, &self.inputs, gas)?, - outputs: asc_new(heap, &self.outputs, gas)?, + to: asc_new(heap, self.to(), gas).await?, + from: asc_new(heap, self.from(), gas).await?, + block: asc_new(heap, &self.block, gas).await?, + transaction: asc_new(heap, &self.transaction, gas).await?, + inputs: asc_new(heap, &self.inputs, gas).await?, + outputs: asc_new(heap, &self.outputs, gas).await?, }) } } +#[async_trait] impl ToAscObj for ethabi::LogParam { - fn to_asc_obj( + async fn to_asc_obj( &self, heap: &mut H, gas: &GasCounter, ) -> Result { Ok(AscLogParam { - name: asc_new(heap, self.name.as_str(), gas)?, - value: asc_new(heap, &self.value, gas)?, + name: asc_new(heap, self.name.as_str(), gas).await?, + value: asc_new(heap, &self.value, gas).await?, }) } } diff --git a/chain/ethereum/src/runtime/runtime_adapter.rs b/chain/ethereum/src/runtime/runtime_adapter.rs index 5ebb8a62570..8b11ada37cc 100644 --- a/chain/ethereum/src/runtime/runtime_adapter.rs +++ b/chain/ethereum/src/runtime/runtime_adapter.rs @@ -14,6 +14,7 @@ use graph::data::store::scalar::BigInt; use graph::data::subgraph::{API_VERSION_0_0_4, API_VERSION_0_0_9}; use graph::data_source; use graph::data_source::common::{ContractCall, MappingABI}; +use graph::futures03::FutureExt as _; use graph::prelude::web3::types::H160; use graph::runtime::gas::Gas; use graph::runtime::{AscIndexId, IndexForAscTypeId}; @@ -95,20 +96,27 @@ impl blockchain::RuntimeAdapter for RuntimeAdapter { let call_cache = call_cache.clone(); let abis = abis.clone(); move |ctx, wasm_ptr| { - let eth_adapter = - eth_adapters.call_or_cheapest(Some(&NodeCapabilities { - archive, - traces: false, - }))?; - ethereum_call( - ð_adapter, - call_cache.clone(), - ctx, - wasm_ptr, - &abis, - eth_call_gas, - ) - .map(|ptr| ptr.wasm_ptr()) + let eth_adapters = eth_adapters.cheap_clone(); + let call_cache = call_cache.cheap_clone(); + let abis = abis.cheap_clone(); + async move { + let eth_adapter = + eth_adapters.call_or_cheapest(Some(&NodeCapabilities { + archive, + traces: false, + }))?; + ethereum_call( + ð_adapter, + call_cache.clone(), + ctx, + wasm_ptr, + &abis, + eth_call_gas, + ) + .await + .map(|ptr| ptr.wasm_ptr()) + } + .boxed() } }), }, @@ -117,26 +125,37 @@ impl blockchain::RuntimeAdapter for RuntimeAdapter { func: Arc::new({ let eth_adapters = eth_adapters.clone(); move |ctx, wasm_ptr| { - let eth_adapter = - eth_adapters.unverified_cheapest_with(&NodeCapabilities { - archive, - traces: false, - })?; - eth_get_balance(ð_adapter, ctx, wasm_ptr).map(|ptr| ptr.wasm_ptr()) + let eth_adapters = eth_adapters.cheap_clone(); + async move { + let eth_adapter = + eth_adapters.unverified_cheapest_with(&NodeCapabilities { + archive, + traces: false, + })?; + eth_get_balance(ð_adapter, ctx, wasm_ptr) + .await + .map(|ptr| ptr.wasm_ptr()) + } + .boxed() } }), }, HostFn { name: "ethereum.hasCode", func: Arc::new({ - let eth_adapters = eth_adapters.clone(); move |ctx, wasm_ptr| { - let eth_adapter = - eth_adapters.unverified_cheapest_with(&NodeCapabilities { - archive, - traces: false, - })?; - eth_has_code(ð_adapter, ctx, wasm_ptr).map(|ptr| ptr.wasm_ptr()) + let eth_adapters = eth_adapters.cheap_clone(); + async move { + let eth_adapter = + eth_adapters.unverified_cheapest_with(&NodeCapabilities { + archive, + traces: false, + })?; + eth_has_code(ð_adapter, ctx, wasm_ptr) + .await + .map(|ptr| ptr.wasm_ptr()) + } + .boxed() } }), }, @@ -170,10 +189,10 @@ impl blockchain::RuntimeAdapter for RuntimeAdapter { } /// function ethereum.call(call: SmartContractCall): Array | null -fn ethereum_call( +async fn ethereum_call( eth_adapter: &EthereumAdapter, call_cache: Arc, - ctx: HostFnCtx, + ctx: HostFnCtx<'_>, wasm_ptr: u32, abis: &[Arc], eth_call_gas: Option, @@ -199,14 +218,15 @@ fn ethereum_call( abis, eth_call_gas, ctx.metrics.cheap_clone(), - )?; + ) + .await?; match result { - Some(tokens) => Ok(asc_new(ctx.heap, tokens.as_slice(), &ctx.gas)?), + Some(tokens) => Ok(asc_new(ctx.heap, tokens.as_slice(), &ctx.gas).await?), None => Ok(AscPtr::null()), } } -fn eth_get_balance( +async fn eth_get_balance( eth_adapter: &EthereumAdapter, ctx: HostFnCtx<'_>, wasm_ptr: u32, @@ -225,12 +245,14 @@ fn eth_get_balance( let address: H160 = asc_get(ctx.heap, wasm_ptr.into(), &ctx.gas, 0)?; - let result = graph::block_on(eth_adapter.get_balance(logger, address, block_ptr.clone())); + let result = eth_adapter + .get_balance(logger, address, block_ptr.clone()) + .await; match result { Ok(v) => { let bigint = BigInt::from_unsigned_u256(&v); - Ok(asc_new(ctx.heap, &bigint, &ctx.gas)?) + Ok(asc_new(ctx.heap, &bigint, &ctx.gas).await?) } // Retry on any kind of error Err(EthereumRpcError::Web3Error(e)) => Err(HostExportError::PossibleReorg(e.into())), @@ -240,7 +262,7 @@ fn eth_get_balance( } } -fn eth_has_code( +async fn eth_has_code( eth_adapter: &EthereumAdapter, ctx: HostFnCtx<'_>, wasm_ptr: u32, @@ -259,11 +281,13 @@ fn eth_has_code( let address: H160 = asc_get(ctx.heap, wasm_ptr.into(), &ctx.gas, 0)?; - let result = graph::block_on(eth_adapter.get_code(logger, address, block_ptr.clone())) + let result = eth_adapter + .get_code(logger, address, block_ptr.clone()) + .await .map(|v| !v.0.is_empty()); match result { - Ok(v) => Ok(asc_new(ctx.heap, &AscWrapped { inner: v }, &ctx.gas)?), + Ok(v) => Ok(asc_new(ctx.heap, &AscWrapped { inner: v }, &ctx.gas).await?), // Retry on any kind of error Err(EthereumRpcError::Web3Error(e)) => Err(HostExportError::PossibleReorg(e.into())), Err(EthereumRpcError::Timeout) => Err(HostExportError::PossibleReorg( @@ -273,7 +297,7 @@ fn eth_has_code( } /// Returns `Ok(None)` if the call was reverted. -fn eth_call( +async fn eth_call( eth_adapter: &EthereumAdapter, call_cache: Arc, logger: &Logger, @@ -331,11 +355,10 @@ fn eth_call( // Run Ethereum call in tokio runtime let logger1 = logger.clone(); let call_cache = call_cache.clone(); - let (result, source) = - match graph::block_on(eth_adapter.contract_call(&logger1, &call, call_cache)) { - Ok((result, source)) => (Ok(result), source), - Err(e) => (Err(e), call::Source::Rpc), - }; + let (result, source) = match eth_adapter.contract_call(&logger1, &call, call_cache).await { + Ok((result, source)) => (Ok(result), source), + Err(e) => (Err(e), call::Source::Rpc), + }; let result = match result { Ok(res) => Ok(res), diff --git a/chain/ethereum/src/trigger.rs b/chain/ethereum/src/trigger.rs index c87a0211ae9..017e3f58194 100644 --- a/chain/ethereum/src/trigger.rs +++ b/chain/ethereum/src/trigger.rs @@ -4,6 +4,7 @@ use graph::data::subgraph::API_VERSION_0_0_2; use graph::data::subgraph::API_VERSION_0_0_6; use graph::data::subgraph::API_VERSION_0_0_7; use graph::data_source::common::DeclaredCall; +use graph::prelude::async_trait; use graph::prelude::ethabi::ethereum_types::H160; use graph::prelude::ethabi::ethereum_types::H256; use graph::prelude::ethabi::ethereum_types::U128; @@ -129,8 +130,9 @@ impl std::fmt::Debug for MappingTrigger { } } +#[async_trait] impl ToAscPtr for MappingTrigger { - fn to_asc_ptr( + async fn to_asc_ptr( self, heap: &mut H, gas: &GasCounter, @@ -159,28 +161,31 @@ impl ToAscPtr for MappingTrigger { >, _, _, - >(heap, &(ethereum_event_data, receipt.as_deref()), gas)? + >(heap, &(ethereum_event_data, receipt.as_deref()), gas) + .await? .erase() } else if api_version >= &API_VERSION_0_0_6 { asc_new::< AscEthereumEvent, _, _, - >(heap, ðereum_event_data, gas)? + >(heap, ðereum_event_data, gas) + .await? .erase() } else if api_version >= &API_VERSION_0_0_2 { asc_new::< AscEthereumEvent, _, _, - >(heap, ðereum_event_data, gas)? + >(heap, ðereum_event_data, gas) + .await? .erase() } else { asc_new::< AscEthereumEvent, _, _, - >(heap, ðereum_event_data, gas)? + >(heap, ðereum_event_data, gas).await? .erase() } } @@ -197,25 +202,33 @@ impl ToAscPtr for MappingTrigger { AscEthereumCall_0_0_3, _, _, - >(heap, &call, gas)? + >(heap, &call, gas) + .await? .erase() } else if heap.api_version() >= &Version::new(0, 0, 3) { asc_new::< AscEthereumCall_0_0_3, _, _, - >(heap, &call, gas)? + >(heap, &call, gas) + .await? .erase() } else { - asc_new::(heap, &call, gas)?.erase() + asc_new::(heap, &call, gas) + .await? + .erase() } } MappingTrigger::Block { block } => { let block = EthereumBlockData::from(block.as_ref()); if heap.api_version() >= &Version::new(0, 0, 6) { - asc_new::(heap, &block, gas)?.erase() + asc_new::(heap, &block, gas) + .await? + .erase() } else { - asc_new::(heap, &block, gas)?.erase() + asc_new::(heap, &block, gas) + .await? + .erase() } } }) diff --git a/chain/near/src/runtime/abi.rs b/chain/near/src/runtime/abi.rs index 252a4ffa49f..7b6da023c95 100644 --- a/chain/near/src/runtime/abi.rs +++ b/chain/near/src/runtime/abi.rs @@ -1,124 +1,137 @@ use crate::codec; use crate::trigger::ReceiptWithOutcome; use graph::anyhow::anyhow; +use graph::prelude::async_trait; use graph::runtime::gas::GasCounter; use graph::runtime::{asc_new, AscHeap, AscPtr, DeterministicHostError, HostExportError, ToAscObj}; use graph_runtime_wasm::asc_abi::class::{Array, AscEnum, EnumPayload, Uint8Array}; pub(crate) use super::generated::*; +#[async_trait] impl ToAscObj for codec::Block { - fn to_asc_obj( + async fn to_asc_obj( &self, heap: &mut H, gas: &GasCounter, ) -> Result { Ok(AscBlock { - author: asc_new(heap, &self.author, gas)?, - header: asc_new(heap, self.header(), gas)?, - chunks: asc_new(heap, &self.chunk_headers, gas)?, + author: asc_new(heap, &self.author, gas).await?, + header: asc_new(heap, self.header(), gas).await?, + chunks: asc_new(heap, &self.chunk_headers, gas).await?, }) } } +#[async_trait] impl ToAscObj for codec::BlockHeader { - fn to_asc_obj( + async fn to_asc_obj( &self, heap: &mut H, gas: &GasCounter, ) -> Result { - let chunk_mask = Array::new(self.chunk_mask.as_ref(), heap, gas)?; + let chunk_mask = Array::new(self.chunk_mask.as_ref(), heap, gas).await?; Ok(AscBlockHeader { height: self.height, prev_height: self.prev_height, - epoch_id: asc_new(heap, self.epoch_id.as_ref().unwrap(), gas)?, - next_epoch_id: asc_new(heap, self.next_epoch_id.as_ref().unwrap(), gas)?, - hash: asc_new(heap, self.hash.as_ref().unwrap(), gas)?, - prev_hash: asc_new(heap, self.prev_hash.as_ref().unwrap(), gas)?, - prev_state_root: asc_new(heap, self.prev_state_root.as_ref().unwrap(), gas)?, - chunk_receipts_root: asc_new(heap, self.chunk_receipts_root.as_ref().unwrap(), gas)?, - chunk_headers_root: asc_new(heap, self.chunk_headers_root.as_ref().unwrap(), gas)?, - chunk_tx_root: asc_new(heap, self.chunk_tx_root.as_ref().unwrap(), gas)?, - outcome_root: asc_new(heap, self.outcome_root.as_ref().unwrap(), gas)?, + epoch_id: asc_new(heap, self.epoch_id.as_ref().unwrap(), gas).await?, + next_epoch_id: asc_new(heap, self.next_epoch_id.as_ref().unwrap(), gas).await?, + hash: asc_new(heap, self.hash.as_ref().unwrap(), gas).await?, + prev_hash: asc_new(heap, self.prev_hash.as_ref().unwrap(), gas).await?, + prev_state_root: asc_new(heap, self.prev_state_root.as_ref().unwrap(), gas).await?, + chunk_receipts_root: asc_new(heap, self.chunk_receipts_root.as_ref().unwrap(), gas) + .await?, + chunk_headers_root: asc_new(heap, self.chunk_headers_root.as_ref().unwrap(), gas) + .await?, + chunk_tx_root: asc_new(heap, self.chunk_tx_root.as_ref().unwrap(), gas).await?, + outcome_root: asc_new(heap, self.outcome_root.as_ref().unwrap(), gas).await?, chunks_included: self.chunks_included, - challenges_root: asc_new(heap, self.challenges_root.as_ref().unwrap(), gas)?, + challenges_root: asc_new(heap, self.challenges_root.as_ref().unwrap(), gas).await?, timestamp_nanosec: self.timestamp_nanosec, - random_value: asc_new(heap, self.random_value.as_ref().unwrap(), gas)?, - validator_proposals: asc_new(heap, &self.validator_proposals, gas)?, - chunk_mask: AscPtr::alloc_obj(chunk_mask, heap, gas)?, - gas_price: asc_new(heap, self.gas_price.as_ref().unwrap(), gas)?, + random_value: asc_new(heap, self.random_value.as_ref().unwrap(), gas).await?, + validator_proposals: asc_new(heap, &self.validator_proposals, gas).await?, + chunk_mask: AscPtr::alloc_obj(chunk_mask, heap, gas).await?, + gas_price: asc_new(heap, self.gas_price.as_ref().unwrap(), gas).await?, block_ordinal: self.block_ordinal, - total_supply: asc_new(heap, self.total_supply.as_ref().unwrap(), gas)?, - challenges_result: asc_new(heap, &self.challenges_result, gas)?, - last_final_block: asc_new(heap, self.last_final_block.as_ref().unwrap(), gas)?, - last_ds_final_block: asc_new(heap, self.last_ds_final_block.as_ref().unwrap(), gas)?, - next_bp_hash: asc_new(heap, self.next_bp_hash.as_ref().unwrap(), gas)?, - block_merkle_root: asc_new(heap, self.block_merkle_root.as_ref().unwrap(), gas)?, - epoch_sync_data_hash: asc_new(heap, self.epoch_sync_data_hash.as_slice(), gas)?, - approvals: asc_new(heap, &self.approvals, gas)?, - signature: asc_new(heap, &self.signature.as_ref().unwrap(), gas)?, + total_supply: asc_new(heap, self.total_supply.as_ref().unwrap(), gas).await?, + challenges_result: asc_new(heap, &self.challenges_result, gas).await?, + last_final_block: asc_new(heap, self.last_final_block.as_ref().unwrap(), gas).await?, + last_ds_final_block: asc_new(heap, self.last_ds_final_block.as_ref().unwrap(), gas) + .await?, + next_bp_hash: asc_new(heap, self.next_bp_hash.as_ref().unwrap(), gas).await?, + block_merkle_root: asc_new(heap, self.block_merkle_root.as_ref().unwrap(), gas).await?, + epoch_sync_data_hash: asc_new(heap, self.epoch_sync_data_hash.as_slice(), gas).await?, + approvals: asc_new(heap, &self.approvals, gas).await?, + signature: asc_new(heap, &self.signature.as_ref().unwrap(), gas).await?, latest_protocol_version: self.latest_protocol_version, }) } } +#[async_trait] impl ToAscObj for codec::ChunkHeader { - fn to_asc_obj( + async fn to_asc_obj( &self, heap: &mut H, gas: &GasCounter, ) -> Result { Ok(AscChunkHeader { - chunk_hash: asc_new(heap, self.chunk_hash.as_slice(), gas)?, - signature: asc_new(heap, &self.signature.as_ref().unwrap(), gas)?, - prev_block_hash: asc_new(heap, self.prev_block_hash.as_slice(), gas)?, - prev_state_root: asc_new(heap, self.prev_state_root.as_slice(), gas)?, - encoded_merkle_root: asc_new(heap, self.encoded_merkle_root.as_slice(), gas)?, + chunk_hash: asc_new(heap, self.chunk_hash.as_slice(), gas).await?, + signature: asc_new(heap, &self.signature.as_ref().unwrap(), gas).await?, + prev_block_hash: asc_new(heap, self.prev_block_hash.as_slice(), gas).await?, + prev_state_root: asc_new(heap, self.prev_state_root.as_slice(), gas).await?, + encoded_merkle_root: asc_new(heap, self.encoded_merkle_root.as_slice(), gas).await?, encoded_length: self.encoded_length, height_created: self.height_created, height_included: self.height_included, shard_id: self.shard_id, gas_used: self.gas_used, gas_limit: self.gas_limit, - balance_burnt: asc_new(heap, self.balance_burnt.as_ref().unwrap(), gas)?, - outgoing_receipts_root: asc_new(heap, self.outgoing_receipts_root.as_slice(), gas)?, - tx_root: asc_new(heap, self.tx_root.as_slice(), gas)?, - validator_proposals: asc_new(heap, &self.validator_proposals, gas)?, + balance_burnt: asc_new(heap, self.balance_burnt.as_ref().unwrap(), gas).await?, + outgoing_receipts_root: asc_new(heap, self.outgoing_receipts_root.as_slice(), gas) + .await?, + tx_root: asc_new(heap, self.tx_root.as_slice(), gas).await?, + validator_proposals: asc_new(heap, &self.validator_proposals, gas).await?, _padding: 0, }) } } +#[async_trait] impl ToAscObj for Vec { - fn to_asc_obj( + async fn to_asc_obj( &self, heap: &mut H, gas: &GasCounter, ) -> Result { - let content: Result, _> = self.iter().map(|x| asc_new(heap, x, gas)).collect(); - let content = content?; - Ok(AscChunkHeaderArray(Array::new(&content, heap, gas)?)) + let mut content = Vec::new(); + for x in self { + content.push(asc_new(heap, x, gas).await?); + } + Ok(AscChunkHeaderArray(Array::new(&content, heap, gas).await?)) } } +#[async_trait] impl ToAscObj for ReceiptWithOutcome { - fn to_asc_obj( + async fn to_asc_obj( &self, heap: &mut H, gas: &GasCounter, ) -> Result { Ok(AscReceiptWithOutcome { - outcome: asc_new(heap, &self.outcome, gas)?, - receipt: asc_new(heap, &self.receipt, gas)?, - block: asc_new(heap, self.block.as_ref(), gas)?, + outcome: asc_new(heap, &self.outcome, gas).await?, + receipt: asc_new(heap, &self.receipt, gas).await?, + block: asc_new(heap, self.block.as_ref(), gas).await?, }) } } +#[async_trait] impl ToAscObj for codec::Receipt { - fn to_asc_obj( + async fn to_asc_obj( &self, heap: &mut H, gas: &GasCounter, @@ -133,21 +146,23 @@ impl ToAscObj for codec::Receipt { }; Ok(AscActionReceipt { - id: asc_new(heap, &self.receipt_id.as_ref().unwrap(), gas)?, - predecessor_id: asc_new(heap, &self.predecessor_id, gas)?, - receiver_id: asc_new(heap, &self.receiver_id, gas)?, - signer_id: asc_new(heap, &action.signer_id, gas)?, - signer_public_key: asc_new(heap, action.signer_public_key.as_ref().unwrap(), gas)?, - gas_price: asc_new(heap, action.gas_price.as_ref().unwrap(), gas)?, - output_data_receivers: asc_new(heap, &action.output_data_receivers, gas)?, - input_data_ids: asc_new(heap, &action.input_data_ids, gas)?, - actions: asc_new(heap, &action.actions, gas)?, + id: asc_new(heap, &self.receipt_id.as_ref().unwrap(), gas).await?, + predecessor_id: asc_new(heap, &self.predecessor_id, gas).await?, + receiver_id: asc_new(heap, &self.receiver_id, gas).await?, + signer_id: asc_new(heap, &action.signer_id, gas).await?, + signer_public_key: asc_new(heap, action.signer_public_key.as_ref().unwrap(), gas) + .await?, + gas_price: asc_new(heap, action.gas_price.as_ref().unwrap(), gas).await?, + output_data_receivers: asc_new(heap, &action.output_data_receivers, gas).await?, + input_data_ids: asc_new(heap, &action.input_data_ids, gas).await?, + actions: asc_new(heap, &action.actions, gas).await?, }) } } +#[async_trait] impl ToAscObj for codec::Action { - fn to_asc_obj( + async fn to_asc_obj( &self, heap: &mut H, gas: &GasCounter, @@ -155,35 +170,35 @@ impl ToAscObj for codec::Action { let (kind, payload) = match self.action.as_ref().unwrap() { codec::action::Action::CreateAccount(action) => ( AscActionKind::CreateAccount, - asc_new(heap, action, gas)?.to_payload(), + asc_new(heap, action, gas).await?.to_payload(), ), codec::action::Action::DeployContract(action) => ( AscActionKind::DeployContract, - asc_new(heap, action, gas)?.to_payload(), + asc_new(heap, action, gas).await?.to_payload(), ), codec::action::Action::FunctionCall(action) => ( AscActionKind::FunctionCall, - asc_new(heap, action, gas)?.to_payload(), + asc_new(heap, action, gas).await?.to_payload(), ), codec::action::Action::Transfer(action) => ( AscActionKind::Transfer, - asc_new(heap, action, gas)?.to_payload(), + asc_new(heap, action, gas).await?.to_payload(), ), codec::action::Action::Stake(action) => ( AscActionKind::Stake, - asc_new(heap, action, gas)?.to_payload(), + asc_new(heap, action, gas).await?.to_payload(), ), codec::action::Action::AddKey(action) => ( AscActionKind::AddKey, - asc_new(heap, action, gas)?.to_payload(), + asc_new(heap, action, gas).await?.to_payload(), ), codec::action::Action::DeleteKey(action) => ( AscActionKind::DeleteKey, - asc_new(heap, action, gas)?.to_payload(), + asc_new(heap, action, gas).await?.to_payload(), ), codec::action::Action::DeleteAccount(action) => ( AscActionKind::DeleteAccount, - asc_new(heap, action, gas)?.to_payload(), + asc_new(heap, action, gas).await?.to_payload(), ), }; @@ -195,20 +210,24 @@ impl ToAscObj for codec::Action { } } +#[async_trait] impl ToAscObj for Vec { - fn to_asc_obj( + async fn to_asc_obj( &self, heap: &mut H, gas: &GasCounter, ) -> Result { - let content: Result, _> = self.iter().map(|x| asc_new(heap, x, gas)).collect(); - let content = content?; - Ok(AscActionEnumArray(Array::new(&content, heap, gas)?)) + let mut content = Vec::new(); + for x in self { + content.push(asc_new(heap, x, gas).await?); + } + Ok(AscActionEnumArray(Array::new(&content, heap, gas).await?)) } } +#[async_trait] impl ToAscObj for codec::CreateAccountAction { - fn to_asc_obj( + async fn to_asc_obj( &self, _heap: &mut H, _gas: &GasCounter, @@ -217,88 +236,95 @@ impl ToAscObj for codec::CreateAccountAction { } } +#[async_trait] impl ToAscObj for codec::DeployContractAction { - fn to_asc_obj( + async fn to_asc_obj( &self, heap: &mut H, gas: &GasCounter, ) -> Result { Ok(AscDeployContractAction { - code: asc_new(heap, self.code.as_slice(), gas)?, + code: asc_new(heap, self.code.as_slice(), gas).await?, }) } } +#[async_trait] impl ToAscObj for codec::FunctionCallAction { - fn to_asc_obj( + async fn to_asc_obj( &self, heap: &mut H, gas: &GasCounter, ) -> Result { Ok(AscFunctionCallAction { - method_name: asc_new(heap, &self.method_name, gas)?, - args: asc_new(heap, self.args.as_slice(), gas)?, + method_name: asc_new(heap, &self.method_name, gas).await?, + args: asc_new(heap, self.args.as_slice(), gas).await?, gas: self.gas, - deposit: asc_new(heap, self.deposit.as_ref().unwrap(), gas)?, + deposit: asc_new(heap, self.deposit.as_ref().unwrap(), gas).await?, _padding: 0, }) } } +#[async_trait] impl ToAscObj for codec::TransferAction { - fn to_asc_obj( + async fn to_asc_obj( &self, heap: &mut H, gas: &GasCounter, ) -> Result { Ok(AscTransferAction { - deposit: asc_new(heap, self.deposit.as_ref().unwrap(), gas)?, + deposit: asc_new(heap, self.deposit.as_ref().unwrap(), gas).await?, }) } } +#[async_trait] impl ToAscObj for codec::StakeAction { - fn to_asc_obj( + async fn to_asc_obj( &self, heap: &mut H, gas: &GasCounter, ) -> Result { Ok(AscStakeAction { - stake: asc_new(heap, self.stake.as_ref().unwrap(), gas)?, - public_key: asc_new(heap, self.public_key.as_ref().unwrap(), gas)?, + stake: asc_new(heap, self.stake.as_ref().unwrap(), gas).await?, + public_key: asc_new(heap, self.public_key.as_ref().unwrap(), gas).await?, }) } } +#[async_trait] impl ToAscObj for codec::AddKeyAction { - fn to_asc_obj( + async fn to_asc_obj( &self, heap: &mut H, gas: &GasCounter, ) -> Result { Ok(AscAddKeyAction { - public_key: asc_new(heap, self.public_key.as_ref().unwrap(), gas)?, - access_key: asc_new(heap, self.access_key.as_ref().unwrap(), gas)?, + public_key: asc_new(heap, self.public_key.as_ref().unwrap(), gas).await?, + access_key: asc_new(heap, self.access_key.as_ref().unwrap(), gas).await?, }) } } +#[async_trait] impl ToAscObj for codec::AccessKey { - fn to_asc_obj( + async fn to_asc_obj( &self, heap: &mut H, gas: &GasCounter, ) -> Result { Ok(AscAccessKey { nonce: self.nonce, - permission: asc_new(heap, self.permission.as_ref().unwrap(), gas)?, + permission: asc_new(heap, self.permission.as_ref().unwrap(), gas).await?, _padding: 0, }) } } +#[async_trait] impl ToAscObj for codec::AccessKeyPermission { - fn to_asc_obj( + async fn to_asc_obj( &self, heap: &mut H, gas: &GasCounter, @@ -306,11 +332,11 @@ impl ToAscObj for codec::AccessKeyPermission { let (kind, payload) = match self.permission.as_ref().unwrap() { codec::access_key_permission::Permission::FunctionCall(permission) => ( AscAccessKeyPermissionKind::FunctionCall, - asc_new(heap, permission, gas)?.to_payload(), + asc_new(heap, permission, gas).await?.to_payload(), ), codec::access_key_permission::Permission::FullAccess(permission) => ( AscAccessKeyPermissionKind::FullAccess, - asc_new(heap, permission, gas)?.to_payload(), + asc_new(heap, permission, gas).await?.to_payload(), ), }; @@ -322,8 +348,9 @@ impl ToAscObj for codec::AccessKeyPermission { } } +#[async_trait] impl ToAscObj for codec::FunctionCallPermission { - fn to_asc_obj( + async fn to_asc_obj( &self, heap: &mut H, gas: &GasCounter, @@ -331,17 +358,18 @@ impl ToAscObj for codec::FunctionCallPermission { Ok(AscFunctionCallPermission { // The `allowance` field is one of the few fields that can actually be None for real allowance: match self.allowance.as_ref() { - Some(allowance) => asc_new(heap, allowance, gas)?, + Some(allowance) => asc_new(heap, allowance, gas).await?, None => AscPtr::null(), }, - receiver_id: asc_new(heap, &self.receiver_id, gas)?, - method_names: asc_new(heap, &self.method_names, gas)?, + receiver_id: asc_new(heap, &self.receiver_id, gas).await?, + method_names: asc_new(heap, &self.method_names, gas).await?, }) } } +#[async_trait] impl ToAscObj for codec::FullAccessPermission { - fn to_asc_obj( + async fn to_asc_obj( &self, _heap: &mut H, _gas: &GasCounter, @@ -350,57 +378,64 @@ impl ToAscObj for codec::FullAccessPermission { } } +#[async_trait] impl ToAscObj for codec::DeleteKeyAction { - fn to_asc_obj( + async fn to_asc_obj( &self, heap: &mut H, gas: &GasCounter, ) -> Result { Ok(AscDeleteKeyAction { - public_key: asc_new(heap, self.public_key.as_ref().unwrap(), gas)?, + public_key: asc_new(heap, self.public_key.as_ref().unwrap(), gas).await?, }) } } +#[async_trait] impl ToAscObj for codec::DeleteAccountAction { - fn to_asc_obj( + async fn to_asc_obj( &self, heap: &mut H, gas: &GasCounter, ) -> Result { Ok(AscDeleteAccountAction { - beneficiary_id: asc_new(heap, &self.beneficiary_id, gas)?, + beneficiary_id: asc_new(heap, &self.beneficiary_id, gas).await?, }) } } +#[async_trait] impl ToAscObj for codec::DataReceiver { - fn to_asc_obj( + async fn to_asc_obj( &self, heap: &mut H, gas: &GasCounter, ) -> Result { Ok(AscDataReceiver { - data_id: asc_new(heap, self.data_id.as_ref().unwrap(), gas)?, - receiver_id: asc_new(heap, &self.receiver_id, gas)?, + data_id: asc_new(heap, self.data_id.as_ref().unwrap(), gas).await?, + receiver_id: asc_new(heap, &self.receiver_id, gas).await?, }) } } +#[async_trait] impl ToAscObj for Vec { - fn to_asc_obj( + async fn to_asc_obj( &self, heap: &mut H, gas: &GasCounter, ) -> Result { - let content: Result, _> = self.iter().map(|x| asc_new(heap, x, gas)).collect(); - let content = content?; - Ok(AscDataReceiverArray(Array::new(&content, heap, gas)?)) + let mut content = Vec::new(); + for x in self { + content.push(asc_new(heap, x, gas).await?); + } + Ok(AscDataReceiverArray(Array::new(&content, heap, gas).await?)) } } +#[async_trait] impl ToAscObj for codec::ExecutionOutcomeWithId { - fn to_asc_obj( + async fn to_asc_obj( &self, heap: &mut H, gas: &GasCounter, @@ -408,21 +443,22 @@ impl ToAscObj for codec::ExecutionOutcomeWithId { let outcome = self.outcome.as_ref().unwrap(); Ok(AscExecutionOutcome { - proof: asc_new(heap, &self.proof.as_ref().unwrap().path, gas)?, - block_hash: asc_new(heap, self.block_hash.as_ref().unwrap(), gas)?, - id: asc_new(heap, self.id.as_ref().unwrap(), gas)?, - logs: asc_new(heap, &outcome.logs, gas)?, - receipt_ids: asc_new(heap, &outcome.receipt_ids, gas)?, + proof: asc_new(heap, &self.proof.as_ref().unwrap().path, gas).await?, + block_hash: asc_new(heap, self.block_hash.as_ref().unwrap(), gas).await?, + id: asc_new(heap, self.id.as_ref().unwrap(), gas).await?, + logs: asc_new(heap, &outcome.logs, gas).await?, + receipt_ids: asc_new(heap, &outcome.receipt_ids, gas).await?, gas_burnt: outcome.gas_burnt, - tokens_burnt: asc_new(heap, outcome.tokens_burnt.as_ref().unwrap(), gas)?, - executor_id: asc_new(heap, &outcome.executor_id, gas)?, - status: asc_new(heap, outcome.status.as_ref().unwrap(), gas)?, + tokens_burnt: asc_new(heap, outcome.tokens_burnt.as_ref().unwrap(), gas).await?, + executor_id: asc_new(heap, &outcome.executor_id, gas).await?, + status: asc_new(heap, outcome.status.as_ref().unwrap(), gas).await?, }) } } +#[async_trait] impl ToAscObj for codec::execution_outcome::Status { - fn to_asc_obj( + async fn to_asc_obj( &self, heap: &mut H, gas: &GasCounter, @@ -433,12 +469,14 @@ impl ToAscObj for codec::execution_outcome::Status { ( AscSuccessStatusKind::Value, - asc_new(heap, bytes.as_slice(), gas)?.to_payload(), + asc_new(heap, bytes.as_slice(), gas).await?.to_payload(), ) } codec::execution_outcome::Status::SuccessReceiptId(receipt_id) => ( AscSuccessStatusKind::ReceiptId, - asc_new(heap, receipt_id.id.as_ref().unwrap(), gas)?.to_payload(), + asc_new(heap, receipt_id.id.as_ref().unwrap(), gas) + .await? + .to_payload(), ), codec::execution_outcome::Status::Failure(_) => { return Err(DeterministicHostError::from(anyhow!( @@ -462,14 +500,15 @@ impl ToAscObj for codec::execution_outcome::Status { } } +#[async_trait] impl ToAscObj for codec::MerklePathItem { - fn to_asc_obj( + async fn to_asc_obj( &self, heap: &mut H, gas: &GasCounter, ) -> Result { Ok(AscMerklePathItem { - hash: asc_new(heap, self.hash.as_ref().unwrap(), gas)?, + hash: asc_new(heap, self.hash.as_ref().unwrap(), gas).await?, direction: match self.direction { 0 => AscDirection::Left, 1 => AscDirection::Right, @@ -485,20 +524,26 @@ impl ToAscObj for codec::MerklePathItem { } } +#[async_trait] impl ToAscObj for Vec { - fn to_asc_obj( + async fn to_asc_obj( &self, heap: &mut H, gas: &GasCounter, ) -> Result { - let content: Result, _> = self.iter().map(|x| asc_new(heap, x, gas)).collect(); - let content = content?; - Ok(AscMerklePathItemArray(Array::new(&content, heap, gas)?)) + let mut content = Vec::new(); + for x in self { + content.push(asc_new(heap, x, gas).await?); + } + Ok(AscMerklePathItemArray( + Array::new(&content, heap, gas).await?, + )) } } +#[async_trait] impl ToAscObj for codec::Signature { - fn to_asc_obj( + async fn to_asc_obj( &self, heap: &mut H, gas: &GasCounter, @@ -515,25 +560,29 @@ impl ToAscObj for codec::Signature { .into()) } }, - bytes: asc_new(heap, self.bytes.as_slice(), gas)?, + bytes: asc_new(heap, self.bytes.as_slice(), gas).await?, }) } } +#[async_trait] impl ToAscObj for Vec { - fn to_asc_obj( + async fn to_asc_obj( &self, heap: &mut H, gas: &GasCounter, ) -> Result { - let content: Result, _> = self.iter().map(|x| asc_new(heap, x, gas)).collect(); - let content = content?; - Ok(AscSignatureArray(Array::new(&content, heap, gas)?)) + let mut content = Vec::new(); + for x in self { + content.push(asc_new(heap, x, gas).await?); + } + Ok(AscSignatureArray(Array::new(&content, heap, gas).await?)) } } +#[async_trait] impl ToAscObj for codec::PublicKey { - fn to_asc_obj( + async fn to_asc_obj( &self, heap: &mut H, gas: &GasCounter, @@ -550,86 +599,103 @@ impl ToAscObj for codec::PublicKey { .into()) } }, - bytes: asc_new(heap, self.bytes.as_slice(), gas)?, + bytes: asc_new(heap, self.bytes.as_slice(), gas).await?, }) } } +#[async_trait] impl ToAscObj for codec::ValidatorStake { - fn to_asc_obj( + async fn to_asc_obj( &self, heap: &mut H, gas: &GasCounter, ) -> Result { Ok(AscValidatorStake { - account_id: asc_new(heap, &self.account_id, gas)?, - public_key: asc_new(heap, self.public_key.as_ref().unwrap(), gas)?, - stake: asc_new(heap, self.stake.as_ref().unwrap(), gas)?, + account_id: asc_new(heap, &self.account_id, gas).await?, + public_key: asc_new(heap, self.public_key.as_ref().unwrap(), gas).await?, + stake: asc_new(heap, self.stake.as_ref().unwrap(), gas).await?, }) } } +#[async_trait] impl ToAscObj for Vec { - fn to_asc_obj( + async fn to_asc_obj( &self, heap: &mut H, gas: &GasCounter, ) -> Result { - let content: Result, _> = self.iter().map(|x| asc_new(heap, x, gas)).collect(); - let content = content?; - Ok(AscValidatorStakeArray(Array::new(&content, heap, gas)?)) + let mut content = Vec::new(); + for x in self { + content.push(asc_new(heap, x, gas).await?); + } + Ok(AscValidatorStakeArray( + Array::new(&content, heap, gas).await?, + )) } } +#[async_trait] impl ToAscObj for codec::SlashedValidator { - fn to_asc_obj( + async fn to_asc_obj( &self, heap: &mut H, gas: &GasCounter, ) -> Result { Ok(AscSlashedValidator { - account_id: asc_new(heap, &self.account_id, gas)?, + account_id: asc_new(heap, &self.account_id, gas).await?, is_double_sign: self.is_double_sign, }) } } +#[async_trait] impl ToAscObj for Vec { - fn to_asc_obj( + async fn to_asc_obj( &self, heap: &mut H, gas: &GasCounter, ) -> Result { - let content: Result, _> = self.iter().map(|x| asc_new(heap, x, gas)).collect(); - let content = content?; - Ok(AscSlashedValidatorArray(Array::new(&content, heap, gas)?)) + let mut content = Vec::new(); + for x in self { + content.push(asc_new(heap, x, gas).await?); + } + Ok(AscSlashedValidatorArray( + Array::new(&content, heap, gas).await?, + )) } } +#[async_trait] impl ToAscObj for codec::CryptoHash { - fn to_asc_obj( + async fn to_asc_obj( &self, heap: &mut H, gas: &GasCounter, ) -> Result { - self.bytes.to_asc_obj(heap, gas) + self.bytes.to_asc_obj(heap, gas).await } } +#[async_trait] impl ToAscObj for Vec { - fn to_asc_obj( + async fn to_asc_obj( &self, heap: &mut H, gas: &GasCounter, ) -> Result { - let content: Result, _> = self.iter().map(|x| asc_new(heap, x, gas)).collect(); - let content = content?; - Ok(AscCryptoHashArray(Array::new(&content, heap, gas)?)) + let mut content = Vec::new(); + for x in self { + content.push(asc_new(heap, x, gas).await?); + } + Ok(AscCryptoHashArray(Array::new(&content, heap, gas).await?)) } } +#[async_trait] impl ToAscObj for codec::BigInt { - fn to_asc_obj( + async fn to_asc_obj( &self, heap: &mut H, gas: &GasCounter, @@ -637,6 +703,6 @@ impl ToAscObj for codec::BigInt { // Bytes are reversed to align with BigInt bytes endianess let reversed: Vec = self.bytes.iter().rev().copied().collect(); - reversed.to_asc_obj(heap, gas) + reversed.to_asc_obj(heap, gas).await } } diff --git a/chain/near/src/trigger.rs b/chain/near/src/trigger.rs index c48668cdd8e..a05ea7d4d22 100644 --- a/chain/near/src/trigger.rs +++ b/chain/near/src/trigger.rs @@ -2,6 +2,7 @@ use graph::blockchain::Block; use graph::blockchain::MappingTriggerTrait; use graph::blockchain::TriggerData; use graph::derive::CheapClone; +use graph::prelude::async_trait; use graph::prelude::hex; use graph::prelude::web3::types::H256; use graph::prelude::BlockNumber; @@ -38,15 +39,16 @@ impl std::fmt::Debug for NearTrigger { } } +#[async_trait] impl ToAscPtr for NearTrigger { - fn to_asc_ptr( + async fn to_asc_ptr( self, heap: &mut H, gas: &GasCounter, ) -> Result, HostExportError> { Ok(match self { - NearTrigger::Block(block) => asc_new(heap, block.as_ref(), gas)?.erase(), - NearTrigger::Receipt(receipt) => asc_new(heap, receipt.as_ref(), gas)?.erase(), + NearTrigger::Block(block) => asc_new(heap, block.as_ref(), gas).await?.erase(), + NearTrigger::Receipt(receipt) => asc_new(heap, receipt.as_ref(), gas).await?.erase(), }) } } @@ -160,20 +162,23 @@ mod tests { data::subgraph::API_VERSION_0_0_5, prelude::{hex, BigInt}, runtime::{gas::GasCounter, DeterministicHostError, HostExportError}, + tokio, util::mem::init_slice, }; - #[test] - fn block_trigger_to_asc_ptr() { + #[tokio::test] + async fn block_trigger_to_asc_ptr() { let mut heap = BytesHeap::new(API_VERSION_0_0_5); let trigger = NearTrigger::Block(Arc::new(block())); - let result = trigger.to_asc_ptr(&mut heap, &GasCounter::new(GasMetrics::mock())); + let result = trigger + .to_asc_ptr(&mut heap, &GasCounter::new(GasMetrics::mock())) + .await; assert!(result.is_ok()); } - #[test] - fn receipt_trigger_to_asc_ptr() { + #[tokio::test] + async fn receipt_trigger_to_asc_ptr() { let mut heap = BytesHeap::new(API_VERSION_0_0_5); let trigger = NearTrigger::Receipt(Arc::new(ReceiptWithOutcome { block: Arc::new(block()), @@ -181,7 +186,9 @@ mod tests { receipt: receipt().unwrap(), })); - let result = trigger.to_asc_ptr(&mut heap, &GasCounter::new(GasMetrics::mock())); + let result = trigger + .to_asc_ptr(&mut heap, &GasCounter::new(GasMetrics::mock())) + .await; assert!(result.is_ok()); } @@ -444,8 +451,9 @@ mod tests { } } + #[async_trait] impl AscHeap for BytesHeap { - fn raw_new( + async fn raw_new( &mut self, bytes: &[u8], _gas: &GasCounter, @@ -501,7 +509,7 @@ mod tests { &self.api_version } - fn asc_type_id( + async fn asc_type_id( &mut self, type_id_index: graph::runtime::IndexForAscTypeId, ) -> Result { diff --git a/chain/substreams/src/trigger.rs b/chain/substreams/src/trigger.rs index 405b6f8a116..0d9a8c7898f 100644 --- a/chain/substreams/src/trigger.rs +++ b/chain/substreams/src/trigger.rs @@ -39,9 +39,10 @@ impl blockchain::TriggerData for TriggerData { } } +#[async_trait] impl ToAscPtr for TriggerData { // substreams doesn't rely on wasm on the graph-node so this is not needed. - fn to_asc_ptr( + async fn to_asc_ptr( self, _heap: &mut H, _gas: &graph::runtime::gas::GasCounter, diff --git a/graph/src/blockchain/mod.rs b/graph/src/blockchain/mod.rs index 3bf54f64659..7768ea7f6e9 100644 --- a/graph/src/blockchain/mod.rs +++ b/graph/src/blockchain/mod.rs @@ -35,6 +35,7 @@ use crate::{ }; use anyhow::{anyhow, Context, Error}; use async_trait::async_trait; +use futures03::future::BoxFuture; use graph_derive::CheapClone; use serde::de::DeserializeOwned; use serde::{Deserialize, Serialize}; @@ -545,9 +546,14 @@ pub struct HostFnCtx<'a> { #[derive(Clone, CheapClone)] pub struct HostFn { pub name: &'static str, - pub func: Arc Result>, + pub func: Arc< + dyn Send + + Sync + + for<'a> Fn(HostFnCtx<'a>, u32) -> BoxFuture<'a, Result>, + >, } +#[async_trait] pub trait RuntimeAdapter: Send + Sync { fn host_fns(&self, ds: &data_source::DataSource) -> Result, Error>; } diff --git a/graph/src/runtime/asc_heap.rs b/graph/src/runtime/asc_heap.rs index abd86d9dbf1..6de4cc46a06 100644 --- a/graph/src/runtime/asc_heap.rs +++ b/graph/src/runtime/asc_heap.rs @@ -6,6 +6,7 @@ use super::{ gas::GasCounter, AscIndexId, AscPtr, AscType, DeterministicHostError, HostExportError, IndexForAscTypeId, }; +use crate::prelude::async_trait; // A 128 limit is plenty for any subgraph, while the `fn recursion_limit` test ensures it is not // large enough to cause stack overflows. @@ -15,9 +16,14 @@ const MAX_RECURSION_DEPTH: usize = 128; /// for reading and writing Rust structs from and to Asc. /// /// The implementor must provide the direct Asc interface with `raw_new` and `get`. -pub trait AscHeap { +#[async_trait] +pub trait AscHeap: Send { /// Allocate new space and write `bytes`, return the allocated address. - fn raw_new(&mut self, bytes: &[u8], gas: &GasCounter) -> Result; + async fn raw_new( + &mut self, + bytes: &[u8], + gas: &GasCounter, + ) -> Result; fn read<'a>( &self, @@ -30,7 +36,10 @@ pub trait AscHeap { fn api_version(&self) -> &Version; - fn asc_type_id(&mut self, type_id_index: IndexForAscTypeId) -> Result; + async fn asc_type_id( + &mut self, + type_id_index: IndexForAscTypeId, + ) -> Result; } /// Instantiate `rust_obj` as an Asc object of class `C`. @@ -38,7 +47,7 @@ pub trait AscHeap { /// /// This operation is expensive as it requires a call to `raw_new` for every /// nested object. -pub fn asc_new( +pub async fn asc_new( heap: &mut H, rust_obj: &T, gas: &GasCounter, @@ -47,12 +56,12 @@ where C: AscType + AscIndexId, T: ToAscObj, { - let obj = rust_obj.to_asc_obj(heap, gas)?; - AscPtr::alloc_obj(obj, heap, gas) + let obj = rust_obj.to_asc_obj(heap, gas).await?; + AscPtr::alloc_obj(obj, heap, gas).await } /// Map an optional object to its Asc equivalent if Some, otherwise return a missing field error. -pub fn asc_new_or_missing( +pub async fn asc_new_or_missing( heap: &mut H, object: &Option, gas: &GasCounter, @@ -60,29 +69,29 @@ pub fn asc_new_or_missing( field_name: &str, ) -> Result, HostExportError> where - H: AscHeap + ?Sized, + H: AscHeap + Send + ?Sized, O: ToAscObj, A: AscType + AscIndexId, { match object { - Some(o) => asc_new(heap, o, gas), + Some(o) => asc_new(heap, o, gas).await, None => Err(missing_field_error(type_name, field_name)), } } /// Map an optional object to its Asc equivalent if Some, otherwise return null. -pub fn asc_new_or_null( +pub async fn asc_new_or_null( heap: &mut H, object: &Option, gas: &GasCounter, ) -> Result, HostExportError> where - H: AscHeap + ?Sized, + H: AscHeap + Send + ?Sized, O: ToAscObj, A: AscType + AscIndexId, { match object { - Some(o) => asc_new(heap, o, gas), + Some(o) => asc_new(heap, o, gas).await, None => Ok(AscPtr::null()), } } @@ -118,26 +127,29 @@ where } /// Type that can be converted to an Asc object of class `C`. +#[async_trait] pub trait ToAscObj { - fn to_asc_obj( + async fn to_asc_obj( &self, heap: &mut H, gas: &GasCounter, ) -> Result; } -impl> ToAscObj for &T { - fn to_asc_obj( +#[async_trait] +impl + Sync> ToAscObj for &T { + async fn to_asc_obj( &self, heap: &mut H, gas: &GasCounter, ) -> Result { - (*self).to_asc_obj(heap, gas) + (*self).to_asc_obj(heap, gas).await } } +#[async_trait] impl ToAscObj for bool { - fn to_asc_obj( + async fn to_asc_obj( &self, _heap: &mut H, _gas: &GasCounter, diff --git a/graph/src/runtime/asc_ptr.rs b/graph/src/runtime/asc_ptr.rs index cc2815bbcd9..7a51805269e 100644 --- a/graph/src/runtime/asc_ptr.rs +++ b/graph/src/runtime/asc_ptr.rs @@ -84,7 +84,7 @@ impl AscPtr { } /// Allocate `asc_obj` as an Asc object of class `C`. - pub fn alloc_obj( + pub async fn alloc_obj( asc_obj: C, heap: &mut H, gas: &GasCounter, @@ -94,7 +94,7 @@ impl AscPtr { { match heap.api_version() { version if version <= &API_VERSION_0_0_4 => { - let heap_ptr = heap.raw_new(&asc_obj.to_asc_bytes()?, gas)?; + let heap_ptr = heap.raw_new(&asc_obj.to_asc_bytes()?, gas).await?; Ok(AscPtr::new(heap_ptr)) } _ => { @@ -110,10 +110,11 @@ impl AscPtr { C::INDEX_ASC_TYPE_ID, asc_obj.content_len(&bytes), bytes.len(), - )?; + ) + .await?; let header_len = header.len() as u32; - let heap_ptr = heap.raw_new(&[header, bytes].concat(), gas)?; + let heap_ptr = heap.raw_new(&[header, bytes].concat(), gas).await?; // Use header length as offset. so the AscPtr points directly at the content. Ok(AscPtr::new(heap_ptr + header_len)) @@ -140,7 +141,7 @@ impl AscPtr { /// - rt_id: u32 -> identifier for the class being allocated /// - rt_size: u32 -> content size /// Only used for version >= 0.0.5. - fn generate_header( + async fn generate_header( heap: &mut H, type_id_index: IndexForAscTypeId, content_length: usize, @@ -150,7 +151,7 @@ impl AscPtr { let gc_info: [u8; 4] = (0u32).to_le_bytes(); let gc_info2: [u8; 4] = (0u32).to_le_bytes(); - let asc_type_id = heap.asc_type_id(type_id_index)?; + let asc_type_id = heap.asc_type_id(type_id_index).await?; let rt_id: [u8; 4] = asc_type_id.to_le_bytes(); let rt_size: [u8; 4] = (content_length as u32).to_le_bytes(); diff --git a/graph/src/runtime/mod.rs b/graph/src/runtime/mod.rs index 6732a57429e..cba8a69b0cc 100644 --- a/graph/src/runtime/mod.rs +++ b/graph/src/runtime/mod.rs @@ -21,6 +21,8 @@ use std::mem::size_of; use self::gas::GasCounter; +use crate::prelude::async_trait; + /// Marker trait for AssemblyScript types that the id should /// be in the header. pub trait AscIndexId { @@ -337,8 +339,9 @@ pub enum IndexForAscTypeId { UnitTestNetworkUnitTestTypeBoolArray = u32::MAX, } +#[async_trait] impl ToAscObj for IndexForAscTypeId { - fn to_asc_obj( + async fn to_asc_obj( &self, _heap: &mut H, _gas: &GasCounter, diff --git a/runtime/test/src/test.rs b/runtime/test/src/test.rs index 53a84aec5f1..f2db34af862 100644 --- a/runtime/test/src/test.rs +++ b/runtime/test/src/test.rs @@ -112,6 +112,7 @@ async fn test_valid_module_and_store_with_timeout( host_metrics, experimental_features, ) + .await .unwrap(); (module, store.subgraph_store(), deployment) @@ -139,179 +140,220 @@ pub async fn test_module_latest(subgraph_id: &str, wasm_file: &str) -> WasmInsta .0 } +#[async_trait] pub trait WasmInstanceExt { - fn invoke_export0_void(&mut self, f: &str) -> Result<(), Error>; - fn invoke_export1_val_void(&mut self, f: &str, v: V) -> Result<(), Error>; + async fn invoke_export0_void(&mut self, f: &str) -> Result<(), Error>; + async fn invoke_export1_val_void( + &mut self, + f: &str, + v: V, + ) -> Result<(), Error>; #[allow(dead_code)] - fn invoke_export0(&mut self, f: &str) -> AscPtr; - fn invoke_export1(&mut self, f: &str, arg: &T) -> AscPtr + async fn invoke_export0(&mut self, f: &str) -> AscPtr; + async fn invoke_export1(&mut self, f: &str, arg: &T) -> AscPtr where - C: AscType + AscIndexId, - T: ToAscObj + ?Sized; - fn invoke_export2(&mut self, f: &str, arg0: &T1, arg1: &T2) -> AscPtr + C: AscType + AscIndexId + Send, + T: ToAscObj + Sync + ?Sized; + async fn invoke_export2( + &mut self, + f: &str, + arg0: &T1, + arg1: &T2, + ) -> AscPtr where - C1: AscType + AscIndexId, - C2: AscType + AscIndexId, - T1: ToAscObj + ?Sized, - T2: ToAscObj + ?Sized; - fn invoke_export2_void( + C1: AscType + AscIndexId + Send, + C2: AscType + AscIndexId + Send, + T1: ToAscObj + Sync + ?Sized, + T2: ToAscObj + Sync + ?Sized; + async fn invoke_export2_void( &mut self, f: &str, arg0: &T1, arg1: &T2, ) -> Result<(), Error> where - C1: AscType + AscIndexId, - C2: AscType + AscIndexId, - T1: ToAscObj + ?Sized, - T2: ToAscObj + ?Sized; - fn invoke_export0_val(&mut self, func: &str) -> V; - fn invoke_export1_val(&mut self, func: &str, v: &T) -> V + C1: AscType + AscIndexId + Send, + C2: AscType + AscIndexId + Send, + T1: ToAscObj + Sync + ?Sized, + T2: ToAscObj + Sync + ?Sized; + async fn invoke_export0_val(&mut self, func: &str) -> V; + async fn invoke_export1_val(&mut self, func: &str, v: &T) -> V where - C: AscType + AscIndexId, - T: ToAscObj + ?Sized; - fn takes_ptr_returns_ptr(&mut self, f: &str, arg: AscPtr) -> AscPtr; - fn takes_val_returns_ptr

(&mut self, fn_name: &str, val: impl wasmtime::WasmTy) -> AscPtr

; + C: AscType + AscIndexId + Send, + T: ToAscObj + Sync + ?Sized; + async fn takes_ptr_returns_ptr(&mut self, f: &str, arg: AscPtr) -> AscPtr; + async fn takes_val_returns_ptr

( + &mut self, + fn_name: &str, + val: impl wasmtime::WasmTy, + ) -> AscPtr

; } +#[async_trait] impl WasmInstanceExt for WasmInstance { - fn invoke_export0_void(&mut self, f: &str) -> Result<(), Error> { + async fn invoke_export0_void(&mut self, f: &str) -> Result<(), Error> { let func = self .get_func(f) .typed(&self.store.as_context()) .unwrap() .clone(); - func.call(&mut self.store.as_context_mut(), ()) + func.call_async(&mut self.store.as_context_mut(), ()).await } - fn invoke_export0(&mut self, f: &str) -> AscPtr { + async fn invoke_export0(&mut self, f: &str) -> AscPtr { let func = self .get_func(f) .typed(&self.store.as_context()) .unwrap() .clone(); - let ptr: u32 = func.call(&mut self.store.as_context_mut(), ()).unwrap(); + let ptr: u32 = func + .call_async(&mut self.store.as_context_mut(), ()) + .await + .unwrap(); ptr.into() } - fn takes_ptr_returns_ptr(&mut self, f: &str, arg: AscPtr) -> AscPtr { + async fn takes_ptr_returns_ptr(&mut self, f: &str, arg: AscPtr) -> AscPtr { let func = self .get_func(f) .typed(&self.store.as_context()) .unwrap() .clone(); let ptr: u32 = func - .call(&mut self.store.as_context_mut(), arg.wasm_ptr()) + .call_async(&mut self.store.as_context_mut(), arg.wasm_ptr()) + .await .unwrap(); ptr.into() } - fn invoke_export1(&mut self, f: &str, arg: &T) -> AscPtr + async fn invoke_export1(&mut self, f: &str, arg: &T) -> AscPtr where - C: AscType + AscIndexId, - T: ToAscObj + ?Sized, + C: AscType + AscIndexId + Send, + T: ToAscObj + Sync + ?Sized, { let func = self .get_func(f) .typed(&self.store.as_context()) .unwrap() .clone(); - let ptr = self.asc_new(arg).unwrap(); + let ptr = self.asc_new(arg).await.unwrap(); let ptr: u32 = func - .call(&mut self.store.as_context_mut(), ptr.wasm_ptr()) + .call_async(&mut self.store.as_context_mut(), ptr.wasm_ptr()) + .await .unwrap(); ptr.into() } - fn invoke_export1_val_void(&mut self, f: &str, v: V) -> Result<(), Error> { + async fn invoke_export1_val_void( + &mut self, + f: &str, + v: V, + ) -> Result<(), Error> { let func = self .get_func(f) .typed::(&self.store.as_context()) .unwrap() .clone(); - func.call(&mut self.store.as_context_mut(), v)?; + func.call_async(&mut self.store.as_context_mut(), v).await?; Ok(()) } - fn invoke_export2(&mut self, f: &str, arg0: &T1, arg1: &T2) -> AscPtr + async fn invoke_export2( + &mut self, + f: &str, + arg0: &T1, + arg1: &T2, + ) -> AscPtr where - C1: AscType + AscIndexId, - C2: AscType + AscIndexId, - T1: ToAscObj + ?Sized, - T2: ToAscObj + ?Sized, + C1: AscType + AscIndexId + Send, + C2: AscType + AscIndexId + Send, + T1: ToAscObj + Sync + ?Sized, + T2: ToAscObj + Sync + ?Sized, { let func = self .get_func(f) .typed(&self.store.as_context()) .unwrap() .clone(); - let arg0 = self.asc_new(arg0).unwrap(); - let arg1 = self.asc_new(arg1).unwrap(); + let arg0 = self.asc_new(arg0).await.unwrap(); + let arg1 = self.asc_new(arg1).await.unwrap(); let ptr: u32 = func - .call( + .call_async( &mut self.store.as_context_mut(), (arg0.wasm_ptr(), arg1.wasm_ptr()), ) + .await .unwrap(); ptr.into() } - fn invoke_export2_void( + async fn invoke_export2_void( &mut self, f: &str, arg0: &T1, arg1: &T2, ) -> Result<(), Error> where - C1: AscType + AscIndexId, - C2: AscType + AscIndexId, - T1: ToAscObj + ?Sized, - T2: ToAscObj + ?Sized, + C1: AscType + AscIndexId + Send, + C2: AscType + AscIndexId + Send, + T1: ToAscObj + Sync + ?Sized, + T2: ToAscObj + Sync + ?Sized, { let func = self .get_func(f) .typed(&self.store.as_context()) .unwrap() .clone(); - let arg0 = self.asc_new(arg0).unwrap(); - let arg1 = self.asc_new(arg1).unwrap(); - func.call( + let arg0 = self.asc_new(arg0).await.unwrap(); + let arg1 = self.asc_new(arg1).await.unwrap(); + func.call_async( &mut self.store.as_context_mut(), (arg0.wasm_ptr(), arg1.wasm_ptr()), ) + .await } - fn invoke_export0_val(&mut self, func: &str) -> V { + async fn invoke_export0_val(&mut self, func: &str) -> V { let func = self .get_func(func) .typed(&self.store.as_context()) .unwrap() .clone(); - func.call(&mut self.store.as_context_mut(), ()).unwrap() + func.call_async(&mut self.store.as_context_mut(), ()) + .await + .unwrap() } - fn invoke_export1_val(&mut self, func: &str, v: &T) -> V + async fn invoke_export1_val(&mut self, func: &str, v: &T) -> V where - C: AscType + AscIndexId, - T: ToAscObj + ?Sized, + C: AscType + AscIndexId + Send, + T: ToAscObj + Sync + ?Sized, { let func = self .get_func(func) .typed(&self.store.as_context()) .unwrap() .clone(); - let ptr = self.asc_new(v).unwrap(); - func.call(&mut self.store.as_context_mut(), ptr.wasm_ptr()) + let ptr = self.asc_new(v).await.unwrap(); + func.call_async(&mut self.store.as_context_mut(), ptr.wasm_ptr()) + .await .unwrap() } - fn takes_val_returns_ptr

(&mut self, fn_name: &str, val: impl wasmtime::WasmTy) -> AscPtr

{ + async fn takes_val_returns_ptr

( + &mut self, + fn_name: &str, + val: impl wasmtime::WasmTy, + ) -> AscPtr

{ let func = self .get_func(fn_name) .typed(&self.store.as_context()) .unwrap() .clone(); - let ptr: u32 = func.call(&mut self.store.as_context_mut(), val).unwrap(); + let ptr: u32 = func + .call_async(&mut self.store.as_context_mut(), val) + .await + .unwrap(); ptr.into() } } @@ -329,22 +371,28 @@ async fn test_json_conversions(api_version: Version, gas_used: u64) { // test u64 conversion let number = 9223372036850770800; - let converted: i64 = module.invoke_export1_val("testToU64", &number.to_string()); + let converted: i64 = module + .invoke_export1_val("testToU64", &number.to_string()) + .await; assert_eq!(number, u64::from_le_bytes(converted.to_le_bytes())); // test i64 conversion let number = -9223372036850770800; - let converted: i64 = module.invoke_export1_val("testToI64", &number.to_string()); + let converted: i64 = module + .invoke_export1_val("testToI64", &number.to_string()) + .await; assert_eq!(number, converted); // test f64 conversion let number = -9223372036850770.92345034; - let converted: f64 = module.invoke_export1_val("testToF64", &number.to_string()); + let converted: f64 = module + .invoke_export1_val("testToF64", &number.to_string()) + .await; assert_eq!(number, converted); // test BigInt conversion let number = "-922337203685077092345034"; - let big_int_obj: AscPtr = module.invoke_export1("testToBigInt", number); + let big_int_obj: AscPtr = module.invoke_export1("testToBigInt", number).await; let bytes: Vec = module.asc_get(big_int_obj).unwrap(); assert_eq!( @@ -379,7 +427,7 @@ async fn test_json_parsing(api_version: Version, gas_used: u64) { // Parse valid JSON and get it back let s = "\"foo\""; // Valid because there are quotes around `foo` let bytes: &[u8] = s.as_ref(); - let return_value: AscPtr = module.invoke_export1("handleJsonError", bytes); + let return_value: AscPtr = module.invoke_export1("handleJsonError", bytes).await; let output: String = module.asc_get(return_value).unwrap(); assert_eq!(output, "OK: foo, ERROR: false"); @@ -388,14 +436,14 @@ async fn test_json_parsing(api_version: Version, gas_used: u64) { // Parse invalid JSON and handle the error gracefully let s = "foo"; // Invalid because there are no quotes around `foo` let bytes: &[u8] = s.as_ref(); - let return_value: AscPtr = module.invoke_export1("handleJsonError", bytes); + let return_value: AscPtr = module.invoke_export1("handleJsonError", bytes).await; let output: String = module.asc_get(return_value).unwrap(); assert_eq!(output, "ERROR: true"); // Parse JSON that's too long and handle the error gracefully let s = format!("\"f{}\"", "o".repeat(10_000_000)); let bytes: &[u8] = s.as_ref(); - let return_value: AscPtr = module.invoke_export1("handleJsonError", bytes); + let return_value: AscPtr = module.invoke_export1("handleJsonError", bytes).await; let output: String = module.asc_get(return_value).unwrap(); assert_eq!(output, "ERROR: true"); @@ -412,29 +460,21 @@ async fn json_parsing_v0_0_5() { } async fn test_ipfs_cat(api_version: Version) { - // Ipfs host functions use `block_on` which must be called from a sync context, - // so we replicate what we do `spawn_module`. - let runtime = tokio::runtime::Handle::current(); - std::thread::spawn(move || { - let _runtime_guard = runtime.enter(); - - let fut = add_files_to_local_ipfs_node_for_testing(["42".as_bytes().to_vec()]); - let hash = graph::block_on(fut).unwrap()[0].hash.to_owned(); - - let mut module = graph::block_on(test_module( - "ipfsCat", - mock_data_source( - &wasm_file_path("ipfs_cat.wasm", api_version.clone()), - api_version.clone(), - ), - api_version, - )); - let converted: AscPtr = module.invoke_export1("ipfsCatString", &hash); - let data: String = module.asc_get(converted).unwrap(); - assert_eq!(data, "42"); - }) - .join() - .unwrap(); + let fut = add_files_to_local_ipfs_node_for_testing(["42".as_bytes().to_vec()]); + let hash = fut.await.unwrap()[0].hash.to_owned(); + + let mut module = test_module( + "ipfsCat", + mock_data_source( + &wasm_file_path("ipfs_cat.wasm", api_version.clone()), + api_version.clone(), + ), + api_version, + ) + .await; + let converted: AscPtr = module.invoke_export1("ipfsCatString", &hash).await; + let data: String = module.asc_get(converted).unwrap(); + assert_eq!(data, "42"); } #[tokio::test(flavor = "multi_thread")] @@ -449,29 +489,21 @@ async fn ipfs_cat_v0_0_5() { #[tokio::test(flavor = "multi_thread")] async fn test_ipfs_block() { - // Ipfs host functions use `block_on` which must be called from a sync context, - // so we replicate what we do `spawn_module`. - let runtime = tokio::runtime::Handle::current(); - std::thread::spawn(move || { - let _runtime_guard = runtime.enter(); - - let fut = add_files_to_local_ipfs_node_for_testing(["42".as_bytes().to_vec()]); - let hash = graph::block_on(fut).unwrap()[0].hash.to_owned(); - - let mut module = graph::block_on(test_module( - "ipfsBlock", - mock_data_source( - &wasm_file_path("ipfs_block.wasm", API_VERSION_0_0_5), - API_VERSION_0_0_5, - ), + let fut = add_files_to_local_ipfs_node_for_testing(["42".as_bytes().to_vec()]); + let hash = fut.await.unwrap()[0].hash.to_owned(); + + let mut module = test_module( + "ipfsBlock", + mock_data_source( + &wasm_file_path("ipfs_block.wasm", API_VERSION_0_0_5), API_VERSION_0_0_5, - )); - let converted: AscPtr = module.invoke_export1("ipfsBlockHex", &hash); - let data: String = module.asc_get(converted).unwrap(); - assert_eq!(data, "0x0a080802120234321802"); - }) - .join() - .unwrap(); + ), + API_VERSION_0_0_5, + ) + .await; + let converted: AscPtr = module.invoke_export1("ipfsBlockHex", &hash).await; + let data: String = module.asc_get(converted).unwrap(); + assert_eq!(data, "0x0a080802120234321802"); } // The user_data value we use with calls to ipfs_map @@ -506,47 +538,40 @@ async fn run_ipfs_map( .to_owned() }; - // Ipfs host functions use `block_on` which must be called from a sync context, - // so we replicate what we do `spawn_module`. - let runtime = tokio::runtime::Handle::current(); - std::thread::spawn(move || { - let _runtime_guard = runtime.enter(); + let (mut instance, _, _) = test_valid_module_and_store( + subgraph_id, + mock_data_source( + &wasm_file_path("ipfs_map.wasm", api_version.clone()), + api_version.clone(), + ), + api_version, + ) + .await; - let (mut instance, _, _) = graph::block_on(test_valid_module_and_store( - subgraph_id, - mock_data_source( - &wasm_file_path("ipfs_map.wasm", api_version.clone()), - api_version.clone(), - ), - api_version, - )); + let value = instance.asc_new(&hash).await.unwrap(); + let user_data = instance.asc_new(USER_DATA).await.unwrap(); - let value = instance.asc_new(&hash).unwrap(); - let user_data = instance.asc_new(USER_DATA).unwrap(); + // Invoke the callback + let func = instance + .get_func("ipfsMap") + .typed::<(u32, u32), ()>(&instance.store.as_context()) + .unwrap() + .clone(); + func.call_async( + &mut instance.store.as_context_mut(), + (value.wasm_ptr(), user_data.wasm_ptr()), + ) + .await?; + let mut mods = instance + .take_ctx() + .take_state() + .entity_cache + .as_modifications(0)? + .modifications; - // Invoke the callback - let func = instance - .get_func("ipfsMap") - .typed::<(u32, u32), ()>(&instance.store.as_context()) - .unwrap() - .clone(); - func.call( - &mut instance.store.as_context_mut(), - (value.wasm_ptr(), user_data.wasm_ptr()), - )?; - let mut mods = instance - .take_ctx() - .take_state() - .entity_cache - .as_modifications(0)? - .modifications; - - // Bring the modifications into a predictable order (by entity_id) - mods.sort_by(|a, b| a.key().entity_id.partial_cmp(&b.key().entity_id).unwrap()); - Ok(mods) - }) - .join() - .unwrap() + // Bring the modifications into a predictable order (by entity_id) + mods.sort_by(|a, b| a.key().entity_id.partial_cmp(&b.key().entity_id).unwrap()); + Ok(mods) } async fn test_ipfs_map(api_version: Version, json_error_msg: &str) { @@ -619,28 +644,21 @@ async fn ipfs_map_v0_0_5() { } async fn test_ipfs_fail(api_version: Version) { - let runtime = tokio::runtime::Handle::current(); - - // Ipfs host functions use `block_on` which must be called from a sync context, - // so we replicate what we do `spawn_module`. - std::thread::spawn(move || { - let _runtime_guard = runtime.enter(); - - let mut module = graph::block_on(test_module( - "ipfsFail", - mock_data_source( - &wasm_file_path("ipfs_cat.wasm", api_version.clone()), - api_version.clone(), - ), - api_version, - )); + let mut module = test_module( + "ipfsFail", + mock_data_source( + &wasm_file_path("ipfs_cat.wasm", api_version.clone()), + api_version.clone(), + ), + api_version, + ) + .await; - assert!(module - .invoke_export1::<_, _, AscString>("ipfsCat", "invalid hash") - .is_null()); - }) - .join() - .unwrap(); + // ipfs_cat failures are surfaced as null pointers. See PR #749 + let ptr = module + .invoke_export1::<_, _, AscString>("ipfsCat", "invalid hash") + .await; + assert!(ptr.is_null()); } #[tokio::test(flavor = "multi_thread")] @@ -665,7 +683,7 @@ async fn test_crypto_keccak256(api_version: Version) { .await; let input: &[u8] = "eth".as_ref(); - let hash: AscPtr = module.invoke_export1("hash", input); + let hash: AscPtr = module.invoke_export1("hash", input).await; let hash: Vec = module.asc_get(hash).unwrap(); assert_eq!( hex::encode(hash), @@ -696,19 +714,20 @@ async fn test_big_int_to_hex(api_version: Version, gas_used: u64) { // Convert zero to hex let zero = BigInt::from_unsigned_u256(&U256::zero()); - let zero_hex_ptr: AscPtr = instance.invoke_export1("big_int_to_hex", &zero); + let zero_hex_ptr: AscPtr = instance.invoke_export1("big_int_to_hex", &zero).await; let zero_hex_str: String = instance.asc_get(zero_hex_ptr).unwrap(); assert_eq!(zero_hex_str, "0x0"); // Convert 1 to hex let one = BigInt::from_unsigned_u256(&U256::one()); - let one_hex_ptr: AscPtr = instance.invoke_export1("big_int_to_hex", &one); + let one_hex_ptr: AscPtr = instance.invoke_export1("big_int_to_hex", &one).await; let one_hex_str: String = instance.asc_get(one_hex_ptr).unwrap(); assert_eq!(one_hex_str, "0x1"); // Convert U256::max_value() to hex let u256_max = BigInt::from_unsigned_u256(&U256::max_value()); - let u256_max_hex_ptr: AscPtr = instance.invoke_export1("big_int_to_hex", &u256_max); + let u256_max_hex_ptr: AscPtr = + instance.invoke_export1("big_int_to_hex", &u256_max).await; let u256_max_hex_str: String = instance.asc_get(u256_max_hex_ptr).unwrap(); assert_eq!( u256_max_hex_str, @@ -733,11 +752,13 @@ async fn test_big_int_size_limit() { let len = BigInt::MAX_BITS / 8; module .invoke_export1_val_void("bigIntWithLength", len) + .await .unwrap(); let len = BigInt::MAX_BITS / 8 + 1; let err = module .invoke_export1_val_void("bigIntWithLength", len) + .await .unwrap_err(); assert!( format!("{err:?}").contains("BigInt is too big, total bits 435416 (max 435412)"), @@ -770,42 +791,42 @@ async fn test_big_int_arithmetic(api_version: Version, gas_used: u64) { // 0 + 1 = 1 let zero = BigInt::from(0); let one = BigInt::from(1); - let result_ptr: AscPtr = module.invoke_export2("plus", &zero, &one); + let result_ptr: AscPtr = module.invoke_export2("plus", &zero, &one).await; let result: BigInt = module.asc_get(result_ptr).unwrap(); assert_eq!(result, BigInt::from(1)); // 127 + 1 = 128 let zero = BigInt::from(127); let one = BigInt::from(1); - let result_ptr: AscPtr = module.invoke_export2("plus", &zero, &one); + let result_ptr: AscPtr = module.invoke_export2("plus", &zero, &one).await; let result: BigInt = module.asc_get(result_ptr).unwrap(); assert_eq!(result, BigInt::from(128)); // 5 - 10 = -5 let five = BigInt::from(5); let ten = BigInt::from(10); - let result_ptr: AscPtr = module.invoke_export2("minus", &five, &ten); + let result_ptr: AscPtr = module.invoke_export2("minus", &five, &ten).await; let result: BigInt = module.asc_get(result_ptr).unwrap(); assert_eq!(result, BigInt::from(-5)); // -20 * 5 = -100 let minus_twenty = BigInt::from(-20); let five = BigInt::from(5); - let result_ptr: AscPtr = module.invoke_export2("times", &minus_twenty, &five); + let result_ptr: AscPtr = module.invoke_export2("times", &minus_twenty, &five).await; let result: BigInt = module.asc_get(result_ptr).unwrap(); assert_eq!(result, BigInt::from(-100)); // 5 / 2 = 2 let five = BigInt::from(5); let two = BigInt::from(2); - let result_ptr: AscPtr = module.invoke_export2("dividedBy", &five, &two); + let result_ptr: AscPtr = module.invoke_export2("dividedBy", &five, &two).await; let result: BigInt = module.asc_get(result_ptr).unwrap(); assert_eq!(result, BigInt::from(2)); // 5 % 2 = 1 let five = BigInt::from(5); let two = BigInt::from(2); - let result_ptr: AscPtr = module.invoke_export2("mod", &five, &two); + let result_ptr: AscPtr = module.invoke_export2("mod", &five, &two).await; let result: BigInt = module.asc_get(result_ptr).unwrap(); assert_eq!(result, BigInt::from(1)); @@ -836,7 +857,8 @@ async fn test_abort(api_version: Version, error_msg: &str) { .get_func("abort") .typed(&instance.store.as_context()) .unwrap() - .call(&mut instance.store.as_context_mut(), ()); + .call_async(&mut instance.store.as_context_mut(), ()) + .await; let err = res.unwrap_err(); assert!(format!("{err:?}").contains(error_msg)); } @@ -871,7 +893,9 @@ async fn test_bytes_to_base58(api_version: Version, gas_used: u64) { .await; let bytes = hex::decode("12207D5A99F603F231D53A4F39D1521F98D2E8BB279CF29BEBFD0687DC98458E7F89") .unwrap(); - let result_ptr: AscPtr = module.invoke_export1("bytes_to_base58", bytes.as_slice()); + let result_ptr: AscPtr = module + .invoke_export1("bytes_to_base58", bytes.as_slice()) + .await; let base58: String = module.asc_get(result_ptr).unwrap(); assert_eq!(base58, "QmWmyoMoctfbAaiEs2G46gpeUmhqFRDW6KWo64y5r581Vz"); @@ -933,7 +957,9 @@ async fn run_data_source_create( .await; instance.store.data_mut().ctx.state.enter_handler(); - instance.invoke_export2_void("dataSourceCreate", &name, ¶ms)?; + instance + .invoke_export2_void("dataSourceCreate", &name, ¶ms) + .await?; instance.store.data_mut().ctx.state.exit_handler(); assert_eq!(instance.gas_used(), gas_used); @@ -969,12 +995,13 @@ async fn test_ens_name_by_hash(api_version: Version) { let hash = "0x7f0c1b04d1a4926f9c635a030eeb611d4c26e5e73291b32a1c7a4ac56935b5b3"; let name = "dealdrafts"; test_store::insert_ens_name(hash, name); - let converted: AscPtr = module.invoke_export1("nameByHash", hash); + let converted: AscPtr = module.invoke_export1("nameByHash", hash).await; let data: String = module.asc_get(converted).unwrap(); assert_eq!(data, name); assert!(module .invoke_export1::<_, _, AscString>("nameByHash", "impossible keccak hash") + .await .is_null()); } @@ -1011,8 +1038,8 @@ async fn test_entity_store(api_version: Version) { .await .unwrap(); - let get_user = move |module: &mut WasmInstance, id: &str| -> Option { - let entity_ptr: AscPtr = module.invoke_export1("getUser", id); + let get_user = async move |module: &mut WasmInstance, id: &str| -> Option { + let entity_ptr: AscPtr = module.invoke_export1("getUser", id).await; if entity_ptr.is_null() { None } else { @@ -1028,20 +1055,21 @@ async fn test_entity_store(api_version: Version) { } }; - let load_and_set_user_name = |module: &mut WasmInstance, id: &str, name: &str| { + let load_and_set_user_name = async |module: &mut WasmInstance, id: &str, name: &str| { module .invoke_export2_void("loadAndSetUserName", id, name) + .await .unwrap(); }; // store.get of a nonexistent user - assert_eq!(None, get_user(&mut instance, "herobrine")); + assert_eq!(None, get_user(&mut instance, "herobrine").await); // store.get of an existing user - let steve = get_user(&mut instance, "steve").unwrap(); + let steve = get_user(&mut instance, "steve").await.unwrap(); assert_eq!(Some(&Value::from("Steve")), steve.get("name")); // Load, set, save cycle for an existing entity - load_and_set_user_name(&mut instance, "steve", "Steve-O"); + load_and_set_user_name(&mut instance, "steve", "Steve-O").await; // We need to empty the cache for the next test let writable = store @@ -1064,7 +1092,7 @@ async fn test_entity_store(api_version: Version) { } // Load, set, save cycle for a new entity with fulltext API - load_and_set_user_name(&mut instance, "herobrine", "Brine-O"); + load_and_set_user_name(&mut instance, "herobrine", "Brine-O").await; let mut fulltext_entities = BTreeMap::new(); let mut fulltext_fields = BTreeMap::new(); fulltext_fields.insert("name".to_string(), vec!["search".to_string()]); @@ -1141,7 +1169,10 @@ async fn test_allocate_global(api_version: Version) { .await; // Assert globals can be allocated and don't break the heap - instance.invoke_export0_void("assert_global_works").unwrap(); + instance + .invoke_export0_void("assert_global_works") + .await + .unwrap(); } #[tokio::test] @@ -1165,7 +1196,7 @@ async fn test_null_ptr_read(api_version: Version) -> Result<(), Error> { ) .await; - module.invoke_export0_void("nullPtrRead") + module.invoke_export0_void("nullPtrRead").await } #[tokio::test] @@ -1189,7 +1220,7 @@ async fn test_safe_null_ptr_read(api_version: Version) -> Result<(), Error> { ) .await; - module.invoke_export0_void("safeNullPtrRead") + module.invoke_export0_void("safeNullPtrRead").await } #[tokio::test] @@ -1208,7 +1239,7 @@ async fn safe_null_ptr_read_0_0_5() { #[tokio::test] async fn test_array_blowup() { let mut module = test_module_latest("ArrayBlowup", "array_blowup.wasm").await; - let err = module.invoke_export0_void("arrayBlowup").unwrap_err(); + let err = module.invoke_export0_void("arrayBlowup").await.unwrap_err(); assert!(format!("{err:?}").contains("Gas limit exceeded. Used: 11286295575421")); } @@ -1216,31 +1247,37 @@ async fn test_array_blowup() { async fn test_boolean() { let mut module = test_module_latest("boolean", "boolean.wasm").await; - let true_: i32 = module.invoke_export0_val("testReturnTrue"); + let true_: i32 = module.invoke_export0_val("testReturnTrue").await; assert_eq!(true_, 1); - let false_: i32 = module.invoke_export0_val("testReturnFalse"); + let false_: i32 = module.invoke_export0_val("testReturnFalse").await; assert_eq!(false_, 0); // non-zero values are true for x in (-10i32..10).filter(|&x| x != 0) { - assert!(module.invoke_export1_val_void("testReceiveTrue", x).is_ok(),); + assert!(module + .invoke_export1_val_void("testReceiveTrue", x) + .await + .is_ok(),); } // zero is not true assert!(module .invoke_export1_val_void("testReceiveTrue", 0i32) + .await .is_err()); // zero is false assert!(module .invoke_export1_val_void("testReceiveFalse", 0i32) + .await .is_ok()); // non-zero values are not false for x in (-10i32..10).filter(|&x| x != 0) { assert!(module .invoke_export1_val_void("testReceiveFalse", x) + .await .is_err()); } } @@ -1252,12 +1289,14 @@ async fn recursion_limit() { // An error about 'unknown key' means the entity was fully read with no stack overflow. module .invoke_export1_val_void("recursionLimit", 128) + .await .unwrap_err() .to_string() .contains("Unknown key `foobar`"); let err = module .invoke_export1_val_void("recursionLimit", 129) + .await .unwrap_err(); assert!( format!("{err:?}").contains("recursion limit reached"), @@ -1710,49 +1749,50 @@ async fn test_yaml_parsing(api_version: Version, gas_used: u64) { ) .await; - let mut test = |input: &str, expected: &str| { - let ptr: AscPtr = module.invoke_export1("handleYaml", input.as_bytes()); + let mut test = async |input: &str, expected: &str| { + let ptr: AscPtr = module.invoke_export1("handleYaml", input.as_bytes()).await; let resp: String = module.asc_get(ptr).unwrap(); assert_eq!(resp, expected, "failed on input: {input}"); }; // Test invalid YAML; - test("{a: 1, - b: 2}", "error"); + test("{a: 1, - b: 2}", "error").await; // Test size limit; - test(&"x".repeat(10_000_0001), "error"); + test(&"x".repeat(10_000_0001), "error").await; // Test nulls; - test("null", "(0) null"); + test("null", "(0) null").await; // Test booleans; - test("false", "(1) false"); - test("true", "(1) true"); + test("false", "(1) false").await; + test("true", "(1) true").await; // Test numbers; - test("12345", "(2) 12345"); - test("12345.6789", "(2) 12345.6789"); + test("12345", "(2) 12345").await; + test("12345.6789", "(2) 12345.6789").await; // Test strings; - test("aa bb cc", "(3) aa bb cc"); - test("\"aa bb cc\"", "(3) aa bb cc"); + test("aa bb cc", "(3) aa bb cc").await; + test("\"aa bb cc\"", "(3) aa bb cc").await; // Test arrays; - test("[1, 2, 3, 4]", "(4) [(2) 1, (2) 2, (2) 3, (2) 4]"); - test("- 1\n- 2\n- 3\n- 4", "(4) [(2) 1, (2) 2, (2) 3, (2) 4]"); + test("[1, 2, 3, 4]", "(4) [(2) 1, (2) 2, (2) 3, (2) 4]").await; + test("- 1\n- 2\n- 3\n- 4", "(4) [(2) 1, (2) 2, (2) 3, (2) 4]").await; // Test objects; - test("{a: 1, b: 2, c: 3}", "(5) {a: (2) 1, b: (2) 2, c: (2) 3}"); - test("a: 1\nb: 2\nc: 3", "(5) {a: (2) 1, b: (2) 2, c: (2) 3}"); + test("{a: 1, b: 2, c: 3}", "(5) {a: (2) 1, b: (2) 2, c: (2) 3}").await; + test("a: 1\nb: 2\nc: 3", "(5) {a: (2) 1, b: (2) 2, c: (2) 3}").await; // Test tagged values; - test("!AA bb cc", "(6) !AA (3) bb cc"); + test("!AA bb cc", "(6) !AA (3) bb cc").await; // Test nesting; test( "aa:\n bb:\n - cc: !DD ee", "(5) {aa: (5) {bb: (4) [(5) {cc: (6) !DD (3) ee}]}}", - ); + ) + .await; assert_eq!(module.gas_used(), gas_used, "gas used"); } diff --git a/runtime/test/src/test/abi.rs b/runtime/test/src/test/abi.rs index b681287c50c..422bd25b2d1 100644 --- a/runtime/test/src/test/abi.rs +++ b/runtime/test/src/test/abi.rs @@ -22,7 +22,8 @@ async fn test_unbounded_loop(api_version: Version) { .get_func("loop") .typed(&mut instance.store.as_context_mut()) .unwrap() - .call(&mut instance.store.as_context_mut(), ()); + .call_async(&mut instance.store.as_context_mut(), ()) + .await; let err = res.unwrap_err(); assert!( format!("{err:?}").contains("wasm trap: interrupt"), @@ -55,7 +56,8 @@ async fn test_unbounded_recursion(api_version: Version) { .get_func("rabbit_hole") .typed(&mut instance.store.as_context_mut()) .unwrap() - .call(&mut instance.store.as_context_mut(), ()); + .call_async(&mut instance.store.as_context_mut(), ()) + .await; let err_msg = res.unwrap_err(); assert!( format!("{err_msg:?}").contains("call stack exhausted"), @@ -91,7 +93,8 @@ async fn test_abi_array(api_version: Version, gas_used: u64) { "3".to_owned(), "4".to_owned(), ]; - let new_vec_obj: AscPtr>> = module.invoke_export1("test_array", &vec); + let new_vec_obj: AscPtr>> = + module.invoke_export1("test_array", &vec).await; let new_vec: Vec = module.asc_get(new_vec_obj).unwrap(); assert_eq!(module.gas_used(), gas_used); @@ -129,8 +132,9 @@ async fn test_abi_subarray(api_version: Version) { .await; let vec: Vec = vec![1, 2, 3, 4]; - let new_vec_obj: AscPtr> = - module.invoke_export1("byte_array_third_quarter", vec.as_slice()); + let new_vec_obj: AscPtr> = module + .invoke_export1("byte_array_third_quarter", vec.as_slice()) + .await; let new_vec: Vec = module.asc_get(new_vec_obj).unwrap(); assert_eq!(new_vec, vec![3]); @@ -158,7 +162,7 @@ async fn test_abi_bytes_and_fixed_bytes(api_version: Version) { .await; let bytes1: Vec = vec![42, 45, 7, 245, 45]; let bytes2: Vec = vec![3, 12, 0, 1, 255]; - let new_vec_obj: AscPtr = module.invoke_export2("concat", &*bytes1, &*bytes2); + let new_vec_obj: AscPtr = module.invoke_export2("concat", &*bytes1, &*bytes2).await; // This should be bytes1 and bytes2 concatenated. let new_vec: Vec = module.asc_get(new_vec_obj).unwrap(); @@ -193,28 +197,37 @@ async fn test_abi_ethabi_token_identity(api_version: Version) { let address = H160([1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]); let token_address = Token::Address(address); - let new_address_obj: AscPtr = - instance.invoke_export1("token_to_address", &token_address); + let new_address_obj: AscPtr = instance + .invoke_export1("token_to_address", &token_address) + .await; - let new_token_ptr = instance.takes_ptr_returns_ptr("token_from_address", new_address_obj); + let new_token_ptr = instance + .takes_ptr_returns_ptr("token_from_address", new_address_obj) + .await; let new_token = instance.asc_get(new_token_ptr).unwrap(); assert_eq!(token_address, new_token); // Token::Bytes let token_bytes = Token::Bytes(vec![42, 45, 7, 245, 45]); - let new_bytes_obj: AscPtr = - instance.invoke_export1("token_to_bytes", &token_bytes); - let new_token_ptr = instance.takes_ptr_returns_ptr("token_from_bytes", new_bytes_obj); + let new_bytes_obj: AscPtr = instance + .invoke_export1("token_to_bytes", &token_bytes) + .await; + let new_token_ptr = instance + .takes_ptr_returns_ptr("token_from_bytes", new_bytes_obj) + .await; let new_token = instance.asc_get(new_token_ptr).unwrap(); assert_eq!(token_bytes, new_token); // Token::Int let int_token = Token::Int(U256([256, 453452345, 0, 42])); - let new_int_obj: AscPtr = instance.invoke_export1("token_to_int", &int_token); + let new_int_obj: AscPtr = + instance.invoke_export1("token_to_int", &int_token).await; - let new_token_ptr = instance.takes_ptr_returns_ptr("token_from_int", new_int_obj); + let new_token_ptr = instance + .takes_ptr_returns_ptr("token_from_int", new_int_obj) + .await; let new_token = instance.asc_get(new_token_ptr).unwrap(); assert_eq!(int_token, new_token); @@ -222,8 +235,11 @@ async fn test_abi_ethabi_token_identity(api_version: Version) { // Token::Uint let uint_token = Token::Uint(U256([256, 453452345, 0, 42])); - let new_uint_obj: AscPtr = instance.invoke_export1("token_to_uint", &uint_token); - let new_token_ptr = instance.takes_ptr_returns_ptr("token_from_uint", new_uint_obj); + let new_uint_obj: AscPtr = + instance.invoke_export1("token_to_uint", &uint_token).await; + let new_token_ptr = instance + .takes_ptr_returns_ptr("token_from_uint", new_uint_obj) + .await; let new_token = instance.asc_get(new_token_ptr).unwrap(); assert_eq!(uint_token, new_token); @@ -232,29 +248,35 @@ async fn test_abi_ethabi_token_identity(api_version: Version) { // Token::Bool let token_bool = Token::Bool(true); - let token_bool_ptr = instance.asc_new(&token_bool).unwrap(); + let token_bool_ptr = instance.asc_new(&token_bool).await.unwrap(); let func = instance .get_func("token_to_bool") .typed(&mut instance.store.as_context_mut()) .unwrap() .clone(); let boolean: i32 = func - .call( + .call_async( &mut instance.store.as_context_mut(), token_bool_ptr.wasm_ptr(), ) + .await .unwrap(); - let new_token_ptr = instance.takes_val_returns_ptr("token_from_bool", boolean); + let new_token_ptr = instance + .takes_val_returns_ptr("token_from_bool", boolean) + .await; let new_token = instance.asc_get(new_token_ptr).unwrap(); assert_eq!(token_bool, new_token); // Token::String let token_string = Token::String("漢字Go🇧🇷".into()); - let new_string_obj: AscPtr = - instance.invoke_export1("token_to_string", &token_string); - let new_token_ptr = instance.takes_ptr_returns_ptr("token_from_string", new_string_obj); + let new_string_obj: AscPtr = instance + .invoke_export1("token_to_string", &token_string) + .await; + let new_token_ptr = instance + .takes_ptr_returns_ptr("token_from_string", new_string_obj) + .await; let new_token = instance.asc_get(new_token_ptr).unwrap(); assert_eq!(token_string, new_token); @@ -262,10 +284,13 @@ async fn test_abi_ethabi_token_identity(api_version: Version) { // Token::Array let token_array = Token::Array(vec![token_address, token_bytes, token_bool]); let token_array_nested = Token::Array(vec![token_string, token_array]); - let new_array_obj: AscEnumArray = - instance.invoke_export1("token_to_array", &token_array_nested); + let new_array_obj: AscEnumArray = instance + .invoke_export1("token_to_array", &token_array_nested) + .await; - let new_token_ptr = instance.takes_ptr_returns_ptr("token_from_array", new_array_obj); + let new_token_ptr = instance + .takes_ptr_returns_ptr("token_from_array", new_array_obj) + .await; let new_token: Token = instance.asc_get(new_token_ptr).unwrap(); assert_eq!(new_token, token_array_nested); @@ -302,43 +327,54 @@ async fn test_abi_store_value(api_version: Version) { .typed(&mut instance.store.as_context_mut()) .unwrap() .clone(); - let ptr: u32 = func.call(&mut instance.store.as_context_mut(), ()).unwrap(); + let ptr: u32 = func + .call_async(&mut instance.store.as_context_mut(), ()) + .await + .unwrap(); let null_value_ptr: AscPtr> = ptr.into(); let null_value: Value = instance.asc_get(null_value_ptr).unwrap(); assert_eq!(null_value, Value::Null); // Value::String let string = "some string"; - let new_value_ptr = instance.invoke_export1("value_from_string", string); + let new_value_ptr = instance.invoke_export1("value_from_string", string).await; let new_value: Value = instance.asc_get(new_value_ptr).unwrap(); assert_eq!(new_value, Value::from(string)); // Value::Int let int = i32::min_value(); - let new_value_ptr = instance.takes_val_returns_ptr("value_from_int", int); + let new_value_ptr = instance.takes_val_returns_ptr("value_from_int", int).await; let new_value: Value = instance.asc_get(new_value_ptr).unwrap(); assert_eq!(new_value, Value::Int(int)); // Value::Int8 let int8 = i64::min_value(); - let new_value_ptr = instance.takes_val_returns_ptr("value_from_int8", int8); + let new_value_ptr = instance + .takes_val_returns_ptr("value_from_int8", int8) + .await; let new_value: Value = instance.asc_get(new_value_ptr).unwrap(); assert_eq!(new_value, Value::Int8(int8)); // Value::BigDecimal let big_decimal = BigDecimal::from_str("3.14159001").unwrap(); - let new_value_ptr = instance.invoke_export1("value_from_big_decimal", &big_decimal); + let new_value_ptr = instance + .invoke_export1("value_from_big_decimal", &big_decimal) + .await; let new_value: Value = instance.asc_get(new_value_ptr).unwrap(); assert_eq!(new_value, Value::BigDecimal(big_decimal)); let big_decimal = BigDecimal::new(10.into(), 5); - let new_value_ptr = instance.invoke_export1("value_from_big_decimal", &big_decimal); + let new_value_ptr = instance + .invoke_export1("value_from_big_decimal", &big_decimal) + .await; let new_value: Value = instance.asc_get(new_value_ptr).unwrap(); assert_eq!(new_value, Value::BigDecimal(1_000_000.into())); // Value::Bool let boolean = true; - let new_value_ptr = instance.takes_val_returns_ptr("value_from_bool", boolean as i32); + let new_value_ptr = instance + .takes_val_returns_ptr("value_from_bool", boolean as i32) + .await; let new_value: Value = instance.asc_get(new_value_ptr).unwrap(); assert_eq!(new_value, Value::Bool(boolean)); @@ -349,9 +385,10 @@ async fn test_abi_store_value(api_version: Version) { .unwrap() .clone(); - let wasm_ptr = instance.asc_new(string).unwrap().wasm_ptr(); + let wasm_ptr = instance.asc_new(string).await.unwrap().wasm_ptr(); let new_value_ptr: u32 = func - .call(&mut instance.store.as_context_mut(), (wasm_ptr, int)) + .call_async(&mut instance.store.as_context_mut(), (wasm_ptr, int)) + .await .unwrap(); let new_value_ptr = AscPtr::from(new_value_ptr); let new_value: Value = instance.asc_get(new_value_ptr).unwrap(); @@ -364,7 +401,7 @@ async fn test_abi_store_value(api_version: Version) { Value::String("foo".to_owned()), Value::String("bar".to_owned()), ]; - let new_value_ptr = instance.invoke_export1("value_from_array", array); + let new_value_ptr = instance.invoke_export1("value_from_array", array).await; let new_value: Value = instance.asc_get(new_value_ptr).unwrap(); assert_eq!( new_value, @@ -376,13 +413,13 @@ async fn test_abi_store_value(api_version: Version) { // Value::Bytes let bytes: &[u8] = &[0, 2, 5]; - let new_value_ptr = instance.invoke_export1("value_from_bytes", bytes); + let new_value_ptr = instance.invoke_export1("value_from_bytes", bytes).await; let new_value: Value = instance.asc_get(new_value_ptr).unwrap(); assert_eq!(new_value, Value::Bytes(bytes.into())); // Value::BigInt let bytes: &[u8] = &[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]; - let new_value_ptr = instance.invoke_export1("value_from_bigint", bytes); + let new_value_ptr = instance.invoke_export1("value_from_bigint", bytes).await; let new_value: Value = instance.asc_get(new_value_ptr).unwrap(); assert_eq!( new_value, @@ -413,7 +450,7 @@ async fn test_abi_h160(api_version: Version) { let address = H160::zero(); // As an `Uint8Array` - let new_address_obj: AscPtr = module.invoke_export1("test_address", &address); + let new_address_obj: AscPtr = module.invoke_export1("test_address", &address).await; // This should have 1 added to the first and last byte. let new_address: H160 = module.asc_get(new_address_obj).unwrap(); @@ -445,7 +482,7 @@ async fn test_string(api_version: Version) { ) .await; let string = " 漢字Double_Me🇧🇷 "; - let trimmed_string_obj: AscPtr = module.invoke_export1("repeat_twice", string); + let trimmed_string_obj: AscPtr = module.invoke_export1("repeat_twice", string).await; let doubled_string: String = module.asc_get(trimmed_string_obj).unwrap(); assert_eq!(doubled_string, string.repeat(2)); } @@ -473,8 +510,9 @@ async fn test_abi_big_int(api_version: Version) { // Test passing in 0 and increment it by 1 let old_uint = U256::zero(); - let new_uint_obj: AscPtr = - module.invoke_export1("test_uint", &BigInt::from_unsigned_u256(&old_uint)); + let new_uint_obj: AscPtr = module + .invoke_export1("test_uint", &BigInt::from_unsigned_u256(&old_uint)) + .await; let new_uint: BigInt = module.asc_get(new_uint_obj).unwrap(); assert_eq!(new_uint, BigInt::from(1_i32)); let new_uint = new_uint.to_unsigned_u256(); @@ -482,7 +520,7 @@ async fn test_abi_big_int(api_version: Version) { // Test passing in -50 and increment it by 1 let old_uint = BigInt::from(-50); - let new_uint_obj: AscPtr = module.invoke_export1("test_uint", &old_uint); + let new_uint_obj: AscPtr = module.invoke_export1("test_uint", &old_uint).await; let new_uint: BigInt = module.asc_get(new_uint_obj).unwrap(); assert_eq!(new_uint, BigInt::from(-49_i32)); let new_uint_from_u256 = BigInt::from_signed_u256(&new_uint.to_signed_u256()); @@ -512,7 +550,7 @@ async fn test_big_int_to_string(api_version: Version) { let big_int_str = "30145144166666665000000000000000000"; let big_int = BigInt::from_str(big_int_str).unwrap(); - let string_obj: AscPtr = module.invoke_export1("big_int_to_string", &big_int); + let string_obj: AscPtr = module.invoke_export1("big_int_to_string", &big_int).await; let string: String = module.asc_get(string_obj).unwrap(); assert_eq!(string, big_int_str); } @@ -543,7 +581,10 @@ async fn test_invalid_discriminant(api_version: Version) { .typed(&mut instance.store.as_context_mut()) .unwrap() .clone(); - let ptr: u32 = func.call(&mut instance.store.as_context_mut(), ()).unwrap(); + let ptr: u32 = func + .call_async(&mut instance.store.as_context_mut(), ()) + .await + .unwrap(); let _value: Value = instance.asc_get(ptr.into()).unwrap(); } diff --git a/runtime/test/src/test_padding.rs b/runtime/test/src/test_padding.rs index fc6e922692f..bf633d3dc73 100644 --- a/runtime/test/src/test_padding.rs +++ b/runtime/test/src/test_padding.rs @@ -60,22 +60,23 @@ pub mod data { IndexForAscTypeId::UnitTestNetworkUnitTestTypeBool; } - use graph::runtime::HostExportError; pub use graph::runtime::{ asc_new, gas::GasCounter, AscHeap, AscIndexId, AscPtr, AscType, AscValue, DeterministicHostError, IndexForAscTypeId, ToAscObj, }; + use graph::{prelude::async_trait, runtime::HostExportError}; use graph_runtime_wasm::asc_abi::class::AscString; + #[async_trait] impl ToAscObj for Bad { - fn to_asc_obj( + async fn to_asc_obj( &self, heap: &mut H, gas: &GasCounter, ) -> Result { Ok(AscBad { nonce: self.nonce, - str_suff: asc_new(heap, &self.str_suff, gas)?, + str_suff: asc_new(heap, &self.str_suff, gas).await?, tail: self.tail, }) } @@ -126,15 +127,16 @@ pub mod data { IndexForAscTypeId::UnitTestNetworkUnitTestTypeBool; } + #[async_trait] impl ToAscObj for BadFixed { - fn to_asc_obj( + async fn to_asc_obj( &self, heap: &mut H, gas: &GasCounter, ) -> Result { Ok(AscBadFixed { nonce: self.nonce, - str_suff: asc_new(heap, &self.str_suff, gas)?, + str_suff: asc_new(heap, &self.str_suff, gas).await?, _padding: 0, tail: self.tail, }) @@ -179,7 +181,7 @@ async fn manual_padding_should_fail(api_version: semver::Version) { tail: i64::MAX as u64, }; - let new_obj = instance.asc_new(&parm).unwrap(); + let new_obj = instance.asc_new(&parm).await.unwrap(); let func = instance .get_func("test_padding_manual") @@ -187,7 +189,9 @@ async fn manual_padding_should_fail(api_version: semver::Version) { .unwrap() .clone(); - let res: Result<(), _> = func.call(&mut instance.store.as_context_mut(), new_obj.wasm_ptr()); + let res: Result<(), _> = func + .call_async(&mut instance.store.as_context_mut(), new_obj.wasm_ptr()) + .await; assert!( res.is_err(), @@ -212,7 +216,7 @@ async fn manual_padding_manualy_fixed_ok(api_version: semver::Version) { ) .await; - let new_obj = instance.asc_new(&parm).unwrap(); + let new_obj = instance.asc_new(&parm).await.unwrap(); let func = instance .get_func("test_padding_manual") @@ -220,7 +224,9 @@ async fn manual_padding_manualy_fixed_ok(api_version: semver::Version) { .unwrap() .clone(); - let res: Result<(), _> = func.call(&mut instance.store.as_context_mut(), new_obj.wasm_ptr()); + let res: Result<(), _> = func + .call_async(&mut instance.store.as_context_mut(), new_obj.wasm_ptr()) + .await; assert!(res.is_ok(), "{:?}", res.err()); } diff --git a/runtime/wasm/src/asc_abi/class.rs b/runtime/wasm/src/asc_abi/class.rs index 210752582bf..4fe5b3192cd 100644 --- a/runtime/wasm/src/asc_abi/class.rs +++ b/runtime/wasm/src/asc_abi/class.rs @@ -1,3 +1,4 @@ +use async_trait::async_trait; use ethabi; use graph::{ @@ -92,18 +93,18 @@ pub enum TypedArray { } impl TypedArray { - pub fn new( + pub async fn new( content: &[T], heap: &mut H, gas: &GasCounter, ) -> Result { match heap.api_version() { version if version <= &API_VERSION_0_0_4 => Ok(Self::ApiVersion0_0_4( - v0_0_4::TypedArray::new(content, heap, gas)?, + v0_0_4::TypedArray::new(content, heap, gas).await?, + )), + _ => Ok(Self::ApiVersion0_0_5( + v0_0_5::TypedArray::new(content, heap, gas).await?, )), - _ => Ok(Self::ApiVersion0_0_5(v0_0_5::TypedArray::new( - content, heap, gas, - )?)), } } @@ -146,13 +147,14 @@ impl AscType for TypedArray { pub struct Bytes<'a>(pub &'a Vec); pub type Uint8Array = TypedArray; +#[async_trait] impl ToAscObj for Bytes<'_> { - fn to_asc_obj( + async fn to_asc_obj( &self, heap: &mut H, gas: &GasCounter, ) -> Result { - self.0.to_asc_obj(heap, gas) + self.0.to_asc_obj(heap, gas).await } } @@ -272,18 +274,18 @@ pub enum Array { } impl Array { - pub fn new( + pub async fn new( content: &[T], heap: &mut H, gas: &GasCounter, ) -> Result { match heap.api_version() { version if version <= &API_VERSION_0_0_4 => Ok(Self::ApiVersion0_0_4( - v0_0_4::Array::new(content, heap, gas)?, + v0_0_4::Array::new(content, heap, gas).await?, + )), + _ => Ok(Self::ApiVersion0_0_5( + v0_0_5::Array::new(content, heap, gas).await?, )), - _ => Ok(Self::ApiVersion0_0_5(v0_0_5::Array::new( - content, heap, gas, - )?)), } } diff --git a/runtime/wasm/src/asc_abi/v0_0_4.rs b/runtime/wasm/src/asc_abi/v0_0_4.rs index 4fabbc425e7..c4098ac0889 100644 --- a/runtime/wasm/src/asc_abi/v0_0_4.rs +++ b/runtime/wasm/src/asc_abi/v0_0_4.rs @@ -149,7 +149,7 @@ pub struct TypedArray { } impl TypedArray { - pub(crate) fn new( + pub(crate) async fn new( content: &[T], heap: &mut H, gas: &GasCounter, @@ -160,7 +160,7 @@ impl TypedArray { } else { unreachable!("Only the correct ArrayBuffer will be constructed") }; - let ptr = AscPtr::alloc_obj(buffer, heap, gas)?; + let ptr = AscPtr::alloc_obj(buffer, heap, gas).await?; Ok(TypedArray { byte_length: buffer_byte_length, buffer: AscPtr::new(ptr.wasm_ptr()), @@ -303,13 +303,13 @@ pub struct Array { } impl Array { - pub fn new( + pub async fn new( content: &[T], heap: &mut H, gas: &GasCounter, ) -> Result { let arr_buffer = class::ArrayBuffer::new(content, heap.api_version())?; - let arr_buffer_ptr = AscPtr::alloc_obj(arr_buffer, heap, gas)?; + let arr_buffer_ptr = AscPtr::alloc_obj(arr_buffer, heap, gas).await?; Ok(Array { buffer: AscPtr::new(arr_buffer_ptr.wasm_ptr()), // If this cast would overflow, the above line has already panicked. diff --git a/runtime/wasm/src/asc_abi/v0_0_5.rs b/runtime/wasm/src/asc_abi/v0_0_5.rs index 71333eb91f1..906f6ff1cf6 100644 --- a/runtime/wasm/src/asc_abi/v0_0_5.rs +++ b/runtime/wasm/src/asc_abi/v0_0_5.rs @@ -114,14 +114,14 @@ pub struct TypedArray { } impl TypedArray { - pub(crate) fn new( + pub(crate) async fn new( content: &[T], heap: &mut H, gas: &GasCounter, ) -> Result { let buffer = class::ArrayBuffer::new(content, heap.api_version())?; let byte_length = content.len() as u32; - let ptr = AscPtr::alloc_obj(buffer, heap, gas)?; + let ptr = AscPtr::alloc_obj(buffer, heap, gas).await?; Ok(TypedArray { buffer: AscPtr::new(ptr.wasm_ptr()), // new AscPtr necessary to convert type parameter data_start: ptr.wasm_ptr(), @@ -264,13 +264,13 @@ pub struct Array { } impl Array { - pub fn new( + pub async fn new( content: &[T], heap: &mut H, gas: &GasCounter, ) -> Result { let arr_buffer = class::ArrayBuffer::new(content, heap.api_version())?; - let buffer = AscPtr::alloc_obj(arr_buffer, heap, gas)?; + let buffer = AscPtr::alloc_obj(arr_buffer, heap, gas).await?; let buffer_data_length = buffer.read_len(heap, gas)?; Ok(Array { buffer: AscPtr::new(buffer.wasm_ptr()), diff --git a/runtime/wasm/src/host_exports.rs b/runtime/wasm/src/host_exports.rs index f57c9d3b528..cdc6b5379d5 100644 --- a/runtime/wasm/src/host_exports.rs +++ b/runtime/wasm/src/host_exports.rs @@ -5,7 +5,7 @@ use std::time::{Duration, Instant}; use graph::data::subgraph::API_VERSION_0_0_8; use graph::data::value::Word; -use graph::futures03::stream::StreamExt; +use graph::futures03::StreamExt; use graph::schema::EntityType; use never::Never; use semver::Version; @@ -476,17 +476,23 @@ impl HostExports { )) } - pub(crate) fn ipfs_cat(&self, logger: &Logger, link: String) -> Result, anyhow::Error> { + pub(crate) async fn ipfs_cat( + &self, + logger: &Logger, + link: String, + ) -> Result, anyhow::Error> { // Does not consume gas because this is not a part of the deterministic feature set. // Ideally this would first consume gas for fetching the file stats, and then again // for the bytes of the file. - graph::block_on(self.link_resolver.cat( - &LinkResolverContext::new(&self.subgraph_id, logger), - &Link { link }, - )) + self.link_resolver + .cat( + &LinkResolverContext::new(&self.subgraph_id, logger), + &Link { link }, + ) + .await } - pub(crate) fn ipfs_get_block( + pub(crate) async fn ipfs_get_block( &self, logger: &Logger, link: String, @@ -494,10 +500,12 @@ impl HostExports { // Does not consume gas because this is not a part of the deterministic feature set. // Ideally this would first consume gas for fetching the file stats, and then again // for the bytes of the file. - graph::block_on(self.link_resolver.get_block( - &LinkResolverContext::new(&self.subgraph_id, logger), - &Link { link }, - )) + self.link_resolver + .get_block( + &LinkResolverContext::new(&self.subgraph_id, logger), + &Link { link }, + ) + .await } // Read the IPFS file `link`, split it into JSON objects, and invoke the @@ -507,7 +515,7 @@ impl HostExports { // which is identical to `module` when it was first started. The signature // of the callback must be `callback(JSONValue, Value)`, and the `userData` // parameter is passed to the callback without any changes - pub(crate) fn ipfs_map( + pub(crate) async fn ipfs_map( &self, wasm_ctx: &WasmInstanceData, link: String, @@ -540,20 +548,26 @@ impl HostExports { let logger = ctx.logger.new(o!("ipfs_map" => link.clone())); let result = { - let mut stream: JsonValueStream = graph::block_on(self.link_resolver.json_stream( - &LinkResolverContext::new(&self.subgraph_id, &logger), - &Link { link }, - ))?; + let mut stream: JsonValueStream = self + .link_resolver + .json_stream( + &LinkResolverContext::new(&self.subgraph_id, &logger), + &Link { link }, + ) + .await?; let mut v = Vec::new(); - while let Some(sv) = graph::block_on(stream.next()) { + while let Some(sv) = stream.next().await { let sv = sv?; - let module = WasmInstance::from_valid_module_with_ctx( + let module = WasmInstance::from_valid_module_with_ctx_boxed( valid_module.clone(), ctx.derive_with_empty_block_state(), host_metrics.clone(), wasm_ctx.experimental_features, - )?; - let result = module.handle_json_callback(&callback, &sv.value, &user_data)?; + ) + .await?; + let result = module + .handle_json_callback(&callback, &sv.value, &user_data) + .await?; // Log progress every 15s if last_log.elapsed() > Duration::from_secs(15) { debug!( diff --git a/runtime/wasm/src/mapping.rs b/runtime/wasm/src/mapping.rs index c5678bf2aa2..0e06c125c1a 100644 --- a/runtime/wasm/src/mapping.rs +++ b/runtime/wasm/src/mapping.rs @@ -36,8 +36,16 @@ where // Create channel for event handling requests let (mapping_request_sender, mapping_request_receiver) = mpsc::channel(100); - // wasmtime instances are not `Send` therefore they cannot be scheduled by - // the regular tokio executor, so we create a dedicated thread. + // It used to be that we had to create a dedicated thread since wasmtime + // instances were not `Send` and could therefore not be scheduled by the + // regular tokio executor. This isn't an issue anymore, but we still + // spawn a dedicated thread since running WASM code async can block and + // lock up the executor. See [the wasmtime + // docs](https://docs.rs/wasmtime/latest/wasmtime/struct.Config.html#execution-in-poll) + // on how this should be handled properly. As that is a fairly large + // change to how we use wasmtime, we keep the threading model for now. + // Once we are confident that things are working that way, we should + // revisit this and remove the dedicated thread. // // In case of failure, this thread may panic or simply terminate, // dropping the `mapping_request_receiver` which ultimately causes the @@ -59,24 +67,29 @@ where } = request; let logger = ctx.logger.clone(); - let result = panic::catch_unwind(AssertUnwindSafe(|| { - instantiate_module::( + let handle_fut = async { + let result = instantiate_module::( valid_module.cheap_clone(), ctx, host_metrics.cheap_clone(), experimental_features, ) - .map_err(Into::into) - .and_then(|module| match inner { - WasmRequestInner::TriggerRequest(trigger) => { - handle_trigger(&logger, module, trigger, host_metrics.cheap_clone()) - } - WasmRequestInner::BlockRequest(BlockRequest { - block_data, - handler, - }) => module.handle_block(&logger, &handler, block_data), - }) - })); + .await; + match result { + Ok(module) => match inner { + WasmRequestInner::TriggerRequest(trigger) => { + handle_trigger(&logger, module, trigger, host_metrics.cheap_clone()) + .await + } + WasmRequestInner::BlockRequest(BlockRequest { + block_data, + handler, + }) => module.handle_block(&logger, &handler, block_data).await, + }, + Err(e) => Err(MappingError::Unknown(e)), + } + }; + let result = panic::catch_unwind(AssertUnwindSafe(|| graph::block_on(handle_fut))); let result = match result { Ok(result) => result, @@ -111,7 +124,7 @@ where Ok(mapping_request_sender) } -fn instantiate_module( +async fn instantiate_module( valid_module: Arc, ctx: MappingContext, host_metrics: Arc, @@ -128,10 +141,11 @@ where host_metrics.cheap_clone(), experimental_features, ) + .await .context("module instantiation failed") } -fn handle_trigger( +async fn handle_trigger( logger: &Logger, module: WasmInstance, trigger: TriggerWithHandler>, @@ -146,7 +160,7 @@ where if ENV_VARS.log_trigger_data { debug!(logger, "trigger data: {:?}", trigger); } - module.handle_trigger(trigger) + module.handle_trigger(trigger).await } pub struct WasmRequest { @@ -312,6 +326,7 @@ impl ValidModule { config.cranelift_nan_canonicalization(true); // For NaN determinism. config.cranelift_opt_level(wasmtime::OptLevel::None); config.max_wasm_stack(ENV_VARS.mappings.max_stack_size); + config.async_support(true); let engine = &wasmtime::Engine::new(&config)?; let module = wasmtime::Module::from_binary(engine, &raw_module)?; diff --git a/runtime/wasm/src/module/context.rs b/runtime/wasm/src/module/context.rs index 510f1bb7224..881d7eb6c88 100644 --- a/runtime/wasm/src/module/context.rs +++ b/runtime/wasm/src/module/context.rs @@ -132,7 +132,7 @@ impl WasmInstanceData { } impl WasmInstanceContext<'_> { - fn store_get_scoped( + async fn store_get_scoped( &mut self, gas: &GasCounter, entity_ptr: AscPtr, @@ -168,7 +168,7 @@ impl WasmInstanceContext<'_> { let ret = match entity_option { Some(entity) => { let _section = host_metrics.stopwatch.start_section("store_get_asc_new"); - asc_new(self, &entity.sorted_ref(), gas)? + asc_new(self, &entity.sorted_ref(), gas).await? } None => match &debug_fork { Some(fork) => { @@ -182,8 +182,8 @@ impl WasmInstanceContext<'_> { Some(entity) => { let _section = host_metrics.stopwatch.start_section("store_get_asc_new"); - let entity = asc_new(self, &entity.sorted(), gas)?; - self.store_set(gas, entity_ptr, id_ptr, entity)?; + let entity = asc_new(self, &entity.sorted(), gas).await?; + self.store_set(gas, entity_ptr, id_ptr, entity).await?; entity } None => AscPtr::null(), @@ -201,7 +201,7 @@ impl WasmInstanceContext<'_> { impl WasmInstanceContext<'_> { /// function abort(message?: string | null, fileName?: string | null, lineNumber?: u32, columnNumber?: u32): void /// Always returns a trap. - pub fn abort( + pub async fn abort( &mut self, gas: &GasCounter, message_ptr: AscPtr, @@ -240,7 +240,7 @@ impl WasmInstanceContext<'_> { } /// function store.set(entity: string, id: string, data: Entity): void - pub fn store_set( + pub async fn store_set( &mut self, gas: &GasCounter, entity_ptr: AscPtr, @@ -282,7 +282,7 @@ impl WasmInstanceContext<'_> { } /// function store.remove(entity: string, id: string): void - pub fn store_remove( + pub async fn store_remove( &mut self, gas: &GasCounter, entity_ptr: AscPtr, @@ -310,27 +310,29 @@ impl WasmInstanceContext<'_> { } /// function store.get(entity: string, id: string): Entity | null - pub fn store_get( + pub async fn store_get( &mut self, gas: &GasCounter, entity_ptr: AscPtr, id_ptr: AscPtr, ) -> Result, HostExportError> { self.store_get_scoped(gas, entity_ptr, id_ptr, GetScope::Store) + .await } /// function store.get_in_block(entity: string, id: string): Entity | null - pub fn store_get_in_block( + pub async fn store_get_in_block( &mut self, gas: &GasCounter, entity_ptr: AscPtr, id_ptr: AscPtr, ) -> Result, HostExportError> { self.store_get_scoped(gas, entity_ptr, id_ptr, GetScope::InBlock) + .await } /// function store.loadRelated(entity_type: string, id: string, field: string): Array - pub fn store_load_related( + pub async fn store_load_related( &mut self, gas: &GasCounter, @@ -352,12 +354,12 @@ impl WasmInstanceContext<'_> { let entities: Vec> = entities.into_iter().map(|entity| entity.sorted()).collect(); - let ret = asc_new(self, &entities, gas)?; + let ret = asc_new(self, &entities, gas).await?; Ok(ret) } /// function typeConversion.bytesToString(bytes: Bytes): string - pub fn bytes_to_string( + pub async fn bytes_to_string( &mut self, gas: &GasCounter, bytes_ptr: AscPtr, @@ -367,14 +369,14 @@ impl WasmInstanceContext<'_> { let ctx = &mut self.as_mut().ctx; let string = host_exports.bytes_to_string(&ctx.logger, bytes, gas, &mut ctx.state)?; - asc_new(self, &string, gas) + asc_new(self, &string, gas).await } /// Converts bytes to a hex string. /// function typeConversion.bytesToHex(bytes: Bytes): string /// References: /// https://godoc.org/github.com/ethereum/go-ethereum/common/hexutil#hdr-Encoding_Rules /// https://github.com/ethereum/web3.js/blob/f98fe1462625a6c865125fecc9cb6b414f0a5e83/packages/web3-utils/src/utils.js#L283 - pub fn bytes_to_hex( + pub async fn bytes_to_hex( &mut self, gas: &GasCounter, bytes_ptr: AscPtr, @@ -392,11 +394,11 @@ impl WasmInstanceContext<'_> { // Even an empty string must be prefixed with `0x`. // Encodes each byte as a two hex digits. let hex = format!("0x{}", hex::encode(bytes)); - asc_new(self, &hex, gas) + asc_new(self, &hex, gas).await } /// function typeConversion.bigIntToString(n: Uint8Array): string - pub fn big_int_to_string( + pub async fn big_int_to_string( &mut self, gas: &GasCounter, big_int_ptr: AscPtr, @@ -409,11 +411,11 @@ impl WasmInstanceContext<'_> { gas::DEFAULT_GAS_OP.with_args(gas::complexity::Mul, (&n, &n)), "big_int_to_string", )?; - asc_new(self, &n.to_string(), gas) + asc_new(self, &n.to_string(), gas).await } /// function bigInt.fromString(x: string): BigInt - pub fn big_int_from_string( + pub async fn big_int_from_string( &mut self, gas: &GasCounter, string_ptr: AscPtr, @@ -422,11 +424,11 @@ impl WasmInstanceContext<'_> { let s = asc_get(self, string_ptr, gas)?; let ctx = &mut self.as_mut().ctx; let result = host_exports.big_int_from_string(s, gas, &mut ctx.state)?; - asc_new(self, &result, gas) + asc_new(self, &result, gas).await } /// function typeConversion.bigIntToHex(n: Uint8Array): string - pub fn big_int_to_hex( + pub async fn big_int_to_hex( &mut self, gas: &GasCounter, big_int_ptr: AscPtr, @@ -435,11 +437,11 @@ impl WasmInstanceContext<'_> { let host_exports = self.as_ref().ctx.host_exports.cheap_clone(); let ctx = &mut self.as_mut().ctx; let hex = host_exports.big_int_to_hex(n, gas, &mut ctx.state)?; - asc_new(self, &hex, gas) + asc_new(self, &hex, gas).await } /// function typeConversion.stringToH160(s: String): H160 - pub fn string_to_h160( + pub async fn string_to_h160( &mut self, gas: &GasCounter, str_ptr: AscPtr, @@ -448,11 +450,11 @@ impl WasmInstanceContext<'_> { let host_exports = self.as_ref().ctx.host_exports.cheap_clone(); let ctx = &mut self.as_mut().ctx; let h160 = host_exports.string_to_h160(&s, gas, &mut ctx.state)?; - asc_new(self, &h160, gas) + asc_new(self, &h160, gas).await } /// function json.fromBytes(bytes: Bytes): JSONValue - pub fn json_from_bytes( + pub async fn json_from_bytes( &mut self, gas: &GasCounter, bytes_ptr: AscPtr, @@ -469,11 +471,11 @@ impl WasmInstanceContext<'_> { ) }) .map_err(DeterministicHostError::from)?; - asc_new(self, &result, gas) + asc_new(self, &result, gas).await } /// function json.try_fromBytes(bytes: Bytes): Result - pub fn json_try_from_bytes( + pub async fn json_try_from_bytes( &mut self, gas: &GasCounter, bytes_ptr: AscPtr, @@ -495,11 +497,11 @@ impl WasmInstanceContext<'_> { // result type expected by mappings true }); - asc_new(self, &result, gas) + asc_new(self, &result, gas).await } /// function ipfs.cat(link: String): Bytes - pub fn ipfs_cat( + pub async fn ipfs_cat( &mut self, gas: &GasCounter, link_ptr: AscPtr, @@ -520,10 +522,10 @@ impl WasmInstanceContext<'_> { let link = asc_get(self, link_ptr, gas)?; let host_exports = self.as_ref().ctx.host_exports.cheap_clone(); let logger = self.as_ref().ctx.logger.cheap_clone(); - let ipfs_res = host_exports.ipfs_cat(&logger, link); + let ipfs_res = host_exports.ipfs_cat(&logger, link).await; let logger = self.as_ref().ctx.logger.cheap_clone(); match ipfs_res { - Ok(bytes) => asc_new(self, &*bytes, gas).map_err(Into::into), + Ok(bytes) => asc_new(self, &*bytes, gas).await.map_err(Into::into), // Return null in case of error. Err(e) => { @@ -536,7 +538,7 @@ impl WasmInstanceContext<'_> { } /// function ipfs.getBlock(link: String): Bytes - pub fn ipfs_get_block( + pub async fn ipfs_get_block( &mut self, gas: &GasCounter, link_ptr: AscPtr, @@ -556,9 +558,11 @@ impl WasmInstanceContext<'_> { let link = asc_get(self, link_ptr, gas)?; let host_exports = self.as_ref().ctx.host_exports.cheap_clone(); - let ipfs_res = host_exports.ipfs_get_block(&self.as_ref().ctx.logger, link); + let ipfs_res = host_exports + .ipfs_get_block(&self.as_ref().ctx.logger, link) + .await; match ipfs_res { - Ok(bytes) => asc_new(self, &*bytes, gas).map_err(Into::into), + Ok(bytes) => asc_new(self, &*bytes, gas).await.map_err(Into::into), // Return null in case of error. Err(e) => { @@ -571,7 +575,7 @@ impl WasmInstanceContext<'_> { } /// function ipfs.map(link: String, callback: String, flags: String[]): void - pub fn ipfs_map( + pub async fn ipfs_map( &mut self, gas: &GasCounter, link_ptr: AscPtr, @@ -603,8 +607,9 @@ impl WasmInstanceContext<'_> { self.suspend_timeout(); let start_time = Instant::now(); let host_exports = self.as_ref().ctx.host_exports.cheap_clone(); - let output_states = - host_exports.ipfs_map(self.as_ref(), link.clone(), &callback, user_data, flags)?; + let output_states = host_exports + .ipfs_map(self.as_ref(), link.clone(), &callback, user_data, flags) + .await?; self.start_timeout(); debug!( @@ -624,7 +629,7 @@ impl WasmInstanceContext<'_> { /// Expects a decimal string. /// function json.toI64(json: String): i64 - pub fn json_to_i64( + pub async fn json_to_i64( &mut self, gas: &GasCounter, json_ptr: AscPtr, @@ -637,7 +642,7 @@ impl WasmInstanceContext<'_> { /// Expects a decimal string. /// function json.toU64(json: String): u64 - pub fn json_to_u64( + pub async fn json_to_u64( &mut self, gas: &GasCounter, @@ -651,7 +656,7 @@ impl WasmInstanceContext<'_> { /// Expects a decimal string. /// function json.toF64(json: String): f64 - pub fn json_to_f64( + pub async fn json_to_f64( &mut self, gas: &GasCounter, json_ptr: AscPtr, @@ -664,7 +669,7 @@ impl WasmInstanceContext<'_> { /// Expects a decimal string. /// function json.toBigInt(json: String): BigInt - pub fn json_to_big_int( + pub async fn json_to_big_int( &mut self, gas: &GasCounter, @@ -674,11 +679,11 @@ impl WasmInstanceContext<'_> { let json = asc_get(self, json_ptr, gas)?; let ctx = &mut self.as_mut().ctx; let big_int = host_exports.json_to_big_int(json, gas, &mut ctx.state)?; - asc_new(self, &*big_int, gas) + asc_new(self, &*big_int, gas).await } /// function crypto.keccak256(input: Bytes): Bytes - pub fn crypto_keccak_256( + pub async fn crypto_keccak_256( &mut self, gas: &GasCounter, @@ -689,11 +694,11 @@ impl WasmInstanceContext<'_> { let ctx = &mut self.as_mut().ctx; let input = host_exports.crypto_keccak_256(input, gas, &mut ctx.state)?; - asc_new(self, input.as_ref(), gas) + asc_new(self, input.as_ref(), gas).await } /// function bigInt.plus(x: BigInt, y: BigInt): BigInt - pub fn big_int_plus( + pub async fn big_int_plus( &mut self, gas: &GasCounter, @@ -706,11 +711,11 @@ impl WasmInstanceContext<'_> { let ctx = &mut self.as_mut().ctx; let result = host_exports.big_int_plus(x, y, gas, &mut ctx.state)?; - asc_new(self, &result, gas) + asc_new(self, &result, gas).await } /// function bigInt.minus(x: BigInt, y: BigInt): BigInt - pub fn big_int_minus( + pub async fn big_int_minus( &mut self, gas: &GasCounter, @@ -723,11 +728,11 @@ impl WasmInstanceContext<'_> { let ctx = &mut self.as_mut().ctx; let result = host_exports.big_int_minus(x, y, gas, &mut ctx.state)?; - asc_new(self, &result, gas) + asc_new(self, &result, gas).await } /// function bigInt.times(x: BigInt, y: BigInt): BigInt - pub fn big_int_times( + pub async fn big_int_times( &mut self, gas: &GasCounter, @@ -740,11 +745,11 @@ impl WasmInstanceContext<'_> { let ctx = &mut self.as_mut().ctx; let result = host_exports.big_int_times(x, y, gas, &mut ctx.state)?; - asc_new(self, &result, gas) + asc_new(self, &result, gas).await } /// function bigInt.dividedBy(x: BigInt, y: BigInt): BigInt - pub fn big_int_divided_by( + pub async fn big_int_divided_by( &mut self, gas: &GasCounter, @@ -757,11 +762,11 @@ impl WasmInstanceContext<'_> { let ctx = &mut self.as_mut().ctx; let result = host_exports.big_int_divided_by(x, y, gas, &mut ctx.state)?; - asc_new(self, &result, gas) + asc_new(self, &result, gas).await } /// function bigInt.dividedByDecimal(x: BigInt, y: BigDecimal): BigDecimal - pub fn big_int_divided_by_decimal( + pub async fn big_int_divided_by_decimal( &mut self, gas: &GasCounter, @@ -775,11 +780,11 @@ impl WasmInstanceContext<'_> { let ctx = &mut self.as_mut().ctx; let result = host_exports.big_decimal_divided_by(x, y, gas, &mut ctx.state)?; - asc_new(self, &result, gas) + asc_new(self, &result, gas).await } /// function bigInt.mod(x: BigInt, y: BigInt): BigInt - pub fn big_int_mod( + pub async fn big_int_mod( &mut self, gas: &GasCounter, @@ -792,11 +797,11 @@ impl WasmInstanceContext<'_> { let ctx = &mut self.as_mut().ctx; let result = host_exports.big_int_mod(x, y, gas, &mut ctx.state)?; - asc_new(self, &result, gas) + asc_new(self, &result, gas).await } /// function bigInt.pow(x: BigInt, exp: u8): BigInt - pub fn big_int_pow( + pub async fn big_int_pow( &mut self, gas: &GasCounter, @@ -809,11 +814,11 @@ impl WasmInstanceContext<'_> { let ctx = &mut self.as_mut().ctx; let result = host_exports.big_int_pow(x, exp, gas, &mut ctx.state)?; - asc_new(self, &result, gas) + asc_new(self, &result, gas).await } /// function bigInt.bitOr(x: BigInt, y: BigInt): BigInt - pub fn big_int_bit_or( + pub async fn big_int_bit_or( &mut self, gas: &GasCounter, @@ -826,11 +831,11 @@ impl WasmInstanceContext<'_> { let ctx = &mut self.as_mut().ctx; let result = host_exports.big_int_bit_or(x, y, gas, &mut ctx.state)?; - asc_new(self, &result, gas) + asc_new(self, &result, gas).await } /// function bigInt.bitAnd(x: BigInt, y: BigInt): BigInt - pub fn big_int_bit_and( + pub async fn big_int_bit_and( &mut self, gas: &GasCounter, @@ -843,11 +848,11 @@ impl WasmInstanceContext<'_> { let ctx = &mut self.as_mut().ctx; let result = host_exports.big_int_bit_and(x, y, gas, &mut ctx.state)?; - asc_new(self, &result, gas) + asc_new(self, &result, gas).await } /// function bigInt.leftShift(x: BigInt, bits: u8): BigInt - pub fn big_int_left_shift( + pub async fn big_int_left_shift( &mut self, gas: &GasCounter, @@ -859,11 +864,11 @@ impl WasmInstanceContext<'_> { let host_exports = self.as_ref().ctx.host_exports.cheap_clone(); let ctx = &mut self.as_mut().ctx; let result = host_exports.big_int_left_shift(x, bits, gas, &mut ctx.state)?; - asc_new(self, &result, gas) + asc_new(self, &result, gas).await } /// function bigInt.rightShift(x: BigInt, bits: u8): BigInt - pub fn big_int_right_shift( + pub async fn big_int_right_shift( &mut self, gas: &GasCounter, @@ -876,11 +881,11 @@ impl WasmInstanceContext<'_> { let ctx = &mut self.as_mut().ctx; let result = host_exports.big_int_right_shift(x, bits, gas, &mut ctx.state)?; - asc_new(self, &result, gas) + asc_new(self, &result, gas).await } /// function typeConversion.bytesToBase58(bytes: Bytes): string - pub fn bytes_to_base58( + pub async fn bytes_to_base58( &mut self, gas: &GasCounter, @@ -890,11 +895,11 @@ impl WasmInstanceContext<'_> { let host_exports = self.as_ref().ctx.host_exports.cheap_clone(); let ctx = &mut self.as_mut().ctx; let result = host_exports.bytes_to_base58(bytes, gas, &mut ctx.state)?; - asc_new(self, &result, gas) + asc_new(self, &result, gas).await } /// function bigDecimal.toString(x: BigDecimal): string - pub fn big_decimal_to_string( + pub async fn big_decimal_to_string( &mut self, gas: &GasCounter, @@ -904,11 +909,11 @@ impl WasmInstanceContext<'_> { let host_exports = self.as_ref().ctx.host_exports.cheap_clone(); let ctx = &mut self.as_mut().ctx; let result = host_exports.big_decimal_to_string(x, gas, &mut ctx.state)?; - asc_new(self, &result, gas) + asc_new(self, &result, gas).await } /// function bigDecimal.fromString(x: string): BigDecimal - pub fn big_decimal_from_string( + pub async fn big_decimal_from_string( &mut self, gas: &GasCounter, @@ -918,11 +923,11 @@ impl WasmInstanceContext<'_> { let host_exports = self.as_ref().ctx.host_exports.cheap_clone(); let ctx = &mut self.as_mut().ctx; let result = host_exports.big_decimal_from_string(s, gas, &mut ctx.state)?; - asc_new(self, &result, gas) + asc_new(self, &result, gas).await } /// function bigDecimal.plus(x: BigDecimal, y: BigDecimal): BigDecimal - pub fn big_decimal_plus( + pub async fn big_decimal_plus( &mut self, gas: &GasCounter, x_ptr: AscPtr, @@ -934,11 +939,11 @@ impl WasmInstanceContext<'_> { let ctx = &mut self.as_mut().ctx; let result = host_exports.big_decimal_plus(x, y, gas, &mut ctx.state)?; - asc_new(self, &result, gas) + asc_new(self, &result, gas).await } /// function bigDecimal.minus(x: BigDecimal, y: BigDecimal): BigDecimal - pub fn big_decimal_minus( + pub async fn big_decimal_minus( &mut self, gas: &GasCounter, x_ptr: AscPtr, @@ -950,11 +955,11 @@ impl WasmInstanceContext<'_> { let ctx = &mut self.as_mut().ctx; let result = host_exports.big_decimal_minus(x, y, gas, &mut ctx.state)?; - asc_new(self, &result, gas) + asc_new(self, &result, gas).await } /// function bigDecimal.times(x: BigDecimal, y: BigDecimal): BigDecimal - pub fn big_decimal_times( + pub async fn big_decimal_times( &mut self, gas: &GasCounter, x_ptr: AscPtr, @@ -966,11 +971,11 @@ impl WasmInstanceContext<'_> { let ctx = &mut self.as_mut().ctx; let result = host_exports.big_decimal_times(x, y, gas, &mut ctx.state)?; - asc_new(self, &result, gas) + asc_new(self, &result, gas).await } /// function bigDecimal.dividedBy(x: BigDecimal, y: BigDecimal): BigDecimal - pub fn big_decimal_divided_by( + pub async fn big_decimal_divided_by( &mut self, gas: &GasCounter, x_ptr: AscPtr, @@ -982,11 +987,11 @@ impl WasmInstanceContext<'_> { let ctx = &mut self.as_mut().ctx; let result = host_exports.big_decimal_divided_by(x, y, gas, &mut ctx.state)?; - asc_new(self, &result, gas) + asc_new(self, &result, gas).await } /// function bigDecimal.equals(x: BigDecimal, y: BigDecimal): bool - pub fn big_decimal_equals( + pub async fn big_decimal_equals( &mut self, gas: &GasCounter, x_ptr: AscPtr, @@ -1001,7 +1006,7 @@ impl WasmInstanceContext<'_> { } /// function dataSource.create(name: string, params: Array): void - pub fn data_source_create( + pub async fn data_source_create( &mut self, gas: &GasCounter, name_ptr: AscPtr, @@ -1024,7 +1029,7 @@ impl WasmInstanceContext<'_> { } /// function createWithContext(name: string, params: Array, context: DataSourceContext): void - pub fn data_source_create_with_context( + pub async fn data_source_create_with_context( &mut self, gas: &GasCounter, name_ptr: AscPtr, @@ -1051,29 +1056,29 @@ impl WasmInstanceContext<'_> { } /// function dataSource.address(): Bytes - pub fn data_source_address( + pub async fn data_source_address( &mut self, gas: &GasCounter, ) -> Result, HostExportError> { let host_exports = self.as_ref().ctx.host_exports.cheap_clone(); let ctx = &mut self.as_mut().ctx; let addr = host_exports.data_source_address(gas, &mut ctx.state)?; - asc_new(self, addr.as_slice(), gas) + asc_new(self, addr.as_slice(), gas).await } /// function dataSource.network(): String - pub fn data_source_network( + pub async fn data_source_network( &mut self, gas: &GasCounter, ) -> Result, HostExportError> { let host_exports = self.as_ref().ctx.host_exports.cheap_clone(); let ctx = &mut self.as_mut().ctx; let data_source_network = host_exports.data_source_network(gas, &mut ctx.state)?; - asc_new(self, &data_source_network, gas) + asc_new(self, &data_source_network, gas).await } /// function dataSource.context(): DataSourceContext - pub fn data_source_context( + pub async fn data_source_context( &mut self, gas: &GasCounter, ) -> Result, HostExportError> { @@ -1084,10 +1089,10 @@ impl WasmInstanceContext<'_> { .map(|e| e.sorted()) .unwrap_or(vec![]); - asc_new(self, &ds_ctx, gas) + asc_new(self, &ds_ctx, gas).await } - pub fn ens_name_by_hash( + pub async fn ens_name_by_hash( &mut self, gas: &GasCounter, hash_ptr: AscPtr, @@ -1104,11 +1109,13 @@ impl WasmInstanceContext<'_> { } // map `None` to `null`, and `Some(s)` to a runtime string - name.map(|name| asc_new(self, &*name, gas).map_err(Into::into)) - .unwrap_or(Ok(AscPtr::null())) + match name { + Some(name) => asc_new(self, &*name, gas).await.map_err(Into::into), + None => Ok(AscPtr::null()), + } } - pub fn log_log( + pub async fn log_log( &mut self, gas: &GasCounter, level: u32, @@ -1122,7 +1129,7 @@ impl WasmInstanceContext<'_> { } /// function encode(token: ethereum.Value): Bytes | null - pub fn ethereum_encode( + pub async fn ethereum_encode( &mut self, gas: &GasCounter, token_ptr: AscPtr>, @@ -1132,12 +1139,14 @@ impl WasmInstanceContext<'_> { let ctx = &mut self.as_mut().ctx; let data = host_exports.ethereum_encode(token, gas, &mut ctx.state); // return `null` if it fails - data.map(|bytes| asc_new(self, &*bytes, gas)) - .unwrap_or(Ok(AscPtr::null())) + match data { + Ok(bytes) => asc_new(self, &*bytes, gas).await, + Err(_) => Ok(AscPtr::null()), + } } /// function decode(types: String, data: Bytes): ethereum.Value | null - pub fn ethereum_decode( + pub async fn ethereum_decode( &mut self, gas: &GasCounter, types_ptr: AscPtr, @@ -1150,13 +1159,14 @@ impl WasmInstanceContext<'_> { let result = host_exports.ethereum_decode(types, data, gas, &mut ctx.state); // return `null` if it fails - result - .map(|param| asc_new(self, ¶m, gas)) - .unwrap_or(Ok(AscPtr::null())) + match result { + Ok(token) => asc_new(self, &token, gas).await, + Err(_) => Ok(AscPtr::null()), + } } /// function arweave.transactionData(txId: string): Bytes | null - pub fn arweave_transaction_data( + pub async fn arweave_transaction_data( &self, _gas: &GasCounter, _tx_id: AscPtr, @@ -1167,7 +1177,7 @@ impl WasmInstanceContext<'_> { } /// function box.profile(address: string): JSONValue | null - pub fn box_profile( + pub async fn box_profile( &self, _gas: &GasCounter, _address: AscPtr, @@ -1178,7 +1188,7 @@ impl WasmInstanceContext<'_> { } /// function yaml.fromBytes(bytes: Bytes): YAMLValue - pub fn yaml_from_bytes( + pub async fn yaml_from_bytes( &mut self, gas: &GasCounter, bytes_ptr: AscPtr, @@ -1197,11 +1207,11 @@ impl WasmInstanceContext<'_> { ); })?; - asc_new(self, &yaml_value, gas) + asc_new(self, &yaml_value, gas).await } /// function yaml.try_fromBytes(bytes: Bytes): Result - pub fn yaml_try_from_bytes( + pub async fn yaml_try_from_bytes( &mut self, gas: &GasCounter, bytes_ptr: AscPtr, @@ -1223,7 +1233,7 @@ impl WasmInstanceContext<'_> { true }); - asc_new(self, &result, gas) + asc_new(self, &result, gas).await } } diff --git a/runtime/wasm/src/module/instance.rs b/runtime/wasm/src/module/instance.rs index cddac22f9fc..21560bb4fe5 100644 --- a/runtime/wasm/src/module/instance.rs +++ b/runtime/wasm/src/module/instance.rs @@ -2,6 +2,8 @@ use std::sync::atomic::{AtomicBool, Ordering}; use std::time::Instant; use anyhow::Error; +use graph::futures03::FutureExt as _; +use graph::prelude::web3::futures::future::BoxFuture; use graph::slog::SendSyncRefUnwindSafeKV; use semver::Version; @@ -57,19 +59,22 @@ mod impl_for_tests { asc_get(&ctx, asc_ptr, &self.gas) } - pub fn asc_new(&mut self, rust_obj: &T) -> Result, HostExportError> + pub async fn asc_new( + &mut self, + rust_obj: &T, + ) -> Result, HostExportError> where P: AscType + AscIndexId, T: ToAscObj

, { let mut ctx = WasmInstanceContext::new(&mut self.store); - asc_new(&mut ctx, rust_obj, &self.gas) + asc_new(&mut ctx, rust_obj, &self.gas).await } } } impl WasmInstance { - pub(crate) fn handle_json_callback( + pub(crate) async fn handle_json_callback( mut self, handler_name: &str, value: &serde_json::Value, @@ -79,9 +84,9 @@ impl WasmInstance { let gas = GasCounter::new(gas_metrics); let mut ctx = self.instance_ctx(); let (value, user_data) = { - let value = asc_new(&mut ctx, value, &gas); + let value = asc_new(&mut ctx, value, &gas).await; - let user_data = asc_new(&mut ctx, user_data, &gas); + let user_data = asc_new(&mut ctx, user_data, &gas).await; (value, user_data) }; @@ -93,10 +98,11 @@ impl WasmInstance { .get_func(self.store.as_context_mut(), handler_name) .with_context(|| format!("function {} not found", handler_name))? .typed::<(u32, u32), ()>(self.store.as_context_mut())? - .call( + .call_async( self.store.as_context_mut(), (value?.wasm_ptr(), user_data?.wasm_ptr()), ) + .await .with_context(|| format!("Failed to handle callback '{}'", handler_name))?; let mut wasm_ctx = self.store.into_data(); @@ -105,7 +111,7 @@ impl WasmInstance { Ok(wasm_ctx.take_state()) } - pub(crate) fn handle_block( + pub(crate) async fn handle_block( mut self, _logger: &Logger, handler_name: &str, @@ -113,14 +119,15 @@ impl WasmInstance { ) -> Result<(BlockState, Gas), MappingError> { let gas = self.gas.clone(); let mut ctx = self.instance_ctx(); - let obj = block_data.to_vec().to_asc_obj(&mut ctx, &gas)?; + let obj = block_data.to_vec().to_asc_obj(&mut ctx, &gas).await?; - let obj = AscPtr::alloc_obj(obj, &mut ctx, &gas)?; + let obj = AscPtr::alloc_obj(obj, &mut ctx, &gas).await?; self.invoke_handler(handler_name, obj, Arc::new(o!()), None) + .await } - pub(crate) fn handle_trigger( + pub(crate) async fn handle_trigger( mut self, trigger: TriggerWithHandler>, ) -> Result<(BlockState, Gas), MappingError> @@ -132,9 +139,10 @@ impl WasmInstance { let logging_extras = trigger.logging_extras().cheap_clone(); let error_context = trigger.trigger.error_context(); let mut ctx = self.instance_ctx(); - let asc_trigger = trigger.to_asc_ptr(&mut ctx, &gas)?; + let asc_trigger = trigger.to_asc_ptr(&mut ctx, &gas).await?; self.invoke_handler(&handler_name, asc_trigger, logging_extras, error_context) + .await } pub fn take_ctx(self) -> WasmInstanceData { @@ -157,7 +165,7 @@ impl WasmInstance { self.gas.get().value() } - fn invoke_handler( + async fn invoke_handler( mut self, handler: &str, arg: AscPtr, @@ -177,45 +185,47 @@ impl WasmInstance { self.instance_ctx().as_mut().ctx.state.enter_handler(); // This `match` will return early if there was a non-deterministic trap. - let deterministic_error: Option = - match func.call(self.store.as_context_mut(), arg.wasm_ptr()) { - Ok(()) => { - assert!(self.instance_ctx().as_ref().possible_reorg == false); - assert!(self.instance_ctx().as_ref().deterministic_host_trap == false); - None - } - Err(trap) if self.instance_ctx().as_ref().possible_reorg => { - self.instance_ctx().as_mut().ctx.state.exit_handler(); - return Err(MappingError::PossibleReorg(trap.into())); - } + let deterministic_error: Option = match func + .call_async(self.store.as_context_mut(), arg.wasm_ptr()) + .await + { + Ok(()) => { + assert!(self.instance_ctx().as_ref().possible_reorg == false); + assert!(self.instance_ctx().as_ref().deterministic_host_trap == false); + None + } + Err(trap) if self.instance_ctx().as_ref().possible_reorg => { + self.instance_ctx().as_mut().ctx.state.exit_handler(); + return Err(MappingError::PossibleReorg(trap.into())); + } - // Treat timeouts anywhere in the error chain as a special case to have a better error - // message. Any `TrapCode::Interrupt` is assumed to be a timeout. - // See also: runtime-timeouts - Err(trap) - if trap - .chain() - .any(|e| e.downcast_ref::() == Some(&Trap::Interrupt)) => - { - self.instance_ctx().as_mut().ctx.state.exit_handler(); - return Err(MappingError::Unknown(Error::from(trap).context(format!( + // Treat timeouts anywhere in the error chain as a special case to have a better error + // message. Any `TrapCode::Interrupt` is assumed to be a timeout. + // See also: runtime-timeouts + Err(trap) + if trap + .chain() + .any(|e| e.downcast_ref::() == Some(&Trap::Interrupt)) => + { + self.instance_ctx().as_mut().ctx.state.exit_handler(); + return Err(MappingError::Unknown(Error::from(trap).context(format!( "Handler '{}' hit the timeout of '{}' seconds", handler, self.instance_ctx().as_ref().valid_module.timeout.unwrap().as_secs() )))); - } - Err(trap) => { - let trap_is_deterministic = is_trap_deterministic(&trap) - || self.instance_ctx().as_ref().deterministic_host_trap; - match trap_is_deterministic { - true => Some(trap), - false => { - self.instance_ctx().as_mut().ctx.state.exit_handler(); - return Err(MappingError::Unknown(trap)); - } + } + Err(trap) => { + let trap_is_deterministic = is_trap_deterministic(&trap) + || self.instance_ctx().as_ref().deterministic_host_trap; + match trap_is_deterministic { + true => Some(trap), + false => { + self.instance_ctx().as_mut().ctx.state.exit_handler(); + return Err(MappingError::Unknown(trap)); } } - }; + } + }; if let Some(deterministic_error) = deterministic_error { let deterministic_error = match error_context { @@ -260,7 +270,7 @@ impl WasmInstance { impl WasmInstance { /// Instantiates the module and sets it to be interrupted after `timeout`. - pub fn from_valid_module_with_ctx( + pub async fn from_valid_module_with_ctx( valid_module: Arc, ctx: MappingContext, host_metrics: Arc, @@ -294,12 +304,26 @@ impl WasmInstance { let gas = GasCounter::new(host_metrics.gas_metrics.clone()); let deterministic_host_trap = Arc::new(AtomicBool::new(false)); + // Helper to turn a parameter name into 'u32' for a tuple type + // (param1, parma2, ..) : (u32, u32, ..) + macro_rules! param_u32 { + ($param:ident) => { + u32 + }; + } + + // The difficulty with this macro is that it needs to turn a list of + // parameter names into a tuple declaration (param1, parma2, ..) : + // (u32, u32, ..), but also for an empty parameter list, it needs to + // produce '(): ()'. In the first case we need a trailing comma, in + // the second case we don't. That's why there are two separate + // expansions, one with and one without params macro_rules! link { ($wasm_name:expr, $rust_name:ident, $($param:ident),*) => { link!($wasm_name, $rust_name, "host_export_other",$($param),*) }; - ($wasm_name:expr, $rust_name:ident, $section:expr, $($param:ident),*) => { + ($wasm_name:expr, $rust_name:ident, $section:expr, $($param:ident),+) => { let modules = valid_module .import_name_to_modules .get($wasm_name) @@ -309,38 +333,86 @@ impl WasmInstance { // link an import with all the modules that require it. for module in modules { let gas = gas.cheap_clone(); - linker.func_wrap( + linker.func_wrap_async( module, $wasm_name, move |mut caller: wasmtime::Caller<'_, WasmInstanceData>, - $($param: u32),*| { - let host_metrics = caller.data().host_metrics.cheap_clone(); - let _section = host_metrics.stopwatch.start_section($section); - - #[allow(unused_mut)] - let mut ctx = WasmInstanceContext::new(&mut caller); - let result = ctx.$rust_name( - &gas, - $($param.into()),* - ); - match result { - Ok(result) => Ok(result.into_wasm_ret()), - Err(e) => { - match IntoTrap::determinism_level(&e) { - DeterminismLevel::Deterministic => { - ctx.as_mut().deterministic_host_trap = true; - } - DeterminismLevel::PossibleReorg => { - ctx.as_mut().possible_reorg = true; + ($($param),*,) : ($(param_u32!($param)),*,)| { + let gas = gas.cheap_clone(); + Box::new(async move { + let host_metrics = caller.data().host_metrics.cheap_clone(); + let _section = host_metrics.stopwatch.start_section($section); + + #[allow(unused_mut)] + let mut ctx = std::pin::pin!(WasmInstanceContext::new(&mut caller)); + let result = ctx.$rust_name( + &gas, + $($param.into()),* + ).await; + let ctx = ctx.get_mut(); + match result { + Ok(result) => Ok(result.into_wasm_ret()), + Err(e) => { + match IntoTrap::determinism_level(&e) { + DeterminismLevel::Deterministic => { + ctx.as_mut().deterministic_host_trap = true; + } + DeterminismLevel::PossibleReorg => { + ctx.as_mut().possible_reorg = true; + } + DeterminismLevel::Unimplemented + | DeterminismLevel::NonDeterministic => {} } - DeterminismLevel::Unimplemented - | DeterminismLevel::NonDeterministic => {} + + Err(e.into()) } + } + }) }, + )?; + } + }; + + ($wasm_name:expr, $rust_name:ident, $section:expr,) => { + let modules = valid_module + .import_name_to_modules + .get($wasm_name) + .into_iter() + .flatten(); - Err(e.into()) + // link an import with all the modules that require it. + for module in modules { + let gas = gas.cheap_clone(); + linker.func_wrap_async( + module, + $wasm_name, + move |mut caller: wasmtime::Caller<'_, WasmInstanceData>, + _ : ()| { + let gas = gas.cheap_clone(); + Box::new(async move { + let host_metrics = caller.data().host_metrics.cheap_clone(); + let _section = host_metrics.stopwatch.start_section($section); + + #[allow(unused_mut)] + let mut ctx = WasmInstanceContext::new(&mut caller); + let result = ctx.$rust_name(&gas).await; + match result { + Ok(result) => Ok(result.into_wasm_ret()), + Err(e) => { + match IntoTrap::determinism_level(&e) { + DeterminismLevel::Deterministic => { + ctx.as_mut().deterministic_host_trap = true; + } + DeterminismLevel::PossibleReorg => { + ctx.as_mut().possible_reorg = true; + } + DeterminismLevel::Unimplemented + | DeterminismLevel::NonDeterministic => {} + } + + Err(e.into()) + } } - } - }, + }) }, )?; } }; @@ -357,41 +429,46 @@ impl WasmInstance { for module in modules { let host_fn = host_fn.cheap_clone(); let gas = gas.cheap_clone(); - linker.func_wrap( + linker.func_wrap_async( module, host_fn.name, - move |mut caller: wasmtime::Caller<'_, WasmInstanceData>, call_ptr: u32| { - let start = Instant::now(); - - let name_for_metrics = host_fn.name.replace('.', "_"); - let host_metrics = caller.data().host_metrics.cheap_clone(); - let stopwatch = host_metrics.stopwatch.cheap_clone(); - let _section = - stopwatch.start_section(&format!("host_export_{}", name_for_metrics)); - - let ctx = HostFnCtx { - logger: caller.data().ctx.logger.cheap_clone(), - block_ptr: caller.data().ctx.block_ptr.cheap_clone(), - gas: gas.cheap_clone(), - metrics: host_metrics.cheap_clone(), - heap: &mut WasmInstanceContext::new(&mut caller), - }; - let ret = (host_fn.func)(ctx, call_ptr).map_err(|e| match e { - HostExportError::Deterministic(e) => { - caller.data_mut().deterministic_host_trap = true; - e - } - HostExportError::PossibleReorg(e) => { - caller.data_mut().possible_reorg = true; - e - } - HostExportError::Unknown(e) => e, - })?; - host_metrics.observe_host_fn_execution_time( - start.elapsed().as_secs_f64(), - &name_for_metrics, - ); - Ok(ret) + move |mut caller: wasmtime::Caller<'_, WasmInstanceData>, + (call_ptr,): (u32,)| { + let host_fn = host_fn.cheap_clone(); + let gas = gas.cheap_clone(); + Box::new(async move { + let start = Instant::now(); + + let name_for_metrics = host_fn.name.replace('.', "_"); + let host_metrics = caller.data().host_metrics.cheap_clone(); + let stopwatch = host_metrics.stopwatch.cheap_clone(); + let _section = stopwatch + .start_section(&format!("host_export_{}", name_for_metrics)); + + let ctx = HostFnCtx { + logger: caller.data().ctx.logger.cheap_clone(), + block_ptr: caller.data().ctx.block_ptr.cheap_clone(), + gas: gas.cheap_clone(), + metrics: host_metrics.cheap_clone(), + heap: &mut WasmInstanceContext::new(&mut caller), + }; + let ret = (host_fn.func)(ctx, call_ptr).await.map_err(|e| match e { + HostExportError::Deterministic(e) => { + caller.data_mut().deterministic_host_trap = true; + e + } + HostExportError::PossibleReorg(e) => { + caller.data_mut().possible_reorg = true; + e + } + HostExportError::Unknown(e) => e, + })?; + host_metrics.observe_host_fn_execution_time( + start.elapsed().as_secs_f64(), + &name_for_metrics, + ); + Ok(ret) + }) }, )?; } @@ -535,7 +612,9 @@ impl WasmInstance { })?; } - let instance = linker.instantiate(store.as_context_mut(), &valid_module.module)?; + let instance = linker + .instantiate_async(store.as_context_mut(), &valid_module.module) + .await?; let asc_heap = AscHeapCtx::new( &instance, @@ -552,7 +631,8 @@ impl WasmInstance { .get_func(store.as_context_mut(), &start_func) .context(format!("`{start_func}` function not found"))? .typed::<(), ()>(store.as_context_mut())? - .call(store.as_context_mut(), ())?; + .call_async(store.as_context_mut(), ()) + .await?; } match api_version { @@ -562,7 +642,8 @@ impl WasmInstance { .get_func(store.as_context_mut(), "_start") .context("`_start` function not found")? .typed::<(), ()>(store.as_context_mut())? - .call(store.as_context_mut(), ())?; + .call_async(store.as_context_mut(), ()) + .await?; } } @@ -572,4 +653,26 @@ impl WasmInstance { store, }) } + + /// Similar to `from_valid_module_with_ctx` but returns a boxed future. + /// This is needed to allow mutually recursive calls of futures, e.g., + /// in `ipfs_map` as that is a host function that calls back into WASM + /// code which in turn might call back into host functions. + pub fn from_valid_module_with_ctx_boxed( + valid_module: Arc, + ctx: MappingContext, + host_metrics: Arc, + experimental_features: ExperimentalFeatures, + ) -> BoxFuture<'static, Result> { + async move { + WasmInstance::from_valid_module_with_ctx( + valid_module, + ctx, + host_metrics, + experimental_features, + ) + .await + } + .boxed() + } } diff --git a/runtime/wasm/src/module/mod.rs b/runtime/wasm/src/module/mod.rs index b59ec61456c..3b64451571d 100644 --- a/runtime/wasm/src/module/mod.rs +++ b/runtime/wasm/src/module/mod.rs @@ -53,58 +53,67 @@ pub trait IntoTrap { /// A flexible interface for writing a type to AS memory, any pointer can be returned. /// Use `AscPtr::erased` to convert `AscPtr` into `AscPtr<()>`. +#[async_trait] pub trait ToAscPtr { - fn to_asc_ptr( + async fn to_asc_ptr( self, heap: &mut H, gas: &GasCounter, ) -> Result, HostExportError>; } +#[async_trait] impl ToAscPtr for offchain::TriggerData { - fn to_asc_ptr( + async fn to_asc_ptr( self, heap: &mut H, gas: &GasCounter, ) -> Result, HostExportError> { - asc_new(heap, self.data.as_ref() as &[u8], gas).map(|ptr| ptr.erase()) + asc_new(heap, self.data.as_ref() as &[u8], gas) + .await + .map(|ptr| ptr.erase()) } } +#[async_trait] impl ToAscPtr for subgraph::MappingEntityTrigger { - fn to_asc_ptr( + async fn to_asc_ptr( self, heap: &mut H, gas: &GasCounter, ) -> Result, HostExportError> { - asc_new(heap, &self.data.entity.entity.sorted_ref(), gas).map(|ptr| ptr.erase()) + asc_new(heap, &self.data.entity.entity.sorted_ref(), gas) + .await + .map(|ptr| ptr.erase()) } } +#[async_trait] impl ToAscPtr for MappingTrigger where C::MappingTrigger: ToAscPtr, { - fn to_asc_ptr( + async fn to_asc_ptr( self, heap: &mut H, gas: &GasCounter, ) -> Result, HostExportError> { match self { - MappingTrigger::Onchain(trigger) => trigger.to_asc_ptr(heap, gas), - MappingTrigger::Offchain(trigger) => trigger.to_asc_ptr(heap, gas), - MappingTrigger::Subgraph(trigger) => trigger.to_asc_ptr(heap, gas), + MappingTrigger::Onchain(trigger) => trigger.to_asc_ptr(heap, gas).await, + MappingTrigger::Offchain(trigger) => trigger.to_asc_ptr(heap, gas).await, + MappingTrigger::Subgraph(trigger) => trigger.to_asc_ptr(heap, gas).await, } } } -impl ToAscPtr for TriggerWithHandler { - fn to_asc_ptr( +#[async_trait] +impl ToAscPtr for TriggerWithHandler { + async fn to_asc_ptr( self, heap: &mut H, gas: &GasCounter, ) -> Result, HostExportError> { - self.trigger.to_asc_ptr(heap, gas) + self.trigger.to_asc_ptr(heap, gas).await } } @@ -243,8 +252,13 @@ fn host_export_error_from_trap(trap: Error, context: String) -> HostExportError } } +#[async_trait] impl AscHeap for WasmInstanceContext<'_> { - fn raw_new(&mut self, bytes: &[u8], gas: &GasCounter) -> Result { + async fn raw_new( + &mut self, + bytes: &[u8], + gas: &GasCounter, + ) -> Result { // The cost of writing to wasm memory from the host is the same as of writing from wasm // using load instructions. gas.consume_host_fn_with_metrics( @@ -268,7 +282,8 @@ impl AscHeap for WasmInstanceContext<'_> { // of the node. let memory_allocate = &self.asc_heap().cheap_clone().memory_allocate; let mut start_ptr = memory_allocate - .call(self.as_context_mut(), arena_size) + .call_async(self.as_context_mut(), arena_size) + .await .unwrap(); match &self.asc_heap().api_version { @@ -348,12 +363,16 @@ impl AscHeap for WasmInstanceContext<'_> { &self.asc_heap().api_version } - fn asc_type_id(&mut self, type_id_index: IndexForAscTypeId) -> Result { + async fn asc_type_id( + &mut self, + type_id_index: IndexForAscTypeId, + ) -> Result { let asc_heap = self.asc_heap().cheap_clone(); let func = asc_heap.id_of_type.as_ref().unwrap(); // Unwrap ok because it's only called on correct apiVersion, look for AscPtr::generate_header - func.call(self.as_context_mut(), type_id_index as u32) + func.call_async(self.as_context_mut(), type_id_index as u32) + .await .map_err(|trap| { host_export_error_from_trap( trap, diff --git a/runtime/wasm/src/to_from/external.rs b/runtime/wasm/src/to_from/external.rs index 046f9d4433a..ca9f994d8a9 100644 --- a/runtime/wasm/src/to_from/external.rs +++ b/runtime/wasm/src/to_from/external.rs @@ -1,3 +1,4 @@ +use async_trait::async_trait; use ethabi; use graph::data::store::scalar::Timestamp; @@ -13,23 +14,25 @@ use graph::{prelude::web3::types as web3, runtime::AscHeap}; use crate::asc_abi::class::*; +#[async_trait] impl ToAscObj for web3::H160 { - fn to_asc_obj( + async fn to_asc_obj( &self, heap: &mut H, gas: &GasCounter, ) -> Result { - self.0.to_asc_obj(heap, gas) + self.0.to_asc_obj(heap, gas).await } } +#[async_trait] impl ToAscObj for web3::Bytes { - fn to_asc_obj( + async fn to_asc_obj( &self, heap: &mut H, gas: &GasCounter, ) -> Result { - self.0.to_asc_obj(heap, gas) + self.0.to_asc_obj(heap, gas).await } } @@ -57,36 +60,39 @@ impl FromAscObj for web3::H256 { } } +#[async_trait] impl ToAscObj for web3::H256 { - fn to_asc_obj( + async fn to_asc_obj( &self, heap: &mut H, gas: &GasCounter, ) -> Result { - self.0.to_asc_obj(heap, gas) + self.0.to_asc_obj(heap, gas).await } } +#[async_trait] impl ToAscObj for web3::U128 { - fn to_asc_obj( + async fn to_asc_obj( &self, heap: &mut H, gas: &GasCounter, ) -> Result { let mut bytes: [u8; 16] = [0; 16]; self.to_little_endian(&mut bytes); - bytes.to_asc_obj(heap, gas) + bytes.to_asc_obj(heap, gas).await } } +#[async_trait] impl ToAscObj for BigInt { - fn to_asc_obj( + async fn to_asc_obj( &self, heap: &mut H, gas: &GasCounter, ) -> Result { let bytes = self.to_signed_bytes_le(); - bytes.to_asc_obj(heap, gas) + bytes.to_asc_obj(heap, gas).await } } @@ -102,8 +108,9 @@ impl FromAscObj for BigInt { } } +#[async_trait] impl ToAscObj for BigDecimal { - fn to_asc_obj( + async fn to_asc_obj( &self, heap: &mut H, gas: &GasCounter, @@ -112,8 +119,8 @@ impl ToAscObj for BigDecimal { // so "exponent" is the opposite of what you'd expect. let (digits, negative_exp) = self.as_bigint_and_exponent(); Ok(AscBigDecimal { - exp: asc_new(heap, &BigInt::from(-negative_exp), gas)?, - digits: asc_new(heap, &BigInt::new(digits)?, gas)?, + exp: asc_new(heap, &BigInt::from(-negative_exp), gas).await?, + digits: asc_new(heap, &BigInt::new(digits)?, gas).await?, }) } } @@ -150,20 +157,24 @@ impl FromAscObj for BigDecimal { } } +#[async_trait] impl ToAscObj>> for Vec { - fn to_asc_obj( + async fn to_asc_obj( &self, heap: &mut H, gas: &GasCounter, ) -> Result>, HostExportError> { - let content: Result, _> = self.iter().map(|x| asc_new(heap, x, gas)).collect(); - let content = content?; - Array::new(&content, heap, gas) + let mut content = Vec::new(); + for x in self { + content.push(asc_new(heap, x.as_str(), gas).await?); + } + Array::new(&content, heap, gas).await } } +#[async_trait] impl ToAscObj> for ethabi::Token { - fn to_asc_obj( + async fn to_asc_obj( &self, heap: &mut H, gas: &GasCounter, @@ -172,22 +183,24 @@ impl ToAscObj> for ethabi::Token { let kind = EthereumValueKind::get_kind(self); let payload = match self { - Address(address) => asc_new::(heap, address, gas)?.to_payload(), - FixedBytes(bytes) | Bytes(bytes) => { - asc_new::(heap, &**bytes, gas)?.to_payload() - } + Address(address) => asc_new::(heap, address, gas) + .await? + .to_payload(), + FixedBytes(bytes) | Bytes(bytes) => asc_new::(heap, &**bytes, gas) + .await? + .to_payload(), Int(uint) => { let n = BigInt::from_signed_u256(uint); - asc_new(heap, &n, gas)?.to_payload() + asc_new(heap, &n, gas).await?.to_payload() } Uint(uint) => { let n = BigInt::from_unsigned_u256(uint); - asc_new(heap, &n, gas)?.to_payload() + asc_new(heap, &n, gas).await?.to_payload() } Bool(b) => *b as u64, - String(string) => asc_new(heap, &**string, gas)?.to_payload(), - FixedArray(tokens) | Array(tokens) => asc_new(heap, &**tokens, gas)?.to_payload(), - Tuple(tokens) => asc_new(heap, &**tokens, gas)?.to_payload(), + String(string) => asc_new(heap, &**string, gas).await?.to_payload(), + FixedArray(tokens) | Array(tokens) => asc_new(heap, &**tokens, gas).await?.to_payload(), + Tuple(tokens) => asc_new(heap, &**tokens, gas).await?.to_payload(), }; Ok(AscEnum { @@ -299,8 +312,9 @@ impl FromAscObj> for store::Value { } } +#[async_trait] impl ToAscObj> for store::Value { - fn to_asc_obj( + async fn to_asc_obj( &self, heap: &mut H, gas: &GasCounter, @@ -308,21 +322,21 @@ impl ToAscObj> for store::Value { use self::store::Value; let payload = match self { - Value::String(string) => asc_new(heap, string.as_str(), gas)?.into(), + Value::String(string) => asc_new(heap, string.as_str(), gas).await?.into(), Value::Int(n) => EnumPayload::from(*n), Value::Int8(n) => EnumPayload::from(*n), Value::Timestamp(n) => EnumPayload::from(n), - Value::BigDecimal(n) => asc_new(heap, n, gas)?.into(), + Value::BigDecimal(n) => asc_new(heap, n, gas).await?.into(), Value::Bool(b) => EnumPayload::from(*b), - Value::List(array) => asc_new(heap, array.as_slice(), gas)?.into(), + Value::List(array) => asc_new(heap, array.as_slice(), gas).await?.into(), Value::Null => EnumPayload(0), Value::Bytes(bytes) => { - let bytes_obj: AscPtr = asc_new(heap, bytes.as_slice(), gas)?; + let bytes_obj: AscPtr = asc_new(heap, bytes.as_slice(), gas).await?; bytes_obj.into() } Value::BigInt(big_int) => { let bytes_obj: AscPtr = - asc_new(heap, &*big_int.to_signed_bytes_le(), gas)?; + asc_new(heap, &*big_int.to_signed_bytes_le(), gas).await?; bytes_obj.into() } }; @@ -335,57 +349,64 @@ impl ToAscObj> for store::Value { } } +#[async_trait] impl ToAscObj for serde_json::Map { - fn to_asc_obj( + async fn to_asc_obj( &self, heap: &mut H, gas: &GasCounter, ) -> Result { Ok(AscTypedMap { - entries: asc_new(heap, &*self.iter().collect::>(), gas)?, + entries: asc_new(heap, &*self.iter().collect::>(), gas).await?, }) } } // Used for serializing entities. +#[async_trait] impl ToAscObj for Vec<(Word, store::Value)> { - fn to_asc_obj( + async fn to_asc_obj( &self, heap: &mut H, gas: &GasCounter, ) -> Result { Ok(AscTypedMap { - entries: asc_new(heap, self.as_slice(), gas)?, + entries: asc_new(heap, self.as_slice(), gas).await?, }) } } +#[async_trait] impl ToAscObj for Vec<(&str, &store::Value)> { - fn to_asc_obj( + async fn to_asc_obj( &self, heap: &mut H, gas: &GasCounter, ) -> Result { Ok(AscTypedMap { - entries: asc_new(heap, self.as_slice(), gas)?, + entries: asc_new(heap, self.as_slice(), gas).await?, }) } } +#[async_trait] impl ToAscObj>> for Vec> { - fn to_asc_obj( + async fn to_asc_obj( &self, heap: &mut H, gas: &GasCounter, ) -> Result>, HostExportError> { - let content: Result, _> = self.iter().map(|x| asc_new(heap, &x, gas)).collect(); - let content = content?; - Array::new(&content, heap, gas) + let mut content = Vec::new(); + for x in self { + content.push(asc_new(heap, &x, gas).await?); + } + Array::new(&content, heap, gas).await } } +#[async_trait] impl ToAscObj> for serde_json::Value { - fn to_asc_obj( + async fn to_asc_obj( &self, heap: &mut H, gas: &GasCounter, @@ -395,10 +416,10 @@ impl ToAscObj> for serde_json::Value { let payload = match self { Value::Null => EnumPayload(0), Value::Bool(b) => EnumPayload::from(*b), - Value::Number(number) => asc_new(heap, &*number.to_string(), gas)?.into(), - Value::String(string) => asc_new(heap, string.as_str(), gas)?.into(), - Value::Array(array) => asc_new(heap, array.as_slice(), gas)?.into(), - Value::Object(object) => asc_new(heap, object, gas)?.into(), + Value::Number(number) => asc_new(heap, &*number.to_string(), gas).await?.into(), + Value::String(string) => asc_new(heap, string.as_str(), gas).await?.into(), + Value::Array(array) => asc_new(heap, array.as_slice(), gas).await?.into(), + Value::Object(object) => asc_new(heap, object, gas).await?.into(), }; Ok(AscEnum { @@ -422,8 +443,9 @@ impl From for LogLevel { } } -impl ToAscObj> for AscWrapped { - fn to_asc_obj( +#[async_trait] +impl ToAscObj> for AscWrapped { + async fn to_asc_obj( &self, _heap: &mut H, @@ -433,13 +455,14 @@ impl ToAscObj> for AscWrapped { } } +#[async_trait] impl ToAscObj, bool>> for Result where - V: ToAscObj, - VAsc: AscType + AscIndexId, + V: ToAscObj + Sync, + VAsc: AscType + AscIndexId + Sync + Send, AscWrapped>: AscIndexId, { - fn to_asc_obj( + async fn to_asc_obj( &self, heap: &mut H, gas: &GasCounter, @@ -447,9 +470,9 @@ where Ok(match self { Ok(value) => AscResult { value: { - let inner = asc_new(heap, value, gas)?; + let inner = asc_new(heap, value, gas).await?; let wrapped = AscWrapped { inner }; - asc_new(heap, &wrapped, gas)? + asc_new(heap, &wrapped, gas).await? }, error: AscPtr::null(), }, @@ -457,15 +480,16 @@ where value: AscPtr::null(), error: { let wrapped = AscWrapped { inner: true }; - asc_new(heap, &wrapped, gas)? + asc_new(heap, &wrapped, gas).await? }, }, }) } } +#[async_trait] impl ToAscObj> for serde_yaml::Value { - fn to_asc_obj( + async fn to_asc_obj( &self, heap: &mut H, gas: &GasCounter, @@ -475,11 +499,11 @@ impl ToAscObj> for serde_yaml::Value { let payload = match self { Value::Null => EnumPayload(0), Value::Bool(val) => EnumPayload::from(*val), - Value::Number(val) => asc_new(heap, &val.to_string(), gas)?.into(), - Value::String(val) => asc_new(heap, val, gas)?.into(), - Value::Sequence(val) => asc_new(heap, val.as_slice(), gas)?.into(), - Value::Mapping(val) => asc_new(heap, val, gas)?.into(), - Value::Tagged(val) => asc_new(heap, val.as_ref(), gas)?.into(), + Value::Number(val) => asc_new(heap, &val.to_string(), gas).await?.into(), + Value::String(val) => asc_new(heap, val, gas).await?.into(), + Value::Sequence(val) => asc_new(heap, val.as_slice(), gas).await?.into(), + Value::Mapping(val) => asc_new(heap, val, gas).await?.into(), + Value::Tagged(val) => asc_new(heap, val.as_ref(), gas).await?.into(), }; Ok(AscEnum { @@ -490,27 +514,29 @@ impl ToAscObj> for serde_yaml::Value { } } +#[async_trait] impl ToAscObj, AscEnum>> for serde_yaml::Mapping { - fn to_asc_obj( + async fn to_asc_obj( &self, heap: &mut H, gas: &GasCounter, ) -> Result, AscEnum>, HostExportError> { Ok(AscTypedMap { - entries: asc_new(heap, &*self.iter().collect::>(), gas)?, + entries: asc_new(heap, &*self.iter().collect::>(), gas).await?, }) } } +#[async_trait] impl ToAscObj for serde_yaml::value::TaggedValue { - fn to_asc_obj( + async fn to_asc_obj( &self, heap: &mut H, gas: &GasCounter, ) -> Result { Ok(AscYamlTaggedValue { - tag: asc_new(heap, &self.tag.to_string(), gas)?, - value: asc_new(heap, &self.value, gas)?, + tag: asc_new(heap, &self.tag.to_string(), gas).await?, + value: asc_new(heap, &self.value, gas).await?, }) } } diff --git a/runtime/wasm/src/to_from/mod.rs b/runtime/wasm/src/to_from/mod.rs index 6dfb88d82f2..4edb688caf8 100644 --- a/runtime/wasm/src/to_from/mod.rs +++ b/runtime/wasm/src/to_from/mod.rs @@ -1,4 +1,5 @@ use anyhow::anyhow; +use async_trait::async_trait; use std::collections::HashMap; use std::hash::Hash; use std::iter::FromIterator; @@ -17,13 +18,14 @@ use crate::asc_abi::class::*; ///! Standard Rust types go in `mod.rs` and external types in `external.rs`. mod external; -impl ToAscObj> for [T] { - fn to_asc_obj( +#[async_trait] +impl ToAscObj> for [T] { + async fn to_asc_obj( &self, heap: &mut H, gas: &GasCounter, ) -> Result, HostExportError> { - TypedArray::new(self, heap, gas) + TypedArray::new(self, heap, gas).await } } @@ -54,8 +56,9 @@ impl FromAscObj> for } } +#[async_trait] impl ToAscObj for str { - fn to_asc_obj( + async fn to_asc_obj( &self, heap: &mut H, _gas: &GasCounter, @@ -67,8 +70,9 @@ impl ToAscObj for str { } } +#[async_trait] impl ToAscObj for &str { - fn to_asc_obj( + async fn to_asc_obj( &self, heap: &mut H, _gas: &GasCounter, @@ -80,23 +84,25 @@ impl ToAscObj for &str { } } +#[async_trait] impl ToAscObj for String { - fn to_asc_obj( + async fn to_asc_obj( &self, heap: &mut H, gas: &GasCounter, ) -> Result { - self.as_str().to_asc_obj(heap, gas) + self.as_str().to_asc_obj(heap, gas).await } } +#[async_trait] impl ToAscObj for Word { - fn to_asc_obj( + async fn to_asc_obj( &self, heap: &mut H, gas: &GasCounter, ) -> Result { - self.as_str().to_asc_obj(heap, gas) + self.as_str().to_asc_obj(heap, gas).await } } @@ -132,15 +138,20 @@ impl FromAscObj for Word { } } -impl> ToAscObj>> for [T] { - fn to_asc_obj( +#[async_trait] +impl + Sync> ToAscObj>> + for [T] +{ + async fn to_asc_obj( &self, heap: &mut H, gas: &GasCounter, ) -> Result>, HostExportError> { - let content: Result, _> = self.iter().map(|x| asc_new(heap, x, gas)).collect(); - let content = content?; - Array::new(&content, heap, gas) + let mut content = Vec::with_capacity(self.len()); + for x in self { + content.push(asc_new(heap, x, gas).await?); + } + Array::new(&content, heap, gas).await } } @@ -175,17 +186,22 @@ impl, U: From } } -impl, U: ToAscObj> - ToAscObj> for (T, U) +#[async_trait] +impl ToAscObj> for (T, U) +where + K: AscType + AscIndexId + Send, + V: AscType + AscIndexId + Send, + T: ToAscObj + Sync, + U: ToAscObj + Sync, { - fn to_asc_obj( + async fn to_asc_obj( &self, heap: &mut H, gas: &GasCounter, ) -> Result, HostExportError> { Ok(AscTypedMapEntry { - key: asc_new(heap, &self.0, gas)?, - value: asc_new(heap, &self.1, gas)?, + key: asc_new(heap, &self.0, gas).await?, + value: asc_new(heap, &self.1, gas).await?, }) } }