Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions chain/ethereum/src/ethereum_adapter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -698,6 +698,18 @@ impl EthereumAdapter {
.map_err(|e| e.into_inner().unwrap_or(ContractCallError::Timeout))
}

/// Make a raw eth_call without ABI encoding.
/// Used by Rust ABI subgraphs where the SDK handles encoding/decoding.
pub async fn raw_call(
&self,
req: call::Request,
block_ptr: BlockPtr,
gas: Option<u32>,
) -> Result<call::Retval, ContractCallError> {
let logger = self.provider_logger(&self.logger);
self.call(logger, req, block_ptr, gas).await
}

async fn call_and_cache(
&self,
logger: &ProviderLogger,
Expand Down
101 changes: 99 additions & 2 deletions chain/ethereum/src/runtime/runtime_adapter.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use std::{sync::Arc, time::Instant};

use async_trait::async_trait;

use crate::adapter::EthereumRpcError;
use crate::{
capabilities::NodeCapabilities, network::EthereumNetworkAdapters, Chain, ContractCallError,
Expand All @@ -9,7 +11,7 @@
use blockchain::HostFn;
use graph::abi;
use graph::abi::DynSolValueExt;
use graph::blockchain::ChainIdentifier;
use graph::blockchain::{ChainIdentifier, RawEthCall};
use graph::components::subgraph::HostMetrics;
use graph::data::store::ethereum::call;
use graph::data::store::scalar::BigInt;
Expand All @@ -18,7 +20,7 @@
use graph::data_source::common::{ContractCall, MappingABI};
use graph::runtime::gas::Gas;
use graph::runtime::{AscIndexId, IndexForAscTypeId};
use graph::slog::debug;
use graph::slog::{debug, o, Discard};
use graph::{
blockchain::{self, BlockPtr, HostFnCtx},
cheap_clone::CheapClone,
Expand Down Expand Up @@ -185,6 +187,101 @@

Ok(host_fns)
}

fn raw_eth_call(&self) -> Option<Arc<dyn RawEthCall>> {
Some(Arc::new(EthereumRawEthCall {
eth_adapters: self.eth_adapters.cheap_clone(),
call_cache: self.call_cache.cheap_clone(),
eth_call_gas: eth_call_gas(&self.chain_identifier),
}))
}
}

/// Implementation of RawEthCall for Ethereum chains.
/// Used by Rust ABI subgraphs for making raw eth_call without ABI encoding.
pub struct EthereumRawEthCall {
eth_adapters: Arc<EthereumNetworkAdapters>,
call_cache: Arc<dyn EthereumCallCache>,
eth_call_gas: Option<u32>,
}

#[async_trait]
impl RawEthCall for EthereumRawEthCall {
async fn call(
&self,
address: [u8; 20],
calldata: &[u8],
block_ptr: &BlockPtr,
gas: Option<u32>,
) -> Result<Option<Vec<u8>>, HostExportError> {
// Get an adapter suitable for calls (non-archive is fine)
let eth_adapter = self
.eth_adapters
.call_or_cheapest(Some(&NodeCapabilities {
archive: false,
traces: false,
}))
.map_err(|e| HostExportError::Unknown(e.into()))?;

// Create a raw call request
let req = call::Request::new(Address::from(address), calldata.to_vec(), 0);

// Check cache first
let (cached, _missing) = self
.call_cache
.get_calls(&[req.cheap_clone()], block_ptr.cheap_clone())
.await
.unwrap_or_else(|_| (Vec::new(), vec![req.cheap_clone()]));

if let Some(resp) = cached.into_iter().next() {
return match resp.retval {
call::Retval::Value(bytes) => Ok(Some(bytes.to_vec())),
call::Retval::Null => Ok(None),
};
}

// Make the actual call
let result = eth_adapter
.raw_call(
req.cheap_clone(),
block_ptr.cheap_clone(),
gas.or(self.eth_call_gas),
)
.await;

match result {
Ok(retval) => {
// Cache the result
let cache = self.call_cache.cheap_clone();
let _ = cache
.set_call(
&Logger::root(Discard, o!()),
req,
block_ptr.cheap_clone(),
retval.clone(),
)
.await;

match retval {
call::Retval::Value(bytes) => Ok(Some(bytes.to_vec())),
call::Retval::Null => Ok(None),

Check warning on line 267 in chain/ethereum/src/runtime/runtime_adapter.rs

View workflow job for this annotation

GitHub Actions / Check rustfmt style

Diff in /home/runner/work/graph-node/graph-node/chain/ethereum/src/runtime/runtime_adapter.rs
}
}
Err(ContractCallError::AlloyError(e)) => {
Err(HostExportError::PossibleReorg(anyhow::anyhow!(
"eth_call RPC error: {}",
e
)))
}
Err(ContractCallError::Timeout) => Err(HostExportError::PossibleReorg(anyhow::anyhow!(
"eth_call timed out"
))),
Err(e) => Err(HostExportError::Unknown(anyhow::anyhow!(
"eth_call failed: {}",
e
))),
}
}
}

/// function ethereum.call(call: SmartContractCall): Array<Token> | null
Expand Down
73 changes: 73 additions & 0 deletions chain/ethereum/src/trigger.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,10 @@
use graph::runtime::gas::GasCounter;
use graph::runtime::AscHeap;
use graph::runtime::AscPtr;
use graph::runtime::HostExportError;

Check warning on line 23 in chain/ethereum/src/trigger.rs

View workflow job for this annotation

GitHub Actions / Check rustfmt style

Diff in /home/runner/work/graph-node/graph-node/chain/ethereum/src/trigger.rs
use graph::semver::Version;
use graph_runtime_wasm::module::ToAscPtr;
use graph_runtime_wasm::rust_abi::{RustBlockTrigger, RustCallTrigger, RustLogTrigger, ToRustBytes};
use std::{cmp::Ordering, sync::Arc};

use crate::runtime::abi::AscEthereumBlock;
Expand Down Expand Up @@ -649,3 +650,75 @@
&self.call.to
}
}

// ============================================================================
// Rust ABI serialization for Graphite SDK
// ============================================================================

impl ToRustBytes for MappingTrigger {
fn to_rust_bytes(&self) -> Vec<u8> {
match self {
MappingTrigger::Log {
block,
transaction,
log,
params: _,
receipt: _,
calls: _,
} => {
let rust_trigger = RustLogTrigger {
address: log.inner.address.0 .0,
tx_hash: transaction.tx_hash().0,
log_index: log.log_index.unwrap_or(0),

Check warning on line 672 in chain/ethereum/src/trigger.rs

View workflow job for this annotation

GitHub Actions / Check rustfmt style

Diff in /home/runner/work/graph-node/graph-node/chain/ethereum/src/trigger.rs
block_number: block.number_u64(),
block_timestamp: block.inner().header.timestamp,
topics: log
.inner
.data
.topics()
.iter()
.map(|t| t.0)
.collect(),
data: log.inner.data.data.to_vec(),
};
rust_trigger.to_rust_bytes()
}
MappingTrigger::Call {
block,
transaction,
call,
inputs: _,
outputs: _,
} => {
let rust_trigger = RustCallTrigger {
to: call.to.0 .0,
from: call.from.0 .0,
tx_hash: transaction.tx_hash().0,
block_number: block.number_u64(),
block_timestamp: block.inner().header.timestamp,
block_hash: block.inner().header.hash.0,
input: call.input.to_vec(),
output: call.output.to_vec(),
};
rust_trigger.to_rust_bytes()
}
MappingTrigger::Block { block } => {
// Convert U256 difficulty to big-endian bytes
let difficulty: [u8; 32] = block.inner().header.difficulty.to_be_bytes();

let rust_trigger = RustBlockTrigger {
hash: block.inner().header.hash.0,
parent_hash: block.inner().header.parent_hash.0,
number: block.number_u64(),
timestamp: block.inner().header.timestamp,
author: block.inner().header.beneficiary.0 .0,
gas_used: block.inner().header.gas_used,
gas_limit: block.inner().header.gas_limit,
difficulty,
base_fee_per_gas: block.inner().header.base_fee_per_gas.unwrap_or(0),
};
rust_trigger.to_rust_bytes()
}
}
}
}
9 changes: 9 additions & 0 deletions chain/near/src/trigger.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use graph::prelude::BlockNumber;
use graph::runtime::HostExportError;
use graph::runtime::{asc_new, gas::GasCounter, AscHeap, AscPtr};
use graph_runtime_wasm::module::ToAscPtr;
use graph_runtime_wasm::rust_abi::ToRustBytes;
use std::{cmp::Ordering, sync::Arc};

use crate::codec;
Expand Down Expand Up @@ -143,6 +144,14 @@ impl MappingTriggerTrait for NearTrigger {
}
}

impl ToRustBytes for NearTrigger {
fn to_rust_bytes(&self) -> Vec<u8> {
// NEAR triggers are not yet supported by Graphite SDK.
// This stub satisfies the trait bound so Ethereum Rust subgraphs can compile.
unimplemented!("Rust ABI serialization is not yet supported for NEAR triggers")
}
}

pub struct ReceiptWithOutcome {
// REVIEW: Do we want to actually also have those two below behind an `Arc` wrapper?
pub outcome: codec::ExecutionOutcomeWithId,
Expand Down
7 changes: 4 additions & 3 deletions core/src/subgraph/instance_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ use graph::env::EnvVars;
use graph::prelude::{SubgraphInstanceManager as SubgraphInstanceManagerTrait, *};
use graph::{blockchain::BlockchainMap, components::store::DeploymentLocator};
use graph_runtime_wasm::module::ToAscPtr;
use graph_runtime_wasm::rust_abi::ToRustBytes;
use graph_runtime_wasm::RuntimeHostBuilder;
use tokio::task;

Expand Down Expand Up @@ -234,7 +235,7 @@ impl<S: SubgraphStore, AC: amp::Client> SubgraphInstanceManager<S, AC> {
) -> anyhow::Result<SubgraphRunner<C, RuntimeHostBuilder<C>>>
where
C: Blockchain,
<C as Blockchain>::MappingTrigger: ToAscPtr,
<C as Blockchain>::MappingTrigger: ToAscPtr + ToRustBytes,
{
self.build_subgraph_runner_inner(
logger,
Expand Down Expand Up @@ -262,7 +263,7 @@ impl<S: SubgraphStore, AC: amp::Client> SubgraphInstanceManager<S, AC> {
) -> anyhow::Result<SubgraphRunner<C, RuntimeHostBuilder<C>>>
where
C: Blockchain,
<C as Blockchain>::MappingTrigger: ToAscPtr,
<C as Blockchain>::MappingTrigger: ToAscPtr + ToRustBytes,
{
let subgraph_store = self.subgraph_store.cheap_clone();
let registry = self.metrics_registry.cheap_clone();
Expand Down Expand Up @@ -560,7 +561,7 @@ impl<S: SubgraphStore, AC: amp::Client> SubgraphInstanceManager<S, AC> {
runner: SubgraphRunner<C, RuntimeHostBuilder<C>>,
) -> Result<(), Error>
where
<C as Blockchain>::MappingTrigger: ToAscPtr,
<C as Blockchain>::MappingTrigger: ToAscPtr + ToRustBytes,
{
let registry = self.metrics_registry.cheap_clone();
let subgraph_metrics = runner.metrics.subgraph.cheap_clone();
Expand Down
21 changes: 21 additions & 0 deletions graph/src/blockchain/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -546,6 +546,27 @@ pub struct HostFn {
#[async_trait]
pub trait RuntimeAdapter<C: Blockchain>: Send + Sync {
fn host_fns(&self, ds: &data_source::DataSource<C>) -> Result<Vec<HostFn>, Error>;

/// Get a raw eth_call capability for Rust ABI subgraphs.
/// Returns None if the chain doesn't support raw eth_call (e.g., non-EVM chains).
fn raw_eth_call(&self) -> Option<Arc<dyn RawEthCall>> {
None
}
}

/// Trait for making raw eth_call requests without ABI encoding.
/// Used by Rust ABI subgraphs where the SDK handles encoding/decoding.
#[async_trait]
pub trait RawEthCall: Send + Sync {
/// Make a raw eth_call to the given address with the provided calldata.
/// Returns Ok(Some(bytes)) on success, Ok(None) on revert, Err on RPC error.
async fn call(
&self,
address: [u8; 20],
calldata: &[u8],
block_ptr: &BlockPtr,
gas: Option<u32>,
) -> Result<Option<Vec<u8>>, HostExportError>;
}

pub trait NodeCapabilities<C: Blockchain> {
Expand Down
1 change: 1 addition & 0 deletions runtime/test/src/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ pub fn mock_context(
),
proof_of_indexing: SharedProofOfIndexing::ignored(),
host_fns: Arc::new(Vec::new()),
raw_eth_call: None,
debug_fork: None,
mapping_logger: Logger::root(slog::Discard, o!()),
instrument: false,
Expand Down
8 changes: 6 additions & 2 deletions runtime/wasm/src/host.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use async_trait::async_trait;
use graph::futures01::sync::mpsc::Sender;
use graph::futures03::channel::oneshot::channel;

use graph::blockchain::{Blockchain, HostFn, RuntimeAdapter};
use graph::blockchain::{Blockchain, HostFn, RawEthCall, RuntimeAdapter};
use graph::components::store::{EnsLookup, SubgraphFork};
use graph::components::subgraph::{MappingError, SharedProofOfIndexing};
use graph::data_source::{
Expand Down Expand Up @@ -56,7 +56,7 @@ impl<C: Blockchain> RuntimeHostBuilder<C> {

impl<C: Blockchain> RuntimeHostBuilderTrait<C> for RuntimeHostBuilder<C>
where
<C as Blockchain>::MappingTrigger: ToAscPtr,
<C as Blockchain>::MappingTrigger: ToAscPtr + crate::rust_abi::ToRustBytes,
{
type Host = RuntimeHost<C>;
type Req = WasmRequest<C>;
Expand Down Expand Up @@ -106,6 +106,7 @@ where

pub struct RuntimeHost<C: Blockchain> {
host_fns: Arc<Vec<HostFn>>,
raw_eth_call: Option<Arc<dyn RawEthCall>>,
data_source: DataSource<C>,
mapping_request_sender: Sender<WasmRequest<C>>,
host_exports: Arc<HostExports>,
Expand Down Expand Up @@ -143,9 +144,11 @@ where
));

let host_fns = runtime_adapter.host_fns(&data_source).unwrap_or_default();
let raw_eth_call = runtime_adapter.raw_eth_call();

Ok(RuntimeHost {
host_fns: Arc::new(host_fns),
raw_eth_call,
data_source,
mapping_request_sender,
host_exports,
Expand Down Expand Up @@ -189,6 +192,7 @@ where
timestamp: trigger.timestamp(),
proof_of_indexing,
host_fns: self.host_fns.cheap_clone(),
raw_eth_call: self.raw_eth_call.cheap_clone(),
debug_fork: debug_fork.cheap_clone(),
mapping_logger: Logger::new(logger, o!("component" => "UserMapping")),
instrument,
Expand Down
1 change: 1 addition & 0 deletions runtime/wasm/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub mod asc_abi;
pub mod rust_abi;

mod host;
pub mod to_from;
Expand Down
Loading
Loading