Skip to content

Commit

Permalink
EVM construct block (#2272)
Browse files Browse the repository at this point in the history
* Implement evm_try_connect_block and evm_try_finalize_block

* Rename to construct block

* Revert changes on dst20 ffi

* Fixes to use single mutex in txqueue

* Fix lint

* Better refactor

* Fixes to implement cloning txqueuedata in construct block

* Revert test runner

* remove explicit type declaration

* Fix lint

* Fixes for mutex lock to be atomic for construct and finalize block

* lint fix

* Switch atomic ref count onto transactionqueue

* Fix lint

* Remove unused TransactionQueue methods

* Revert default for transactionqueue

* Add comments

* Add comments on errors

* Format imports

---------

Co-authored-by: jouzo <[email protected]>
  • Loading branch information
sieniven and Jouzo authored Aug 4, 2023
1 parent 481719d commit ed30200
Show file tree
Hide file tree
Showing 9 changed files with 182 additions and 181 deletions.
56 changes: 37 additions & 19 deletions lib/ain-evm/src/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,14 @@ use std::sync::Arc;
use anyhow::format_err;
use ethereum::{AccessList, Account, Block, Log, PartialHeader, TransactionV2};
use ethereum_types::{Bloom, BloomInput, H160, U256};
use hex::FromHex;
use log::debug;
use primitive_types::H256;
use vsdb_core::vsdb_set_base_dir;

use crate::backend::{EVMBackend, EVMBackendError, InsufficientBalance, Vicinity};
use crate::block::INITIAL_BASE_FEE;
use crate::executor::TxResponse;
use crate::fee::calculate_prepay_gas_fee;
use crate::fee::{calculate_prepay_gas_fee, get_tx_max_gas_price};
use crate::gas::check_tx_intrinsic_gas;
use crate::receipt::ReceiptService;
use crate::services::SERVICES;
Expand Down Expand Up @@ -164,31 +163,49 @@ impl EVMCoreService {
}))
}

/// Validates a raw tx.
///
/// The validation checks of the tx before we consider it to be valid are:
/// 1. Account nonce check: verify that the tx nonce must be more than or equal to the account nonce.
/// 2. Gas price check: verify that the maximum gas price is minimally of the block initial base fee.
/// 3. Account balance check: verify that the account balance must minimally have the tx prepay gas fee.
/// 4. Intrinsic gas limit check: verify that the tx intrinsic gas is within the tx gas limit.
/// 5. Gas limit check: verify that the tx gas limit is not higher than the maximum gas per block.
///
/// # Arguments
///
/// * `tx` - The raw tx.
/// * `queue_id` - The queue_id queue number.
/// * `use_context` - Flag to call tx with stack executor.
///
/// # Returns
///
/// Returns the signed tx, tx prepay gas fees and the gas used to call the tx.
pub fn validate_raw_tx(
&self,
tx: &str,
queue_id: u64,
use_context: bool,
) -> Result<ValidateTxInfo, Box<dyn Error>> {
debug!("[validate_raw_tx] raw transaction : {:#?}", tx);
let buffer = <Vec<u8>>::from_hex(tx)?;
let tx: TransactionV2 = ethereum::EnvelopedDecodable::decode(&buffer)
let signed_tx = SignedTx::try_from(tx)
.map_err(|_| format_err!("Error: decoding raw tx to TransactionV2"))?;
debug!("[validate_raw_tx] TransactionV2 : {:#?}", tx);
debug!(
"[validate_raw_tx] TransactionV2 : {:#?}",
signed_tx.transaction
);

let block_number = self
.storage
.get_latest_block()
.map(|block| block.header.number)
.unwrap_or_default();

debug!("[validate_raw_tx] block_number : {:#?}", block_number);

let signed_tx: SignedTx = tx.try_into()?;
let nonce = self
.get_nonce(signed_tx.sender, block_number)
.map_err(|e| format_err!("Error getting nonce {e}"))?;

debug!(
"[validate_raw_tx] signed_tx.sender : {:#?}",
signed_tx.sender
Expand All @@ -209,16 +226,21 @@ impl EVMCoreService {
.into());
}

// Validate tx gas price with initial block base fee
let tx_gas_price = get_tx_max_gas_price(&signed_tx);
if tx_gas_price < INITIAL_BASE_FEE {
debug!("[validate_raw_tx] tx gas price is lower than initial block base fee");
return Err(format_err!("tx gas price is lower than initial block base fee").into());
}

let balance = self
.get_balance(signed_tx.sender, block_number)
.map_err(|e| format_err!("Error getting balance {e}"))?;

debug!("[validate_raw_tx] Account balance : {:x?}", balance);

let prepay_fee = calculate_prepay_gas_fee(&signed_tx)?;
debug!("[validate_raw_tx] Account balance : {:x?}", balance);
debug!("[validate_raw_tx] prepay_fee : {:x?}", prepay_fee);

let gas_limit = signed_tx.gas_limit();
// Validate tx prepay fees with account balance
if balance < prepay_fee {
debug!("[validate_raw_tx] insufficient balance to pay fees");
return Err(format_err!("insufficient balance to pay fees").into());
Expand All @@ -227,12 +249,13 @@ impl EVMCoreService {
// Validate tx gas limit with intrinsic gas
check_tx_intrinsic_gas(&signed_tx)?;

// Validate gas limit
let gas_limit = signed_tx.gas_limit();
if gas_limit > MAX_GAS_PER_BLOCK {
debug!("[validate_raw_tx] Gas limit higher than MAX_GAS_PER_BLOCK");
return Err(format_err!("Gas limit higher than MAX_GAS_PER_BLOCK").into());
debug!("[validate_raw_tx] gas limit higher than MAX_GAS_PER_BLOCK");
return Err(format_err!("gas limit higher than MAX_GAS_PER_BLOCK").into());
}

// Get tx gas usage
let used_gas = if use_context {
let TxResponse { used_gas, .. } = self.call(EthCallArgs {
caller: Some(signed_tx.sender),
Expand Down Expand Up @@ -324,11 +347,6 @@ impl EVMCoreService {
self.tx_queues.get_queue_id()
}

pub fn clear(&self, queue_id: u64) -> Result<(), EVMError> {
self.tx_queues.clear(queue_id)?;
Ok(())
}

pub fn remove(&self, queue_id: u64) {
self.tx_queues.remove(queue_id);
}
Expand Down
121 changes: 62 additions & 59 deletions lib/ain-evm/src/evm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@ use std::sync::Arc;

use ain_contracts::{Contracts, CONTRACT_ADDRESSES};
use anyhow::format_err;
use ethereum::{Block, PartialHeader, ReceiptV3, TransactionV2};
use ethereum::{Block, PartialHeader, ReceiptV3};
use ethereum_types::{Bloom, H160, H64, U256};
use hex::FromHex;
use log::debug;
use primitive_types::H256;

Expand All @@ -26,7 +25,7 @@ use crate::traits::Executor;
use crate::transaction::system::{BalanceUpdate, DST20Data, DeployContractData, SystemTx};
use crate::transaction::SignedTx;
use crate::trie::GENESIS_STATE_ROOT;
use crate::txqueue::QueueTx;
use crate::txqueue::{BlockData, QueueTx};

pub struct EVMServices {
pub core: EVMCoreService,
Expand Down Expand Up @@ -100,16 +99,17 @@ impl EVMServices {
}
}

pub fn finalize_block(
pub fn construct_block(
&self,
queue_id: u64,
update_state: bool,
difficulty: u32,
beneficiary: H160,
timestamp: u64,
dvm_block_number: u64,
) -> Result<FinalizedBlockInfo, Box<dyn Error>> {
let queue_len = self.core.tx_queues.count(queue_id)?;
let tx_queue = self.core.tx_queues.get_queue(queue_id)?;
let mut queue = tx_queue.data.lock().unwrap();
let queue_len = queue.transactions.len();
let mut all_transactions = Vec::with_capacity(queue_len);
let mut failed_transactions = Vec::with_capacity(queue_len);
let mut receipts_v3: Vec<ReceiptV3> = Vec::with_capacity(queue_len);
Expand Down Expand Up @@ -149,7 +149,7 @@ impl EVMServices {
};

let base_fee = self.block.calculate_base_fee(parent_hash);
debug!("[finalize_block] Block base fee: {}", base_fee);
debug!("[construct_block] Block base fee: {}", base_fee);

let mut backend = EVMBackend::from_root(
state_root,
Expand All @@ -176,7 +176,7 @@ impl EVMServices {
executor.update_storage(address, storage)?;
}

for queue_item in self.core.tx_queues.get_cloned_vec(queue_id)? {
for queue_item in queue.transactions.clone() {
match queue_item.queue_tx {
QueueTx::SignedTx(signed_tx) => {
let nonce = executor.get_nonce(&signed_tx.sender);
Expand Down Expand Up @@ -214,22 +214,22 @@ impl EVMServices {
}
QueueTx::SystemTx(SystemTx::EvmIn(BalanceUpdate { address, amount })) => {
debug!(
"[finalize_block] EvmIn for address {:x?}, amount: {}, queue_id {}",
"[construct_block] EvmIn for address {:x?}, amount: {}, queue_id {}",
address, amount, queue_id
);
if let Err(e) = executor.add_balance(address, amount) {
debug!("[finalize_block] EvmIn failed with {e}");
debug!("[construct_block] EvmIn failed with {e}");
failed_transactions.push(hex::encode(queue_item.tx_hash));
}
}
QueueTx::SystemTx(SystemTx::EvmOut(BalanceUpdate { address, amount })) => {
debug!(
"[finalize_block] EvmOut for address {}, amount: {}",
"[construct_block] EvmOut for address {}, amount: {}",
address, amount
);

if let Err(e) = executor.sub_balance(address, amount) {
debug!("[finalize_block] EvmOut failed with {e}");
debug!("[construct_block] EvmOut failed with {e}");
failed_transactions.push(hex::encode(queue_item.tx_hash));
}
}
Expand All @@ -239,7 +239,7 @@ impl EVMServices {
address,
})) => {
debug!(
"[finalize_block] DeployContract for address {}, name {}, symbol {}",
"[construct_block] DeployContract for address {}, name {}, symbol {}",
address, name, symbol
);

Expand All @@ -250,7 +250,7 @@ impl EVMServices {
} = EVMServices::dst20_contract(&mut executor, address, name, symbol)?;

if let Err(e) = executor.deploy_contract(address, bytecode, storage) {
debug!("[finalize_block] EvmOut failed with {e}");
debug!("[construct_block] EvmOut failed with {e}");
}
}
QueueTx::SystemTx(SystemTx::DST20Bridge(DST20Data {
Expand All @@ -260,19 +260,19 @@ impl EVMServices {
out,
})) => {
debug!(
"[finalize_block] DST20Bridge for to {}, contract {}, amount {}, out {}",
"[construct_block] DST20Bridge for to {}, contract {}, amount {}, out {}",
to, contract, amount, out
);

match EVMServices::bridge_dst20(&mut executor, contract, to, amount, out) {
Ok(DST20BridgeInfo { address, storage }) => {
if let Err(e) = executor.update_storage(address, storage) {
debug!("[finalize_block] EvmOut failed with {e}");
debug!("[construct_block] EvmOut failed with {e}");
failed_transactions.push(hex::encode(queue_item.tx_hash));
}
}
Err(e) => {
debug!("[finalize_block] EvmOut failed with {e}");
debug!("[construct_block] EvmOut failed with {e}");
failed_transactions.push(hex::encode(queue_item.tx_hash));
}
}
Expand All @@ -282,15 +282,26 @@ impl EVMServices {
executor.commit();
}

let total_burnt_fees = U256::from(total_gas_used) * base_fee;
let total_priority_fees = total_gas_fees - total_burnt_fees;
debug!(
"[construct_block] Total burnt fees : {:#?}",
total_burnt_fees
);
debug!(
"[construct_block] Total priority fees : {:#?}",
total_priority_fees
);

if (total_burnt_fees + total_priority_fees) != queue.total_fees {
return Err(format_err!("EVM block rejected because block total fees != (burnt fees + priority fees). Burnt fees: {}, priority fees: {}, total fees: {}", total_burnt_fees, total_priority_fees, queue.total_fees).into());
}

let block = Block::new(
PartialHeader {
parent_hash,
beneficiary,
state_root: if update_state {
backend.commit()
} else {
backend.root()
},
state_root: backend.commit(),
receipts_root: ReceiptService::get_receipts_root(&receipts_v3),
logs_bloom,
difficulty: U256::from(difficulty),
Expand All @@ -316,8 +327,27 @@ impl EVMServices {
block.header.hash(),
block.header.number,
);
queue.block_data = Some(BlockData {
block: block.clone(),
receipts,
});

Ok(FinalizedBlockInfo {
block_hash: *block.header.hash().as_fixed_bytes(),
failed_transactions,
total_burnt_fees,
total_priority_fees,
})
}

pub fn finalize_block(&self, queue_id: u64) -> Result<(), Box<dyn Error>> {
{
let tx_queue = self.core.tx_queues.get_queue(queue_id)?;
let queue = tx_queue.data.lock().unwrap();
let Some(BlockData { block, receipts }) = queue.block_data.clone() else {
return Err(format_err!("no constructed EVM block exist in queue id").into());
};

if update_state {
debug!(
"[finalize_block] Finalizing block number {:#x}, state_root {:#x}",
block.header.number, block.header.state_root
Expand All @@ -329,48 +359,21 @@ impl EVMServices {
self.receipt.put_receipts(receipts);
self.filters.add_block_to_filters(block.header.hash());
}
self.core.tx_queues.remove(queue_id);

let total_burnt_fees = U256::from(total_gas_used) * base_fee;
let total_priority_fees = total_gas_fees - total_burnt_fees;
debug!(
"[finalize_block] Total burnt fees : {:#?}",
total_burnt_fees
);
debug!(
"[finalize_block] Total priority fees : {:#?}",
total_priority_fees
);

let total_fees = self.core.tx_queues.get_total_fees(queue_id)?;
if (total_burnt_fees + total_priority_fees) != total_fees {
return Err(format_err!("EVM block rejected because block total fees != (burnt fees + priority fees). Burnt fees: {}, priority fees: {}, total fees: {}", total_burnt_fees, total_priority_fees, total_fees).into());
}

if update_state {
self.core.tx_queues.remove(queue_id);
}

Ok(FinalizedBlockInfo {
block_hash: *block.header.hash().as_fixed_bytes(),
failed_transactions,
total_burnt_fees,
total_priority_fees,
})
Ok(())
}

pub fn verify_tx_fees(&self, tx: &str, use_context: bool) -> Result<(), Box<dyn Error>> {
pub fn verify_tx_fees(&self, tx: &str) -> Result<(), Box<dyn Error>> {
debug!("[verify_tx_fees] raw transaction : {:#?}", tx);
let buffer = <Vec<u8>>::from_hex(tx)?;
let tx: TransactionV2 = ethereum::EnvelopedDecodable::decode(&buffer)
let signed_tx = SignedTx::try_from(tx)
.map_err(|_| format_err!("Error: decoding raw tx to TransactionV2"))?;
debug!("[verify_tx_fees] TransactionV2 : {:#?}", tx);
let signed_tx: SignedTx = tx.try_into()?;

let mut block_fee = self.block.calculate_base_fee(H256::zero());
if use_context {
block_fee = self.block.calculate_next_block_base_fee();
}
debug!(
"[verify_tx_fees] TransactionV2 : {:#?}",
signed_tx.transaction
);

let block_fee = self.block.calculate_next_block_base_fee();
let tx_gas_price = get_tx_max_gas_price(&signed_tx);
if tx_gas_price < block_fee {
debug!("[verify_tx_fees] tx gas price is lower than block base fee");
Expand Down
Loading

0 comments on commit ed30200

Please sign in to comment.