From 6cc39b4e4dec5279dbd994976bad43baf84eb762 Mon Sep 17 00:00:00 2001 From: tilacog Date: Tue, 1 Mar 2022 20:12:11 -0300 Subject: [PATCH 01/33] chain/ethereum, graph: Add `Asc` types for Receipts and Logs --- chain/ethereum/src/runtime/abi.rs | 78 ++++++++++++++ graph/src/runtime/mod.rs | 167 +++++++++++++++--------------- 2 files changed, 162 insertions(+), 83 deletions(-) diff --git a/chain/ethereum/src/runtime/abi.rs b/chain/ethereum/src/runtime/abi.rs index a6748715f70..c71f1acc8bb 100644 --- a/chain/ethereum/src/runtime/abi.rs +++ b/chain/ethereum/src/runtime/abi.rs @@ -15,6 +15,7 @@ use crate::trigger::{ use super::runtime_adapter::UnresolvedContractCall; type AscH256 = Uint8Array; +type AscH2048 = Uint8Array; pub struct AscLogParamArray(Array>); @@ -227,6 +228,83 @@ impl AscIndexId for AscEthereumEvent, + pub topics: AscPtr>>, + pub data: AscPtr, + pub block_hash: AscPtr, + pub block_number: AscPtr, + pub transaction_hash: AscPtr, + pub transaction_index: AscPtr, + pub log_index: AscPtr, + pub transaction_log_index: AscPtr, + pub log_type: AscPtr, + pub removed: AscPtr, +} + +impl AscIndexId for AscEthereumLog { + const INDEX_ASC_TYPE_ID: IndexForAscTypeId = IndexForAscTypeId::Log; +} + +#[repr(C)] +#[derive(AscType)] +pub(crate) struct AscEthereumTransactionReceipt { + pub transaction_hash: AscPtr, + pub transaction_index: AscPtr, + pub block_hash: AscPtr, + pub block_number: AscPtr, + pub from: AscPtr, + pub to: AscPtr, + pub cumulative_gas_used: AscPtr, + pub gas_used: AscPtr, + pub contract_address: AscPtr, + pub logs: AscPtr>>, + pub status: AscPtr, + pub root: AscPtr, + pub logs_bloom: AscPtr, + pub transaction_type: AscPtr, + pub effective_gas_price: AscPtr, +} + +impl AscIndexId for AscEthereumTransactionReceipt { + const INDEX_ASC_TYPE_ID: IndexForAscTypeId = IndexForAscTypeId::TransactionReceipt; +} + +/// Introduced in API Version 0.0.7, this is the same as [`AscEthereumTransaction`] with an added +/// `receipt` field. +#[repr(C)] +#[derive(AscType)] +pub(crate) struct AscEthereumEventWithReceipt +where + T: AscType, + B: AscType, +{ + pub address: AscPtr, + pub log_index: AscPtr, + pub transaction_log_index: AscPtr, + pub log_type: AscPtr, + pub block: AscPtr, + pub transaction: AscPtr, + pub params: AscPtr, + pub receipt: AscPtr, +} + +impl AscIndexId for AscEthereumEventWithReceipt { + const INDEX_ASC_TYPE_ID: IndexForAscTypeId = IndexForAscTypeId::EthereumEvent; +} + +impl AscIndexId for AscEthereumEventWithReceipt { + const INDEX_ASC_TYPE_ID: IndexForAscTypeId = IndexForAscTypeId::EthereumEvent; +} + +impl AscIndexId + for AscEthereumEventWithReceipt +{ + const INDEX_ASC_TYPE_ID: IndexForAscTypeId = IndexForAscTypeId::EthereumEvent; +} + #[repr(C)] #[derive(AscType)] pub(crate) struct AscLogParam { diff --git a/graph/src/runtime/mod.rs b/graph/src/runtime/mod.rs index 22fb7d3adb3..d0c8b1c130b 100644 --- a/graph/src/runtime/mod.rs +++ b/graph/src/runtime/mod.rs @@ -197,110 +197,111 @@ pub enum IndexForAscTypeId { ArrayF32 = 49, ArrayF64 = 50, ArrayBigDecimal = 51, + TransactionReceipt = 52, + Log = 53, // Near Type IDs // // Generated with the following shell script: // // ``` - // cat chain/near/src/runtime/generated.rs | grep IndexForAscTypeId::Near | grep -Eo "Near[a-zA-Z0-9]+" | awk '{for(x=1;x<=NF;x++)sub(/$/,"="++i+51",")}1' | sed 's/=/ = /' + // cat chain/near/src/runtime/generated.rs | grep IndexForAscTypeId::Near | grep -Eo "Near[a-zA-Z0-9]+" | awk '{for(x=1;x<=NF;x++)sub(/$/,"="++i+53",")}1' | sed 's/=/ = /' // ``` // - // The `51` literal at the end in the `awk` should be replaced with the last element + // The `53` literal at the end in the `awk` should be replaced with the last element // value in the list above. - NearArrayDataReceiver = 52, - NearArrayCryptoHash = 53, - NearArrayActionEnum = 54, - NearArrayMerklePathItem = 55, - NearArrayValidatorStake = 56, - NearArraySlashedValidator = 57, - NearArraySignature = 58, - NearArrayChunkHeader = 59, - NearAccessKeyPermissionEnum = 60, - NearActionEnum = 61, - NearDirectionEnum = 62, - NearPublicKey = 63, - NearSignature = 64, - NearFunctionCallPermission = 65, - NearFullAccessPermission = 66, - NearAccessKey = 67, - NearDataReceiver = 68, - NearCreateAccountAction = 69, - NearDeployContractAction = 70, - NearFunctionCallAction = 71, - NearTransferAction = 72, - NearStakeAction = 73, - NearAddKeyAction = 74, - NearDeleteKeyAction = 75, - NearDeleteAccountAction = 76, - NearActionReceipt = 77, - NearSuccessStatusEnum = 78, - NearMerklePathItem = 79, - NearExecutionOutcome = 80, - NearSlashedValidator = 81, - NearBlockHeader = 82, - NearValidatorStake = 83, - NearChunkHeader = 84, - NearBlock = 85, - NearReceiptWithOutcome = 86, + NearArrayDataReceiver = 54, + NearArrayCryptoHash = 55, + NearArrayActionEnum = 56, + NearArrayMerklePathItem = 57, + NearArrayValidatorStake = 58, + NearArraySlashedValidator = 59, + NearArraySignature = 60, + NearArrayChunkHeader = 61, + NearAccessKeyPermissionEnum = 62, + NearActionEnum = 63, + NearPublicKey = 64, + NearSignature = 65, + NearFunctionCallPermission = 66, + NearFullAccessPermission = 67, + NearAccessKey = 68, + NearDataReceiver = 69, + NearCreateAccountAction = 70, + NearDeployContractAction = 71, + NearFunctionCallAction = 72, + NearTransferAction = 73, + NearStakeAction = 74, + NearAddKeyAction = 75, + NearDeleteKeyAction = 76, + NearDeleteAccountAction = 77, + NearActionReceipt = 78, + NearSuccessStatusEnum = 79, + NearMerklePathItem = 80, + NearExecutionOutcome = 81, + NearSlashedValidator = 82, + NearBlockHeader = 83, + NearValidatorStake = 84, + NearChunkHeader = 85, + NearBlock = 86, + NearReceiptWithOutcome = 87, // Tendermint Type IDs // // Generated with the following shell script: // // ``` - // cat chain/tendermint/src/runtime/generated.rs | grep IndexForAscTypeId::Tendermint | grep -Eo "Tendermint[a-zA-Z0-9]+" | awk '{for(x=1;x<=NF;x++)sub(/$/,"="++i+86",")}1' | sed 's/=/ = /' + // cat chain/tendermint/src/runtime/generated.rs | grep IndexForAscTypeId::Tendermint | grep -Eo "Tendermint[a-zA-Z0-9]+" | awk '{for(x=1;x<=NF;x++)sub(/$/,"="++i+87",")}1' | sed 's/=/ = /' // ``` // - // The `86` literal at the end in the `awk` should be replaced with the last element + // The `87` literal at the end in the `awk` should be replaced with the last element // value in the list above. - TendermintArrayEventTx = 87, - TendermintArrayEvent = 88, + TendermintArrayEventTx = 88, TendermintArrayCommitSig = 89, TendermintArrayBytes = 90, - TendermintArrayEvidence = 91, - TendermintArrayEventAttribute = 92, - TendermintBlockIDFlagEnum = 93, - TendermintSignedMsgTypeEnum = 94, - TendermintEventList = 95, - TendermintEventBlock = 96, - TendermintResponseBeginBlock = 97, - TendermintResponseEndBlock = 98, - TendermintValidatorUpdate = 99, - TendermintArrayValidatorUpdate = 100, - TendermintConsensusParams = 101, + TendermintArrayEventAttribute = 91, + TendermintArrayValidator = 92, + TendermintArrayEvidence = 93, + TendermintArrayEvent = 94, + TendermintArrayValidatorUpdate = 95, + TendermintBlockIDFlagEnum = 96, + TendermintSignedMsgTypeEnum = 97, + TendermintEventData = 98, + TendermintEventList = 99, + TendermintBlock = 100, + TendermintBlockID = 101, TendermintBlockParams = 102, - TendermintEvidenceParams = 103, - TendermintValidatorParams = 104, - TendermintVersionParams = 105, - TendermintBlock = 106, - TendermintCommit = 107, - TendermintCommitSig = 108, - TendermintHeader = 109, - TendermintConsensus = 110, - TendermintBlockID = 111, - TendermintPartSetHeader = 112, - TendermintData = 113, - TendermintEvidence = 114, - TendermintDuplicateVoteEvidence = 115, - TendermintEventTx = 116, - TendermintEventVote = 117, - TendermintLightClientAttackEvidence = 118, - TendermintLightBlock = 119, - TendermintValidatorSet = 120, - TendermintSignedHeader = 121, - TendermintEvidenceList = 122, - TendermintValidator = 123, - TendermintArrayValidator = 124, - TendermintPublicKey = 125, - TendermintTxResult = 126, - TendermintResponseDeliverTx = 127, - TendermintEvent = 128, - TendermintEventAttribute = 129, - TendermintEventValidatorSetUpdates = 130, - TendermintDuration = 131, - TendermintTimestamp = 132, - TendermintEventData = 133, + TendermintCommit = 103, + TendermintCommitSig = 104, + TendermintConsensus = 105, + TendermintConsensusParams = 106, + TendermintData = 107, + TendermintDuration = 108, + TendermintDuplicateVoteEvidence = 109, + TendermintEvent = 110, + TendermintEventAttribute = 111, + TendermintEventBlock = 112, + TendermintEventTx = 113, + TendermintEventValidatorSetUpdates = 114, + TendermintEventVote = 115, + TendermintEvidence = 116, + TendermintEvidenceList = 117, + TendermintEvidenceParams = 118, + TendermintHeader = 119, + TendermintLightBlock = 120, + TendermintLightClientAttackEvidence = 121, + TendermintPublicKey = 122, + TendermintPartSetHeader = 123, + TendermintResponseBeginBlock = 124, + TendermintResponseEndBlock = 125, + TendermintResponseDeliverTx = 126, + TendermintSignedHeader = 127, + TendermintTimestamp = 128, + TendermintTxResult = 129, + TendermintValidator = 130, + TendermintValidatorParams = 131, + TendermintValidatorSet = 132, + TendermintValidatorUpdate = 133, + TendermintVersionParams = 134, } impl ToAscObj for IndexForAscTypeId { From 3c82623c4fe74057adb9d3dc138ea7c4071a75f1 Mon Sep 17 00:00:00 2001 From: tilacog Date: Wed, 2 Mar 2022 17:26:41 -0300 Subject: [PATCH 02/33] chain/ethereum: Remove fields unavailable in our rust-web3 crate --- chain/ethereum/src/runtime/abi.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/chain/ethereum/src/runtime/abi.rs b/chain/ethereum/src/runtime/abi.rs index c71f1acc8bb..f9cabc45505 100644 --- a/chain/ethereum/src/runtime/abi.rs +++ b/chain/ethereum/src/runtime/abi.rs @@ -255,8 +255,6 @@ pub(crate) struct AscEthereumTransactionReceipt { pub transaction_index: AscPtr, pub block_hash: AscPtr, pub block_number: AscPtr, - pub from: AscPtr, - pub to: AscPtr, pub cumulative_gas_used: AscPtr, pub gas_used: AscPtr, pub contract_address: AscPtr, @@ -264,8 +262,6 @@ pub(crate) struct AscEthereumTransactionReceipt { pub status: AscPtr, pub root: AscPtr, pub logs_bloom: AscPtr, - pub transaction_type: AscPtr, - pub effective_gas_price: AscPtr, } impl AscIndexId for AscEthereumTransactionReceipt { From 398af245d7a671717c0ef485de4e306b73e2e7b5 Mon Sep 17 00:00:00 2001 From: tilacog Date: Wed, 2 Mar 2022 19:07:19 -0300 Subject: [PATCH 03/33] chain/ethereum: Use Array instead of AscPtr --- chain/ethereum/src/runtime/abi.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/chain/ethereum/src/runtime/abi.rs b/chain/ethereum/src/runtime/abi.rs index f9cabc45505..4a057c38922 100644 --- a/chain/ethereum/src/runtime/abi.rs +++ b/chain/ethereum/src/runtime/abi.rs @@ -232,7 +232,7 @@ impl AscIndexId for AscEthereumEvent, - pub topics: AscPtr>>, + pub topics: Array>, pub data: AscPtr, pub block_hash: AscPtr, pub block_number: AscPtr, @@ -241,7 +241,7 @@ pub(crate) struct AscEthereumLog { pub log_index: AscPtr, pub transaction_log_index: AscPtr, pub log_type: AscPtr, - pub removed: AscPtr, + pub removed: AscPtr>, } impl AscIndexId for AscEthereumLog { @@ -258,7 +258,7 @@ pub(crate) struct AscEthereumTransactionReceipt { pub cumulative_gas_used: AscPtr, pub gas_used: AscPtr, pub contract_address: AscPtr, - pub logs: AscPtr>>, + pub logs: Array>, pub status: AscPtr, pub root: AscPtr, pub logs_bloom: AscPtr, From 56addd0627977dfa85dbd84672b090aa96aa5a9d Mon Sep 17 00:00:00 2001 From: tilacog Date: Wed, 2 Mar 2022 19:08:31 -0300 Subject: [PATCH 04/33] chain/etherum: Implement ToAscObj for Receipt and Log --- chain/ethereum/src/runtime/abi.rs | 136 +++++++++++++++++++++++++++--- 1 file changed, 125 insertions(+), 11 deletions(-) diff --git a/chain/ethereum/src/runtime/abi.rs b/chain/ethereum/src/runtime/abi.rs index 4a057c38922..c94ca54d5a4 100644 --- a/chain/ethereum/src/runtime/abi.rs +++ b/chain/ethereum/src/runtime/abi.rs @@ -1,19 +1,25 @@ -use graph::prelude::{ethabi, BigInt}; -use graph::runtime::gas::GasCounter; -use graph::runtime::{asc_get, asc_new, AscPtr, DeterministicHostError, FromAscObj, ToAscObj}; -use graph::runtime::{AscHeap, AscIndexId, AscType, IndexForAscTypeId}; +use super::runtime_adapter::UnresolvedContractCall; +use crate::trigger::{ + EthereumBlockData, EthereumCallData, EthereumEventData, EthereumTransactionData, +}; +use graph::{ + prelude::{ + ethabi, + web3::types::{Log, TransactionReceipt}, + BigInt, + }, + runtime::{ + asc_get, asc_new, gas::GasCounter, AscHeap, AscIndexId, AscPtr, AscType, + DeterministicHostError, FromAscObj, IndexForAscTypeId, ToAscObj, + }, +}; use graph_runtime_derive::AscType; use graph_runtime_wasm::asc_abi::class::{ - Array, AscAddress, AscBigInt, AscEnum, AscH160, AscString, EthereumValueKind, Uint8Array, + Array, AscAddress, AscBigInt, AscEnum, AscH160, AscString, AscWrapped, EthereumValueKind, + Uint8Array, }; use semver::Version; -use crate::trigger::{ - EthereumBlockData, EthereumCallData, EthereumEventData, EthereumTransactionData, -}; - -use super::runtime_adapter::UnresolvedContractCall; - type AscH256 = Uint8Array; type AscH2048 = Uint8Array; @@ -515,6 +521,114 @@ where } } +impl ToAscObj for Log { + fn to_asc_obj( + &self, + heap: &mut H, + gas: &GasCounter, + ) -> Result { + Ok(AscEthereumLog { + address: asc_new(heap, &self.address, gas)?, + topics: { + let asc_ptrs: Vec> = self + .topics + .iter() + .map(|x| asc_new(heap, x, gas)) + .collect::>, _>>()?; + Array::new(&asc_ptrs, heap, 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()))?, + }) + } +} + +impl ToAscObj for TransactionReceipt { + 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()))?, + 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: { + let asc_ptrs: Vec> = self + .logs + .iter() + .map(|log| { + log.to_asc_obj(heap, gas) + .and_then(|asc_log| AscPtr::alloc_obj(asc_log, heap, gas)) + }) + .collect::, _>>()?; + Array::new(&asc_ptrs, heap, 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)?, + }) + } +} + impl ToAscObj for EthereumCallData { fn to_asc_obj( &self, From 581c722b3078af5bc91f40eea947e249ba5f6265 Mon Sep 17 00:00:00 2001 From: tilacog Date: Wed, 2 Mar 2022 20:27:23 -0300 Subject: [PATCH 05/33] chain/ethereum: Put Array fields under a new type and implement the follwing traits for them: - AscType - ToAscObj - AscIndexId --- chain/ethereum/src/runtime/abi.rs | 93 ++++++++++++---- graph/src/runtime/mod.rs | 172 +++++++++++++++--------------- 2 files changed, 158 insertions(+), 107 deletions(-) diff --git a/chain/ethereum/src/runtime/abi.rs b/chain/ethereum/src/runtime/abi.rs index c94ca54d5a4..e2f5e3439c3 100644 --- a/chain/ethereum/src/runtime/abi.rs +++ b/chain/ethereum/src/runtime/abi.rs @@ -5,7 +5,7 @@ use crate::trigger::{ use graph::{ prelude::{ ethabi, - web3::types::{Log, TransactionReceipt}, + web3::types::{Log, TransactionReceipt, H256}, BigInt, }, runtime::{ @@ -53,6 +53,72 @@ impl AscIndexId for AscLogParamArray { const INDEX_ASC_TYPE_ID: IndexForAscTypeId = IndexForAscTypeId::ArrayEventParam; } +pub struct AscTopicArray(Array>); + +impl AscType for AscTopicArray { + fn to_asc_bytes(&self) -> Result, DeterministicHostError> { + self.0.to_asc_bytes() + } + + fn from_asc_bytes( + asc_obj: &[u8], + api_version: &Version, + ) -> Result { + Ok(Self(Array::from_asc_bytes(asc_obj, api_version)?)) + } +} + +impl ToAscObj for Vec { + 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)?)) + } +} + +impl AscIndexId for AscTopicArray { + const INDEX_ASC_TYPE_ID: IndexForAscTypeId = IndexForAscTypeId::ArrayH256; +} + +pub struct AscLogArray(Array>); + +impl AscType for AscLogArray { + fn to_asc_bytes(&self) -> Result, DeterministicHostError> { + self.0.to_asc_bytes() + } + + fn from_asc_bytes( + asc_obj: &[u8], + api_version: &Version, + ) -> Result { + Ok(Self(Array::from_asc_bytes(asc_obj, api_version)?)) + } +} + +impl ToAscObj for Vec { + 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)?)) + } +} + +impl AscIndexId for AscLogArray { + const INDEX_ASC_TYPE_ID: IndexForAscTypeId = IndexForAscTypeId::ArrayLog; +} + #[repr(C)] #[derive(AscType)] pub struct AscUnresolvedContractCall_0_0_4 { @@ -238,7 +304,7 @@ impl AscIndexId for AscEthereumEvent, - pub topics: Array>, + pub topics: AscPtr, pub data: AscPtr, pub block_hash: AscPtr, pub block_number: AscPtr, @@ -264,7 +330,7 @@ pub(crate) struct AscEthereumTransactionReceipt { pub cumulative_gas_used: AscPtr, pub gas_used: AscPtr, pub contract_address: AscPtr, - pub logs: Array>, + pub logs: AscPtr, pub status: AscPtr, pub root: AscPtr, pub logs_bloom: AscPtr, @@ -529,14 +595,7 @@ impl ToAscObj for Log { ) -> Result { Ok(AscEthereumLog { address: asc_new(heap, &self.address, gas)?, - topics: { - let asc_ptrs: Vec> = self - .topics - .iter() - .map(|x| asc_new(heap, x, gas)) - .collect::>, _>>()?; - Array::new(&asc_ptrs, heap, gas)? - }, + topics: asc_new(heap, &self.topics, gas)?, data: asc_new(heap, self.data.0.as_slice(), gas)?, block_hash: self .block_hash @@ -605,17 +664,7 @@ impl ToAscObj for TransactionReceipt { .contract_address .map(|contract_address| asc_new(heap, &contract_address, gas)) .unwrap_or(Ok(AscPtr::null()))?, - logs: { - let asc_ptrs: Vec> = self - .logs - .iter() - .map(|log| { - log.to_asc_obj(heap, gas) - .and_then(|asc_log| AscPtr::alloc_obj(asc_log, heap, gas)) - }) - .collect::, _>>()?; - Array::new(&asc_ptrs, heap, gas)? - }, + logs: asc_new(heap, &self.logs, gas)?, status: self .status .map(|status| asc_new(heap, &BigInt::from(status), gas)) diff --git a/graph/src/runtime/mod.rs b/graph/src/runtime/mod.rs index d0c8b1c130b..54d335d41c2 100644 --- a/graph/src/runtime/mod.rs +++ b/graph/src/runtime/mod.rs @@ -199,109 +199,111 @@ pub enum IndexForAscTypeId { ArrayBigDecimal = 51, TransactionReceipt = 52, Log = 53, + ArrayH256 = 54, + ArrayLog = 55, // Near Type IDs // // Generated with the following shell script: // // ``` - // cat chain/near/src/runtime/generated.rs | grep IndexForAscTypeId::Near | grep -Eo "Near[a-zA-Z0-9]+" | awk '{for(x=1;x<=NF;x++)sub(/$/,"="++i+53",")}1' | sed 's/=/ = /' + // cat chain/near/src/runtime/generated.rs | grep IndexForAscTypeId::Near | grep -Eo "Near[a-zA-Z0-9]+" | awk '{for(x=1;x<=NF;x++)sub(/$/,"="++i+55",")}1' | sed 's/=/ = /' // ``` // - // The `53` literal at the end in the `awk` should be replaced with the last element + // The `55` literal at the end in the `awk` should be replaced with the last element // value in the list above. - NearArrayDataReceiver = 54, - NearArrayCryptoHash = 55, - NearArrayActionEnum = 56, - NearArrayMerklePathItem = 57, - NearArrayValidatorStake = 58, - NearArraySlashedValidator = 59, - NearArraySignature = 60, - NearArrayChunkHeader = 61, - NearAccessKeyPermissionEnum = 62, - NearActionEnum = 63, - NearPublicKey = 64, - NearSignature = 65, - NearFunctionCallPermission = 66, - NearFullAccessPermission = 67, - NearAccessKey = 68, - NearDataReceiver = 69, - NearCreateAccountAction = 70, - NearDeployContractAction = 71, - NearFunctionCallAction = 72, - NearTransferAction = 73, - NearStakeAction = 74, - NearAddKeyAction = 75, - NearDeleteKeyAction = 76, - NearDeleteAccountAction = 77, - NearActionReceipt = 78, - NearSuccessStatusEnum = 79, - NearMerklePathItem = 80, - NearExecutionOutcome = 81, - NearSlashedValidator = 82, - NearBlockHeader = 83, - NearValidatorStake = 84, - NearChunkHeader = 85, - NearBlock = 86, - NearReceiptWithOutcome = 87, + NearArrayDataReceiver = 56, + NearArrayCryptoHash = 57, + NearArrayActionEnum = 58, + NearArrayMerklePathItem = 59, + NearArrayValidatorStake = 60, + NearArraySlashedValidator = 61, + NearArraySignature = 62, + NearArrayChunkHeader = 63, + NearAccessKeyPermissionEnum = 64, + NearActionEnum = 65, + NearPublicKey = 66, + NearSignature = 67, + NearFunctionCallPermission = 68, + NearFullAccessPermission = 69, + NearAccessKey = 70, + NearDataReceiver = 71, + NearCreateAccountAction = 72, + NearDeployContractAction = 73, + NearFunctionCallAction = 74, + NearTransferAction = 75, + NearStakeAction = 76, + NearAddKeyAction = 77, + NearDeleteKeyAction = 78, + NearDeleteAccountAction = 79, + NearActionReceipt = 80, + NearSuccessStatusEnum = 81, + NearMerklePathItem = 82, + NearExecutionOutcome = 83, + NearSlashedValidator = 84, + NearBlockHeader = 85, + NearValidatorStake = 86, + NearChunkHeader = 87, + NearBlock = 88, + NearReceiptWithOutcome = 89, // Tendermint Type IDs // // Generated with the following shell script: // // ``` - // cat chain/tendermint/src/runtime/generated.rs | grep IndexForAscTypeId::Tendermint | grep -Eo "Tendermint[a-zA-Z0-9]+" | awk '{for(x=1;x<=NF;x++)sub(/$/,"="++i+87",")}1' | sed 's/=/ = /' + // cat chain/tendermint/src/runtime/generated.rs | grep IndexForAscTypeId::Tendermint | grep -Eo "Tendermint[a-zA-Z0-9]+" | awk '{for(x=1;x<=NF;x++)sub(/$/,"="++i+89",")}1' | sed 's/=/ = /' // ``` // - // The `87` literal at the end in the `awk` should be replaced with the last element + // The `89` literal at the end in the `awk` should be replaced with the last element // value in the list above. - TendermintArrayEventTx = 88, - TendermintArrayCommitSig = 89, - TendermintArrayBytes = 90, - TendermintArrayEventAttribute = 91, - TendermintArrayValidator = 92, - TendermintArrayEvidence = 93, - TendermintArrayEvent = 94, - TendermintArrayValidatorUpdate = 95, - TendermintBlockIDFlagEnum = 96, - TendermintSignedMsgTypeEnum = 97, - TendermintEventData = 98, - TendermintEventList = 99, - TendermintBlock = 100, - TendermintBlockID = 101, - TendermintBlockParams = 102, - TendermintCommit = 103, - TendermintCommitSig = 104, - TendermintConsensus = 105, - TendermintConsensusParams = 106, - TendermintData = 107, - TendermintDuration = 108, - TendermintDuplicateVoteEvidence = 109, - TendermintEvent = 110, - TendermintEventAttribute = 111, - TendermintEventBlock = 112, - TendermintEventTx = 113, - TendermintEventValidatorSetUpdates = 114, - TendermintEventVote = 115, - TendermintEvidence = 116, - TendermintEvidenceList = 117, - TendermintEvidenceParams = 118, - TendermintHeader = 119, - TendermintLightBlock = 120, - TendermintLightClientAttackEvidence = 121, - TendermintPublicKey = 122, - TendermintPartSetHeader = 123, - TendermintResponseBeginBlock = 124, - TendermintResponseEndBlock = 125, - TendermintResponseDeliverTx = 126, - TendermintSignedHeader = 127, - TendermintTimestamp = 128, - TendermintTxResult = 129, - TendermintValidator = 130, - TendermintValidatorParams = 131, - TendermintValidatorSet = 132, - TendermintValidatorUpdate = 133, - TendermintVersionParams = 134, + TendermintArrayEventTx = 90, + TendermintArrayCommitSig = 91, + TendermintArrayBytes = 92, + TendermintArrayEventAttribute = 93, + TendermintArrayValidator = 94, + TendermintArrayEvidence = 95, + TendermintArrayEvent = 96, + TendermintArrayValidatorUpdate = 97, + TendermintBlockIDFlagEnum = 98, + TendermintSignedMsgTypeEnum = 99, + TendermintEventData = 100, + TendermintEventList = 101, + TendermintBlock = 102, + TendermintBlockID = 103, + TendermintBlockParams = 104, + TendermintCommit = 105, + TendermintCommitSig = 106, + TendermintConsensus = 107, + TendermintConsensusParams = 108, + TendermintData = 109, + TendermintDuration = 110, + TendermintDuplicateVoteEvidence = 111, + TendermintEvent = 112, + TendermintEventAttribute = 113, + TendermintEventBlock = 114, + TendermintEventTx = 115, + TendermintEventValidatorSetUpdates = 116, + TendermintEventVote = 117, + TendermintEvidence = 118, + TendermintEvidenceList = 119, + TendermintEvidenceParams = 120, + TendermintHeader = 121, + TendermintLightBlock = 122, + TendermintLightClientAttackEvidence = 123, + TendermintPublicKey = 124, + TendermintPartSetHeader = 125, + TendermintResponseBeginBlock = 126, + TendermintResponseEndBlock = 127, + TendermintResponseDeliverTx = 128, + TendermintSignedHeader = 129, + TendermintTimestamp = 130, + TendermintTxResult = 131, + TendermintValidator = 132, + TendermintValidatorParams = 133, + TendermintValidatorSet = 134, + TendermintValidatorUpdate = 135, + TendermintVersionParams = 136, } impl ToAscObj for IndexForAscTypeId { From 6e1e0112bd24aa23b3bae65c467328e1df610531 Mon Sep 17 00:00:00 2001 From: tilacog Date: Wed, 2 Mar 2022 21:08:38 -0300 Subject: [PATCH 06/33] chain/ethereum: Implement ToAscObj for (EthereumEvent, Receipt) --- chain/ethereum/src/runtime/abi.rs | 35 +++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/chain/ethereum/src/runtime/abi.rs b/chain/ethereum/src/runtime/abi.rs index e2f5e3439c3..89f3b04a40c 100644 --- a/chain/ethereum/src/runtime/abi.rs +++ b/chain/ethereum/src/runtime/abi.rs @@ -587,6 +587,41 @@ where } } +impl ToAscObj> for (EthereumEventData, TransactionReceipt) +where + T: AscType + AscIndexId, + B: AscType + AscIndexId, + EthereumTransactionData: ToAscObj, + EthereumBlockData: ToAscObj, +{ + fn to_asc_obj( + &self, + heap: &mut H, + gas: &GasCounter, + ) -> Result, DeterministicHostError> { + let (event_data, receipt) = self; + let AscEthereumEvent { + address, + log_index, + transaction_log_index, + log_type, + block, + transaction, + params, + } = event_data.to_asc_obj(heap, gas)?; + Ok(AscEthereumEventWithReceipt { + address, + log_index, + transaction_log_index, + log_type, + block, + transaction, + params, + receipt: asc_new(heap, receipt, gas)?, + }) + } +} + impl ToAscObj for Log { fn to_asc_obj( &self, From d2df846ba416b1afc0c7448630c963a61afd029f Mon Sep 17 00:00:00 2001 From: tilacog Date: Fri, 4 Mar 2022 17:28:19 -0300 Subject: [PATCH 07/33] chain/ethereum: Optional receipts in `EthereumTrigger::Log` variant Refactors the `blocks_with_triggers` function to also collect transaction receipts for Log triggers. --- chain/ethereum/src/data_source.rs | 5 +- chain/ethereum/src/ethereum_adapter.rs | 222 +++++++++++++++++-------- chain/ethereum/src/trigger.rs | 36 ++-- store/postgres/src/chain_store.rs | 2 +- 4 files changed, 180 insertions(+), 85 deletions(-) diff --git a/chain/ethereum/src/data_source.rs b/chain/ethereum/src/data_source.rs index 2bb1b611736..b0ec12181c5 100644 --- a/chain/ethereum/src/data_source.rs +++ b/chain/ethereum/src/data_source.rs @@ -433,7 +433,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, @@ -471,7 +471,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 @@ -572,6 +572,7 @@ impl DataSource { transaction: Arc::new(transaction), log: log.cheap_clone(), params, + receipt: receipt.clone(), }, event_handler.handler, logging_extras, diff --git a/chain/ethereum/src/ethereum_adapter.rs b/chain/ethereum/src/ethereum_adapter.rs index 33e03377aff..3d43dda25bb 100644 --- a/chain/ethereum/src/ethereum_adapter.rs +++ b/chain/ethereum/src/ethereum_adapter.rs @@ -1,5 +1,6 @@ use futures::future; use futures::prelude::*; +use futures03::{future::BoxFuture, stream::FuturesUnordered}; use graph::blockchain::BlockHash; use graph::blockchain::ChainIdentifier; use graph::components::transaction_receipt::LightTransactionReceipt; @@ -10,7 +11,7 @@ use graph::prelude::tokio::try_join; use graph::{ blockchain::{block_stream::BlockWithTriggers, BlockPtr, IngestorError}, prelude::{ - anyhow::{self, anyhow, bail}, + anyhow::{self, anyhow, bail, ensure}, async_trait, debug, error, ethabi, futures03::{self, compat::Future01CompatExt, FutureExt, StreamExt, TryStreamExt}, hex, info, retry, serde_json as json, stream, tiny_keccak, trace, warn, @@ -1284,87 +1285,83 @@ pub(crate) async fn blocks_with_triggers( let eth = adapter.clone(); let call_filter = EthereumCallFilter::from(&filter.block); - let mut trigger_futs: futures::stream::FuturesUnordered< - Box, Error = Error> + Send>, - > = futures::stream::FuturesUnordered::new(); + // Scan the block range to find relevant triggers + let trigger_futs: FuturesUnordered, anyhow::Error>>> = + FuturesUnordered::new(); - // Scan the block range from triggers to find relevant blocks + // Scan for Logs if !filter.log.is_empty() { - trigger_futs.push(Box::new( - eth.logs_in_block_range( - &logger, - subgraph_metrics.clone(), - from, - to, - filter.log.clone(), - ) - .map_ok(|logs: Vec| { - logs.into_iter() - .map(Arc::new) - .map(EthereumTrigger::Log) - .collect() - }) - .compat(), - )) + let logs_future = get_logs_and_transactions( + eth.clone(), + &logger, + subgraph_metrics.clone(), + from, + to, + filter.log.clone(), + ) + .boxed(); + trigger_futs.push(logs_future) } - + // Scan for Calls if !filter.call.is_empty() { - trigger_futs.push(Box::new( - eth.calls_in_block_range(&logger, subgraph_metrics.clone(), from, to, &filter.call) - .map(Arc::new) - .map(EthereumTrigger::Call) - .collect(), - )); + let calls_future = eth + .calls_in_block_range(&logger, subgraph_metrics.clone(), from, to, &filter.call) + .map(Arc::new) + .map(EthereumTrigger::Call) + .collect() + .compat() + .boxed(); + trigger_futs.push(calls_future) } + // Scan for Blocks if filter.block.trigger_every_block { - trigger_futs.push(Box::new( - adapter - .block_range_to_ptrs(logger.clone(), from, to) - .map(move |ptrs| { - ptrs.into_iter() - .map(|ptr| EthereumTrigger::Block(ptr, EthereumBlockTriggerType::Every)) - .collect() - }), - )) + let block_future = adapter + .block_range_to_ptrs(logger.clone(), from, to) + .map(move |ptrs| { + ptrs.into_iter() + .map(|ptr| EthereumTrigger::Block(ptr, EthereumBlockTriggerType::Every)) + .collect() + }) + .compat() + .boxed(); + trigger_futs.push(block_future) } else if !filter.block.contract_addresses.is_empty() { // To determine which blocks include a call to addresses // in the block filter, transform the `block_filter` into // a `call_filter` and run `blocks_with_calls` - trigger_futs.push(Box::new( - eth.calls_in_block_range(&logger, subgraph_metrics.clone(), from, to, &call_filter) - .map(|call| { - EthereumTrigger::Block( - BlockPtr::from(&call), - EthereumBlockTriggerType::WithCallTo(call.to), - ) - }) - .collect(), - )); + let block_future = eth + .calls_in_block_range(&logger, subgraph_metrics.clone(), from, to, &call_filter) + .map(|call| { + EthereumTrigger::Block( + BlockPtr::from(&call), + EthereumBlockTriggerType::WithCallTo(call.to), + ) + }) + .collect() + .compat() + .boxed(); + trigger_futs.push(block_future) } - let logger1 = logger.cheap_clone(); - let logger2 = logger.cheap_clone(); - let eth_clone = eth.cheap_clone(); - let (triggers, to_hash) = trigger_futs - .concat2() - .join( - adapter - .clone() - .block_hash_by_block_number(&logger, to) - .then(move |to_hash| match to_hash { - Ok(n) => n.ok_or_else(|| { - warn!(logger2, - "Ethereum endpoint is behind"; - "url" => eth_clone.url_hostname() - ); - anyhow!("Block {} not found in the chain", to) - }), - Err(e) => Err(e), - }), - ) + // join on triger futures + let triggers: Vec = trigger_futs.try_concat().await?; + + // get hash for "to" block + let to_hash = match adapter + .block_hash_by_block_number(&logger, to) .compat() - .await?; + .await? + { + Some(hash) => hash, + None => { + warn!(logger, + "Ethereum endpoint is behind"; + "url" => eth.url_hostname() + ); + bail!("Block {} not found in the chain", to) + } + }; let mut block_hashes: HashSet = triggers.iter().map(EthereumTrigger::block_hash).collect(); @@ -1381,7 +1378,7 @@ pub(crate) async fn blocks_with_triggers( triggers_by_block.entry(to).or_insert(Vec::new()); let blocks = adapter - .load_blocks(logger1, chain_store.clone(), block_hashes) + .load_blocks(logger.cheap_clone(), chain_store.clone(), block_hashes) .and_then( move |block| match triggers_by_block.remove(&(block.number() as BlockNumber)) { Some(triggers) => Ok(BlockWithTriggers::new( @@ -1399,7 +1396,6 @@ pub(crate) async fn blocks_with_triggers( .await?; // Filter out call triggers that come from unsuccessful transactions - let mut blocks = if unified_api_version .equal_or_greater_than(&graph::data::subgraph::API_VERSION_0_0_5) { @@ -1492,7 +1488,7 @@ pub(crate) fn parse_log_triggers( .logs .iter() .filter(move |log| log_filter.matches(log)) - .map(move |log| EthereumTrigger::Log(Arc::new(log.clone()))) + .map(move |log| EthereumTrigger::Log(Arc::new(log.clone()), Some(receipt.clone()))) }) .collect() } @@ -1829,3 +1825,87 @@ fn resolve_transaction_receipt( } } } + +/// Retrieves logs and the associated transact receipts, if required by the [`EthereumLogFilter`]. +pub(super) async fn get_logs_and_transactions( + adapter: Arc, + logger: &Logger, + subgraph_metrics: Arc, + from: BlockNumber, + to: BlockNumber, + log_filter: EthereumLogFilter, +) -> Result, anyhow::Error> { + // obtain logs externally + let logs = adapter + .logs_in_block_range(logger, subgraph_metrics, from, to, log_filter) + .await?; + + todo!( + "transaction receipts should be conditionally collected considering the API Version and the manifest. \ + we should early return here with only the logs and without attempting to fetch the receipts." + ); + + // not all logs have transaction hashes + let transaction_hashes: Vec<_> = logs.iter().filter_map(|log| log.transaction_hash).collect(); + + // obtain receipts externally + let transaction_receipts_by_hash = + get_transaction_receipts_for_transaction_hashes(&adapter, &transaction_hashes).await?; + + // associate each log with its receipt, when possible + let mut log_and_receipt_pairs = Vec::new(); + for log in logs.into_iter() { + let optional_receipt = log + .transaction_hash + .and_then(|txn| transaction_receipts_by_hash.get(&txn).cloned()); + let value = EthereumTrigger::Log(Arc::new(log), optional_receipt); + log_and_receipt_pairs.push(value); + } + + Ok(log_and_receipt_pairs) +} + +pub(super) async fn get_transaction_receipts_for_transaction_hashes( + adapter: &EthereumAdapter, + transaction_hashes: &[H256], +) -> Result, anyhow::Error> { + use std::collections::hash_map::Entry::Vacant; + + let mut receipts_by_hash: HashMap = HashMap::new(); + + // return early if input set is empty + if transaction_hashes.is_empty() { + return Ok(receipts_by_hash); + } + + // Keep record of all unique transact hashes that we'll request receipts for. + let mut unique_hashes: HashSet<&H256> = transaction_hashes.iter().collect(); + + // Request transact receipts concurrently + let receipt_futures = FuturesUnordered::new(); + for &hash in &unique_hashes { + let receipt_future = fetch_receipt_from_ethereum_client(adapter, hash); + receipt_futures.push(receipt_future) + } + let receipts: Vec<_> = receipt_futures.try_collect().await?; + + // build a map between transaction hashes and their receipts + for receipt in receipts.into_iter() { + if !unique_hashes.remove(&receipt.transaction_hash) { + bail!("Received a receipt for a different transaction hash") + } + if let Vacant(entry) = receipts_by_hash.entry(receipt.transaction_hash.clone()) { + entry.insert(receipt); + } else { + bail!("Received a duplicate transaction receipt") + } + } + + // confidence check: all unique hashes should have been used + ensure!( + unique_hashes.is_empty(), + "Didn't receive all necessary transaction receipts" + ); + + Ok(receipts_by_hash) +} diff --git a/chain/ethereum/src/trigger.rs b/chain/ethereum/src/trigger.rs index 0414b920dde..7427121df81 100644 --- a/chain/ethereum/src/trigger.rs +++ b/chain/ethereum/src/trigger.rs @@ -11,6 +11,7 @@ use graph::prelude::ethabi::LogParam; use graph::prelude::web3::types::Block; use graph::prelude::web3::types::Log; use graph::prelude::web3::types::Transaction; +use graph::prelude::web3::types::TransactionReceipt; use graph::prelude::BlockNumber; use graph::prelude::BlockPtr; use graph::prelude::{CheapClone, EthereumCall}; @@ -42,6 +43,7 @@ pub enum MappingTrigger { transaction: Arc, log: Arc, params: Vec, + receipt: Option, }, Call { block: Arc, @@ -80,6 +82,7 @@ impl std::fmt::Debug for MappingTrigger { transaction, log, params, + receipt: _, } => MappingTriggerWithoutBlock::Log { _transaction: transaction.cheap_clone(), _log: log.cheap_clone(), @@ -116,7 +119,14 @@ impl blockchain::MappingTrigger for MappingTrigger { transaction, log, params, + receipt, } => { + // TODO: + // match receipt { + // Some(_) => todo!(), + // None => todo!(), + // } + let ethereum_event_data = EthereumEventData { block: EthereumBlockData::from(block.as_ref()), transaction: EthereumTransactionData::from(transaction.deref()), @@ -199,7 +209,7 @@ impl blockchain::MappingTrigger for MappingTrigger { pub enum EthereumTrigger { Block(BlockPtr, EthereumBlockTriggerType), Call(Arc), - Log(Arc), + Log(Arc, Option), } impl PartialEq for EthereumTrigger { @@ -211,8 +221,10 @@ impl PartialEq for EthereumTrigger { (Self::Call(a), Self::Call(b)) => a == b, - (Self::Log(a), Self::Log(b)) => { - a.transaction_hash == b.transaction_hash && a.log_index == b.log_index + (Self::Log(a, a_receipt), Self::Log(b, b_receipt)) => { + a.transaction_hash == b.transaction_hash + && a.log_index == b.log_index + && a_receipt == b_receipt } _ => false, @@ -233,7 +245,9 @@ impl EthereumTrigger { match self { EthereumTrigger::Block(block_ptr, _) => block_ptr.number, EthereumTrigger::Call(call) => call.block_number, - EthereumTrigger::Log(log) => i32::try_from(log.block_number.unwrap().as_u64()).unwrap(), + EthereumTrigger::Log(log, _) => { + i32::try_from(log.block_number.unwrap().as_u64()).unwrap() + } } } @@ -241,7 +255,7 @@ impl EthereumTrigger { match self { EthereumTrigger::Block(block_ptr, _) => block_ptr.hash_as_h256(), EthereumTrigger::Call(call) => call.block_hash, - EthereumTrigger::Log(log) => log.block_hash.unwrap(), + EthereumTrigger::Log(log, _) => log.block_hash.unwrap(), } } } @@ -260,24 +274,24 @@ impl Ord for EthereumTrigger { (Self::Call(a), Self::Call(b)) => a.transaction_index.cmp(&b.transaction_index), // Events are ordered by their log index - (Self::Log(a), Self::Log(b)) => a.log_index.cmp(&b.log_index), + (Self::Log(a, _), Self::Log(b, _)) => a.log_index.cmp(&b.log_index), // Calls vs. events are logged by their tx index; // if they are from the same transaction, events come first - (Self::Call(a), Self::Log(b)) + (Self::Call(a), Self::Log(b, _)) if a.transaction_index == b.transaction_index.unwrap().as_u64() => { Ordering::Greater } - (Self::Log(a), Self::Call(b)) + (Self::Log(a, _), Self::Call(b)) if a.transaction_index.unwrap().as_u64() == b.transaction_index => { Ordering::Less } - (Self::Call(a), Self::Log(b)) => a + (Self::Call(a), Self::Log(b, _)) => a .transaction_index .cmp(&b.transaction_index.unwrap().as_u64()), - (Self::Log(a), Self::Call(b)) => a + (Self::Log(a, _), Self::Call(b)) => a .transaction_index .unwrap() .as_u64() @@ -295,7 +309,7 @@ impl PartialOrd for EthereumTrigger { impl TriggerData for EthereumTrigger { fn error_context(&self) -> std::string::String { let transaction_id = match self { - EthereumTrigger::Log(log) => log.transaction_hash, + EthereumTrigger::Log(log, _) => log.transaction_hash, EthereumTrigger::Call(call) => call.transaction_hash, EthereumTrigger::Block(..) => None, }; diff --git a/store/postgres/src/chain_store.rs b/store/postgres/src/chain_store.rs index 0a120d8dee2..e6a09f2efcb 100644 --- a/store/postgres/src/chain_store.rs +++ b/store/postgres/src/chain_store.rs @@ -1153,7 +1153,7 @@ mod data { .unwrap(); } - /// Queries the database for all the transaction receipts in a given block range. + /// Queries the database for all the transaction receipts in a given block. pub(crate) fn find_transaction_receipts_in_block( &self, conn: &PgConnection, From c34c52c5f61587507d35e990623a4f10e47270d5 Mon Sep 17 00:00:00 2001 From: tilacog Date: Tue, 15 Mar 2022 18:05:17 -0300 Subject: [PATCH 08/33] chain/ethereum: Use booleans as edges for LogFilters inner graph --- chain/ethereum/src/adapter.rs | 28 +++++++++++++++------------- chain/ethereum/src/data_source.rs | 2 ++ 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/chain/ethereum/src/adapter.rs b/chain/ethereum/src/adapter.rs index 0400f2eea29..9ca2f7ba287 100644 --- a/chain/ethereum/src/adapter.rs +++ b/chain/ethereum/src/adapter.rs @@ -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, + /// Edges are of `bool` type and indicates when a trigger requires a transaction receipt. + contracts_and_events_graph: GraphMap, - // Event sigs with no associated address, matching on all addresses. - wildcard_events: HashSet, + /// 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, } impl Into> for EthereumLogFilter { @@ -248,10 +250,8 @@ 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) } } } @@ -259,17 +259,19 @@ impl EthereumLogFilter { pub fn from_data_sources<'a>(iter: impl IntoIterator) -> 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); } } } @@ -298,8 +300,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); } @@ -322,7 +324,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 diff --git a/chain/ethereum/src/data_source.rs b/chain/ethereum/src/data_source.rs index b0ec12181c5..6353572ad4c 100644 --- a/chain/ethereum/src/data_source.rs +++ b/chain/ethereum/src/data_source.rs @@ -997,6 +997,8 @@ pub struct MappingEventHandler { pub event: String, pub topic0: Option, pub handler: String, + #[serde(default)] + pub receipt: bool, } impl MappingEventHandler { From d641db0c14cbaa4d181e42020bcc7541c548e053 Mon Sep 17 00:00:00 2001 From: tilacog Date: Tue, 15 Mar 2022 20:23:38 -0300 Subject: [PATCH 09/33] chain/ethereum: Conditionally collect receipts based on manifest --- chain/ethereum/src/adapter.rs | 18 ++++++++++++++++++ chain/ethereum/src/ethereum_adapter.rs | 13 +++++++------ 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/chain/ethereum/src/adapter.rs b/chain/ethereum/src/adapter.rs index 9ca2f7ba287..025b5ab2e04 100644 --- a/chain/ethereum/src/adapter.rs +++ b/chain/ethereum/src/adapter.rs @@ -256,6 +256,24 @@ impl EthereumLogFilter { } } + /// Similar to [`matches`], checks if a transaction receipt is required for this log filter. + pub fn requires_transaction_receipt(&self, log: &Log) -> bool { + // First topic should be event sig + if let Some(sig) = log.topics.first() { + let contract = LogFilterNode::Contract(log.address); + let event = LogFilterNode::Event(*sig); + matches!(self.wildcard_events.get(sig), Some(true)) + || 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) -> Self { let mut this = EthereumLogFilter::default(); for ds in iter { diff --git a/chain/ethereum/src/ethereum_adapter.rs b/chain/ethereum/src/ethereum_adapter.rs index 3d43dda25bb..2f179d201f4 100644 --- a/chain/ethereum/src/ethereum_adapter.rs +++ b/chain/ethereum/src/ethereum_adapter.rs @@ -1840,13 +1840,14 @@ pub(super) async fn get_logs_and_transactions( .logs_in_block_range(logger, subgraph_metrics, from, to, log_filter) .await?; - todo!( - "transaction receipts should be conditionally collected considering the API Version and the manifest. \ - we should early return here with only the logs and without attempting to fetch the receipts." - ); + todo!("transaction receipts should be conditionally collected considering the API Version."); - // not all logs have transaction hashes - let transaction_hashes: Vec<_> = logs.iter().filter_map(|log| log.transaction_hash).collect(); + // not all logs have associated transaction hashes, nor do all triggers require them. + let transaction_hashes: Vec<_> = logs + .iter() + .filter(|log| log_filter.requires_transaction_receipt(log)) + .filter_map(|log| log.transaction_hash) + .collect(); // obtain receipts externally let transaction_receipts_by_hash = From afdc18d321b3e28de5539e0ed04633898102de3d Mon Sep 17 00:00:00 2001 From: tilacog Date: Tue, 15 Mar 2022 21:25:17 -0300 Subject: [PATCH 10/33] chain/ethereum: Conditionally collect receipts based on api version --- chain/ethereum/src/ethereum_adapter.rs | 9 ++++++--- graph/src/data/subgraph/api_version.rs | 3 +++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/chain/ethereum/src/ethereum_adapter.rs b/chain/ethereum/src/ethereum_adapter.rs index 2f179d201f4..61efcedf09f 100644 --- a/chain/ethereum/src/ethereum_adapter.rs +++ b/chain/ethereum/src/ethereum_adapter.rs @@ -5,6 +5,7 @@ use graph::blockchain::BlockHash; use graph::blockchain::ChainIdentifier; use graph::components::transaction_receipt::LightTransactionReceipt; use graph::data::subgraph::UnifiedMappingApiVersion; +use graph::data::subgraph::API_VERSION_0_0_7; use graph::prelude::ethabi::ParamType; use graph::prelude::ethabi::Token; use graph::prelude::tokio::try_join; @@ -1298,6 +1299,7 @@ pub(crate) async fn blocks_with_triggers( from, to, filter.log.clone(), + &unified_api_version, ) .boxed(); trigger_futs.push(logs_future) @@ -1834,17 +1836,18 @@ pub(super) async fn get_logs_and_transactions( from: BlockNumber, to: BlockNumber, log_filter: EthereumLogFilter, + unified_api_version: &UnifiedMappingApiVersion, ) -> Result, anyhow::Error> { // obtain logs externally let logs = adapter - .logs_in_block_range(logger, subgraph_metrics, from, to, log_filter) + .logs_in_block_range(logger, subgraph_metrics, from, to, log_filter.clone()) .await?; - todo!("transaction receipts should be conditionally collected considering the API Version."); - // not all logs have associated transaction hashes, nor do all triggers require them. + // we also restrict receipts retrieval for some api versions. let transaction_hashes: Vec<_> = logs .iter() + .filter(|_| unified_api_version.equal_or_greater_than(&API_VERSION_0_0_7)) .filter(|log| log_filter.requires_transaction_receipt(log)) .filter_map(|log| log.transaction_hash) .collect(); diff --git a/graph/src/data/subgraph/api_version.rs b/graph/src/data/subgraph/api_version.rs index 3a393600d8f..083fea8201a 100644 --- a/graph/src/data/subgraph/api_version.rs +++ b/graph/src/data/subgraph/api_version.rs @@ -9,6 +9,9 @@ use super::SubgraphManifestValidationError; /// different API versions if at least one of them is equal to or higher than `0.0.5`. pub const API_VERSION_0_0_5: Version = Version::new(0, 0, 5); +/// Enables event handlers to require transaction receipts in the runtime. +pub const API_VERSION_0_0_7: Version = Version::new(0, 0, 7); + /// Before this check was introduced, there were already subgraphs in the wild with spec version /// 0.0.3, due to confusion with the api version. To avoid breaking those, we accept 0.0.3 though it /// doesn't exist. From 85304d57fd0d7434695908697490ce167e497eba Mon Sep 17 00:00:00 2001 From: tilacog Date: Tue, 15 Mar 2022 21:25:41 -0300 Subject: [PATCH 11/33] chain/ethereum: Minor refactors and variable renaming --- chain/ethereum/src/ethereum_adapter.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/chain/ethereum/src/ethereum_adapter.rs b/chain/ethereum/src/ethereum_adapter.rs index 61efcedf09f..75ac1719a9a 100644 --- a/chain/ethereum/src/ethereum_adapter.rs +++ b/chain/ethereum/src/ethereum_adapter.rs @@ -5,6 +5,7 @@ use graph::blockchain::BlockHash; use graph::blockchain::ChainIdentifier; use graph::components::transaction_receipt::LightTransactionReceipt; use graph::data::subgraph::UnifiedMappingApiVersion; +use graph::data::subgraph::API_VERSION_0_0_5; use graph::data::subgraph::API_VERSION_0_0_7; use graph::prelude::ethabi::ParamType; use graph::prelude::ethabi::Token; @@ -1398,9 +1399,9 @@ pub(crate) async fn blocks_with_triggers( .await?; // Filter out call triggers that come from unsuccessful transactions - let mut blocks = if unified_api_version - .equal_or_greater_than(&graph::data::subgraph::API_VERSION_0_0_5) - { + let mut blocks = if unified_api_version.equal_or_greater_than(&API_VERSION_0_0_5) { + let section = + stopwatch_metrics.start_section("filter_call_triggers_from_unsuccessful_transactions"); let futures = blocks.into_iter().map(|block| { filter_call_triggers_from_unsuccessful_transactions(block, ð, &chain_store, &logger) }); @@ -1857,16 +1858,16 @@ pub(super) async fn get_logs_and_transactions( get_transaction_receipts_for_transaction_hashes(&adapter, &transaction_hashes).await?; // associate each log with its receipt, when possible - let mut log_and_receipt_pairs = Vec::new(); + let mut log_triggers = Vec::new(); for log in logs.into_iter() { let optional_receipt = log .transaction_hash .and_then(|txn| transaction_receipts_by_hash.get(&txn).cloned()); let value = EthereumTrigger::Log(Arc::new(log), optional_receipt); - log_and_receipt_pairs.push(value); + log_triggers.push(value); } - Ok(log_and_receipt_pairs) + Ok(log_triggers) } pub(super) async fn get_transaction_receipts_for_transaction_hashes( From 226a20d40fc19692d92deba2163e28b9f7526ec8 Mon Sep 17 00:00:00 2001 From: tilacog Date: Wed, 16 Mar 2022 18:37:25 -0300 Subject: [PATCH 12/33] chain/ethereum, graph: conditionally build the correct `AscType` --- chain/ethereum/src/trigger.rs | 61 +++++++++++++++----------- graph/src/data/subgraph/api_version.rs | 4 ++ 2 files changed, 40 insertions(+), 25 deletions(-) diff --git a/chain/ethereum/src/trigger.rs b/chain/ethereum/src/trigger.rs index 7427121df81..f31392b783a 100644 --- a/chain/ethereum/src/trigger.rs +++ b/chain/ethereum/src/trigger.rs @@ -1,5 +1,8 @@ use graph::blockchain; use graph::blockchain::TriggerData; +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::prelude::ethabi::ethereum_types::H160; use graph::prelude::ethabi::ethereum_types::H256; use graph::prelude::ethabi::ethereum_types::U128; @@ -30,6 +33,7 @@ use crate::runtime::abi::AscEthereumBlock_0_0_6; use crate::runtime::abi::AscEthereumCall; use crate::runtime::abi::AscEthereumCall_0_0_3; use crate::runtime::abi::AscEthereumEvent; +use crate::runtime::abi::AscEthereumEventWithReceipt; use crate::runtime::abi::AscEthereumTransaction_0_0_1; use crate::runtime::abi::AscEthereumTransaction_0_0_2; use crate::runtime::abi::AscEthereumTransaction_0_0_6; @@ -121,12 +125,7 @@ impl blockchain::MappingTrigger for MappingTrigger { params, receipt, } => { - // TODO: - // match receipt { - // Some(_) => todo!(), - // None => todo!(), - // } - + let api_version = heap.api_version(); let ethereum_event_data = EthereumEventData { block: EthereumBlockData::from(block.as_ref()), transaction: EthereumTransactionData::from(transaction.deref()), @@ -136,28 +135,40 @@ impl blockchain::MappingTrigger for MappingTrigger { log_type: log.log_type.clone(), params, }; - let api_version = heap.api_version(); - if api_version >= Version::new(0, 0, 6) { - asc_new::< - AscEthereumEvent, + match (api_version, receipt) { + (api_version, Some(receipt)) if api_version >= API_VERSION_0_0_7 => { + asc_new::< + AscEthereumEventWithReceipt< + AscEthereumTransaction_0_0_6, + AscEthereumBlock_0_0_6, + >, + _, + _, + >(heap, &(ethereum_event_data, receipt), gas)? + .erase() + } + (api_version, _) if api_version >= API_VERSION_0_0_6 => { + asc_new::< + AscEthereumEvent, + _, + _, + >(heap, ðereum_event_data, gas)? + .erase() + } + (api_version, _) if api_version >= API_VERSION_0_0_2 => { + asc_new::< + AscEthereumEvent, + _, + _, + >(heap, ðereum_event_data, gas)? + .erase() + } + _ => asc_new::< + AscEthereumEvent, _, _, >(heap, ðereum_event_data, gas)? - .erase() - } else if api_version >= Version::new(0, 0, 2) { - asc_new::, _, _>( - heap, - ðereum_event_data, - gas - )? - .erase() - } else { - asc_new::, _, _>( - heap, - ðereum_event_data, - gas - )? - .erase() + .erase(), } } MappingTrigger::Call { diff --git a/graph/src/data/subgraph/api_version.rs b/graph/src/data/subgraph/api_version.rs index 083fea8201a..ec91b2b468e 100644 --- a/graph/src/data/subgraph/api_version.rs +++ b/graph/src/data/subgraph/api_version.rs @@ -5,10 +5,14 @@ use thiserror::Error; use super::SubgraphManifestValidationError; +pub const API_VERSION_0_0_2: Version = Version::new(0, 0, 2); + /// This version adds a new subgraph validation step that rejects manifests whose mappings have /// different API versions if at least one of them is equal to or higher than `0.0.5`. pub const API_VERSION_0_0_5: Version = Version::new(0, 0, 5); +pub const API_VERSION_0_0_6: Version = Version::new(0, 0, 6); + /// Enables event handlers to require transaction receipts in the runtime. pub const API_VERSION_0_0_7: Version = Version::new(0, 0, 7); From 298623ae0f8850be9fa7148ac3f60d915e6f2745 Mon Sep 17 00:00:00 2001 From: tilacog Date: Fri, 18 Mar 2022 15:23:13 -0300 Subject: [PATCH 13/33] chain/ethereum: Change visibility and document helper functions --- chain/ethereum/src/ethereum_adapter.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/chain/ethereum/src/ethereum_adapter.rs b/chain/ethereum/src/ethereum_adapter.rs index 75ac1719a9a..429b1d27047 100644 --- a/chain/ethereum/src/ethereum_adapter.rs +++ b/chain/ethereum/src/ethereum_adapter.rs @@ -1830,7 +1830,7 @@ fn resolve_transaction_receipt( } /// Retrieves logs and the associated transact receipts, if required by the [`EthereumLogFilter`]. -pub(super) async fn get_logs_and_transactions( +async fn get_logs_and_transactions( adapter: Arc, logger: &Logger, subgraph_metrics: Arc, @@ -1870,7 +1870,9 @@ pub(super) async fn get_logs_and_transactions( Ok(log_triggers) } -pub(super) async fn get_transaction_receipts_for_transaction_hashes( +/// Tries to retrive all transaction receipts for a set of transaction hashes. +/// Deduplicates input before making requests and returns the results mapped by hash. +async fn get_transaction_receipts_for_transaction_hashes( adapter: &EthereumAdapter, transaction_hashes: &[H256], ) -> Result, anyhow::Error> { From 86cc81f9189466296331696a4b7fc4351a2a70a7 Mon Sep 17 00:00:00 2001 From: tilacog Date: Fri, 18 Mar 2022 21:34:48 -0300 Subject: [PATCH 14/33] chain/ethereum: Remove dead code --- chain/ethereum/src/ethereum_adapter.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/chain/ethereum/src/ethereum_adapter.rs b/chain/ethereum/src/ethereum_adapter.rs index 429b1d27047..eaf0ac02976 100644 --- a/chain/ethereum/src/ethereum_adapter.rs +++ b/chain/ethereum/src/ethereum_adapter.rs @@ -1400,8 +1400,6 @@ pub(crate) async fn blocks_with_triggers( // Filter out call triggers that come from unsuccessful transactions let mut blocks = if unified_api_version.equal_or_greater_than(&API_VERSION_0_0_5) { - let section = - stopwatch_metrics.start_section("filter_call_triggers_from_unsuccessful_transactions"); let futures = blocks.into_iter().map(|block| { filter_call_triggers_from_unsuccessful_transactions(block, ð, &chain_store, &logger) }); From e9650b70a75eef13a131a8bc5ab4a579327fb32a Mon Sep 17 00:00:00 2001 From: tilacog Date: Fri, 18 Mar 2022 21:43:51 -0300 Subject: [PATCH 15/33] chain/ethereum: Fix missing parameter --- chain/ethereum/src/adapter.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/chain/ethereum/src/adapter.rs b/chain/ethereum/src/adapter.rs index 025b5ab2e04..7b3a59291d3 100644 --- a/chain/ethereum/src/adapter.rs +++ b/chain/ethereum/src/adapter.rs @@ -297,13 +297,13 @@ impl EthereumLogFilter { 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 } From 20ed6a7b5b089ed8569d751cb191441fd33b8b81 Mon Sep 17 00:00:00 2001 From: tilacog Date: Mon, 21 Mar 2022 19:22:07 -0300 Subject: [PATCH 16/33] chain/ethereum: Update tests --- chain/ethereum/src/adapter.rs | 12 ++++++------ chain/ethereum/src/tests.rs | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/chain/ethereum/src/adapter.rs b/chain/ethereum/src/adapter.rs index 7b3a59291d3..1d25043ff67 100644 --- a/chain/ethereum/src/adapter.rs +++ b/chain/ethereum/src/adapter.rs @@ -1010,7 +1010,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![ @@ -1067,17 +1067,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 { @@ -1313,7 +1313,7 @@ fn complete_log_filter() { contracts_and_events_graph.add_edge( LogFilterNode::Contract(contract), LogFilterNode::Event(event), - (), + false, ); } } @@ -1321,7 +1321,7 @@ fn complete_log_filter() { // 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(); diff --git a/chain/ethereum/src/tests.rs b/chain/ethereum/src/tests.rs index ac631121a27..eee594fdbc4 100644 --- a/chain/ethereum/src/tests.rs +++ b/chain/ethereum/src/tests.rs @@ -60,15 +60,15 @@ fn test_trigger_ordering() { // Event with transaction_index 1 and log_index 0; // should be the first element after sorting - let log1 = EthereumTrigger::Log(create_log(1, 0)); + let log1 = EthereumTrigger::Log(create_log(1, 0), None); // Event with transaction_index 1 and log_index 1; // should be the second element after sorting - let log2 = EthereumTrigger::Log(create_log(1, 1)); + let log2 = EthereumTrigger::Log(create_log(1, 1), None); // Event with transaction_index 2 and log_index 5; // should come after call1 and before call2 after sorting - let log3 = EthereumTrigger::Log(create_log(2, 5)); + let log3 = EthereumTrigger::Log(create_log(2, 5), None); let triggers = vec![ // Call triggers; these should be in the order 1, 2, 4, 3 after sorting From c329a03c5120a288c990121cb8c09e0576c2e4b8 Mon Sep 17 00:00:00 2001 From: tilacog Date: Mon, 21 Mar 2022 20:01:38 -0300 Subject: [PATCH 17/33] chain/ethereum: New subgraph manifest validation --- chain/ethereum/src/data_source.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/chain/ethereum/src/data_source.rs b/chain/ethereum/src/data_source.rs index 6353572ad4c..194560c5288 100644 --- a/chain/ethereum/src/data_source.rs +++ b/chain/ethereum/src/data_source.rs @@ -198,6 +198,20 @@ impl blockchain::DataSource for DataSource { errors.push(anyhow!("data source has duplicated block handlers")); } + // Validate that event handlers don't require receipts for API verisions 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 } From 4827a174c5af10e2908bc33fdb8616537e606eba Mon Sep 17 00:00:00 2001 From: tilacog Date: Tue, 29 Mar 2022 15:54:10 -0300 Subject: [PATCH 18/33] graph: Bumps the maximum default for api and spec versions --- graph/src/data/subgraph/api_version.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/graph/src/data/subgraph/api_version.rs b/graph/src/data/subgraph/api_version.rs index ec91b2b468e..b0bfa949d7b 100644 --- a/graph/src/data/subgraph/api_version.rs +++ b/graph/src/data/subgraph/api_version.rs @@ -11,6 +11,7 @@ pub const API_VERSION_0_0_2: Version = Version::new(0, 0, 2); /// different API versions if at least one of them is equal to or higher than `0.0.5`. pub const API_VERSION_0_0_5: Version = Version::new(0, 0, 5); +// Adds two new fields to the Transaction object: nonce and input pub const API_VERSION_0_0_6: Version = Version::new(0, 0, 6); /// Enables event handlers to require transaction receipts in the runtime. @@ -24,6 +25,9 @@ pub const SPEC_VERSION_0_0_3: Version = Version::new(0, 0, 3); /// This version supports subgraph feature management. pub const SPEC_VERSION_0_0_4: Version = Version::new(0, 0, 4); +/// This version supports event handlers having access to transaction receipts. +pub const SPEC_VERSION_0_0_5: Version = Version::new(0, 0, 5); + pub const MIN_SPEC_VERSION: Version = Version::new(0, 0, 2); #[derive(Clone, PartialEq, Debug)] From 6240d5fc404260b8bd0ac628bef70f98c5348da1 Mon Sep 17 00:00:00 2001 From: tilacog Date: Tue, 22 Mar 2022 20:44:51 -0300 Subject: [PATCH 19/33] chain/ethereum: Tests for `requires_transaction_receipt` function --- chain/ethereum/src/adapter.rs | 121 ++++++++++++++++++++++--- chain/ethereum/src/ethereum_adapter.rs | 8 +- 2 files changed, 116 insertions(+), 13 deletions(-) diff --git a/chain/ethereum/src/adapter.rs b/chain/ethereum/src/adapter.rs index 1d25043ff67..34c28535beb 100644 --- a/chain/ethereum/src/adapter.rs +++ b/chain/ethereum/src/adapter.rs @@ -257,18 +257,21 @@ impl EthereumLogFilter { } /// Similar to [`matches`], checks if a transaction receipt is required for this log filter. - pub fn requires_transaction_receipt(&self, log: &Log) -> bool { - // First topic should be event sig - if let Some(sig) = log.topics.first() { - let contract = LogFilterNode::Contract(log.address); - let event = LogFilterNode::Event(*sig); - matches!(self.wildcard_events.get(sig), Some(true)) - || self - .contracts_and_events_graph - .all_edges() - .any(|(s, t, r)| { - *r && (s == contract && t == event) || (t == contract && s == event) - }) + 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 } @@ -1353,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))); +} diff --git a/chain/ethereum/src/ethereum_adapter.rs b/chain/ethereum/src/ethereum_adapter.rs index eaf0ac02976..95e7248b9a2 100644 --- a/chain/ethereum/src/ethereum_adapter.rs +++ b/chain/ethereum/src/ethereum_adapter.rs @@ -1847,7 +1847,13 @@ async fn get_logs_and_transactions( let transaction_hashes: Vec<_> = logs .iter() .filter(|_| unified_api_version.equal_or_greater_than(&API_VERSION_0_0_7)) - .filter(|log| log_filter.requires_transaction_receipt(log)) + .filter(|log| { + if let Some(signature) = log.topics.first() { + log_filter.requires_transaction_receipt(signature, Some(&log.address)) + } else { + false + } + }) .filter_map(|log| log.transaction_hash) .collect(); From a762482dd9ea904607c790cbaba77764c90f7f7e Mon Sep 17 00:00:00 2001 From: tilacog Date: Thu, 24 Mar 2022 20:20:49 -0300 Subject: [PATCH 20/33] chain/ethereum: Use a retry strategy in `get_logs_and_transactions` In this commit we stop calling `fetch_receipts_from_ethereum_client` and use `fetch_transaction_receipts_with_retry` instead, so we can take advantage of both its retry scheme and transaction receipt resolution mechanism. Calling this latest function requires block hashes, so we had to move from a `Vec` to a `Map`. --- chain/ethereum/src/ethereum_adapter.rs | 66 ++++++++++++++++++-------- 1 file changed, 47 insertions(+), 19 deletions(-) diff --git a/chain/ethereum/src/ethereum_adapter.rs b/chain/ethereum/src/ethereum_adapter.rs index 95e7248b9a2..a783dda9ffa 100644 --- a/chain/ethereum/src/ethereum_adapter.rs +++ b/chain/ethereum/src/ethereum_adapter.rs @@ -1844,7 +1844,7 @@ async fn get_logs_and_transactions( // not all logs have associated transaction hashes, nor do all triggers require them. // we also restrict receipts retrieval for some api versions. - let transaction_hashes: Vec<_> = logs + let transaction_hashes_by_block: HashMap> = logs .iter() .filter(|_| unified_api_version.equal_or_greater_than(&API_VERSION_0_0_7)) .filter(|log| { @@ -1854,12 +1854,29 @@ async fn get_logs_and_transactions( false } }) - .filter_map(|log| log.transaction_hash) - .collect(); + .filter_map(|log| { + if let (Some(block), Some(txn)) = (log.block_hash, log.transaction_hash) { + Some((block, txn)) + } else { + // TODO: Should this case be considered an error? + None + } + }) + .fold( + HashMap::>::new(), + |mut acc, (block_hash, txn_hash)| { + acc.entry(block_hash).or_default().insert(txn_hash); + acc + }, + ); // obtain receipts externally - let transaction_receipts_by_hash = - get_transaction_receipts_for_transaction_hashes(&adapter, &transaction_hashes).await?; + let transaction_receipts_by_hash = get_transaction_receipts_for_transaction_hashes( + &adapter, + &transaction_hashes_by_block, + logger.cheap_clone(), + ) + .await?; // associate each log with its receipt, when possible let mut log_triggers = Vec::new(); @@ -1875,34 +1892,45 @@ async fn get_logs_and_transactions( } /// Tries to retrive all transaction receipts for a set of transaction hashes. -/// Deduplicates input before making requests and returns the results mapped by hash. async fn get_transaction_receipts_for_transaction_hashes( adapter: &EthereumAdapter, - transaction_hashes: &[H256], + transaction_hashes_by_block: &HashMap>, + logger: Logger, ) -> Result, anyhow::Error> { use std::collections::hash_map::Entry::Vacant; let mut receipts_by_hash: HashMap = HashMap::new(); - // return early if input set is empty - if transaction_hashes.is_empty() { + // Return early if input set is empty + if transaction_hashes_by_block.is_empty() { return Ok(receipts_by_hash); } - // Keep record of all unique transact hashes that we'll request receipts for. - let mut unique_hashes: HashSet<&H256> = transaction_hashes.iter().collect(); + // Keep a record of all unique transaction hashes for which we'll request receipts. We will + // later use this to check if we have collected the receipts from all required transactions. + let mut unique_transaction_hashes: HashSet<&H256> = HashSet::new(); - // Request transact receipts concurrently + // Request transaction receipts concurrently let receipt_futures = FuturesUnordered::new(); - for &hash in &unique_hashes { - let receipt_future = fetch_receipt_from_ethereum_client(adapter, hash); - receipt_futures.push(receipt_future) + + let web3 = Arc::clone(&adapter.web3); + for (block_hash, transaction_hashes) in transaction_hashes_by_block { + for transaction_hash in transaction_hashes { + unique_transaction_hashes.insert(transaction_hash); + let receipt_future = fetch_transaction_receipt_with_retry( + web3.cheap_clone(), + *transaction_hash, + *block_hash, + logger.cheap_clone(), + ); + receipt_futures.push(receipt_future) + } } let receipts: Vec<_> = receipt_futures.try_collect().await?; - // build a map between transaction hashes and their receipts + // Build a map between transaction hashes and their receipts for receipt in receipts.into_iter() { - if !unique_hashes.remove(&receipt.transaction_hash) { + if !unique_transaction_hashes.remove(&receipt.transaction_hash) { bail!("Received a receipt for a different transaction hash") } if let Vacant(entry) = receipts_by_hash.entry(receipt.transaction_hash.clone()) { @@ -1912,9 +1940,9 @@ async fn get_transaction_receipts_for_transaction_hashes( } } - // confidence check: all unique hashes should have been used + // Confidence check: all unique hashes should have been used ensure!( - unique_hashes.is_empty(), + unique_transaction_hashes.is_empty(), "Didn't receive all necessary transaction receipts" ); From 954ec9ce136dfac507a501a0445ca74a8a100940 Mon Sep 17 00:00:00 2001 From: tilacog Date: Thu, 24 Mar 2022 20:34:46 -0300 Subject: [PATCH 21/33] chain/ethereum: Fix wrong struct name --- chain/ethereum/src/runtime/abi.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chain/ethereum/src/runtime/abi.rs b/chain/ethereum/src/runtime/abi.rs index 89f3b04a40c..0a56096e98f 100644 --- a/chain/ethereum/src/runtime/abi.rs +++ b/chain/ethereum/src/runtime/abi.rs @@ -340,7 +340,7 @@ impl AscIndexId for AscEthereumTransactionReceipt { const INDEX_ASC_TYPE_ID: IndexForAscTypeId = IndexForAscTypeId::TransactionReceipt; } -/// Introduced in API Version 0.0.7, this is the same as [`AscEthereumTransaction`] with an added +/// Introduced in API Version 0.0.7, this is the same as [`AscEthereumEvent`] with an added /// `receipt` field. #[repr(C)] #[derive(AscType)] From a7720ce099aeba27d47ffea910b85da7f12fe2f4 Mon Sep 17 00:00:00 2001 From: tilacog Date: Mon, 28 Mar 2022 18:34:21 -0300 Subject: [PATCH 22/33] chain/ethereum: Fix broken tests Some assertions were supposed to be negated. --- chain/ethereum/src/adapter.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/chain/ethereum/src/adapter.rs b/chain/ethereum/src/adapter.rs index 34c28535beb..057c9fbbe03 100644 --- a/chain/ethereum/src/adapter.rs +++ b/chain/ethereum/src/adapter.rs @@ -1443,9 +1443,9 @@ fn log_filter_require_transacion_receipt_method() { !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(&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))); From c2b1a59fe7a868fe0c84b85942809414872c1746 Mon Sep 17 00:00:00 2001 From: tilacog Date: Mon, 28 Mar 2022 19:53:09 -0300 Subject: [PATCH 23/33] chain/ethereum: Fix typo in comments --- chain/ethereum/src/data_source.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chain/ethereum/src/data_source.rs b/chain/ethereum/src/data_source.rs index 194560c5288..325e1c2b866 100644 --- a/chain/ethereum/src/data_source.rs +++ b/chain/ethereum/src/data_source.rs @@ -198,7 +198,7 @@ impl blockchain::DataSource for DataSource { errors.push(anyhow!("data source has duplicated block handlers")); } - // Validate that event handlers don't require receipts for API verisions lower than 0.0.7 + // 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 { From 0053c188c344943dc9b13aae91a3b78e13e23427 Mon Sep 17 00:00:00 2001 From: tilacog Date: Mon, 28 Mar 2022 20:18:13 -0300 Subject: [PATCH 24/33] chain/ethereum: Renamed `AscEthereumEventWithReceipt` to `AscEthereumEvent_0_0_7` Also changed the implementation of `ToAscObj` to use a `Option` instead of a `Receipt`, since `AscPtr`s can be nullable. --- chain/ethereum/src/runtime/abi.rs | 24 ++++++++------- chain/ethereum/src/trigger.rs | 49 +++++++++++++++---------------- 2 files changed, 37 insertions(+), 36 deletions(-) diff --git a/chain/ethereum/src/runtime/abi.rs b/chain/ethereum/src/runtime/abi.rs index 0a56096e98f..a6dc58d5448 100644 --- a/chain/ethereum/src/runtime/abi.rs +++ b/chain/ethereum/src/runtime/abi.rs @@ -344,7 +344,7 @@ impl AscIndexId for AscEthereumTransactionReceipt { /// `receipt` field. #[repr(C)] #[derive(AscType)] -pub(crate) struct AscEthereumEventWithReceipt +pub(crate) struct AscEthereumEvent_0_0_7 where T: AscType, B: AscType, @@ -359,17 +359,15 @@ where pub receipt: AscPtr, } -impl AscIndexId for AscEthereumEventWithReceipt { +impl AscIndexId for AscEthereumEvent_0_0_7 { const INDEX_ASC_TYPE_ID: IndexForAscTypeId = IndexForAscTypeId::EthereumEvent; } -impl AscIndexId for AscEthereumEventWithReceipt { +impl AscIndexId for AscEthereumEvent_0_0_7 { const INDEX_ASC_TYPE_ID: IndexForAscTypeId = IndexForAscTypeId::EthereumEvent; } -impl AscIndexId - for AscEthereumEventWithReceipt -{ +impl AscIndexId for AscEthereumEvent_0_0_7 { const INDEX_ASC_TYPE_ID: IndexForAscTypeId = IndexForAscTypeId::EthereumEvent; } @@ -587,7 +585,8 @@ where } } -impl ToAscObj> for (EthereumEventData, TransactionReceipt) +impl ToAscObj> + for (EthereumEventData, Option) where T: AscType + AscIndexId, B: AscType + AscIndexId, @@ -598,7 +597,7 @@ where &self, heap: &mut H, gas: &GasCounter, - ) -> Result, DeterministicHostError> { + ) -> Result, DeterministicHostError> { let (event_data, receipt) = self; let AscEthereumEvent { address, @@ -609,7 +608,12 @@ where transaction, params, } = event_data.to_asc_obj(heap, gas)?; - Ok(AscEthereumEventWithReceipt { + let asc_receipt = if let Some(receipt_data) = receipt { + asc_new(heap, receipt_data, gas)? + } else { + AscPtr::null() + }; + Ok(AscEthereumEvent_0_0_7 { address, log_index, transaction_log_index, @@ -617,7 +621,7 @@ where block, transaction, params, - receipt: asc_new(heap, receipt, gas)?, + receipt: asc_receipt, }) } } diff --git a/chain/ethereum/src/trigger.rs b/chain/ethereum/src/trigger.rs index f31392b783a..9c4720045ad 100644 --- a/chain/ethereum/src/trigger.rs +++ b/chain/ethereum/src/trigger.rs @@ -33,7 +33,7 @@ use crate::runtime::abi::AscEthereumBlock_0_0_6; use crate::runtime::abi::AscEthereumCall; use crate::runtime::abi::AscEthereumCall_0_0_3; use crate::runtime::abi::AscEthereumEvent; -use crate::runtime::abi::AscEthereumEventWithReceipt; +use crate::runtime::abi::AscEthereumEvent_0_0_7; use crate::runtime::abi::AscEthereumTransaction_0_0_1; use crate::runtime::abi::AscEthereumTransaction_0_0_2; use crate::runtime::abi::AscEthereumTransaction_0_0_6; @@ -135,40 +135,37 @@ impl blockchain::MappingTrigger for MappingTrigger { log_type: log.log_type.clone(), params, }; - match (api_version, receipt) { - (api_version, Some(receipt)) if api_version >= API_VERSION_0_0_7 => { - asc_new::< - AscEthereumEventWithReceipt< - AscEthereumTransaction_0_0_6, - AscEthereumBlock_0_0_6, - >, - _, - _, - >(heap, &(ethereum_event_data, receipt), gas)? - .erase() - } - (api_version, _) if api_version >= API_VERSION_0_0_6 => { - asc_new::< - AscEthereumEvent, - _, - _, - >(heap, ðereum_event_data, gas)? - .erase() - } - (api_version, _) if api_version >= API_VERSION_0_0_2 => { - asc_new::< + if api_version >= API_VERSION_0_0_7 { + asc_new::< + AscEthereumEvent_0_0_7< + AscEthereumTransaction_0_0_6, + AscEthereumBlock_0_0_6, + >, + _, + _, + >(heap, &(ethereum_event_data, receipt), gas)? + .erase() + } else if api_version >= API_VERSION_0_0_6 { + asc_new::< + AscEthereumEvent, + _, + _, + >(heap, ðereum_event_data, gas)? + .erase() + } else if api_version >= API_VERSION_0_0_2 { + asc_new::< AscEthereumEvent, _, _, >(heap, ðereum_event_data, gas)? .erase() - } - _ => asc_new::< + } else { + asc_new::< AscEthereumEvent, _, _, >(heap, ðereum_event_data, gas)? - .erase(), + .erase() } } MappingTrigger::Call { From 9cda473fc2ca94fcd5bed140f85d18ddc53ba3b7 Mon Sep 17 00:00:00 2001 From: tilacog Date: Mon, 28 Mar 2022 20:36:08 -0300 Subject: [PATCH 25/33] chain/ethereum: Use `AscAddress` instead of `AscH160` --- chain/ethereum/src/runtime/abi.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chain/ethereum/src/runtime/abi.rs b/chain/ethereum/src/runtime/abi.rs index a6dc58d5448..68ca647dee5 100644 --- a/chain/ethereum/src/runtime/abi.rs +++ b/chain/ethereum/src/runtime/abi.rs @@ -303,7 +303,7 @@ impl AscIndexId for AscEthereumEvent, + pub address: AscPtr, pub topics: AscPtr, pub data: AscPtr, pub block_hash: AscPtr, From 8068b6a073df4d724ea7a9a5f718182c9f0a206d Mon Sep 17 00:00:00 2001 From: tilacog Date: Mon, 28 Mar 2022 21:03:53 -0300 Subject: [PATCH 26/33] chain/ethereum: Fix typo in comment --- chain/ethereum/src/ethereum_adapter.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chain/ethereum/src/ethereum_adapter.rs b/chain/ethereum/src/ethereum_adapter.rs index a783dda9ffa..1b0628630dd 100644 --- a/chain/ethereum/src/ethereum_adapter.rs +++ b/chain/ethereum/src/ethereum_adapter.rs @@ -1827,7 +1827,7 @@ fn resolve_transaction_receipt( } } -/// Retrieves logs and the associated transact receipts, if required by the [`EthereumLogFilter`]. +/// Retrieves logs and the associated transaction receipts, if required by the [`EthereumLogFilter`]. async fn get_logs_and_transactions( adapter: Arc, logger: &Logger, From 5c08031c0e987c770acf3414e828122cae381723 Mon Sep 17 00:00:00 2001 From: tilacog Date: Mon, 28 Mar 2022 21:10:58 -0300 Subject: [PATCH 27/33] chain/ethereum: Explain the unwrapping of txn and block hashes for the Log type --- chain/ethereum/src/ethereum_adapter.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/chain/ethereum/src/ethereum_adapter.rs b/chain/ethereum/src/ethereum_adapter.rs index 1b0628630dd..583d50dc205 100644 --- a/chain/ethereum/src/ethereum_adapter.rs +++ b/chain/ethereum/src/ethereum_adapter.rs @@ -1858,7 +1858,8 @@ async fn get_logs_and_transactions( if let (Some(block), Some(txn)) = (log.block_hash, log.transaction_hash) { Some((block, txn)) } else { - // TODO: Should this case be considered an error? + // Absent block and transaction data might happen for pending transactions, which we + // don't handle. None } }) From 2008afafce8e8ac8ed4f3a9d0ee4c126083e9c1d Mon Sep 17 00:00:00 2001 From: tilacog Date: Mon, 28 Mar 2022 21:25:37 -0300 Subject: [PATCH 28/33] chain/ethereum: Comments in upper case --- chain/ethereum/src/ethereum_adapter.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/chain/ethereum/src/ethereum_adapter.rs b/chain/ethereum/src/ethereum_adapter.rs index 583d50dc205..62115a00457 100644 --- a/chain/ethereum/src/ethereum_adapter.rs +++ b/chain/ethereum/src/ethereum_adapter.rs @@ -1837,13 +1837,13 @@ async fn get_logs_and_transactions( log_filter: EthereumLogFilter, unified_api_version: &UnifiedMappingApiVersion, ) -> Result, anyhow::Error> { - // obtain logs externally + // Obtain logs externally let logs = adapter .logs_in_block_range(logger, subgraph_metrics, from, to, log_filter.clone()) .await?; - // not all logs have associated transaction hashes, nor do all triggers require them. - // we also restrict receipts retrieval for some api versions. + // Not all logs have associated transaction hashes, nor do all triggers require them. + // We also restrict receipts retrieval for some api versions. let transaction_hashes_by_block: HashMap> = logs .iter() .filter(|_| unified_api_version.equal_or_greater_than(&API_VERSION_0_0_7)) @@ -1871,7 +1871,7 @@ async fn get_logs_and_transactions( }, ); - // obtain receipts externally + // Obtain receipts externally let transaction_receipts_by_hash = get_transaction_receipts_for_transaction_hashes( &adapter, &transaction_hashes_by_block, @@ -1879,7 +1879,7 @@ async fn get_logs_and_transactions( ) .await?; - // associate each log with its receipt, when possible + // Associate each log with its receipt, when possible let mut log_triggers = Vec::new(); for log in logs.into_iter() { let optional_receipt = log From 1fae709eedc1e293f70b1ce0bd621dcae66e67c5 Mon Sep 17 00:00:00 2001 From: tilacog Date: Tue, 29 Mar 2022 16:40:36 -0300 Subject: [PATCH 29/33] chain/ethereum: Remove unecessary implementations of AscType --- chain/ethereum/src/runtime/abi.rs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/chain/ethereum/src/runtime/abi.rs b/chain/ethereum/src/runtime/abi.rs index 68ca647dee5..af8b7600d42 100644 --- a/chain/ethereum/src/runtime/abi.rs +++ b/chain/ethereum/src/runtime/abi.rs @@ -359,14 +359,6 @@ where pub receipt: AscPtr, } -impl AscIndexId for AscEthereumEvent_0_0_7 { - const INDEX_ASC_TYPE_ID: IndexForAscTypeId = IndexForAscTypeId::EthereumEvent; -} - -impl AscIndexId for AscEthereumEvent_0_0_7 { - const INDEX_ASC_TYPE_ID: IndexForAscTypeId = IndexForAscTypeId::EthereumEvent; -} - impl AscIndexId for AscEthereumEvent_0_0_7 { const INDEX_ASC_TYPE_ID: IndexForAscTypeId = IndexForAscTypeId::EthereumEvent; } From 20b2d1a0e10e8a2909a67f78e72cf351190747f1 Mon Sep 17 00:00:00 2001 From: tilacog Date: Tue, 29 Mar 2022 17:48:23 -0300 Subject: [PATCH 30/33] chain/ethereum: Reorder enum variants They must match the same ordering as https://github.com/graphprotocol/graph-ts/blob/main/global/global.ts --- graph/src/runtime/mod.rs | 179 ++++++++++++++++++++------------------- 1 file changed, 91 insertions(+), 88 deletions(-) diff --git a/graph/src/runtime/mod.rs b/graph/src/runtime/mod.rs index 54d335d41c2..d23300b891b 100644 --- a/graph/src/runtime/mod.rs +++ b/graph/src/runtime/mod.rs @@ -197,113 +197,116 @@ pub enum IndexForAscTypeId { ArrayF32 = 49, ArrayF64 = 50, ArrayBigDecimal = 51, - TransactionReceipt = 52, - Log = 53, - ArrayH256 = 54, - ArrayLog = 55, // Near Type IDs // // Generated with the following shell script: // // ``` - // cat chain/near/src/runtime/generated.rs | grep IndexForAscTypeId::Near | grep -Eo "Near[a-zA-Z0-9]+" | awk '{for(x=1;x<=NF;x++)sub(/$/,"="++i+55",")}1' | sed 's/=/ = /' + // cat chain/near/src/runtime/generated.rs | grep IndexForAscTypeId::Near | grep -Eo "Near[a-zA-Z0-9]+" | awk '{for(x=1;x<=NF;x++)sub(/$/,"="++i+51",")}1' | sed 's/=/ = /' // ``` // - // The `55` literal at the end in the `awk` should be replaced with the last element + // The `51` literal at the end in the `awk` should be replaced with the last element // value in the list above. - NearArrayDataReceiver = 56, - NearArrayCryptoHash = 57, - NearArrayActionEnum = 58, - NearArrayMerklePathItem = 59, - NearArrayValidatorStake = 60, - NearArraySlashedValidator = 61, - NearArraySignature = 62, - NearArrayChunkHeader = 63, - NearAccessKeyPermissionEnum = 64, - NearActionEnum = 65, - NearPublicKey = 66, - NearSignature = 67, - NearFunctionCallPermission = 68, - NearFullAccessPermission = 69, - NearAccessKey = 70, - NearDataReceiver = 71, - NearCreateAccountAction = 72, - NearDeployContractAction = 73, - NearFunctionCallAction = 74, - NearTransferAction = 75, - NearStakeAction = 76, - NearAddKeyAction = 77, - NearDeleteKeyAction = 78, - NearDeleteAccountAction = 79, - NearActionReceipt = 80, - NearSuccessStatusEnum = 81, - NearMerklePathItem = 82, - NearExecutionOutcome = 83, - NearSlashedValidator = 84, - NearBlockHeader = 85, - NearValidatorStake = 86, - NearChunkHeader = 87, - NearBlock = 88, - NearReceiptWithOutcome = 89, + NearArrayDataReceiver = 52, + NearArrayCryptoHash = 53, + NearArrayActionEnum = 54, + NearArrayMerklePathItem = 55, + NearArrayValidatorStake = 56, + NearArraySlashedValidator = 57, + NearArraySignature = 58, + NearArrayChunkHeader = 59, + NearAccessKeyPermissionEnum = 60, + NearActionEnum = 61, + NearDirectionEnum = 62, + NearPublicKey = 63, + NearSignature = 64, + NearFunctionCallPermission = 65, + NearFullAccessPermission = 66, + NearAccessKey = 67, + NearDataReceiver = 68, + NearCreateAccountAction = 69, + NearDeployContractAction = 70, + NearFunctionCallAction = 71, + NearTransferAction = 72, + NearStakeAction = 73, + NearAddKeyAction = 74, + NearDeleteKeyAction = 75, + NearDeleteAccountAction = 76, + NearActionReceipt = 77, + NearSuccessStatusEnum = 78, + NearMerklePathItem = 79, + NearExecutionOutcome = 80, + NearSlashedValidator = 81, + NearBlockHeader = 82, + NearValidatorStake = 83, + NearChunkHeader = 84, + NearBlock = 85, + NearReceiptWithOutcome = 86, // Tendermint Type IDs // // Generated with the following shell script: // // ``` - // cat chain/tendermint/src/runtime/generated.rs | grep IndexForAscTypeId::Tendermint | grep -Eo "Tendermint[a-zA-Z0-9]+" | awk '{for(x=1;x<=NF;x++)sub(/$/,"="++i+89",")}1' | sed 's/=/ = /' + // cat chain/tendermint/src/runtime/generated.rs | grep IndexForAscTypeId::Tendermint | grep -Eo "Tendermint[a-zA-Z0-9]+" | awk '{for(x=1;x<=NF;x++)sub(/$/,"="++i+86",")}1' | sed 's/=/ = /' // ``` // - // The `89` literal at the end in the `awk` should be replaced with the last element + // The `86` literal at the end in the `awk` should be replaced with the last element // value in the list above. - TendermintArrayEventTx = 90, - TendermintArrayCommitSig = 91, - TendermintArrayBytes = 92, - TendermintArrayEventAttribute = 93, - TendermintArrayValidator = 94, - TendermintArrayEvidence = 95, - TendermintArrayEvent = 96, - TendermintArrayValidatorUpdate = 97, - TendermintBlockIDFlagEnum = 98, - TendermintSignedMsgTypeEnum = 99, - TendermintEventData = 100, - TendermintEventList = 101, - TendermintBlock = 102, - TendermintBlockID = 103, - TendermintBlockParams = 104, - TendermintCommit = 105, - TendermintCommitSig = 106, - TendermintConsensus = 107, - TendermintConsensusParams = 108, - TendermintData = 109, - TendermintDuration = 110, - TendermintDuplicateVoteEvidence = 111, - TendermintEvent = 112, - TendermintEventAttribute = 113, - TendermintEventBlock = 114, - TendermintEventTx = 115, - TendermintEventValidatorSetUpdates = 116, + TendermintArrayEventTx = 87, + TendermintArrayEvent = 88, + TendermintArrayCommitSig = 89, + TendermintArrayBytes = 90, + TendermintArrayEvidence = 91, + TendermintArrayEventAttribute = 92, + TendermintBlockIDFlagEnum = 93, + TendermintSignedMsgTypeEnum = 94, + TendermintEventList = 95, + TendermintEventBlock = 96, + TendermintResponseBeginBlock = 97, + TendermintResponseEndBlock = 98, + TendermintValidatorUpdate = 99, + TendermintArrayValidatorUpdate = 100, + TendermintConsensusParams = 101, + TendermintBlockParams = 102, + TendermintEvidenceParams = 103, + TendermintValidatorParams = 104, + TendermintVersionParams = 105, + TendermintBlock = 106, + TendermintCommit = 107, + TendermintCommitSig = 108, + TendermintHeader = 109, + TendermintConsensus = 110, + TendermintBlockID = 111, + TendermintPartSetHeader = 112, + TendermintData = 113, + TendermintEvidence = 114, + TendermintDuplicateVoteEvidence = 115, + TendermintEventTx = 116, TendermintEventVote = 117, - TendermintEvidence = 118, - TendermintEvidenceList = 119, - TendermintEvidenceParams = 120, - TendermintHeader = 121, - TendermintLightBlock = 122, - TendermintLightClientAttackEvidence = 123, - TendermintPublicKey = 124, - TendermintPartSetHeader = 125, - TendermintResponseBeginBlock = 126, - TendermintResponseEndBlock = 127, - TendermintResponseDeliverTx = 128, - TendermintSignedHeader = 129, - TendermintTimestamp = 130, - TendermintTxResult = 131, - TendermintValidator = 132, - TendermintValidatorParams = 133, - TendermintValidatorSet = 134, - TendermintValidatorUpdate = 135, - TendermintVersionParams = 136, + TendermintLightClientAttackEvidence = 118, + TendermintLightBlock = 119, + TendermintValidatorSet = 120, + TendermintSignedHeader = 121, + TendermintEvidenceList = 122, + TendermintValidator = 123, + TendermintArrayValidator = 124, + TendermintPublicKey = 125, + TendermintTxResult = 126, + TendermintResponseDeliverTx = 127, + TendermintEvent = 128, + TendermintEventAttribute = 129, + TendermintEventValidatorSetUpdates = 130, + TendermintDuration = 131, + TendermintTimestamp = 132, + TendermintEventData = 133, + + // More Ethereum tyes + TransactionReceipt = 134, + Log = 135, + ArrayH256 = 136, + ArrayLog = 137, } impl ToAscObj for IndexForAscTypeId { From 32028aa5c156afe30c6c5a52038aa76d947673bd Mon Sep 17 00:00:00 2001 From: tilacog Date: Wed, 30 Mar 2022 18:09:07 -0300 Subject: [PATCH 31/33] runtime: Tests for `bool` primitive type --- runtime/test/src/test.rs | 54 ++++++++++++++++++ .../wasm_test/api_version_0_0_5/boolean.ts | 17 ++++++ .../wasm_test/api_version_0_0_5/boolean.wasm | Bin 0 -> 3542 bytes 3 files changed, 71 insertions(+) create mode 100644 runtime/test/wasm_test/api_version_0_0_5/boolean.ts create mode 100644 runtime/test/wasm_test/api_version_0_0_5/boolean.wasm diff --git a/runtime/test/src/test.rs b/runtime/test/src/test.rs index cba94b3c60d..b4575d051d0 100644 --- a/runtime/test/src/test.rs +++ b/runtime/test/src/test.rs @@ -135,6 +135,11 @@ async fn test_module_latest(subgraph_id: &str, wasm_file: &str) -> WasmInstance< trait WasmInstanceExt { fn invoke_export0_void(&self, f: &str) -> Result<(), wasmtime::Trap>; + fn invoke_export1_val_void( + &self, + f: &str, + v: V, + ) -> Result<(), wasmtime::Trap>; fn invoke_export0(&self, f: &str) -> AscPtr; fn invoke_export1(&mut self, f: &str, arg: &T) -> AscPtr where @@ -157,6 +162,7 @@ trait WasmInstanceExt { 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 where C: AscType + AscIndexId, @@ -195,6 +201,16 @@ impl WasmInstanceExt for WasmInstance { ptr.into() } + fn invoke_export1_val_void( + &self, + f: &str, + v: V, + ) -> Result<(), wasmtime::Trap> { + let func = self.get_func(f).typed().unwrap().clone(); + func.call(v)?; + Ok(()) + } + fn invoke_export2(&mut self, f: &str, arg0: &T1, arg1: &T2) -> AscPtr where C1: AscType + AscIndexId, @@ -229,6 +245,11 @@ impl WasmInstanceExt for WasmInstance { func.call((arg0.wasm_ptr(), arg1.wasm_ptr())) } + fn invoke_export0_val(&mut self, func: &str) -> V { + let func = self.get_func(func).typed().unwrap().clone(); + func.call(()).unwrap() + } + fn invoke_export1_val(&mut self, func: &str, v: &T) -> V where C: AscType + AscIndexId, @@ -1089,3 +1110,36 @@ async fn test_array_blowup() { .to_string() .contains("Gas limit exceeded. Used: 11286295575421")); } + +#[tokio::test] +async fn test_boolean() { + let mut module = test_module_latest("boolean", "boolean.wasm").await; + + let true_: i32 = module.invoke_export0_val("testReturnTrue"); + assert_eq!(true_, 1); + + let false_: i32 = module.invoke_export0_val("testReturnFalse"); + 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(),); + } + + // zero is not true + assert!(module + .invoke_export1_val_void("testReceiveTrue", 0i32) + .is_err()); + + // zero is false + assert!(module + .invoke_export1_val_void("testReceiveFalse", 0i32) + .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) + .is_err()); + } +} diff --git a/runtime/test/wasm_test/api_version_0_0_5/boolean.ts b/runtime/test/wasm_test/api_version_0_0_5/boolean.ts new file mode 100644 index 00000000000..7bf85ca45b1 --- /dev/null +++ b/runtime/test/wasm_test/api_version_0_0_5/boolean.ts @@ -0,0 +1,17 @@ +export * from "./common/global" + +export function testReceiveTrue(a: bool): void { + assert(a) +} + +export function testReceiveFalse(a: bool): void { + assert(!a) +} + +export function testReturnTrue(): bool { + return true +} + +export function testReturnFalse(): bool { + return false +} diff --git a/runtime/test/wasm_test/api_version_0_0_5/boolean.wasm b/runtime/test/wasm_test/api_version_0_0_5/boolean.wasm new file mode 100644 index 0000000000000000000000000000000000000000..ba80672b1bba1f34d3f9c365d86329971fa49221 GIT binary patch literal 3542 zcmcIn%W@k<6umt?l4XzE8oy#}$M(pw9lsMlVhkk2acDzIQw=bY}_w{PFR(_NCtYn}=s66KyFqt&7# z5ka($h>i$iw(t;-iI$;+^y?Qn?@1#FDJMrq1Q}8aVVZ`4Ae1+Rl%^@pNaUm-k$-8t z+Y&BWF%h@O6-)+`WHOm@OnIgrrhn^Kc(wC?3f0T;`j{-HekR2G~ocolwGZyXDnm+Xm#HR2(s7F49WELA@ zmtOVw#hHKjbp25nRO`>>_)yY!gTQ;SfBx*5AIJ$iF&@^#-Dp5g4t7i%JF)F*##{8k=wyv)#8TFnhMXNv($Tes7pAV|E^F#LmoC3weSXhB zuAX`|IiKz@%5cBYsL6#Z*-B(my*8d5I0!%W1ONQ=W3P7Z%f+rqkHSXa#{)~F*+~zZ zje2ZfPInV6oVZOu{$Q*VUQ5GndRxv&mE?GxPQGKf85;?ao~Cino@D^TzbwdUQg(x%#&bY~*)# zxmItbZ}vmKd0q?u!7Zxbt%)RZ?Al&38oWJ_3`Xbaqh0xSvCFWrBky!uwl?Knw`FHr zx?N!ocjbPU%OS$t?Y1Dyy>1J_9CTSUd~~-3KKHv0d*)6)=&L@hG@eyLl#KccIE{^C zFZ9XERVsD=3$meS)jAFKR4V6nZVmNTD#wjl%|8xl*s4^5FsxRdcuij(=1=|8M)1O* ze5D!Ukkd$SUhuaT^?hF$ITE?n9V!V&xOV6kPu!y0YJOfa9CD0z%srOR+?Oxq2@?;w z_C-lLV%BuzY|b&A+{4L|>6m-?l6wGunKa0p zb==>`ExA)RkwsQnltfv$=1Ivg;D}WuI_;cX^D_%88IE~kBVEU|4acyisr>Mg*Bq6x zuuHa_uuQT|YYZv4bpOEIbIJVyE4Y$Zv_@e+Xj==3)#TcM$okb3_>q7_|Gy8miM^D^ zwL+1~6IyX^QY!;;RVx&_Jf)Qhnbs-?nb9f_akT1zl(Z^9X0_^tl(p)E%xPso=C$gF zENG=5*R&ddENWGRENNv!mbDs$tY|d^S<`A5vaZz#WJ9Y_$fj0TAX{3ELAJGe4RTYf zambEV6Oh-nnuP3Xbrtf4R#T8SwVH;!rPU1NmR1hrwpJy`+gi;+?r2qp>}fRzakZL< z>}$0ExvSMR$UUtVAqQG5L9|xOko#J#Kptqd+P`MGqG-7UNWcVgKpyA;3P3N=2UtKq zpnxJ^14Fo6e1BZqp%Yu;?i#o&(e)v_ zAD4)1<=`aQ17Na?7`9Jv!X2_Ci3kH!#*IW`V(uL~XIbCnZ~^+Qy<60I1e%M0OEr8N zbW9$FR7I2 zyPZC9F1vt7XNXdv3#=DlUYt#ZZotO4iO5f)%xduPkZC49|Fry3ZX=8X@Z&_>A-6!p ze+0ftRP@Gu(HE|qKD`80Q+&53^w3#B8Kn3?5BRowDvsi zPo~CLr&Uwg ECwpqqEdT%j literal 0 HcmV?d00001 From 66006335c004f02003aeb744d9763f13a2175471 Mon Sep 17 00:00:00 2001 From: tilacog Date: Wed, 30 Mar 2022 19:58:37 -0300 Subject: [PATCH 32/33] chain/ethereum: Uppercase comments, more descriptive variable name --- chain/ethereum/src/adapter.rs | 4 ++-- chain/ethereum/src/runtime/abi.rs | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/chain/ethereum/src/adapter.rs b/chain/ethereum/src/adapter.rs index 057c9fbbe03..dd190b4718d 100644 --- a/chain/ethereum/src/adapter.rs +++ b/chain/ethereum/src/adapter.rs @@ -1434,11 +1434,11 @@ fn log_filter_require_transacion_receipt_method() { assert!(!filter.requires_transaction_receipt(&event_signature_c, Some(&contract_a))); assert!(!filter.requires_transaction_receipt(&event_signature_c, Some(&contract_b))); - // wildcard events + // 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 + // Alien events and contracts always return false assert!( !filter.requires_transaction_receipt(&alien_event_signature, Some(&alien_contract_address)) ); diff --git a/chain/ethereum/src/runtime/abi.rs b/chain/ethereum/src/runtime/abi.rs index af8b7600d42..af236a18dfb 100644 --- a/chain/ethereum/src/runtime/abi.rs +++ b/chain/ethereum/src/runtime/abi.rs @@ -590,7 +590,7 @@ where heap: &mut H, gas: &GasCounter, ) -> Result, DeterministicHostError> { - let (event_data, receipt) = self; + let (event_data, optional_receipt) = self; let AscEthereumEvent { address, log_index, @@ -600,7 +600,7 @@ where transaction, params, } = event_data.to_asc_obj(heap, gas)?; - let asc_receipt = if let Some(receipt_data) = receipt { + let receipt = if let Some(receipt_data) = optional_receipt { asc_new(heap, receipt_data, gas)? } else { AscPtr::null() @@ -613,7 +613,7 @@ where block, transaction, params, - receipt: asc_receipt, + receipt, }) } } From d63232bab14217628a4ef959a279469d464a37a8 Mon Sep 17 00:00:00 2001 From: tilacog Date: Wed, 30 Mar 2022 20:49:37 -0300 Subject: [PATCH 33/33] NEWS: Update with info about this very feature --- NEWS.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/NEWS.md b/NEWS.md index 52573b2584e..1294b6901aa 100644 --- a/NEWS.md +++ b/NEWS.md @@ -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: