Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
6cc39b4
chain/ethereum, graph: Add `Asc` types for Receipts and Logs
tilacog Mar 1, 2022
3c82623
chain/ethereum: Remove fields unavailable in our rust-web3 crate
tilacog Mar 2, 2022
398af24
chain/ethereum: Use Array instead of AscPtr<Array>
tilacog Mar 2, 2022
56addd0
chain/etherum: Implement ToAscObj for Receipt and Log
tilacog Mar 2, 2022
581c722
chain/ethereum: Put Array fields under a new type
tilacog Mar 2, 2022
6e1e011
chain/ethereum: Implement ToAscObj for (EthereumEvent, Receipt)
tilacog Mar 3, 2022
d2df846
chain/ethereum: Optional receipts in `EthereumTrigger::Log` variant
tilacog Mar 4, 2022
c34c52c
chain/ethereum: Use booleans as edges for LogFilters inner graph
tilacog Mar 15, 2022
d641db0
chain/ethereum: Conditionally collect receipts based on manifest
tilacog Mar 15, 2022
afdc18d
chain/ethereum: Conditionally collect receipts based on api version
tilacog Mar 16, 2022
85304d5
chain/ethereum: Minor refactors and variable renaming
tilacog Mar 16, 2022
226a20d
chain/ethereum, graph: conditionally build the correct `AscType`
tilacog Mar 16, 2022
298623a
chain/ethereum: Change visibility and document helper functions
tilacog Mar 18, 2022
86cc81f
chain/ethereum: Remove dead code
tilacog Mar 19, 2022
e9650b7
chain/ethereum: Fix missing parameter
tilacog Mar 19, 2022
20ed6a7
chain/ethereum: Update tests
tilacog Mar 21, 2022
c329a03
chain/ethereum: New subgraph manifest validation
tilacog Mar 21, 2022
4827a17
graph: Bumps the maximum default for api and spec versions
tilacog Mar 29, 2022
6240d5f
chain/ethereum: Tests for `requires_transaction_receipt` function
tilacog Mar 22, 2022
a762482
chain/ethereum: Use a retry strategy in `get_logs_and_transactions`
tilacog Mar 24, 2022
954ec9c
chain/ethereum: Fix wrong struct name
tilacog Mar 24, 2022
a7720ce
chain/ethereum: Fix broken tests
tilacog Mar 28, 2022
c2b1a59
chain/ethereum: Fix typo in comments
tilacog Mar 28, 2022
0053c18
chain/ethereum: Renamed `AscEthereumEventWithReceipt` to `AscEthereum…
tilacog Mar 28, 2022
9cda473
chain/ethereum: Use `AscAddress` instead of `AscH160`
tilacog Mar 28, 2022
8068b6a
chain/ethereum: Fix typo in comment
tilacog Mar 29, 2022
5c08031
chain/ethereum: Explain the unwrapping of txn and block hashes
tilacog Mar 29, 2022
2008afa
chain/ethereum: Comments in upper case
tilacog Mar 29, 2022
1fae709
chain/ethereum: Remove unecessary implementations of AscType
tilacog Mar 29, 2022
20b2d1a
chain/ethereum: Reorder enum variants
tilacog Mar 29, 2022
32028aa
runtime: Tests for `bool` primitive type
tilacog Mar 30, 2022
6600633
chain/ethereum: Uppercase comments, more descriptive variable name
tilacog Mar 30, 2022
d63232b
NEWS: Update with info about this very feature
tilacog Mar 30, 2022
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
5 changes: 5 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@
where whitespace characters were part of the terms.
- Adds support for Solidity Custom Errors (issue #2577)

### Api Version 0.0.7 and Spec Version 0.0.5
This release brings API Version 0.0.7 in mappings, which allows Ethereum event handlers to require transaction receipts to be present in the `Event` object.
Refer to [PR #3373](https://github.com/graphprotocol/graph-node/pull/3373) for instructions on how to enable that.


## 0.25.2

This release includes two changes:
Expand Down
165 changes: 141 additions & 24 deletions chain/ethereum/src/adapter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -205,10 +205,12 @@ pub(crate) struct EthereumLogFilter {
/// Log filters can be represented as a bipartite graph between contracts and events. An edge
/// exists between a contract and an event if a data source for the contract has a trigger for
/// the event.
contracts_and_events_graph: GraphMap<LogFilterNode, (), petgraph::Undirected>,
/// Edges are of `bool` type and indicates when a trigger requires a transaction receipt.
contracts_and_events_graph: GraphMap<LogFilterNode, bool, petgraph::Undirected>,

// Event sigs with no associated address, matching on all addresses.
wildcard_events: HashSet<EventSignature>,
/// Event sigs with no associated address, matching on all addresses.
/// Maps to a boolean representing if a trigger requires a transaction receipt.
wildcard_events: HashMap<EventSignature, bool>,
}

impl Into<Vec<LogFilter>> for EthereumLogFilter {
Expand Down Expand Up @@ -248,42 +250,63 @@ impl EthereumLogFilter {
let event = LogFilterNode::Event(*sig);
self.contracts_and_events_graph
.all_edges()
.any(|(s, t, ())| {
(s == contract && t == event) || (t == contract && s == event)
})
|| self.wildcard_events.contains(sig)
.any(|(s, t, _)| (s == contract && t == event) || (t == contract && s == event))
|| self.wildcard_events.contains_key(sig)
}
}
}

/// Similar to [`matches`], checks if a transaction receipt is required for this log filter.
pub fn requires_transaction_receipt(
&self,
event_signature: &H256,
contract_address: Option<&Address>,
) -> bool {
if let Some(true) = self.wildcard_events.get(event_signature) {
true
} else if let Some(address) = contract_address {
let contract = LogFilterNode::Contract(*address);
let event = LogFilterNode::Event(*event_signature);
self.contracts_and_events_graph
.all_edges()
.any(|(s, t, r)| {
*r && (s == contract && t == event) || (t == contract && s == event)
})
} else {
false
}
}

pub fn from_data_sources<'a>(iter: impl IntoIterator<Item = &'a DataSource>) -> Self {
let mut this = EthereumLogFilter::default();
for ds in iter {
for event_sig in ds.mapping.event_handlers.iter().map(|e| e.topic0()) {
for event_handler in ds.mapping.event_handlers.iter() {
let event_sig = event_handler.topic0();
match ds.source.address {
Some(contract) => {
this.contracts_and_events_graph.add_edge(
LogFilterNode::Contract(contract),
LogFilterNode::Event(event_sig),
(),
event_handler.receipt,
);
}
None => {
this.wildcard_events.insert(event_sig);
this.wildcard_events
.insert(event_sig, event_handler.receipt);
}
}
}
}
this
}

pub fn from_mapping(iter: &Mapping) -> Self {
pub fn from_mapping(mapping: &Mapping) -> Self {
let mut this = EthereumLogFilter::default();

for sig in iter.event_handlers.iter().map(|e| e.topic0()) {
this.wildcard_events.insert(sig);
for event_handler in &mapping.event_handlers {
let signature = event_handler.topic0();
this.wildcard_events
.insert(signature, event_handler.receipt);
}

this
}

Expand All @@ -298,8 +321,8 @@ impl EthereumLogFilter {
contracts_and_events_graph,
wildcard_events,
} = other;
for (s, t, ()) in contracts_and_events_graph.all_edges() {
self.contracts_and_events_graph.add_edge(s, t, ());
for (s, t, e) in contracts_and_events_graph.all_edges() {
self.contracts_and_events_graph.add_edge(s, t, *e);
}
self.wildcard_events.extend(wildcard_events);
}
Expand All @@ -322,7 +345,7 @@ impl EthereumLogFilter {
let mut filters = self
.wildcard_events
.into_iter()
.map(EthGetLogsFilter::from_event)
.map(|(event, _)| EthGetLogsFilter::from_event(event))
.collect_vec();

// The current algorithm is to repeatedly find the maximum cardinality vertex and turn all
Expand Down Expand Up @@ -990,7 +1013,7 @@ mod tests {
let mut filter = TriggerFilter {
log: EthereumLogFilter {
contracts_and_events_graph: GraphMap::new(),
wildcard_events: HashSet::new(),
wildcard_events: HashMap::new(),
},
call: EthereumCallFilter {
contract_addresses_function_signatures: HashMap::from_iter(vec![
Expand Down Expand Up @@ -1047,17 +1070,17 @@ mod tests {
filter.log.contracts_and_events_graph.add_edge(
LogFilterNode::Contract(address(10)),
LogFilterNode::Event(sig(100)),
(),
false,
);
filter.log.contracts_and_events_graph.add_edge(
LogFilterNode::Contract(address(10)),
LogFilterNode::Event(sig(101)),
(),
false,
);
filter.log.contracts_and_events_graph.add_edge(
LogFilterNode::Contract(address(20)),
LogFilterNode::Event(sig(100)),
(),
false,
);

let expected_log = MultiLogFilter {
Expand Down Expand Up @@ -1293,15 +1316,15 @@ fn complete_log_filter() {
contracts_and_events_graph.add_edge(
LogFilterNode::Contract(contract),
LogFilterNode::Event(event),
(),
false,
);
}
}

// Run `eth_get_logs_filters`, which is what we want to test.
let logs_filters: Vec<_> = EthereumLogFilter {
contracts_and_events_graph,
wildcard_events: HashSet::new(),
wildcard_events: HashMap::new(),
}
.eth_get_logs_filters()
.collect();
Expand Down Expand Up @@ -1333,3 +1356,97 @@ fn complete_log_filter() {
}
}
}

#[test]
fn log_filter_require_transacion_receipt_method() {
// test data
let event_signature_a = H256::zero();
let event_signature_b = H256::from_low_u64_be(1);
let event_signature_c = H256::from_low_u64_be(2);
let contract_a = Address::from_low_u64_be(3);
let contract_b = Address::from_low_u64_be(4);
let contract_c = Address::from_low_u64_be(5);

let wildcard_event_with_receipt = H256::from_low_u64_be(6);
let wildcard_event_without_receipt = H256::from_low_u64_be(7);
let wildcard_events = [
(wildcard_event_with_receipt, true),
(wildcard_event_without_receipt, false),
]
.into_iter()
.collect();

let alien_event_signature = H256::from_low_u64_be(8); // those will not be inserted in the graph
let alien_contract_address = Address::from_low_u64_be(9);

// test graph nodes
let event_a_node = LogFilterNode::Event(event_signature_a);
let event_b_node = LogFilterNode::Event(event_signature_b);
let event_c_node = LogFilterNode::Event(event_signature_c);
let contract_a_node = LogFilterNode::Contract(contract_a);
let contract_b_node = LogFilterNode::Contract(contract_b);
let contract_c_node = LogFilterNode::Contract(contract_c);

// build test graph with the following layout:
//
// ```dot
// graph bipartite {
//
// // conected and require a receipt
// event_a -- contract_a [ receipt=true ]
// event_b -- contract_b [ receipt=true ]
// event_c -- contract_c [ receipt=true ]
//
// // connected but don't require a receipt
// event_a -- contract_b [ receipt=false ]
// event_b -- contract_a [ receipt=false ]
// }
// ```
let mut contracts_and_events_graph = GraphMap::new();

let event_a_id = contracts_and_events_graph.add_node(event_a_node);
let event_b_id = contracts_and_events_graph.add_node(event_b_node);
let event_c_id = contracts_and_events_graph.add_node(event_c_node);
let contract_a_id = contracts_and_events_graph.add_node(contract_a_node);
let contract_b_id = contracts_and_events_graph.add_node(contract_b_node);
let contract_c_id = contracts_and_events_graph.add_node(contract_c_node);
contracts_and_events_graph.add_edge(event_a_id, contract_a_id, true);
contracts_and_events_graph.add_edge(event_b_id, contract_b_id, true);
contracts_and_events_graph.add_edge(event_a_id, contract_b_id, false);
contracts_and_events_graph.add_edge(event_b_id, contract_a_id, false);
contracts_and_events_graph.add_edge(event_c_id, contract_c_id, true);

let filter = EthereumLogFilter {
contracts_and_events_graph,
wildcard_events,
};

// connected contracts and events graph
assert!(filter.requires_transaction_receipt(&event_signature_a, Some(&contract_a)));
assert!(filter.requires_transaction_receipt(&event_signature_b, Some(&contract_b)));
assert!(filter.requires_transaction_receipt(&event_signature_c, Some(&contract_c)));
assert!(!filter.requires_transaction_receipt(&event_signature_a, Some(&contract_b)));
assert!(!filter.requires_transaction_receipt(&event_signature_b, Some(&contract_a)));

// Event C and Contract C are not connected to the other events and contracts
assert!(!filter.requires_transaction_receipt(&event_signature_a, Some(&contract_c)));
assert!(!filter.requires_transaction_receipt(&event_signature_b, Some(&contract_c)));
assert!(!filter.requires_transaction_receipt(&event_signature_c, Some(&contract_a)));
assert!(!filter.requires_transaction_receipt(&event_signature_c, Some(&contract_b)));

// Wildcard events
assert!(filter.requires_transaction_receipt(&wildcard_event_with_receipt, None));
assert!(!filter.requires_transaction_receipt(&wildcard_event_without_receipt, None));

// Alien events and contracts always return false
assert!(
!filter.requires_transaction_receipt(&alien_event_signature, Some(&alien_contract_address))
);
assert!(!filter.requires_transaction_receipt(&alien_event_signature, None));
assert!(!filter.requires_transaction_receipt(&alien_event_signature, Some(&contract_a)));
assert!(!filter.requires_transaction_receipt(&alien_event_signature, Some(&contract_b)));
assert!(!filter.requires_transaction_receipt(&alien_event_signature, Some(&contract_c)));
assert!(!filter.requires_transaction_receipt(&event_signature_a, Some(&alien_contract_address)));
assert!(!filter.requires_transaction_receipt(&event_signature_b, Some(&alien_contract_address)));
assert!(!filter.requires_transaction_receipt(&event_signature_c, Some(&alien_contract_address)));
}
21 changes: 19 additions & 2 deletions chain/ethereum/src/data_source.rs
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,20 @@ impl blockchain::DataSource<Chain> for DataSource {
errors.push(anyhow!("data source has duplicated block handlers"));
}

// Validate that event handlers don't require receipts for API versions lower than 0.0.7
let api_version = self.api_version();
if api_version < semver::Version::new(0, 0, 7) {
for event_handler in &self.mapping.event_handlers {
if event_handler.receipt {
errors.push(anyhow!(
"data source has event handlers that require transaction receipts, but this \
is only supported for apiVersion >= 0.0.7"
));
break;
}
}
}

errors
}

Expand Down Expand Up @@ -433,7 +447,7 @@ impl DataSource {
let trigger_address = match trigger {
EthereumTrigger::Block(_, EthereumBlockTriggerType::WithCallTo(address)) => address,
EthereumTrigger::Call(call) => &call.to,
EthereumTrigger::Log(log) => &log.address,
EthereumTrigger::Log(log, _) => &log.address,

// Unfiltered block triggers match any data source address.
EthereumTrigger::Block(_, EthereumBlockTriggerType::Every) => return true,
Expand Down Expand Up @@ -471,7 +485,7 @@ impl DataSource {
handler.handler,
)))
}
EthereumTrigger::Log(log) => {
EthereumTrigger::Log(log, receipt) => {
let potential_handlers = self.handlers_for_log(log)?;

// Map event handlers to (event handler, event ABI) pairs; fail if there are
Expand Down Expand Up @@ -572,6 +586,7 @@ impl DataSource {
transaction: Arc::new(transaction),
log: log.cheap_clone(),
params,
receipt: receipt.clone(),
},
event_handler.handler,
logging_extras,
Expand Down Expand Up @@ -996,6 +1011,8 @@ pub struct MappingEventHandler {
pub event: String,
pub topic0: Option<H256>,
pub handler: String,
#[serde(default)]
pub receipt: bool,
}

impl MappingEventHandler {
Expand Down
Loading