Skip to content

Commit

Permalink
graph: enable ethereum host fns for subgraph datasource
Browse files Browse the repository at this point in the history
  • Loading branch information
incrypto32 committed Nov 19, 2024
1 parent a3891bb commit c54b98e
Show file tree
Hide file tree
Showing 13 changed files with 222 additions and 151 deletions.
79 changes: 2 additions & 77 deletions chain/ethereum/src/data_source.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use graph::components::store::{EthereumCallCache, StoredDynamicDataSource};
use graph::components::subgraph::{HostMetrics, InstanceDSTemplateInfo, MappingError};
use graph::components::trigger_processor::RunnableTriggers;
use graph::data::value::Word;
use graph::data_source::common::{MappingABI, UnresolvedMappingABI};
use graph::data_source::CausalityRegion;
use graph::env::ENV_VARS;
use graph::futures03::future::try_join;
Expand Down Expand Up @@ -33,7 +34,7 @@ use graph::{
derive::CheapClone,
prelude::{
async_trait,
ethabi::{Address, Contract, Event, Function, LogParam, ParamType, RawLog},
ethabi::{Address, Event, Function, LogParam, ParamType, RawLog},
serde_json, warn,
web3::types::{Log, Transaction, H256},
BlockNumber, CheapClone, EthereumCall, LightEthereumBlock, LightEthereumBlockExt,
Expand Down Expand Up @@ -1436,82 +1437,6 @@ impl UnresolvedMapping {
}
}

#[derive(Clone, Debug, Hash, Eq, PartialEq, Deserialize)]
pub struct UnresolvedMappingABI {
pub name: String,
pub file: Link,
}

impl UnresolvedMappingABI {
pub async fn resolve(
self,
resolver: &Arc<dyn LinkResolver>,
logger: &Logger,
) -> Result<MappingABI, anyhow::Error> {
let contract_bytes = resolver.cat(logger, &self.file).await.with_context(|| {
format!(
"failed to resolve ABI {} from {}",
self.name, self.file.link
)
})?;
let contract = Contract::load(&*contract_bytes)?;
Ok(MappingABI {
name: self.name,
contract,
})
}
}

#[derive(Clone, Debug, PartialEq)]
pub struct MappingABI {
pub name: String,
pub contract: Contract,
}

impl MappingABI {
pub fn function(
&self,
contract_name: &str,
name: &str,
signature: Option<&str>,
) -> Result<&Function, Error> {
let contract = &self.contract;
let function = match signature {
// Behavior for apiVersion < 0.0.4: look up function by name; for overloaded
// functions this always picks the same overloaded variant, which is incorrect
// and may lead to encoding/decoding errors
None => contract.function(name).with_context(|| {
format!(
"Unknown function \"{}::{}\" called from WASM runtime",
contract_name, name
)
})?,

// Behavior for apiVersion >= 0.0.04: look up function by signature of
// the form `functionName(uint256,string) returns (bytes32,string)`; this
// correctly picks the correct variant of an overloaded function
Some(ref signature) => contract
.functions_by_name(name)
.with_context(|| {
format!(
"Unknown function \"{}::{}\" called from WASM runtime",
contract_name, name
)
})?
.iter()
.find(|f| signature == &f.signature())
.with_context(|| {
format!(
"Unknown function \"{}::{}\" with signature `{}` \
called from WASM runtime",
contract_name, name, signature,
)
})?,
};
Ok(function)
}
}

#[derive(Clone, Debug, Hash, Eq, PartialEq, Deserialize)]
pub struct MappingBlockHandler {
pub handler: String,
Expand Down
2 changes: 1 addition & 1 deletion chain/ethereum/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ pub use buffered_call_cache::BufferedCallCache;

// ETHDEP: These concrete types should probably not be exposed.
pub use data_source::{
BlockHandlerFilter, DataSource, DataSourceTemplate, Mapping, MappingABI, TemplateSource,
BlockHandlerFilter, DataSource, DataSourceTemplate, Mapping, TemplateSource,
};

pub mod chain;
Expand Down
138 changes: 87 additions & 51 deletions chain/ethereum/src/runtime/runtime_adapter.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
use std::{sync::Arc, time::Instant};

use crate::adapter::EthereumRpcError;
use crate::data_source::MappingABI;
use crate::{
capabilities::NodeCapabilities, network::EthereumNetworkAdapters, Chain, ContractCall,
ContractCallError, DataSource, EthereumAdapter, EthereumAdapterTrait, ENV_VARS,
ContractCallError, EthereumAdapter, EthereumAdapterTrait, ENV_VARS,
};
use anyhow::{anyhow, Context, Error};
use blockchain::HostFn;
Expand All @@ -13,6 +12,8 @@ use graph::components::subgraph::HostMetrics;
use graph::data::store::ethereum::call;
use graph::data::store::scalar::BigInt;
use graph::data::subgraph::API_VERSION_0_0_9;
use graph::data_source;
use graph::data_source::common::MappingABI;
use graph::futures03::compat::Future01CompatExt;
use graph::prelude::web3::types::H160;
use graph::runtime::gas::Gas;
Expand Down Expand Up @@ -80,58 +81,93 @@ pub fn eth_call_gas(chain_identifier: &ChainIdentifier) -> Option<u32> {
}

impl blockchain::RuntimeAdapter<Chain> for RuntimeAdapter {
fn host_fns(&self, ds: &DataSource) -> Result<Vec<HostFn>, Error> {
let abis = ds.mapping.abis.clone();
let call_cache = self.call_cache.cheap_clone();
let eth_adapters = self.eth_adapters.cheap_clone();
let archive = ds.mapping.requires_archive()?;
let eth_call_gas = eth_call_gas(&self.chain_identifier);

let ethereum_call = HostFn {
name: "ethereum.call",
func: Arc::new(move |ctx, wasm_ptr| {
// Ethereum calls should prioritise call-only adapters if one is available.
let eth_adapter = eth_adapters.call_or_cheapest(Some(&NodeCapabilities {
archive,
traces: false,
}))?;
ethereum_call(
&eth_adapter,
call_cache.cheap_clone(),
ctx,
wasm_ptr,
&abis,
eth_call_gas,
)
.map(|ptr| ptr.wasm_ptr())
}),
};

let eth_adapters = self.eth_adapters.cheap_clone();
let ethereum_get_balance = HostFn {
name: "ethereum.getBalance",
func: Arc::new(move |ctx, wasm_ptr| {
let eth_adapter = eth_adapters.unverified_cheapest_with(&NodeCapabilities {
archive,
traces: false,
})?;
eth_get_balance(&eth_adapter, ctx, wasm_ptr).map(|ptr| ptr.wasm_ptr())
}),
};
fn host_fns(&self, ds: &data_source::DataSource<Chain>) -> Result<Vec<HostFn>, Error> {
fn create_host_fns(
abis: Arc<Vec<Arc<MappingABI>>>, // Use Arc to ensure `'static` lifetimes.
archive: bool,
call_cache: Arc<dyn EthereumCallCache>,
eth_adapters: Arc<EthereumNetworkAdapters>,
eth_call_gas: Option<u32>,
) -> Vec<HostFn> {
vec![
HostFn {
name: "ethereum.call",
func: Arc::new({
let eth_adapters = eth_adapters.clone();
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(
&eth_adapter,
call_cache.clone(),
ctx,
wasm_ptr,
&abis,
eth_call_gas,
)
.map(|ptr| ptr.wasm_ptr())
}
}),
},
HostFn {
name: "ethereum.getBalance",
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(&eth_adapter, ctx, wasm_ptr).map(|ptr| ptr.wasm_ptr())
}
}),
},
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(&eth_adapter, ctx, wasm_ptr).map(|ptr| ptr.wasm_ptr())
}
}),
},
]
}

let eth_adapters = self.eth_adapters.cheap_clone();
let ethereum_get_code = HostFn {
name: "ethereum.hasCode",
func: Arc::new(move |ctx, wasm_ptr| {
let eth_adapter = eth_adapters.unverified_cheapest_with(&NodeCapabilities {
archive,
traces: false,
})?;
eth_has_code(&eth_adapter, ctx, wasm_ptr).map(|ptr| ptr.wasm_ptr())
}),
let host_fns = match ds {
data_source::DataSource::Onchain(onchain_ds) => {
let abis = Arc::new(onchain_ds.mapping.abis.clone());
let archive = onchain_ds.mapping.requires_archive()?;
let call_cache = self.call_cache.cheap_clone();
let eth_adapters = self.eth_adapters.cheap_clone();
let eth_call_gas = eth_call_gas(&self.chain_identifier);

create_host_fns(abis, archive, call_cache, eth_adapters, eth_call_gas)
}
data_source::DataSource::Subgraph(subgraph_ds) => {
let abis = Arc::new(subgraph_ds.mapping.abis.clone());
let archive = subgraph_ds.mapping.requires_archive()?;
let call_cache = self.call_cache.cheap_clone();
let eth_adapters = self.eth_adapters.cheap_clone();
let eth_call_gas = eth_call_gas(&self.chain_identifier);

create_host_fns(abis, archive, call_cache, eth_adapters, eth_call_gas)
}
data_source::DataSource::Offchain(_) => vec![],
};

Ok(vec![ethereum_call, ethereum_get_balance, ethereum_get_code])
Ok(host_fns)
}
}

Expand Down
3 changes: 2 additions & 1 deletion graph/src/blockchain/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use crate::{
subgraph::InstanceDSTemplateInfo,
},
data::subgraph::UnifiedMappingApiVersion,
data_source,
prelude::{BlockHash, DataSourceTemplateInfo, DeploymentHash},
};
use anyhow::{Error, Result};
Expand Down Expand Up @@ -346,7 +347,7 @@ impl<C: Blockchain> TriggerFilter<C> for MockTriggerFilter {
pub struct MockRuntimeAdapter;

impl<C: Blockchain> RuntimeAdapter<C> for MockRuntimeAdapter {
fn host_fns(&self, _ds: &C::DataSource) -> Result<Vec<HostFn>, Error> {
fn host_fns(&self, _ds: &data_source::DataSource<C>) -> Result<Vec<HostFn>, Error> {
todo!()
}
}
Expand Down
5 changes: 2 additions & 3 deletions graph/src/blockchain/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -453,7 +453,6 @@ where
}
}

// TODO(krishna): Proper ordering for triggers
impl<C: Blockchain> Ord for Trigger<C>
where
C::TriggerData: Ord,
Expand All @@ -463,7 +462,7 @@ where
(Trigger::Chain(data1), Trigger::Chain(data2)) => data1.cmp(data2),
(Trigger::Subgraph(_), Trigger::Chain(_)) => std::cmp::Ordering::Greater,
(Trigger::Chain(_), Trigger::Subgraph(_)) => std::cmp::Ordering::Less,
(Trigger::Subgraph(_), Trigger::Subgraph(_)) => std::cmp::Ordering::Equal,
(Trigger::Subgraph(t1), Trigger::Subgraph(t2)) => t1.entity.vid.cmp(&t2.entity.vid),
}
}
}
Expand Down Expand Up @@ -540,7 +539,7 @@ pub struct HostFn {
}

pub trait RuntimeAdapter<C: Blockchain>: Send + Sync {
fn host_fns(&self, ds: &C::DataSource) -> Result<Vec<HostFn>, Error>;
fn host_fns(&self, ds: &data_source::DataSource<C>) -> Result<Vec<HostFn>, Error>;
}

pub trait NodeCapabilities<C: Blockchain> {
Expand Down
4 changes: 3 additions & 1 deletion graph/src/blockchain/noop_runtime_adapter.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use std::marker::PhantomData;

use crate::data_source;

use super::{Blockchain, HostFn, RuntimeAdapter};

/// A [`RuntimeAdapter`] that does not expose any host functions.
Expand All @@ -16,7 +18,7 @@ impl<C> RuntimeAdapter<C> for NoopRuntimeAdapter<C>
where
C: Blockchain,
{
fn host_fns(&self, _ds: &C::DataSource) -> anyhow::Result<Vec<HostFn>> {
fn host_fns(&self, _ds: &data_source::DataSource<C>) -> anyhow::Result<Vec<HostFn>> {
Ok(vec![])
}
}
Loading

0 comments on commit c54b98e

Please sign in to comment.