Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Support injection of ethereum logs for transactions. #1041

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

241 changes: 181 additions & 60 deletions frame/ethereum/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ use frame_support::{
weights::Weight,
};
use frame_system::{pallet_prelude::OriginFor, CheckWeight, WeightInfo};
use pallet_evm::{BlockHashMapping, FeeCalculator, GasWeightMapping, Runner};
use pallet_evm::{BlockHashMapping, CurrentLogs, FeeCalculator, GasWeightMapping, Runner};
use sp_runtime::{
generic::DigestItem,
traits::{DispatchInfoOf, Dispatchable, One, Saturating, UniqueSaturatedInto, Zero},
Expand Down Expand Up @@ -342,6 +342,10 @@ pub mod pallet {
#[pallet::getter(fn block_hash)]
pub(super) type BlockHash<T: Config> = StorageMap<_, Twox64Concat, U256, H256, ValueQuery>;

/// Injected transactions should have unique nonce, here we store current
#[pallet::storage]
pub(super) type InjectedNonce<T: Config> = StorageValue<_, U256, ValueQuery>;

#[pallet::genesis_config]
#[derive(Default)]
pub struct GenesisConfig {}
Expand Down Expand Up @@ -408,7 +412,7 @@ impl<T: Config> Pallet<T> {
}
};
cumulative_gas_used = used_gas;
Self::logs_bloom(logs, &mut logs_bloom);
Self::logs_bloom(logs.iter(), &mut logs_bloom);
}

let ommers = Vec::<ethereum::Header>::new();
Expand Down Expand Up @@ -462,10 +466,10 @@ impl<T: Config> Pallet<T> {
}
}

fn logs_bloom(logs: Vec<Log>, bloom: &mut Bloom) {
fn logs_bloom<'a>(logs: impl IntoIterator<Item = &'a Log>, bloom: &'a mut Bloom) {
for log in logs {
bloom.accrue(BloomInput::Raw(&log.address[..]));
for topic in log.topics {
for topic in &log.topics {
bloom.accrue(BloomInput::Raw(&topic[..]));
}
}
Expand Down Expand Up @@ -549,68 +553,74 @@ impl<T: Config> Pallet<T> {
let transaction_index = pending.len() as u32;

let (reason, status, used_gas, dest, extra_data) = match info {
CallOrCreateInfo::Call(info) => (
info.exit_reason.clone(),
TransactionStatus {
transaction_hash,
transaction_index,
from: source,
to,
contract_address: None,
logs: info.logs.clone(),
logs_bloom: {
let mut bloom: Bloom = Bloom::default();
Self::logs_bloom(info.logs, &mut bloom);
bloom
CallOrCreateInfo::Call(info) => {
let logs = <CurrentLogs<T>>::take();
(
info.exit_reason.clone(),
TransactionStatus {
transaction_hash,
transaction_index,
from: source,
to,
contract_address: None,
logs_bloom: {
let mut bloom: Bloom = Bloom::default();
Self::logs_bloom(logs.iter(), &mut bloom);
bloom
},
logs,
},
},
info.used_gas,
to,
match info.exit_reason {
ExitReason::Revert(_) => {
const LEN_START: usize = 36;
const MESSAGE_START: usize = 68;

let data = info.value;
let data_len = data.len();
if data_len > MESSAGE_START {
let message_len = U256::from(&data[LEN_START..MESSAGE_START])
.saturated_into::<usize>();
let message_end = MESSAGE_START.saturating_add(
message_len.min(T::ExtraDataLength::get() as usize),
);

if data_len >= message_end {
data[MESSAGE_START..message_end].to_vec()
info.used_gas,
to,
match info.exit_reason {
ExitReason::Revert(_) => {
const LEN_START: usize = 36;
const MESSAGE_START: usize = 68;

let data = info.value;
let data_len = data.len();
if data_len > MESSAGE_START {
let message_len = U256::from(&data[LEN_START..MESSAGE_START])
.saturated_into::<usize>();
let message_end = MESSAGE_START.saturating_add(
message_len.min(T::ExtraDataLength::get() as usize),
);

if data_len >= message_end {
data[MESSAGE_START..message_end].to_vec()
} else {
data
}
} else {
data
}
} else {
data
}
}
_ => vec![],
},
),
CallOrCreateInfo::Create(info) => (
info.exit_reason,
TransactionStatus {
transaction_hash,
transaction_index,
from: source,
to,
contract_address: Some(info.value),
logs: info.logs.clone(),
logs_bloom: {
let mut bloom: Bloom = Bloom::default();
Self::logs_bloom(info.logs, &mut bloom);
bloom
_ => vec![],
},
},
info.used_gas,
Some(info.value),
Vec::new(),
),
)
}
CallOrCreateInfo::Create(info) => {
let logs = <CurrentLogs<T>>::take();
(
info.exit_reason,
TransactionStatus {
transaction_hash,
transaction_index,
from: source,
to,
contract_address: Some(info.value),
logs_bloom: {
let mut bloom: Bloom = Bloom::default();
Self::logs_bloom(logs.iter(), &mut bloom);
bloom
},
logs,
},
info.used_gas,
Some(info.value),
Vec::new(),
)
}
};

let receipt = {
Expand Down Expand Up @@ -670,6 +680,69 @@ impl<T: Config> Pallet<T> {
})
}

pub fn flush_injected_transaction() {
use ethereum::{
EIP658ReceiptData, EnvelopedEncodable, TransactionSignature, TransactionV0,
};

assert!(
fp_consensus::find_pre_log(&frame_system::Pallet::<T>::digest()).is_err(),
"this method is supposed to be called only from other pallets",
);

let logs = <CurrentLogs<T>>::take();
if logs.is_empty() {
return;
}

let nonce = <InjectedNonce<T>>::get()
.checked_add(1u32.into())
.expect("u256 should be enough");
<InjectedNonce<T>>::set(nonce);

let transaction = Transaction::Legacy(TransactionV0 {
nonce,
gas_price: 0.into(),
gas_limit: 0.into(),
action: TransactionAction::Call(H160([0; 20])),
value: 0.into(),
// zero selector, this transaction always has same sender, so all data should be acquired from logs
input: Vec::from([0, 0, 0, 0]),
// if v is not 27 - then we need to pass some other validity checks
signature: TransactionSignature::new(27, H256([0x88; 32]), H256([0x88; 32])).unwrap(),
});

let transaction_hash = H256::from_slice(
sp_io::hashing::keccak_256(&EnvelopedEncodable::encode(&transaction)).as_slice(),
);
let transaction_index = <Pending<T>>::get().len() as u32;

let logs_bloom = {
let mut bloom: Bloom = Bloom::default();
Self::logs_bloom(&logs, &mut bloom);
bloom
};

let status = TransactionStatus {
transaction_hash,
transaction_index,
from: H160::default(),
to: None,
contract_address: None,
logs_bloom,
logs: logs.clone(),
};

let receipt = Receipt::Legacy(EIP658ReceiptData {
status_code: 1,
used_gas: 0u32.into(),
logs_bloom,
logs,
});

<Pending<T>>::append((transaction, status, receipt));
}

/// Get current block hash
pub fn current_block_hash() -> Option<H256> {
Self::current_block().map(|block| block.header.hash())
Expand Down Expand Up @@ -969,3 +1042,51 @@ impl From<InvalidEvmTransactionError> for InvalidTransactionWrapper {
}
}
}

#[derive(TypeInfo, PartialEq, Eq, Clone, Debug, Encode, Decode, Default)]
pub struct FakeTransactionFinalizer<T>(PhantomData<T>);

impl<T> FakeTransactionFinalizer<T> {
pub fn new() -> Self {
Self(Default::default())
}
}

impl<T: Config + TypeInfo + core::fmt::Debug + Send + Sync> sp_runtime::traits::SignedExtension
for FakeTransactionFinalizer<T>
{
const IDENTIFIER: &'static str = "FakeTransactionFinalizer";

type AccountId = T::AccountId;

type Call = T::RuntimeCall;

type AdditionalSigned = ();

type Pre = ();

fn additional_signed(&self) -> Result<Self::AdditionalSigned, TransactionValidityError> {
Ok(())
}

fn pre_dispatch(
self,
_who: &Self::AccountId,
_call: &Self::Call,
_info: &DispatchInfoOf<Self::Call>,
_len: usize,
) -> Result<Self::Pre, TransactionValidityError> {
Ok(())
}

fn post_dispatch(
_pre: Option<Self::Pre>,
_info: &DispatchInfoOf<Self::Call>,
_post_info: &sp_runtime::traits::PostDispatchInfoOf<Self::Call>,
_len: usize,
_result: &sp_runtime::DispatchResult,
) -> Result<(), TransactionValidityError> {
<Pallet<T>>::flush_injected_transaction();
Ok(())
}
}
27 changes: 27 additions & 0 deletions frame/evm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,13 @@ pub mod pallet {
}
}

#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
fn on_finalize(_: T::BlockNumber) {
assert_eq!(<CurrentLogs<T>>::get().len(), 0, "fake transaction finalizer is not initialized, as some logs was left after block is finished");
}
}

#[pallet::call]
impl<T: Config> Pallet<T> {
/// Withdraw balance from EVM into currency/balances pallet.
Expand Down Expand Up @@ -510,6 +517,11 @@ pub mod pallet {
#[pallet::getter(fn account_storages)]
pub type AccountStorages<T: Config> =
StorageDoubleMap<_, Blake2_128Concat, H160, Blake2_128Concat, H256, H256, ValueQuery>;

/// Written on log, reset after transaction
/// Should be empty between transactions
#[pallet::storage]
pub type CurrentLogs<T: Config> = StorageValue<_, Vec<Log>, ValueQuery>;
}

/// Type alias for currency balance.
Expand Down Expand Up @@ -733,6 +745,21 @@ impl<T: Config> Pallet<T> {
<AccountCodes<T>>::insert(address, code);
}

/// Add log to be injected in either real or fake ethereum transaction
pub fn deposit_log(log: Log) {
log::trace!(
target: "evm",
"Inserting mirrored log for {:?}, topics ({}) {:?}, data ({}): {:?}]",
log.address,
log.topics.len(),
log.topics,
log.data.len(),
log.data
);
<CurrentLogs<T>>::append(log);
// Log event is not emitted here, as these logs belong to pallets, which will emit pallet-specific logs on substrate side by themselves
}

/// Get the account basic in EVM format.
pub fn account_basic(address: &H160) -> (Account, frame_support::weights::Weight) {
let account_id = T::AddressMapping::into_account_id(*address);
Expand Down
Loading