65b7f5e640
## Changes
### Clippy Fixes
- Fixed deprecated `cargo_bin` usage in 27 test files (added #![allow(deprecated)])
- Fixed uninlined_format_args in zombienet-sdk-tests
- Fixed subxt API changes in revive/rpc/tests.rs (fetch signature, StorageValue)
- Fixed dead_code warnings in validator-pool and identity-kyc mocks
- Fixed field name `i` -> `_i` in tasks example
### CI Infrastructure
- Added .claude/WORKFLOW_PLAN.md for tracking CI fix progress
- Updated lychee.toml and taplo.toml configs
### Files Modified
- 27 test files with deprecated cargo_bin fix
- bizinikiwi/pezframe/revive/rpc/src/tests.rs (subxt API)
- pezkuwi/pezpallets/validator-pool/src/{mock,tests}.rs
- pezcumulus/teyrchains/pezpallets/identity-kyc/src/mock.rs
- bizinikiwi/pezframe/examples/tasks/src/tests.rs
## Status
- cargo clippy: PASSING
- Next: cargo fmt, zepter, workspace checks
359 lines
11 KiB
Rust
359 lines
11 KiB
Rust
// This file is part of Bizinikiwi.
|
|
|
|
// Copyright (C) Parity Technologies (UK) Ltd.
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
use crate::{
|
|
client::{runtime_api::RuntimeApi, BizinikiwiBlock, BizinikiwiBlockNumber},
|
|
subxt_client::{
|
|
revive::{
|
|
calls::types::EthTransact,
|
|
events::{ContractEmitted, EthExtrinsicRevert},
|
|
},
|
|
SrcChainConfig,
|
|
},
|
|
ClientError, H160, LOG_TARGET,
|
|
};
|
|
|
|
use futures::{stream, StreamExt};
|
|
use pezkuwi_subxt::{blocks::ExtrinsicDetails, OnlineClient};
|
|
use pezpallet_revive::{
|
|
create1,
|
|
evm::{GenericTransaction, Log, ReceiptGasInfo, ReceiptInfo, TransactionSigned, H256, U256},
|
|
};
|
|
use pezsp_core::keccak_256;
|
|
use std::{future::Future, pin::Pin, sync::Arc};
|
|
|
|
type FetchReceiptDataFn = Arc<
|
|
dyn Fn(H256) -> Pin<Box<dyn Future<Output = Option<Vec<ReceiptGasInfo>>> + Send>> + Send + Sync,
|
|
>;
|
|
|
|
type FetchEthBlockHashFn =
|
|
Arc<dyn Fn(H256, u64) -> Pin<Box<dyn Future<Output = Option<H256>> + Send>> + Send + Sync>;
|
|
|
|
type RecoverEthAddressFn = Arc<dyn Fn(&TransactionSigned) -> Result<H160, ()> + Send + Sync>;
|
|
|
|
/// Utility to extract receipts from extrinsics.
|
|
#[derive(Clone)]
|
|
pub struct ReceiptExtractor {
|
|
/// Fetch the receipt data info.
|
|
fetch_receipt_data: FetchReceiptDataFn,
|
|
|
|
/// Fetch ethereum block hash.
|
|
fetch_eth_block_hash: FetchEthBlockHashFn,
|
|
|
|
/// Earliest block number to consider when searching for transaction receipts.
|
|
earliest_receipt_block: Option<BizinikiwiBlockNumber>,
|
|
|
|
/// Recover the ethereum address from a transaction signature.
|
|
recover_eth_address: RecoverEthAddressFn,
|
|
}
|
|
|
|
impl ReceiptExtractor {
|
|
/// Check if the block is before the earliest block.
|
|
pub fn is_before_earliest_block(&self, block_number: BizinikiwiBlockNumber) -> bool {
|
|
block_number < self.earliest_receipt_block.unwrap_or_default()
|
|
}
|
|
|
|
/// Create a new `ReceiptExtractor` with the given native to eth ratio.
|
|
pub async fn new(
|
|
api: OnlineClient<SrcChainConfig>,
|
|
earliest_receipt_block: Option<BizinikiwiBlockNumber>,
|
|
) -> Result<Self, ClientError> {
|
|
Self::new_with_custom_address_recovery(
|
|
api,
|
|
earliest_receipt_block,
|
|
Arc::new(|signed_tx: &TransactionSigned| signed_tx.recover_eth_address()),
|
|
)
|
|
.await
|
|
}
|
|
|
|
/// Create a new `ReceiptExtractor` with the given native to eth ratio.
|
|
///
|
|
/// Specify also a custom Ethereum address recovery logic.
|
|
/// Use `ReceiptExtractor::new` if the default Ethereum address recovery
|
|
/// logic ([`TransactionSigned::recover_eth_address`] based) is enough.
|
|
pub async fn new_with_custom_address_recovery(
|
|
api: OnlineClient<SrcChainConfig>,
|
|
earliest_receipt_block: Option<BizinikiwiBlockNumber>,
|
|
recover_eth_address_fn: RecoverEthAddressFn,
|
|
) -> Result<Self, ClientError> {
|
|
let api_inner = api.clone();
|
|
let fetch_eth_block_hash = Arc::new(move |block_hash, block_number| {
|
|
let api_inner = api_inner.clone();
|
|
|
|
let fut = async move {
|
|
let runtime_api = RuntimeApi::new(api_inner.runtime_api().at(block_hash));
|
|
runtime_api.eth_block_hash(U256::from(block_number)).await.ok().flatten()
|
|
};
|
|
|
|
Box::pin(fut) as Pin<Box<_>>
|
|
});
|
|
|
|
let api_inner = api.clone();
|
|
let fetch_receipt_data = Arc::new(move |block_hash| {
|
|
let api_inner = api_inner.clone();
|
|
|
|
let fut = async move {
|
|
let runtime_api = RuntimeApi::new(api_inner.runtime_api().at(block_hash));
|
|
runtime_api.eth_receipt_data().await.ok()
|
|
};
|
|
|
|
Box::pin(fut) as Pin<Box<_>>
|
|
});
|
|
|
|
Ok(Self {
|
|
fetch_receipt_data,
|
|
fetch_eth_block_hash,
|
|
earliest_receipt_block,
|
|
recover_eth_address: recover_eth_address_fn,
|
|
})
|
|
}
|
|
|
|
#[cfg(test)]
|
|
pub fn new_mock() -> Self {
|
|
let fetch_receipt_data = Arc::new(|_| Box::pin(std::future::ready(None)) as Pin<Box<_>>);
|
|
// This method is useful when testing eth - bizinikiwi mapping.
|
|
let fetch_eth_block_hash = Arc::new(|block_hash: H256, block_number: u64| {
|
|
// Generate hash from bizinikiwi block hash and number
|
|
let bytes: Vec<u8> = [block_hash.as_bytes(), &block_number.to_be_bytes()].concat();
|
|
let eth_block_hash = H256::from(keccak_256(&bytes));
|
|
Box::pin(std::future::ready(Some(eth_block_hash))) as Pin<Box<_>>
|
|
});
|
|
|
|
Self {
|
|
fetch_receipt_data,
|
|
fetch_eth_block_hash,
|
|
earliest_receipt_block: None,
|
|
recover_eth_address: Arc::new(|signed_tx: &TransactionSigned| {
|
|
signed_tx.recover_eth_address()
|
|
}),
|
|
}
|
|
}
|
|
|
|
/// Extract a [`TransactionSigned`] and a [`ReceiptInfo`] from an extrinsic.
|
|
async fn extract_from_extrinsic(
|
|
&self,
|
|
bizinikiwi_block: &BizinikiwiBlock,
|
|
eth_block_hash: H256,
|
|
ext: pezkuwi_subxt::blocks::ExtrinsicDetails<
|
|
SrcChainConfig,
|
|
pezkuwi_subxt::OnlineClient<SrcChainConfig>,
|
|
>,
|
|
call: EthTransact,
|
|
receipt_gas_info: ReceiptGasInfo,
|
|
transaction_index: usize,
|
|
) -> Result<(TransactionSigned, ReceiptInfo), ClientError> {
|
|
let events = ext.events().await?;
|
|
let block_number: U256 = bizinikiwi_block.number().into();
|
|
|
|
let success = !events.has::<EthExtrinsicRevert>().inspect_err(|err| {
|
|
log::debug!(
|
|
target: LOG_TARGET,
|
|
"Failed to lookup for EthExtrinsicRevert event in block {block_number}: {err:?}"
|
|
);
|
|
})?;
|
|
|
|
let transaction_hash = H256(keccak_256(&call.payload));
|
|
|
|
let signed_tx =
|
|
TransactionSigned::decode(&call.payload).map_err(|_| ClientError::TxDecodingFailed)?;
|
|
let from = (self.recover_eth_address)(&signed_tx).map_err(|_| {
|
|
log::error!(target: LOG_TARGET, "Failed to recover eth address from signed tx");
|
|
ClientError::RecoverEthAddressFailed
|
|
})?;
|
|
|
|
let tx_info = GenericTransaction::from_signed(
|
|
signed_tx.clone(),
|
|
receipt_gas_info.effective_gas_price,
|
|
Some(from),
|
|
);
|
|
|
|
// get logs from ContractEmitted event
|
|
let logs = events
|
|
.iter()
|
|
.filter_map(|event_details| {
|
|
let event_details = event_details.ok()?;
|
|
let event = event_details.as_event::<ContractEmitted>().ok()??;
|
|
|
|
Some(Log {
|
|
address: event.contract,
|
|
topics: event.topics,
|
|
data: Some(event.data.into()),
|
|
block_number,
|
|
transaction_hash,
|
|
transaction_index: transaction_index.into(),
|
|
block_hash: eth_block_hash,
|
|
log_index: event_details.index().into(),
|
|
..Default::default()
|
|
})
|
|
})
|
|
.collect();
|
|
|
|
let contract_address = if tx_info.to.is_none() {
|
|
Some(create1(
|
|
&from,
|
|
tx_info
|
|
.nonce
|
|
.unwrap_or_default()
|
|
.try_into()
|
|
.map_err(|_| ClientError::ConversionFailed)?,
|
|
))
|
|
} else {
|
|
None
|
|
};
|
|
|
|
let receipt = ReceiptInfo::new(
|
|
eth_block_hash,
|
|
block_number,
|
|
contract_address,
|
|
from,
|
|
logs,
|
|
tx_info.to,
|
|
receipt_gas_info.effective_gas_price,
|
|
U256::from(receipt_gas_info.gas_used),
|
|
success,
|
|
transaction_hash,
|
|
transaction_index.into(),
|
|
tx_info.r#type.unwrap_or_default(),
|
|
);
|
|
Ok((signed_tx, receipt))
|
|
}
|
|
|
|
/// Extract receipts from block.
|
|
pub async fn extract_from_block(
|
|
&self,
|
|
block: &BizinikiwiBlock,
|
|
) -> Result<Vec<(TransactionSigned, ReceiptInfo)>, ClientError> {
|
|
if self.is_before_earliest_block(block.number()) {
|
|
return Ok(vec![]);
|
|
}
|
|
|
|
let ext_iter = self.get_block_extrinsics(block).await?;
|
|
|
|
let bizinikiwi_block_number = block.number() as u64;
|
|
let bizinikiwi_block_hash = block.hash();
|
|
let eth_block_hash =
|
|
(self.fetch_eth_block_hash)(bizinikiwi_block_hash, bizinikiwi_block_number)
|
|
.await
|
|
.unwrap_or(bizinikiwi_block_hash);
|
|
|
|
// Process extrinsics in order while maintaining parallelism within buffer window
|
|
stream::iter(ext_iter)
|
|
.map(|(ext, call, receipt, ext_idx)| async move {
|
|
self.extract_from_extrinsic(block, eth_block_hash, ext, call, receipt, ext_idx)
|
|
.await
|
|
.inspect_err(|err| {
|
|
log::warn!(target: LOG_TARGET, "Error extracting extrinsic: {err:?}");
|
|
})
|
|
})
|
|
.buffered(10)
|
|
.collect::<Vec<Result<_, _>>>()
|
|
.await
|
|
.into_iter()
|
|
.collect::<Result<Vec<_>, _>>()
|
|
}
|
|
|
|
/// Return the ETH extrinsics of the block grouped with reconstruction receipt info and
|
|
/// extrinsic index
|
|
pub async fn get_block_extrinsics(
|
|
&self,
|
|
block: &BizinikiwiBlock,
|
|
) -> Result<
|
|
impl Iterator<
|
|
Item = (
|
|
ExtrinsicDetails<SrcChainConfig, OnlineClient<SrcChainConfig>>,
|
|
EthTransact,
|
|
ReceiptGasInfo,
|
|
usize,
|
|
),
|
|
>,
|
|
ClientError,
|
|
> {
|
|
// Filter extrinsics from pezpallet_revive
|
|
let extrinsics = block.extrinsics().await.inspect_err(|err| {
|
|
log::debug!(target: LOG_TARGET, "Error fetching for #{:?} extrinsics: {err:?}", block.number());
|
|
})?;
|
|
|
|
let receipt_data = (self.fetch_receipt_data)(block.hash())
|
|
.await
|
|
.ok_or(ClientError::ReceiptDataNotFound)?;
|
|
let extrinsics: Vec<_> = extrinsics
|
|
.iter()
|
|
.enumerate()
|
|
.flat_map(|(ext_idx, ext)| {
|
|
let call = ext.as_extrinsic::<EthTransact>().ok()??;
|
|
Some((ext, call, ext_idx))
|
|
})
|
|
.collect();
|
|
|
|
// Sanity check we received enough data from the pezpallet revive.
|
|
if receipt_data.len() != extrinsics.len() {
|
|
log::error!(
|
|
target: LOG_TARGET,
|
|
"Receipt data length ({}) does not match extrinsics length ({})",
|
|
receipt_data.len(),
|
|
extrinsics.len()
|
|
);
|
|
Err(ClientError::ReceiptDataLengthMismatch)
|
|
} else {
|
|
Ok(extrinsics
|
|
.into_iter()
|
|
.zip(receipt_data)
|
|
.map(|((extr, call, ext_idx), rec)| (extr, call, rec, ext_idx)))
|
|
}
|
|
}
|
|
|
|
/// Extract a [`TransactionSigned`] and a [`ReceiptInfo`] for a specific transaction in a
|
|
/// [`BizinikiwiBlock`]
|
|
pub async fn extract_from_transaction(
|
|
&self,
|
|
block: &BizinikiwiBlock,
|
|
transaction_index: usize,
|
|
) -> Result<(TransactionSigned, ReceiptInfo), ClientError> {
|
|
let ext_iter = self.get_block_extrinsics(block).await?;
|
|
|
|
let (ext, eth_call, receipt_gas_info, _) = ext_iter
|
|
.into_iter()
|
|
.find(|(_, _, _, ext_idx)| *ext_idx == transaction_index)
|
|
.ok_or(ClientError::EthExtrinsicNotFound)?;
|
|
|
|
let bizinikiwi_block_number = block.number() as u64;
|
|
let bizinikiwi_block_hash = block.hash();
|
|
let eth_block_hash =
|
|
(self.fetch_eth_block_hash)(bizinikiwi_block_hash, bizinikiwi_block_number)
|
|
.await
|
|
.unwrap_or(bizinikiwi_block_hash);
|
|
|
|
self.extract_from_extrinsic(
|
|
block,
|
|
eth_block_hash,
|
|
ext,
|
|
eth_call,
|
|
receipt_gas_info,
|
|
transaction_index,
|
|
)
|
|
.await
|
|
}
|
|
|
|
/// Get the Ethereum block hash for the Bizinikiwi block with specific hash.
|
|
pub async fn get_ethereum_block_hash(
|
|
&self,
|
|
block_hash: &H256,
|
|
block_number: u64,
|
|
) -> Option<H256> {
|
|
(self.fetch_eth_block_hash)(*block_hash, block_number).await
|
|
}
|
|
}
|