Skip to content

Commit 113bdd2

Browse files
committed
Add trigger serialization for Rust ABI handlers
1 parent a0485eb commit 113bdd2

6 files changed

Lines changed: 165 additions & 9 deletions

File tree

chain/ethereum/src/trigger.rs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ use graph::runtime::AscPtr;
2323
use graph::runtime::HostExportError;
2424
use graph::semver::Version;
2525
use graph_runtime_wasm::module::ToAscPtr;
26+
use graph_runtime_wasm::rust_abi::{RustLogTrigger, ToRustBytes};
2627
use std::{cmp::Ordering, sync::Arc};
2728

2829
use crate::runtime::abi::AscEthereumBlock;
@@ -649,3 +650,48 @@ impl<'a> EthereumCallData<'a> {
649650
&self.call.to
650651
}
651652
}
653+
654+
// ============================================================================
655+
// Rust ABI serialization for Graphite SDK
656+
// ============================================================================
657+
658+
impl ToRustBytes for MappingTrigger {
659+
fn to_rust_bytes(&self) -> Vec<u8> {
660+
match self {
661+
MappingTrigger::Log {
662+
block,
663+
transaction,
664+
log,
665+
params: _,
666+
receipt: _,
667+
calls: _,
668+
} => {
669+
// Convert to RustLogTrigger for serialization
670+
let rust_trigger = RustLogTrigger {
671+
address: log.inner.address.0 .0,
672+
tx_hash: transaction.tx_hash().0,
673+
log_index: log.log_index.unwrap_or(0),
674+
block_number: block.number_u64(),
675+
block_timestamp: block.inner().header.timestamp,
676+
topics: log
677+
.inner
678+
.data
679+
.topics()
680+
.iter()
681+
.map(|t| t.0)
682+
.collect(),
683+
data: log.inner.data.data.to_vec(),
684+
};
685+
rust_trigger.to_rust_bytes()
686+
}
687+
MappingTrigger::Call { .. } => {
688+
// TODO: Implement call trigger serialization
689+
Vec::new()
690+
}
691+
MappingTrigger::Block { .. } => {
692+
// TODO: Implement block trigger serialization
693+
Vec::new()
694+
}
695+
}
696+
}
697+
}

runtime/wasm/src/host.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ impl<C: Blockchain> RuntimeHostBuilder<C> {
5656

5757
impl<C: Blockchain> RuntimeHostBuilderTrait<C> for RuntimeHostBuilder<C>
5858
where
59-
<C as Blockchain>::MappingTrigger: ToAscPtr,
59+
<C as Blockchain>::MappingTrigger: ToAscPtr + crate::rust_abi::ToRustBytes,
6060
{
6161
type Host = RuntimeHost<C>;
6262
type Req = WasmRequest<C>;

runtime/wasm/src/mapping.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ pub fn spawn_module<C: Blockchain>(
2929
experimental_features: ExperimentalFeatures,
3030
) -> Result<mpsc::Sender<WasmRequest<C>>, anyhow::Error>
3131
where
32-
<C as Blockchain>::MappingTrigger: ToAscPtr,
32+
<C as Blockchain>::MappingTrigger: ToAscPtr + crate::rust_abi::ToRustBytes,
3333
{
3434
static THREAD_COUNT: AtomicUsize = AtomicUsize::new(0);
3535

@@ -150,7 +150,7 @@ async fn handle_trigger<C: Blockchain>(
150150
host_metrics: Arc<HostMetrics>,
151151
) -> Result<(BlockState, Gas), MappingError>
152152
where
153-
<C as Blockchain>::MappingTrigger: ToAscPtr,
153+
<C as Blockchain>::MappingTrigger: ToAscPtr + crate::rust_abi::ToRustBytes,
154154
{
155155
let logger = logger.cheap_clone();
156156

runtime/wasm/src/module/instance.rs

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ impl WasmInstance {
113113
trigger: TriggerWithHandler<MappingTrigger<C>>,
114114
) -> Result<(BlockState, Gas), MappingError>
115115
where
116-
<C as Blockchain>::MappingTrigger: ToAscPtr,
116+
<C as Blockchain>::MappingTrigger: ToAscPtr + crate::rust_abi::ToRustBytes,
117117
{
118118
use crate::rust_abi::MappingLanguage;
119119

@@ -145,15 +145,21 @@ impl WasmInstance {
145145
async fn handle_trigger_rust<C: Blockchain>(
146146
self,
147147
trigger: TriggerWithHandler<MappingTrigger<C>>,
148-
) -> Result<(BlockState, Gas), MappingError> {
148+
) -> Result<(BlockState, Gas), MappingError>
149+
where
150+
<C as Blockchain>::MappingTrigger: crate::rust_abi::ToRustBytes,
151+
{
149152
let handler_name = trigger.handler_name().to_owned();
150153
let logging_extras = trigger.logging_extras().cheap_clone();
151154
let error_context = trigger.trigger.error_context();
152155

153-
// TODO: Serialize trigger to TLV bytes
154-
// For now, use placeholder empty bytes - full implementation requires
155-
// trigger-specific serialization which is complex
156-
let trigger_bytes: Vec<u8> = Vec::new();
156+
// Serialize the trigger to TLV bytes for Rust handlers
157+
use crate::rust_abi::ToRustBytes;
158+
let trigger_bytes = match &trigger.trigger {
159+
MappingTrigger::Onchain(t) => t.to_rust_bytes(),
160+
MappingTrigger::Offchain(_) => Vec::new(), // TODO: Offchain triggers
161+
MappingTrigger::Subgraph(_) => Vec::new(), // TODO: Subgraph triggers
162+
};
157163

158164
self.invoke_handler_rust(&handler_name, &trigger_bytes, logging_extras, error_context)
159165
.await

runtime/wasm/src/rust_abi/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,12 @@
1313
1414
mod entity;
1515
mod host;
16+
mod trigger;
1617
mod types;
1718

1819
pub use entity::{deserialize_entity_data, serialize_entity, EntityData};
1920
pub use host::{is_rust_module, link_rust_host_functions};
21+
pub use trigger::{RustLogTrigger, ToRustBytes};
2022
pub use types::{FromRustWasm, ToRustWasm, ValueTag};
2123

2224
/// Language enum for dispatch.
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
//! Trigger serialization for Rust WASM modules.
2+
//!
3+
//! Provides the `ToRustBytes` trait for serializing blockchain triggers
4+
//! to the TLV format expected by Graphite SDK handlers.
5+
6+
use super::types::ToRustWasm;
7+
use std::io::{self, Write};
8+
9+
/// Trait for serializing trigger data to Rust WASM format.
10+
///
11+
/// Implemented by chain-specific trigger types (e.g., Ethereum MappingTrigger).
12+
/// The serialized format matches what Graphite SDK's `FromWasmBytes` expects.
13+
pub trait ToRustBytes {
14+
/// Serialize to TLV bytes for Rust handlers.
15+
fn to_rust_bytes(&self) -> Vec<u8>;
16+
}
17+
18+
/// Log trigger data in a format suitable for serialization.
19+
///
20+
/// This struct provides a chain-agnostic representation of a log trigger
21+
/// that can be serialized for Rust WASM handlers.
22+
#[derive(Debug, Clone)]
23+
pub struct RustLogTrigger {
24+
/// Contract address that emitted the log (20 bytes)
25+
pub address: [u8; 20],
26+
/// Transaction hash (32 bytes)
27+
pub tx_hash: [u8; 32],
28+
/// Log index within the block
29+
pub log_index: u64,
30+
/// Block number
31+
pub block_number: u64,
32+
/// Block timestamp (Unix seconds)
33+
pub block_timestamp: u64,
34+
/// Log topics (event selector + indexed params)
35+
pub topics: Vec<[u8; 32]>,
36+
/// ABI-encoded non-indexed event data
37+
pub data: Vec<u8>,
38+
}
39+
40+
impl ToRustBytes for RustLogTrigger {
41+
fn to_rust_bytes(&self) -> Vec<u8> {
42+
let mut buf = Vec::new();
43+
self.write_to(&mut buf).expect("write to vec cannot fail");
44+
buf
45+
}
46+
}
47+
48+
impl ToRustWasm for RustLogTrigger {
49+
fn write_to<W: Write>(&self, writer: &mut W) -> io::Result<()> {
50+
// Fixed-size fields first (no length prefix)
51+
writer.write_all(&self.address)?; // 20 bytes
52+
writer.write_all(&self.tx_hash)?; // 32 bytes
53+
writer.write_all(&self.log_index.to_le_bytes())?; // 8 bytes
54+
writer.write_all(&self.block_number.to_le_bytes())?; // 8 bytes
55+
writer.write_all(&self.block_timestamp.to_le_bytes())?; // 8 bytes
56+
57+
// Topics array: count + data
58+
writer.write_all(&(self.topics.len() as u32).to_le_bytes())?;
59+
for topic in &self.topics {
60+
writer.write_all(topic)?; // 32 bytes each
61+
}
62+
63+
// Data: length + bytes
64+
writer.write_all(&(self.data.len() as u32).to_le_bytes())?;
65+
writer.write_all(&self.data)?;
66+
67+
Ok(())
68+
}
69+
}
70+
71+
#[cfg(test)]
72+
mod tests {
73+
use super::*;
74+
75+
#[test]
76+
fn serialize_log_trigger() {
77+
let trigger = RustLogTrigger {
78+
address: [0xde; 20],
79+
tx_hash: [0xab; 32],
80+
log_index: 42,
81+
block_number: 12345678,
82+
block_timestamp: 1700000000,
83+
topics: vec![[0x11; 32], [0x22; 32]],
84+
data: vec![1, 2, 3, 4],
85+
};
86+
87+
let bytes = trigger.to_rust_bytes();
88+
89+
// Verify structure:
90+
// 20 (address) + 32 (tx_hash) + 8*3 (log_index, block_number, timestamp)
91+
// + 4 (topics count) + 64 (2 topics) + 4 (data len) + 4 (data)
92+
assert_eq!(bytes.len(), 20 + 32 + 24 + 4 + 64 + 4 + 4);
93+
94+
// Check address
95+
assert_eq!(&bytes[0..20], &[0xde; 20]);
96+
97+
// Check topics count
98+
let topics_offset = 20 + 32 + 24;
99+
let topics_count = u32::from_le_bytes(bytes[topics_offset..topics_offset+4].try_into().unwrap());
100+
assert_eq!(topics_count, 2);
101+
}
102+
}

0 commit comments

Comments
 (0)