feat: Fetch logs by transaction hash (#4510)
### Description Cosmos indexers now can fetch logs by transaction hash. It allows to reprocess transaction which may be missed by range based indexers. ### Drive-by changes Quite a bit of refactoring ### Related issues Contributes into issue #4300 ### Backward compatibility Yes ### Testing E2E Tests --------- Co-authored-by: Danil Nemirovsky <4614623+ameten@users.noreply.github.com>pull/4537/head
parent
5ecc529e14
commit
4151317b00
@ -0,0 +1,8 @@ |
||||
pub use cosmos::CosmosProvider; |
||||
|
||||
/// cosmos provider
|
||||
mod cosmos; |
||||
/// cosmos grpc provider
|
||||
pub mod grpc; |
||||
/// cosmos rpc provider
|
||||
pub mod rpc; |
@ -0,0 +1,3 @@ |
||||
pub use provider::CosmosProvider; |
||||
|
||||
mod provider; |
@ -1,251 +1,5 @@ |
||||
use async_trait::async_trait; |
||||
use cosmrs::rpc::client::Client; |
||||
use hyperlane_core::{ChainCommunicationError, ChainResult, ContractLocator, LogMeta, H256, U256}; |
||||
use sha256::digest; |
||||
use std::fmt::Debug; |
||||
use tendermint::abci::{Event, EventAttribute}; |
||||
use tendermint::hash::Algorithm; |
||||
use tendermint::Hash; |
||||
use tendermint_rpc::endpoint::block::Response as BlockResponse; |
||||
use tendermint_rpc::endpoint::block_results::Response as BlockResultsResponse; |
||||
use tendermint_rpc::HttpClient; |
||||
use tracing::{debug, instrument, trace}; |
||||
pub use client::CosmosRpcClient; |
||||
pub use provider::{CosmosWasmRpcProvider, ParsedEvent, WasmRpcProvider}; |
||||
|
||||
use crate::address::CosmosAddress; |
||||
use crate::{ConnectionConf, CosmosProvider, HyperlaneCosmosError}; |
||||
|
||||
#[async_trait] |
||||
/// Trait for wasm indexer. Use rpc provider
|
||||
pub trait WasmIndexer: Send + Sync { |
||||
/// Get the finalized block height.
|
||||
async fn get_finalized_block_number(&self) -> ChainResult<u32>; |
||||
|
||||
/// Get logs for the given block using the given parser.
|
||||
async fn get_logs_in_block<T>( |
||||
&self, |
||||
block_number: u32, |
||||
parser: for<'a> fn(&'a Vec<EventAttribute>) -> ChainResult<ParsedEvent<T>>, |
||||
cursor_label: &'static str, |
||||
) -> ChainResult<Vec<(T, LogMeta)>> |
||||
where |
||||
T: Send + Sync + PartialEq + Debug + 'static; |
||||
} |
||||
|
||||
#[derive(Debug, Eq, PartialEq)] |
||||
/// An event parsed from the RPC response.
|
||||
pub struct ParsedEvent<T: PartialEq> { |
||||
contract_address: String, |
||||
event: T, |
||||
} |
||||
|
||||
impl<T: PartialEq> ParsedEvent<T> { |
||||
/// Create a new ParsedEvent.
|
||||
pub fn new(contract_address: String, event: T) -> Self { |
||||
Self { |
||||
contract_address, |
||||
event, |
||||
} |
||||
} |
||||
|
||||
/// Get the inner event
|
||||
pub fn inner(self) -> T { |
||||
self.event |
||||
} |
||||
} |
||||
|
||||
#[derive(Debug, Clone)] |
||||
/// Cosmwasm RPC Provider
|
||||
pub struct CosmosWasmIndexer { |
||||
provider: CosmosProvider, |
||||
contract_address: CosmosAddress, |
||||
target_event_kind: String, |
||||
reorg_period: u32, |
||||
} |
||||
|
||||
impl CosmosWasmIndexer { |
||||
const WASM_TYPE: &'static str = "wasm"; |
||||
|
||||
/// create new Cosmwasm RPC Provider
|
||||
pub fn new( |
||||
conf: ConnectionConf, |
||||
locator: ContractLocator, |
||||
event_type: String, |
||||
reorg_period: u32, |
||||
) -> ChainResult<Self> { |
||||
let provider = CosmosProvider::new( |
||||
locator.domain.clone(), |
||||
conf.clone(), |
||||
Some(locator.clone()), |
||||
None, |
||||
)?; |
||||
Ok(Self { |
||||
provider, |
||||
contract_address: CosmosAddress::from_h256( |
||||
locator.address, |
||||
conf.get_bech32_prefix().as_str(), |
||||
conf.get_contract_address_bytes(), |
||||
)?, |
||||
target_event_kind: format!("{}-{}", Self::WASM_TYPE, event_type), |
||||
reorg_period, |
||||
}) |
||||
} |
||||
|
||||
async fn get_block(client: HttpClient, block_number: u32) -> ChainResult<BlockResponse> { |
||||
Ok(client |
||||
.block(block_number) |
||||
.await |
||||
.map_err(Into::<HyperlaneCosmosError>::into)?) |
||||
} |
||||
|
||||
async fn get_block_results( |
||||
client: HttpClient, |
||||
block_number: u32, |
||||
) -> ChainResult<BlockResultsResponse> { |
||||
Ok(client |
||||
.block_results(block_number) |
||||
.await |
||||
.map_err(Into::<HyperlaneCosmosError>::into)?) |
||||
} |
||||
|
||||
async fn get_latest_block(client: HttpClient) -> ChainResult<BlockResponse> { |
||||
Ok(client |
||||
.latest_block() |
||||
.await |
||||
.map_err(Into::<HyperlaneCosmosError>::into)?) |
||||
} |
||||
} |
||||
|
||||
impl CosmosWasmIndexer { |
||||
// Iterate through all txs, filter out failed txs, find target events
|
||||
// in successful txs, and parse them.
|
||||
fn handle_txs<T>( |
||||
&self, |
||||
block: BlockResponse, |
||||
block_results: BlockResultsResponse, |
||||
parser: for<'a> fn(&'a Vec<EventAttribute>) -> ChainResult<ParsedEvent<T>>, |
||||
cursor_label: &'static str, |
||||
) -> Vec<(T, LogMeta)> |
||||
where |
||||
T: PartialEq + Debug + 'static, |
||||
{ |
||||
let Some(tx_results) = block_results.txs_results else { |
||||
return vec![]; |
||||
}; |
||||
|
||||
let tx_hashes: Vec<H256> = block |
||||
.clone() |
||||
.block |
||||
.data |
||||
.into_iter() |
||||
.filter_map(|tx| hex::decode(digest(tx.as_slice())).ok()) |
||||
.filter_map(|hash| { |
||||
Hash::from_bytes(Algorithm::Sha256, hash.as_slice()) |
||||
.ok() |
||||
.map(|hash| H256::from_slice(hash.as_bytes())) |
||||
}) |
||||
.collect(); |
||||
|
||||
tx_results |
||||
.into_iter() |
||||
.enumerate() |
||||
.filter_map(move |(idx, tx)| { |
||||
let Some(tx_hash) = tx_hashes.get(idx) else { |
||||
debug!(?tx, "No tx hash found for tx"); |
||||
return None; |
||||
}; |
||||
if tx.code.is_err() { |
||||
debug!(?tx_hash, "Not indexing failed transaction"); |
||||
return None; |
||||
} |
||||
Some(self.handle_tx(block.clone(), tx.events, *tx_hash, idx, parser)) |
||||
}) |
||||
.flatten() |
||||
.collect() |
||||
} |
||||
|
||||
// Iter through all events in the tx, looking for any target events
|
||||
// made by the contract we are indexing.
|
||||
fn handle_tx<T>( |
||||
&self, |
||||
block: BlockResponse, |
||||
tx_events: Vec<Event>, |
||||
tx_hash: H256, |
||||
transaction_index: usize, |
||||
parser: for<'a> fn(&'a Vec<EventAttribute>) -> ChainResult<ParsedEvent<T>>, |
||||
) -> impl Iterator<Item = (T, LogMeta)> + '_ |
||||
where |
||||
T: PartialEq + 'static, |
||||
{ |
||||
tx_events.into_iter().enumerate().filter_map(move |(log_idx, event)| { |
||||
if event.kind.as_str() != self.target_event_kind { |
||||
return None; |
||||
} |
||||
|
||||
parser(&event.attributes) |
||||
.map_err(|err| { |
||||
// This can happen if we attempt to parse an event that just happens
|
||||
// to have the same name but a different structure.
|
||||
tracing::trace!(?err, tx_hash=?tx_hash, log_idx, ?event, "Failed to parse event attributes"); |
||||
}) |
||||
.ok() |
||||
.and_then(|parsed_event| { |
||||
// This is crucial! We need to make sure that the contract address
|
||||
// in the event matches the contract address we are indexing.
|
||||
// Otherwise, we might index events from other contracts that happen
|
||||
// to have the same target event name.
|
||||
if parsed_event.contract_address != self.contract_address.address() { |
||||
trace!(tx_hash=?tx_hash, log_idx, ?event, "Event contract address does not match indexer contract address"); |
||||
return None; |
||||
} |
||||
|
||||
Some((parsed_event.event, LogMeta { |
||||
address: self.contract_address.digest(), |
||||
block_number: block.block.header.height.into(), |
||||
block_hash: H256::from_slice(block.block_id.hash.as_bytes()), |
||||
transaction_id: H256::from_slice(tx_hash.as_bytes()).into(), |
||||
transaction_index: transaction_index as u64, |
||||
log_index: U256::from(log_idx), |
||||
})) |
||||
}) |
||||
}) |
||||
} |
||||
} |
||||
|
||||
#[async_trait] |
||||
impl WasmIndexer for CosmosWasmIndexer { |
||||
#[instrument(err, skip(self))] |
||||
#[allow(clippy::blocks_in_conditions)] // TODO: `rustc` 1.80.1 clippy issue
|
||||
async fn get_finalized_block_number(&self) -> ChainResult<u32> { |
||||
let latest_block = Self::get_latest_block(self.provider.rpc().clone()).await?; |
||||
let latest_height: u32 = latest_block |
||||
.block |
||||
.header |
||||
.height |
||||
.value() |
||||
.try_into() |
||||
.map_err(ChainCommunicationError::from_other)?; |
||||
Ok(latest_height.saturating_sub(self.reorg_period)) |
||||
} |
||||
|
||||
#[instrument(err, skip(self, parser))] |
||||
#[allow(clippy::blocks_in_conditions)] // TODO: `rustc` 1.80.1 clippy issue
|
||||
async fn get_logs_in_block<T>( |
||||
&self, |
||||
block_number: u32, |
||||
parser: for<'a> fn(&'a Vec<EventAttribute>) -> ChainResult<ParsedEvent<T>>, |
||||
cursor_label: &'static str, |
||||
) -> ChainResult<Vec<(T, LogMeta)>> |
||||
where |
||||
T: Send + Sync + PartialEq + Debug + 'static, |
||||
{ |
||||
let client = self.provider.rpc().clone(); |
||||
debug!(?block_number, cursor_label, domain=?self.provider.domain, "Getting logs in block"); |
||||
|
||||
// The two calls below could be made in parallel, but on cosmos rate limiting is a bigger problem
|
||||
// than indexing latency, so we do them sequentially.
|
||||
let block = Self::get_block(client.clone(), block_number).await?; |
||||
let block_results = Self::get_block_results(client.clone(), block_number).await?; |
||||
|
||||
Ok(self.handle_txs(block, block_results, parser, cursor_label)) |
||||
} |
||||
} |
||||
mod client; |
||||
mod provider; |
||||
|
@ -0,0 +1,77 @@ |
||||
use cosmrs::proto::tendermint::blocksync::BlockResponse; |
||||
use tendermint::Hash; |
||||
use tendermint_rpc::client::CompatMode; |
||||
use tendermint_rpc::endpoint::{block, block_by_hash, block_results, tx}; |
||||
use tendermint_rpc::{Client, HttpClient}; |
||||
|
||||
use hyperlane_core::ChainResult; |
||||
|
||||
use crate::{ConnectionConf, HyperlaneCosmosError}; |
||||
|
||||
/// Thin wrapper around Cosmos RPC client with error mapping
|
||||
#[derive(Clone, Debug)] |
||||
pub struct CosmosRpcClient { |
||||
client: HttpClient, |
||||
} |
||||
|
||||
impl CosmosRpcClient { |
||||
/// Create new `CosmosRpcClient`
|
||||
pub fn new(conf: &ConnectionConf) -> ChainResult<Self> { |
||||
let client = HttpClient::builder( |
||||
conf.get_rpc_url() |
||||
.parse() |
||||
.map_err(Into::<HyperlaneCosmosError>::into)?, |
||||
) |
||||
// Consider supporting different compatibility modes.
|
||||
.compat_mode(CompatMode::latest()) |
||||
.build() |
||||
.map_err(Into::<HyperlaneCosmosError>::into)?; |
||||
|
||||
Ok(Self { client }) |
||||
} |
||||
|
||||
/// Request block by block height
|
||||
pub async fn get_block(&self, height: u32) -> ChainResult<block::Response> { |
||||
Ok(self |
||||
.client |
||||
.block(height) |
||||
.await |
||||
.map_err(Into::<HyperlaneCosmosError>::into)?) |
||||
} |
||||
|
||||
/// Request block results by block height
|
||||
pub async fn get_block_results(&self, height: u32) -> ChainResult<block_results::Response> { |
||||
Ok(self |
||||
.client |
||||
.block_results(height) |
||||
.await |
||||
.map_err(Into::<HyperlaneCosmosError>::into)?) |
||||
} |
||||
|
||||
/// Request block by block hash
|
||||
pub async fn get_block_by_hash(&self, hash: Hash) -> ChainResult<block_by_hash::Response> { |
||||
Ok(self |
||||
.client |
||||
.block_by_hash(hash) |
||||
.await |
||||
.map_err(Into::<HyperlaneCosmosError>::into)?) |
||||
} |
||||
|
||||
/// Request the latest block
|
||||
pub async fn get_latest_block(&self) -> ChainResult<block::Response> { |
||||
Ok(self |
||||
.client |
||||
.latest_block() |
||||
.await |
||||
.map_err(Into::<HyperlaneCosmosError>::into)?) |
||||
} |
||||
|
||||
/// Request transaction by transaction hash
|
||||
pub async fn get_tx_by_hash(&self, hash: Hash) -> ChainResult<tx::Response> { |
||||
Ok(self |
||||
.client |
||||
.tx(hash, false) |
||||
.await |
||||
.map_err(Into::<HyperlaneCosmosError>::into)?) |
||||
} |
||||
} |
@ -0,0 +1,277 @@ |
||||
use std::fmt::Debug; |
||||
|
||||
use async_trait::async_trait; |
||||
use cosmrs::cosmwasm::MsgExecuteContract; |
||||
use cosmrs::rpc::client::Client; |
||||
use futures::StreamExt; |
||||
use sha256::digest; |
||||
use tendermint::abci::{Event, EventAttribute}; |
||||
use tendermint::hash::Algorithm; |
||||
use tendermint::Hash; |
||||
use tendermint_rpc::client::CompatMode; |
||||
use tendermint_rpc::endpoint::block::Response as BlockResponse; |
||||
use tendermint_rpc::endpoint::block_results::Response as BlockResultsResponse; |
||||
use tendermint_rpc::endpoint::tx; |
||||
use tendermint_rpc::HttpClient; |
||||
use time::OffsetDateTime; |
||||
use tracing::{debug, instrument, trace}; |
||||
|
||||
use hyperlane_core::{ |
||||
ChainCommunicationError, ChainResult, ContractLocator, HyperlaneDomain, LogMeta, H256, U256, |
||||
}; |
||||
|
||||
use crate::address::CosmosAddress; |
||||
use crate::rpc::CosmosRpcClient; |
||||
use crate::{ConnectionConf, CosmosProvider, HyperlaneCosmosError}; |
||||
|
||||
#[async_trait] |
||||
/// Trait for wasm indexer. Use rpc provider
|
||||
pub trait WasmRpcProvider: Send + Sync { |
||||
/// Get the finalized block height.
|
||||
async fn get_finalized_block_number(&self) -> ChainResult<u32>; |
||||
|
||||
/// Get logs for the given block using the given parser.
|
||||
async fn get_logs_in_block<T>( |
||||
&self, |
||||
block_number: u32, |
||||
parser: for<'a> fn(&'a Vec<EventAttribute>) -> ChainResult<ParsedEvent<T>>, |
||||
cursor_label: &'static str, |
||||
) -> ChainResult<Vec<(T, LogMeta)>> |
||||
where |
||||
T: Send + Sync + PartialEq + Debug + 'static; |
||||
|
||||
/// Get logs for the given transaction using the given parser.
|
||||
async fn get_logs_in_tx<T>( |
||||
&self, |
||||
tx_hash: Hash, |
||||
parser: for<'a> fn(&'a Vec<EventAttribute>) -> ChainResult<ParsedEvent<T>>, |
||||
cursor_label: &'static str, |
||||
) -> ChainResult<Vec<(T, LogMeta)>> |
||||
where |
||||
T: Send + Sync + PartialEq + Debug + 'static; |
||||
} |
||||
|
||||
#[derive(Debug, Eq, PartialEq)] |
||||
/// An event parsed from the RPC response.
|
||||
pub struct ParsedEvent<T: PartialEq> { |
||||
contract_address: String, |
||||
event: T, |
||||
} |
||||
|
||||
impl<T: PartialEq> ParsedEvent<T> { |
||||
/// Create a new ParsedEvent.
|
||||
pub fn new(contract_address: String, event: T) -> Self { |
||||
Self { |
||||
contract_address, |
||||
event, |
||||
} |
||||
} |
||||
|
||||
/// Get the inner event
|
||||
pub fn inner(self) -> T { |
||||
self.event |
||||
} |
||||
} |
||||
|
||||
#[derive(Debug, Clone)] |
||||
/// Cosmwasm RPC Provider
|
||||
pub struct CosmosWasmRpcProvider { |
||||
domain: HyperlaneDomain, |
||||
contract_address: CosmosAddress, |
||||
target_event_kind: String, |
||||
reorg_period: u32, |
||||
rpc_client: CosmosRpcClient, |
||||
} |
||||
|
||||
impl CosmosWasmRpcProvider { |
||||
const WASM_TYPE: &'static str = "wasm"; |
||||
|
||||
/// create new Cosmwasm RPC Provider
|
||||
pub fn new( |
||||
conf: ConnectionConf, |
||||
locator: ContractLocator, |
||||
event_type: String, |
||||
reorg_period: u32, |
||||
) -> ChainResult<Self> { |
||||
let rpc_client = CosmosRpcClient::new(&conf)?; |
||||
|
||||
Ok(Self { |
||||
domain: locator.domain.clone(), |
||||
contract_address: CosmosAddress::from_h256( |
||||
locator.address, |
||||
conf.get_bech32_prefix().as_str(), |
||||
conf.get_contract_address_bytes(), |
||||
)?, |
||||
target_event_kind: format!("{}-{}", Self::WASM_TYPE, event_type), |
||||
reorg_period, |
||||
rpc_client, |
||||
}) |
||||
} |
||||
} |
||||
|
||||
impl CosmosWasmRpcProvider { |
||||
// Iterate through all txs, filter out failed txs, find target events
|
||||
// in successful txs, and parse them.
|
||||
fn handle_txs<T>( |
||||
&self, |
||||
block: BlockResponse, |
||||
block_results: BlockResultsResponse, |
||||
parser: for<'a> fn(&'a Vec<EventAttribute>) -> ChainResult<ParsedEvent<T>>, |
||||
cursor_label: &'static str, |
||||
) -> Vec<(T, LogMeta)> |
||||
where |
||||
T: PartialEq + Debug + 'static, |
||||
{ |
||||
let Some(tx_results) = block_results.txs_results else { |
||||
return vec![]; |
||||
}; |
||||
|
||||
let tx_hashes: Vec<Hash> = block |
||||
.clone() |
||||
.block |
||||
.data |
||||
.into_iter() |
||||
.filter_map(|tx| hex::decode(digest(tx.as_slice())).ok()) |
||||
.filter_map(|hash| Hash::from_bytes(Algorithm::Sha256, hash.as_slice()).ok()) |
||||
.collect(); |
||||
|
||||
tx_results |
||||
.into_iter() |
||||
.enumerate() |
||||
.filter_map(move |(idx, tx)| { |
||||
let Some(tx_hash) = tx_hashes.get(idx) else { |
||||
debug!(?tx, "No tx hash found for tx"); |
||||
return None; |
||||
}; |
||||
if tx.code.is_err() { |
||||
debug!(?tx_hash, "Not indexing failed transaction"); |
||||
return None; |
||||
} |
||||
|
||||
// We construct a simplified structure `tx::Response` here so that we can
|
||||
// reuse `handle_tx` method below.
|
||||
let tx_response = tx::Response { |
||||
hash: *tx_hash, |
||||
height: block_results.height, |
||||
index: idx as u32, |
||||
tx_result: tx, |
||||
tx: vec![], |
||||
proof: None, |
||||
}; |
||||
|
||||
let block_hash = H256::from_slice(block.block_id.hash.as_bytes()); |
||||
|
||||
Some(self.handle_tx(tx_response, block_hash, parser)) |
||||
}) |
||||
.flatten() |
||||
.collect() |
||||
} |
||||
|
||||
// Iter through all events in the tx, looking for any target events
|
||||
// made by the contract we are indexing.
|
||||
fn handle_tx<T>( |
||||
&self, |
||||
tx: tx::Response, |
||||
block_hash: H256, |
||||
parser: for<'a> fn(&'a Vec<EventAttribute>) -> ChainResult<ParsedEvent<T>>, |
||||
) -> impl Iterator<Item = (T, LogMeta)> + '_ |
||||
where |
||||
T: PartialEq + 'static, |
||||
{ |
||||
let tx_events = tx.tx_result.events; |
||||
let tx_hash = tx.hash; |
||||
let tx_index = tx.index; |
||||
let block_height = tx.height; |
||||
|
||||
tx_events.into_iter().enumerate().filter_map(move |(log_idx, event)| { |
||||
if event.kind.as_str() != self.target_event_kind { |
||||
return None; |
||||
} |
||||
|
||||
parser(&event.attributes) |
||||
.map_err(|err| { |
||||
// This can happen if we attempt to parse an event that just happens
|
||||
// to have the same name but a different structure.
|
||||
trace!(?err, tx_hash=?tx_hash, log_idx, ?event, "Failed to parse event attributes"); |
||||
}) |
||||
.ok() |
||||
.and_then(|parsed_event| { |
||||
// This is crucial! We need to make sure that the contract address
|
||||
// in the event matches the contract address we are indexing.
|
||||
// Otherwise, we might index events from other contracts that happen
|
||||
// to have the same target event name.
|
||||
if parsed_event.contract_address != self.contract_address.address() { |
||||
trace!(tx_hash=?tx_hash, log_idx, ?event, "Event contract address does not match indexer contract address"); |
||||
return None; |
||||
} |
||||
|
||||
Some((parsed_event.event, LogMeta { |
||||
address: self.contract_address.digest(), |
||||
block_number: block_height.value(), |
||||
block_hash, |
||||
transaction_id: H256::from_slice(tx_hash.as_bytes()).into(), |
||||
transaction_index: tx_index as u64, |
||||
log_index: U256::from(log_idx), |
||||
})) |
||||
}) |
||||
}) |
||||
} |
||||
} |
||||
|
||||
#[async_trait] |
||||
impl WasmRpcProvider for CosmosWasmRpcProvider { |
||||
#[instrument(err, skip(self))] |
||||
#[allow(clippy::blocks_in_conditions)] // TODO: `rustc` 1.80.1 clippy issue
|
||||
async fn get_finalized_block_number(&self) -> ChainResult<u32> { |
||||
let latest_block = self.rpc_client.get_latest_block().await?; |
||||
let latest_height: u32 = latest_block |
||||
.block |
||||
.header |
||||
.height |
||||
.value() |
||||
.try_into() |
||||
.map_err(ChainCommunicationError::from_other)?; |
||||
Ok(latest_height.saturating_sub(self.reorg_period)) |
||||
} |
||||
|
||||
#[instrument(err, skip(self, parser))] |
||||
#[allow(clippy::blocks_in_conditions)] // TODO: `rustc` 1.80.1 clippy issue
|
||||
async fn get_logs_in_block<T>( |
||||
&self, |
||||
block_number: u32, |
||||
parser: for<'a> fn(&'a Vec<EventAttribute>) -> ChainResult<ParsedEvent<T>>, |
||||
cursor_label: &'static str, |
||||
) -> ChainResult<Vec<(T, LogMeta)>> |
||||
where |
||||
T: Send + Sync + PartialEq + Debug + 'static, |
||||
{ |
||||
debug!(?block_number, cursor_label, domain=?self.domain, "Getting logs in block"); |
||||
|
||||
// The two calls below could be made in parallel, but on cosmos rate limiting is a bigger problem
|
||||
// than indexing latency, so we do them sequentially.
|
||||
let block = self.rpc_client.get_block(block_number).await?; |
||||
let block_results = self.rpc_client.get_block_results(block_number).await?; |
||||
|
||||
Ok(self.handle_txs(block, block_results, parser, cursor_label)) |
||||
} |
||||
|
||||
#[instrument(err, skip(self, parser))] |
||||
#[allow(clippy::blocks_in_conditions)] // TODO: `rustc` 1.80.1 clippy issue
|
||||
async fn get_logs_in_tx<T>( |
||||
&self, |
||||
hash: Hash, |
||||
parser: for<'a> fn(&'a Vec<EventAttribute>) -> ChainResult<ParsedEvent<T>>, |
||||
cursor_label: &'static str, |
||||
) -> ChainResult<Vec<(T, LogMeta)>> |
||||
where |
||||
T: Send + Sync + PartialEq + Debug + 'static, |
||||
{ |
||||
debug!(?hash, cursor_label, domain=?self.domain, "Getting logs in transaction"); |
||||
|
||||
let tx = self.rpc_client.get_tx_by_hash(hash).await?; |
||||
let block = self.rpc_client.get_block(tx.height.value() as u32).await?; |
||||
let block_hash = H256::from_slice(block.block_id.hash.as_bytes()); |
||||
|
||||
Ok(self.handle_tx(tx, block_hash, parser).collect()) |
||||
} |
||||
} |
Loading…
Reference in new issue