diff --git a/Cargo.lock b/Cargo.lock index f0cc02e..0fcd7a9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1558,15 +1558,30 @@ dependencies = [ "syn 2.0.105", ] +[[package]] +name = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec 0.6.3", +] + [[package]] name = "bit-set" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" dependencies = [ - "bit-vec", + "bit-vec 0.8.0", ] +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + [[package]] name = "bit-vec" version = "0.8.0" @@ -6228,8 +6243,8 @@ version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fcdab19deb5195a31cf7726a210015ff1496ba1464fd42cb4f537b8b01b471f" dependencies = [ - "bit-set", - "bit-vec", + "bit-set 0.8.0", + "bit-vec 0.8.0", "bitflags 2.9.1", "lazy_static", "num-traits", @@ -9698,6 +9713,7 @@ dependencies = [ "alloy-sol-types", "async-trait", "auto_impl", + "bit-set 0.5.3", "bls_on_arkworks", "bytes 1.10.1", "cfg-if", diff --git a/Cargo.toml b/Cargo.toml index 4cd02ef..f1a2230 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -105,6 +105,7 @@ thiserror = "1.0" tokio = { version = "1.36", features = ["full"] } tokio-stream = "0.1" tracing = "0.1" +bit-set = "0.5.3" # precompiles deps diff --git a/src/consensus/parlia/consensus.rs b/src/consensus/parlia/consensus.rs index 7c4ada1..e2c4b16 100644 --- a/src/consensus/parlia/consensus.rs +++ b/src/consensus/parlia/consensus.rs @@ -1,5 +1,6 @@ -use super::{ParliaHeaderValidator, SnapshotProvider, BscConsensusValidator, Snapshot, TransactionSplitter, SplitTransactions, constants::{DIFF_INTURN, DIFF_NOTURN, EXTRA_VANITY, EXTRA_SEAL, VALIDATOR_NUMBER_SIZE, VALIDATOR_BYTES_LEN_AFTER_LUBAN, VALIDATOR_BYTES_LEN_BEFORE_LUBAN, TURN_LENGTH_SIZE}}; +use super::{ParliaHeaderValidator, SnapshotProvider, BscConsensusValidator, Snapshot, TransactionSplitter, SplitTransactions, VoteAttestation, constants::{DIFF_INTURN, DIFF_NOTURN, EXTRA_VANITY, EXTRA_SEAL, VALIDATOR_NUMBER_SIZE, VALIDATOR_BYTES_LEN_AFTER_LUBAN, VALIDATOR_BYTES_LEN_BEFORE_LUBAN, TURN_LENGTH_SIZE}}; use super::error::ParliaConsensusError; +use alloy_rlp::Decodable; use alloy_consensus::{Header, TxReceipt, Transaction, BlockHeader}; use reth_primitives_traits::{GotExpected, SignerRecoverable}; use crate::{ @@ -514,6 +515,7 @@ where fn get_epoch_length(&self, header: &alloy_consensus::Header) -> u64 { self.get_epoch_length(header) } + fn get_validator_bytes_from_header(&self, header: &alloy_consensus::Header) -> Option> { self.get_validator_bytes_from_header(header) } @@ -521,6 +523,10 @@ where fn get_turn_length_from_header(&self, header: &alloy_consensus::Header) -> Result, ParliaConsensusError> { self.get_turn_length_from_header(header) } + + fn get_vote_attestation_from_header(&self, header: &alloy_consensus::Header) -> Result, ParliaConsensusError> { + self.get_vote_attestation_from_header(header) + } } impl HeaderValidator
for ParliaConsensus @@ -768,4 +774,45 @@ where Ok(Some(turn_length)) } + pub fn get_vote_attestation_from_header( + &self, + header: &Header, + ) -> Result, ParliaConsensusError> { + let extra_len = header.extra_data.len(); + + if extra_len <= EXTRA_VANITY + EXTRA_SEAL { + return Ok(None); + } + + if !self.chain_spec.is_luban_active_at_block(header.number) { + return Ok(None); + } + + let mut raw_attestation_data = if header.number % self.get_epoch_length(header) != 0 { + &header.extra_data[EXTRA_VANITY..extra_len - EXTRA_SEAL] + } else { + let validator_count = + header.extra_data[EXTRA_VANITY + VALIDATOR_NUMBER_SIZE - 1] as usize; + let mut start = + EXTRA_VANITY + VALIDATOR_NUMBER_SIZE + validator_count * VALIDATOR_BYTES_LEN_AFTER_LUBAN; + let is_bohr_active = self.chain_spec.is_bohr_active_at_timestamp(header.timestamp); + if is_bohr_active { + start += TURN_LENGTH_SIZE; + } + let end = extra_len - EXTRA_SEAL; + if end <= start { + return Ok(None) + } + &header.extra_data[start..end] + }; + if raw_attestation_data.is_empty() { + return Ok(None); + } + + Ok(Some( + Decodable::decode(&mut raw_attestation_data) + .map_err(|_| ParliaConsensusError::ABIDecodeInnerError)?, + )) + } + } \ No newline at end of file diff --git a/src/consensus/parlia/constants.rs b/src/consensus/parlia/constants.rs index a2f1049..0bba936 100644 --- a/src/consensus/parlia/constants.rs +++ b/src/consensus/parlia/constants.rs @@ -19,4 +19,6 @@ pub const TURN_LENGTH_SIZE: usize = 1; /// Difficulty for in-turn block (when it's the proposer's turn) pub const DIFF_INTURN: U256 = U256::from_limbs([2, 0, 0, 0]); /// Difficulty for out-of-turn block (when it's not the proposer's turn) -pub const DIFF_NOTURN: U256 = U256::from_limbs([1, 0, 0, 0]); \ No newline at end of file +pub const DIFF_NOTURN: U256 = U256::from_limbs([1, 0, 0, 0]); + +pub const COLLECT_ADDITIONAL_VOTES_REWARD_RATIO: usize = 100; \ No newline at end of file diff --git a/src/consensus/parlia/mod.rs b/src/consensus/parlia/mod.rs index f8fbcad..09f6abb 100644 --- a/src/consensus/parlia/mod.rs +++ b/src/consensus/parlia/mod.rs @@ -51,6 +51,7 @@ pub trait ParliaConsensusObject: fn get_epoch_length(&self, header: &alloy_consensus::Header) -> u64; fn get_validator_bytes_from_header(&self, header: &alloy_consensus::Header) -> Option>; fn get_turn_length_from_header(&self, header: &alloy_consensus::Header) -> Result, ParliaConsensusError>; + fn get_vote_attestation_from_header(&self, header: &alloy_consensus::Header) -> Result, ParliaConsensusError>; } // Note: concrete implementation is provided for `ParliaConsensus` in `consensus.rs` diff --git a/src/node/evm/executor.rs b/src/node/evm/executor.rs index b970dff..fcd417a 100644 --- a/src/node/evm/executor.rs +++ b/src/node/evm/executor.rs @@ -2,21 +2,18 @@ use super::patch::{ patch_chapel_after_tx, patch_chapel_before_tx, patch_mainnet_after_tx, patch_mainnet_before_tx, }; use crate::{ - consensus::{MAX_SYSTEM_REWARD, SYSTEM_ADDRESS, SYSTEM_REWARD_PERCENT, parlia::{HertzPatchManager, VoteAddress, Snapshot}}, + consensus::{SYSTEM_ADDRESS, parlia::{HertzPatchManager, VoteAddress, Snapshot}}, evm::transaction::BscTxEnv, hardforks::BscHardforks, system_contracts::{ feynman_fork::ValidatorElectionInfo, get_upgrade_system_contracts, is_system_transaction, SystemContract, STAKE_HUB_CONTRACT, - SYSTEM_REWARD_CONTRACT, }, }; use alloy_consensus::{Header, Transaction, TxReceipt}; use alloy_eips::{eip7685::Requests, Encodable2718}; use alloy_evm::{block::{ExecutableTx, StateChangeSource}, eth::receipt_builder::ReceiptBuilderCtx}; use alloy_primitives::{uint, Address, TxKind, U256, BlockNumber, Bytes}; -use alloy_sol_macro::sol; -use alloy_sol_types::SolCall; use reth_chainspec::{EthChainSpec, EthereumHardforks, Hardforks}; use reth_evm::{ block::{BlockValidationError, CommitChanges}, @@ -26,7 +23,6 @@ use reth_evm::{ Database, Evm, FromRecoveredTx, FromTxWithEncoded, IntoTxEnv, OnStateHook, RecoveredTx, }; use reth_primitives::TransactionSigned; -use reth_primitives_traits::SignerRecoverable; use reth_provider::BlockExecutionResult; use reth_revm::State; use revm::{ @@ -37,7 +33,7 @@ use revm::{ state::Bytecode, Database as _, DatabaseCommit, }; -use tracing::{debug, trace, warn}; +use tracing::debug; use alloy_eips::eip2935::{HISTORY_STORAGE_ADDRESS, HISTORY_STORAGE_CODE}; use alloy_primitives::keccak256; use std::{collections::HashMap, sync::Arc}; @@ -52,6 +48,7 @@ pub(crate) struct InnerExecutionContext { pub(crate) validators_election_info: Option>, pub(crate) snap: Option, pub(crate) header: Option
, + pub(crate) parent_header: Option
, } pub struct BscBlockExecutor<'a, EVM, Spec, R: ReceiptBuilder> @@ -63,13 +60,13 @@ where /// Inner EVM. pub(super) evm: EVM, /// Gas used in the block. - gas_used: u64, + pub(super) gas_used: u64, /// Receipts of executed transactions. - receipts: Vec, + pub(super) receipts: Vec, /// System txs - system_txs: Vec, + pub(super) system_txs: Vec, /// Receipt builder. - receipt_builder: R, + pub(super) receipt_builder: R, /// System contracts used to trigger fork specific logic. pub(super) system_contracts: SystemContract, /// Hertz patch manager for mainnet compatibility @@ -79,9 +76,9 @@ where /// Context for block execution. _ctx: EthBlockExecutionCtx<'a>, /// Utility to call system caller. - system_caller: SystemCaller, + pub(super) system_caller: SystemCaller, /// State hook. - hook: Option>, + pub(super) hook: Option>, /// Snapshot provider for accessing Parlia validator snapshots. pub(super) snapshot_provider: Option>, /// Parlia consensus instance used (optional during execution). @@ -139,12 +136,11 @@ where validators_election_info: None, snap: None, header: None, + parent_header: None, }, } } - - /// Applies system contract upgrades if the Feynman fork is not yet active. fn upgrade_contracts(&mut self) -> Result<(), BlockExecutionError> { let contracts = get_upgrade_system_contracts( @@ -193,16 +189,10 @@ where &mut self, beneficiary: Address, ) -> Result<(), BlockExecutionError> { - debug!("🏗️ [BSC] deploy_genesis_contracts: beneficiary={:?}, block={}", beneficiary, self.evm.block().number); let txs = self.system_contracts.genesis_contracts_txs(); - trace!("🏗️ [BSC] deploy_genesis_contracts: created {} genesis txs", txs.len()); - - for (i, tx) in txs.iter().enumerate() { - trace!("🏗️ [BSC] deploy_genesis_contracts: executing genesis tx {}/{}: hash={:?}, to={:?}, value={}, gas_limit={}", - i + 1, txs.len(), tx.hash(), tx.to(), tx.value(), tx.gas_limit()); + for (_, tx) in txs.iter().enumerate() { self.transact_system_tx(tx, beneficiary)?; } - trace!("🏗️ [BSC] deploy_genesis_contracts: completed all {} genesis txs", txs.len()); Ok(()) } @@ -211,12 +201,6 @@ where tx: &TransactionSigned, sender: Address, ) -> Result<(), BlockExecutionError> { - trace!("Start to transact_system_tx: sender={:?}, tx_hash={:?}, to={:?}, value={}, gas_limit={}", - sender, tx.hash(), tx.to(), tx.value(), tx.gas_limit()); - - // TODO: Consensus handle reverting slashing system txs (they shouldnt be in the block) - // https://github.com/bnb-chain/reth/blob/main/crates/bsc/evm/src/execute.rs#L602 - let account = self .evm .db_mut() @@ -224,8 +208,6 @@ where .map_err(BlockExecutionError::other)? .unwrap_or_default(); - trace!("transact_system_tx: sender account balance={}, nonce={}", account.balance, account.nonce); - let tx_env = BscTxEnv { base: TxEnv { caller: sender, @@ -234,17 +216,10 @@ where gas_limit: u64::MAX / 2, value: tx.value(), data: tx.input().clone(), - // Setting the gas price to zero enforces that no value is transferred as part of - // the call, and that the call will not count against the block's - // gas limit gas_price: 0, - // The chain ID check is not relevant here and is disabled if set to None chain_id: Some(self.spec.chain().id()), - // Setting the gas priority fee to None ensures the effective gas price is - //derived // from the `gas_price` field, which we need to be zero gas_priority_fee: None, access_list: Default::default(), - // blob fields can be None for this tx blob_hashes: Vec::new(), max_fee_per_blob_gas: 0, tx_type: 0, @@ -253,9 +228,6 @@ where is_system_transaction: true, }; - trace!("transact_system_tx: TxEnv gas_price={}, gas_limit={}, is_system_transaction={}", - tx_env.base.gas_price, tx_env.base.gas_limit, tx_env.is_system_transaction); - let result_and_state = self.evm.transact(tx_env).map_err(BlockExecutionError::other)?; let ResultAndState { result, state } = result_and_state; @@ -266,7 +238,6 @@ where let tx = tx.clone(); let gas_used = result.gas_used(); - trace!("⚙️ [BSC] transact_system_tx: completed, gas_used={}, result={:?}", gas_used, result); self.gas_used += gas_used; self.receipts.push(self.receipt_builder.build_receipt(ReceiptBuilderCtx { tx: &tx, @@ -298,141 +269,6 @@ where Ok(()) } - /// Handle slash system tx - fn handle_slash_tx(&mut self, tx: &TransactionSigned) -> Result<(), BlockExecutionError> { - sol!( - function slash( - address amounts, - ); - ); - - let input = tx.input(); - let is_slash_tx = input.len() >= 4 && input[..4] == slashCall::SELECTOR; - - if is_slash_tx { - // DEBUG: Uncomment to trace slash transaction processing - // debug!("⚔️ [BSC] handle_slash_tx: processing slash tx, hash={:?}", tx.hash()); - let signer = tx.recover_signer().map_err(BlockExecutionError::other)?; - self.transact_system_tx(tx, signer)?; - } - - Ok(()) - } - - /// Handle finality reward system tx. - /// Activated by - /// at - fn handle_finality_reward_tx( - &mut self, - tx: &TransactionSigned, - ) -> Result<(), BlockExecutionError> { - sol!( - function distributeFinalityReward( - address[] validators, - uint256[] weights - ); - ); - - let input = tx.input(); - let is_finality_reward_tx = - input.len() >= 4 && input[..4] == distributeFinalityRewardCall::SELECTOR; - - if is_finality_reward_tx { - debug!("🏆 [BSC] handle_finality_reward_tx: processing finality reward tx, hash={:?}", tx.hash()); - let signer = tx.recover_signer().map_err(BlockExecutionError::other)?; - self.transact_system_tx(tx, signer)?; - } - - Ok(()) - } - - /// Handle update validatorsetv2 system tx. - /// Activated by - fn handle_update_validator_set_v2_tx( - &mut self, - tx: &TransactionSigned, - ) -> Result<(), BlockExecutionError> { - sol!( - function updateValidatorSetV2( - address[] _consensusAddrs, - uint64[] _votingPowers, - bytes[] _voteAddrs - ); - ); - - let input = tx.input(); - let is_update_validator_set_v2_tx = - input.len() >= 4 && input[..4] == updateValidatorSetV2Call::SELECTOR; - - if is_update_validator_set_v2_tx { - - let signer = tx.recover_signer().map_err(BlockExecutionError::other)?; - self.transact_system_tx(tx, signer)?; - } - - Ok(()) - } - - /// Distributes block rewards to the validator. - fn distribute_block_rewards(&mut self, validator: Address) -> Result<(), BlockExecutionError> { - trace!("💰 [BSC] distribute_block_rewards: validator={:?}, block={}", validator, self.evm.block().number); - - let system_account = self - .evm - .db_mut() - .load_cache_account(SYSTEM_ADDRESS) - .map_err(BlockExecutionError::other)?; - - if system_account.account.is_none() || - system_account.account.as_ref().unwrap().info.balance == U256::ZERO - { - trace!("💰 [BSC] distribute_block_rewards: no system balance to distribute"); - return Ok(()); - } - - let (mut block_reward, mut transition) = system_account.drain_balance(); - trace!("💰 [BSC] distribute_block_rewards: drained system balance={}", block_reward); - transition.info = None; - self.evm.db_mut().apply_transition(vec![(SYSTEM_ADDRESS, transition)]); - let balance_increment = vec![(validator, block_reward)]; - - self.evm - .db_mut() - .increment_balances(balance_increment) - .map_err(BlockExecutionError::other)?; - - let system_reward_balance = self - .evm - .db_mut() - .basic(SYSTEM_REWARD_CONTRACT) - .map_err(BlockExecutionError::other)? - .unwrap_or_default() - .balance; - - trace!("💰 [BSC] distribute_block_rewards: system_reward_balance={}", system_reward_balance); - - // Kepler introduced a max system reward limit, so we need to pay the system reward to the - // system contract if the limit is not exceeded. - if !self.spec.is_kepler_active_at_timestamp(self.evm.block().timestamp.to()) && - system_reward_balance < U256::from(MAX_SYSTEM_REWARD) - { - let reward_to_system = block_reward >> SYSTEM_REWARD_PERCENT; - trace!("💰 [BSC] distribute_block_rewards: reward_to_system={}", reward_to_system); - if reward_to_system > 0 { - let tx = self.system_contracts.pay_system_tx(reward_to_system); - trace!("💰 [BSC] distribute_block_rewards: created pay_system_tx, hash={:?}, value={}", tx.hash(), tx.value()); - self.transact_system_tx(&tx, validator)?; - } - - block_reward -= reward_to_system; - } - - let tx = self.system_contracts.pay_validator_tx(validator, block_reward); - trace!("💰 [BSC] distribute_block_rewards: created pay_validator_tx, hash={:?}, value={}", tx.hash(), tx.value()); - self.transact_system_tx(&tx, validator)?; - Ok(()) - } - pub(crate) fn apply_history_storage_account( &mut self, block_number: BlockNumber, @@ -520,37 +356,21 @@ where + RecoveredTx, f: impl for<'b> FnOnce(&'b ExecutionResult<::HaltReason>), ) -> Result { - // Check if it's a system transaction let signer = tx.signer(); let is_system = is_system_transaction(tx.tx(), *signer, self.evm.block().beneficiary); - // DEBUG: Uncomment to trace transaction execution details - // debug!("🔍 [BSC] execute_transaction_with_result_closure: tx_hash={:?}, signer={:?}, beneficiary={:?}, is_system={}, to={:?}, value={}, gas_limit={}, max_fee_per_gas={}", - // tx.tx().hash(), signer, self.evm.block().beneficiary, is_system, tx.tx().to(), tx.tx().value(), tx.tx().gas_limit(), tx.tx().max_fee_per_gas()); - if is_system { - // DEBUG: Uncomment to trace system transaction handling - // debug!("⚙️ [BSC] execute_transaction_with_result_closure: queuing system tx for later execution"); self.system_txs.push(tx.tx().clone()); return Ok(0); } - // DEBUG: Uncomment to trace regular transaction execution - // debug!("🚀 [BSC] execute_transaction_with_result_closure: executing regular tx, block_gas_used={}, block_gas_limit={}, available_gas={}", - // self.gas_used, self.evm.block().gas_limit, self.evm.block().gas_limit - self.gas_used); - - // Apply Hertz patches before transaction execution - // Note: Hertz patches are implemented in the existing patch system - // The HertzPatchManager is available for future enhanced patching - + // TODO: refine it. // apply patches before (legacy - keeping for compatibility) patch_mainnet_before_tx(tx.tx(), self.evm.db_mut())?; patch_chapel_before_tx(tx.tx(), self.evm.db_mut())?; let block_available_gas = self.evm.block().gas_limit - self.gas_used; if tx.tx().gas_limit() > block_available_gas { - warn!("❌ [BSC] execute_transaction_with_result_closure: tx gas limit {} exceeds available block gas {}", - tx.tx().gas_limit(), block_available_gas); return Err(BlockValidationError::TransactionGasLimitMoreThanAvailableBlockGas { transaction_gas_limit: tx.tx().gas_limit(), block_available_gas, @@ -558,12 +378,10 @@ where .into()); } - trace!("🔥 [BSC] execute_transaction_with_result_closure: calling EVM transact for regular tx"); let result_and_state = self .evm .transact(tx) .map_err(|err| { - warn!("❌ [BSC] execute_transaction_with_result_closure: EVM transact failed: {:?}", err); BlockExecutionError::evm(err, tx.tx().trie_hash()) })?; let ResultAndState { result, state } = result_and_state; @@ -578,7 +396,6 @@ where } let gas_used = result.gas_used(); - trace!("✅ [BSC] execute_transaction_with_result_closure: tx completed, gas_used={}, result={:?}", gas_used, result); self.gas_used += gas_used; self.receipts.push(self.receipt_builder.build_receipt(ReceiptBuilderCtx { tx: tx.tx(), @@ -589,10 +406,6 @@ where })); self.evm.db_mut().commit(state); - // Apply Hertz patches after transaction execution - // Note: Hertz patches are implemented in the existing patch system - // The HertzPatchManager is available for future enhanced patching - // apply patches after (legacy - keeping for compatibility) patch_mainnet_after_tx(tx.tx(), self.evm.db_mut())?; patch_chapel_after_tx(tx.tx(), self.evm.db_mut())?; @@ -622,65 +435,8 @@ where } self.finalize_new_block(&self.evm.block().clone())?; - - - // Prepare system transactions list and append slash transactions collected from consensus. - let mut system_txs = self.system_txs.clone(); - - // Drain slashing evidence collected by header-validation for this block. - for spoiled in crate::consensus::parlia::slash_pool::drain() { - use alloy_sol_macro::sol; - use alloy_sol_types::SolCall; - use crate::system_contracts::SLASH_CONTRACT; - sol!( - function slash(address); - ); - let input = slashCall(spoiled).abi_encode(); - let tx = reth_primitives::TransactionSigned::new_unhashed( - reth_primitives::Transaction::Legacy(alloy_consensus::TxLegacy { - chain_id: Some(self.spec.chain().id()), - nonce: 0, - gas_limit: u64::MAX / 2, - gas_price: 0, - value: alloy_primitives::U256::ZERO, - input: alloy_primitives::Bytes::from(input), - to: alloy_primitives::TxKind::Call(Address::from(*SLASH_CONTRACT)), - }), - alloy_primitives::Signature::new(Default::default(), Default::default(), false), - ); - // DEBUG: Uncomment to trace slash transaction creation - // debug!("⚔️ [BSC] finish: added slash tx for spoiled validator {:?}", spoiled); - system_txs.push(tx); - } - - // DEBUG: Uncomment to trace system transaction processing - // debug!("🎯 [BSC] finish: processing {} system txs for slash handling", system_txs.len()); - let system_txs_for_slash = system_txs.clone(); - for (_i, tx) in system_txs_for_slash.iter().enumerate() { - // DEBUG: Uncomment to trace individual slash transaction handling - // debug!("⚔️ [BSC] finish: handling slash tx {}/{}: hash={:?}", i + 1, system_txs_for_slash.len(), tx.hash()); - self.handle_slash_tx(tx)?; - } - - - // ---- post-system-tx handling --------------------------------- - self.distribute_block_rewards(self.evm.block().beneficiary)?; - - if self.spec.is_plato_active_at_block(self.evm.block().number.to()) { - for (_i, tx) in system_txs.iter().enumerate() { - self.handle_finality_reward_tx(tx)?; - } - } - - // TODO: add breathe check and polish it later. - let system_txs_v2 = self.system_txs.clone(); - for (_i, tx) in system_txs_v2.iter().enumerate() { - self.handle_update_validator_set_v2_tx(tx)?; - } - - // TODO: - // Consensus: Slash validator if not in turn + // TODO: refine this part. // ----------------------------------------------------------------- // reth-bsc-trail PATTERN: Create current snapshot from parent snapshot after execution // Get parent snapshot at start, apply current block changes, cache current snapshot diff --git a/src/node/evm/mod.rs b/src/node/evm/mod.rs index 2705e16..7650382 100644 --- a/src/node/evm/mod.rs +++ b/src/node/evm/mod.rs @@ -1,4 +1,5 @@ pub mod error; +pub mod util; use crate::{ evm::{ diff --git a/src/node/evm/post_execution.rs b/src/node/evm/post_execution.rs index c53900b..6cfd707 100644 --- a/src/node/evm/post_execution.rs +++ b/src/node/evm/post_execution.rs @@ -1,16 +1,23 @@ use super::executor::BscBlockExecutor; use super::error::BscBlockExecutionError; -use crate::consensus::parlia::{DIFF_INTURN, VoteAddress, Snapshot, snapshot::DEFAULT_TURN_LENGTH}; +use super::util::set_nonce; +use crate::consensus::parlia::{DIFF_INTURN, VoteAddress, Snapshot, VoteAttestation, snapshot::DEFAULT_TURN_LENGTH, constants::COLLECT_ADDITIONAL_VOTES_REWARD_RATIO, util::is_breathe_block}; +use crate::consensus::{SYSTEM_ADDRESS, MAX_SYSTEM_REWARD, SYSTEM_REWARD_PERCENT}; use crate::evm::transaction::BscTxEnv; +use crate::system_contracts::{SLASH_CONTRACT, SYSTEM_REWARD_CONTRACT, feynman_fork::{ValidatorElectionInfo, get_top_validators_by_voting_power, ElectedValidators}}; use reth_chainspec::{EthChainSpec, EthereumHardforks, Hardforks}; -use reth_evm::{eth::receipt_builder::ReceiptBuilder, execute::BlockExecutionError, Database, Evm, FromRecoveredTx, FromTxWithEncoded, IntoTxEnv}; -use reth_primitives::TransactionSigned; +use reth_evm::{eth::receipt_builder::{ReceiptBuilder, ReceiptBuilderCtx}, execute::BlockExecutionError, Database, Evm, FromRecoveredTx, FromTxWithEncoded, IntoTxEnv, block::StateChangeSource}; +use reth_primitives::{TransactionSigned, Transaction}; use reth_revm::State; -use revm::context::BlockEnv; -use alloy_consensus::{Header, TxReceipt}; -use alloy_primitives::{Address, hex}; +use crate::node::evm::ResultAndState; +use revm::{context::{BlockEnv, TxEnv}, Database as RevmDatabase, DatabaseCommit}; +use alloy_consensus::{Header, TxReceipt, Transaction as AlloyTransaction, SignableTransaction}; +use alloy_primitives::{Address, hex, TxKind, U256}; use std::collections::HashMap; -use tracing::debug; +use tracing::{debug, warn}; +use reth_primitives_traits::GotExpected; +use bit_set::BitSet; + impl<'a, DB, EVM, Spec, R: ReceiptBuilder> BscBlockExecutor<'a, EVM, Spec, R> where @@ -31,15 +38,59 @@ where /// finalize the new block, post check and finalize the system tx. /// depends on parlia, header and snapshot. pub(crate) fn finalize_new_block(&mut self, block: &BlockEnv) -> Result<(), BlockExecutionError> { - tracing::info!("Finalize new block, block_number: {}", block.number); - + tracing::info!("Start to finalize new block, block_number: {}", block.number); self.verify_validators(self.inner_ctx.current_validators.clone(), self.inner_ctx.header.clone())?; self.verify_turn_length(self.inner_ctx.snap.clone(), self.inner_ctx.header.clone())?; - // TODO: finalize the system txs. + // finalize the system txs. if block.difficulty != DIFF_INTURN { + let snap = self.inner_ctx.snap.as_ref().unwrap(); + let spoiled_validator = snap.inturn_validator(); + let signed_recently = if self.spec.is_plato_active_at_block(block.number.to()) { + snap.sign_recently(spoiled_validator) + } else { + snap.recent_proposers.iter().any(|(_, v)| *v == spoiled_validator) + }; + if !signed_recently { + self.slash_spoiled_validator(block.beneficiary, spoiled_validator)?; + tracing::info!("Slash spoiled validator, block_number: {}, spoiled_validator: {}", block.number, spoiled_validator); + } + } + + self.distribute_incoming(block.beneficiary)?; + if self.spec.is_plato_active_at_block(block.number.to()) { + let header = self.inner_ctx.header.as_ref().unwrap().clone(); + self.distribute_finality_reward(&header)?; } + + // update validator set after Feynman upgrade + let header = self.inner_ctx.header.as_ref().unwrap().clone(); + let parent_header = self.inner_ctx.parent_header.as_ref().unwrap().clone(); + if self.spec.is_feynman_active_at_timestamp(header.timestamp) && + is_breathe_block(parent_header.timestamp, header.timestamp) && + !self.spec.is_feynman_transition_at_timestamp(header.timestamp, parent_header.timestamp) + { + let max_elected_validators = self.inner_ctx.max_elected_validators.unwrap_or(U256::from(21)); + let validators_election_info = self.inner_ctx.validators_election_info.clone().unwrap_or_default(); + + self.update_validator_set_v2( + max_elected_validators, + validators_election_info.clone(), + header.beneficiary, + )?; + tracing::info!("Update validator set, block_number: {}, max_elected_validators: {}, validators_election_info: {:?}", + header.number, max_elected_validators, validators_election_info); + } + + if !self.system_txs.is_empty() { + return Err(BscBlockExecutionError::UnexpectedSystemTx.into()); + } + + tracing::info!("Succeed to finalize new block, block_number: {}", block.number); + + // TODO: apply snap and store it in db. + Ok(()) } @@ -129,4 +180,251 @@ where Ok(Some(DEFAULT_TURN_LENGTH)) } + + fn slash_spoiled_validator( + &mut self, + validator: Address, + spoiled_val: Address + ) -> Result<(), BlockExecutionError> { + self.transact_system_tx_v2( + self.system_contracts.slash(spoiled_val), + validator, + )?; + + Ok(()) + } + + fn transact_system_tx_v2(&mut self, transaction: Transaction, sender: Address) -> Result<(), BlockExecutionError> { + let account = self.evm + .db_mut() + .basic(sender) + .map_err(BlockExecutionError::other)? + .unwrap_or_default(); + + let transaction = set_nonce(transaction, account.nonce); + let hash = transaction.signature_hash(); + if self.system_txs.is_empty() || hash != self.system_txs[0].signature_hash() { + // slash tx could fail and not in the block + if let Some(to) = transaction.to() { + if to == SLASH_CONTRACT && + (self.system_txs.is_empty() || + self.system_txs[0].to().unwrap_or_default() != + SLASH_CONTRACT) + { + warn!("slash validator failed"); + return Ok(()); + } + } + debug!("unexpected transaction: {:?}", transaction); + for tx in self.system_txs.iter() { + debug!("left system tx: {:?}", tx); + } + return Err(BscBlockExecutionError::UnexpectedSystemTx.into()); + } + let signed_tx = self.system_txs.remove(0); + + // Create TxEnv first (before moving transaction) + let tx_env = BscTxEnv { + base: TxEnv { + caller: sender, + kind: TxKind::Call(transaction.to().unwrap()), + nonce: account.nonce, + gas_limit: u64::MAX / 2, + value: transaction.value(), + data: transaction.input().clone(), + gas_price: 0, + chain_id: Some(self.spec.chain().id()), + gas_priority_fee: None, + access_list: Default::default(), + blob_hashes: Vec::new(), + max_fee_per_blob_gas: 0, + tx_type: 0, + authorization_list: Default::default(), + }, + is_system_transaction: true, + }; + + let result_and_state = self.evm.transact(tx_env).map_err(BlockExecutionError::other)?; + let ResultAndState { result, state } = result_and_state; + if let Some(hook) = &mut self.hook { + hook.on_state(StateChangeSource::Transaction(self.receipts.len()), &state); + } + + let gas_used = result.gas_used(); + self.gas_used += gas_used; + self.receipts.push(self.receipt_builder.build_receipt(ReceiptBuilderCtx { + tx: &signed_tx, + evm: &self.evm, + result, + state: &state, + cumulative_gas_used: self.gas_used, + })); + self.evm.db_mut().commit(state); + + Ok(()) + } + + fn distribute_incoming( + &mut self, + validator: Address, + ) -> Result<(), BlockExecutionError> { + let system_account = self + .evm + .db_mut() + .load_cache_account(SYSTEM_ADDRESS) + .map_err(BlockExecutionError::other)?; + + if system_account.account.is_none() || + system_account.account.as_ref().unwrap().info.balance == U256::ZERO + { + return Ok(()); + } + + let (mut block_reward, mut transition) = system_account.drain_balance(); + transition.info = None; + self.evm.db_mut().apply_transition(vec![(SYSTEM_ADDRESS, transition)]); + let balance_increment = vec![(validator, block_reward)]; + + self.evm + .db_mut() + .increment_balances(balance_increment) + .map_err(BlockExecutionError::other)?; + + let system_reward_balance = self + .evm + .db_mut() + .basic(SYSTEM_REWARD_CONTRACT) + .map_err(BlockExecutionError::other)? + .unwrap_or_default() + .balance; + + // Kepler introduced a max system reward limit, so we need to pay the system reward to the + // system contract if the limit is not exceeded. + if !self.spec.is_kepler_active_at_timestamp(self.evm.block().timestamp.to()) && + system_reward_balance < U256::from(MAX_SYSTEM_REWARD) + { + let reward_to_system = block_reward >> SYSTEM_REWARD_PERCENT; + if reward_to_system > 0 { + let tx = self.system_contracts.distribute_to_system(reward_to_system); + self.transact_system_tx_v2(tx, validator)?; + tracing::info!("Distribute to system, block_number: {}, reward_to_system: {}", self.evm.block().number, reward_to_system); + } + + block_reward -= reward_to_system; + } + + let tx = self.system_contracts.distribute_to_validator(validator, block_reward); + self.transact_system_tx_v2(tx, validator)?; + tracing::info!("Distribute to validator, block_number: {}, block_reward: {}", self.evm.block().number, block_reward); + + Ok(()) + } + + fn distribute_finality_reward( + &mut self, + header: &Header + ) -> Result<(), BlockExecutionError> { + // distribute finality reward per 200 blocks. + let distribute_interval = 200; + if header.number % distribute_interval != 0 { + return Ok(()); + } + + let validator = header.beneficiary; + let mut accumulated_weights: HashMap = HashMap::new(); + + let start = (header.number - distribute_interval).max(1); + let end = header.number; + let mut target_number = header.number - 1; + for _ in (start..end).rev() { + let header = self.snapshot_provider.as_ref().unwrap().get_checkpoint_header(target_number) + .ok_or_else(|| BlockExecutionError::msg(format!("Header not found for block number: {}", target_number)))?; + + if let Some(attestation) = + self.parlia_consensus.as_ref().unwrap().get_vote_attestation_from_header(&header).map_err(|err| { + BscBlockExecutionError::ParliaConsensusInnerError { error: err.into() } + })? + { + self.process_attestation(&attestation, &header, &mut accumulated_weights)?; + } + target_number = header.number - 1; + } + + let mut validators: Vec
= accumulated_weights.keys().copied().collect(); + validators.sort(); + let weights: Vec = validators.iter().map(|val| accumulated_weights[val]).collect(); + + self.transact_system_tx_v2( + self.system_contracts.distribute_finality_reward(validators, weights), + validator, + )?; + tracing::info!("Distribute finality reward, block_number: {}, validator: {}", self.evm.block().number, validator); + + Ok(()) + } + + fn process_attestation( + &self, + attestation: &VoteAttestation, + parent_header: &Header, + accumulated_weights: &mut std::collections::HashMap, + ) -> Result<(), BlockExecutionError> { + let justified_header = self.snapshot_provider.as_ref().unwrap().get_checkpoint_header(attestation.data.target_number) + .ok_or_else(|| BlockExecutionError::msg(format!("Header not found for block number: {}", attestation.data.target_number)))?; + let parent = self.snapshot_provider.as_ref().unwrap().get_checkpoint_header(justified_header.number - 1) + .ok_or_else(|| BlockExecutionError::msg(format!("Header not found for block number: {}", justified_header.number - 1)))?; + let snapshot = self.snapshot_provider.as_ref().unwrap().snapshot(parent.number); + let validators = &snapshot.unwrap().validators; + let mut validators_bit_set = BitSet::new(); + let vote_address_set = attestation.vote_address_set; + for i in 0..64 { + if (vote_address_set & (1u64 << i)) != 0 { + validators_bit_set.insert(i); + } + } + + if validators_bit_set.len() > validators.len() { + return Err(BscBlockExecutionError::InvalidAttestationVoteCount(GotExpected { + got: validators_bit_set.len() as u64, + expected: validators.len() as u64, + }) + .into()); + } + + let mut valid_vote_count = 0; + for (index, validator) in validators.iter().enumerate() { + if validators_bit_set.contains(index) { + *accumulated_weights.entry(*validator).or_insert(U256::ZERO) += U256::from(1); + valid_vote_count += 1; + } + } + + let quorum = (validators.len() * 2 + 2) / 3; // ceil div + if valid_vote_count > quorum { + let reward = + ((valid_vote_count - quorum) * COLLECT_ADDITIONAL_VOTES_REWARD_RATIO) / 100; + *accumulated_weights.entry(parent_header.beneficiary).or_insert(U256::ZERO) += + U256::from(reward); + } + + Ok(()) + + } + + fn update_validator_set_v2( + &mut self, + max_elected_validators: U256, + validators_election_info: Vec, + validator: Address, + ) -> Result<(), BlockExecutionError> { + let ElectedValidators { validators, voting_powers, vote_addrs } = + get_top_validators_by_voting_power(validators_election_info, max_elected_validators); + + self.transact_system_tx_v2( + self.system_contracts.update_validator_set_v2(validators, voting_powers, vote_addrs), + validator, + )?; + + Ok(()) + } } \ No newline at end of file diff --git a/src/node/evm/pre_execution.rs b/src/node/evm/pre_execution.rs index e610fd1..58ebae2 100644 --- a/src/node/evm/pre_execution.rs +++ b/src/node/evm/pre_execution.rs @@ -51,6 +51,7 @@ where .unwrap() .get_checkpoint_header(block_number - 1) .ok_or(BlockExecutionError::msg("Failed to get parent header from snapshot provider"))?; + self.inner_ctx.parent_header = Some(parent_header.clone()); let snap = self .snapshot_provider diff --git a/src/node/evm/util.rs b/src/node/evm/util.rs new file mode 100644 index 0000000..8e51161 --- /dev/null +++ b/src/node/evm/util.rs @@ -0,0 +1,26 @@ +use reth_primitives::Transaction; + +pub fn set_nonce(transaction: Transaction, nonce: u64) -> Transaction { + match transaction { + Transaction::Legacy(mut tx) => { + tx.nonce = nonce; + Transaction::Legacy(tx) + }, + Transaction::Eip2930(mut tx) => { + tx.nonce = nonce; + Transaction::Eip2930(tx) + }, + Transaction::Eip1559(mut tx) => { + tx.nonce = nonce; + Transaction::Eip1559(tx) + }, + Transaction::Eip4844(mut tx) => { + tx.nonce = nonce; + Transaction::Eip4844(tx) + }, + Transaction::Eip7702(mut tx) => { + tx.nonce = nonce; + Transaction::Eip7702(tx) + }, + } +} \ No newline at end of file diff --git a/src/system_contracts/mod.rs b/src/system_contracts/mod.rs index d0da7da..4e9e321 100644 --- a/src/system_contracts/mod.rs +++ b/src/system_contracts/mod.rs @@ -5,7 +5,7 @@ use crate::{ consensus::parlia::VoteAddress, hardforks::{bsc::BscHardfork, BscHardforks}, }; -use abi::{STAKE_HUB_ABI, VALIDATOR_SET_ABI, VALIDATOR_SET_ABI_BEFORE_LUBAN}; +use abi::{STAKE_HUB_ABI, VALIDATOR_SET_ABI, VALIDATOR_SET_ABI_BEFORE_LUBAN, SLASH_INDICATOR_ABI}; use alloy_chains::Chain; use alloy_consensus::TxLegacy; use alloy_dyn_abi::{DynSolValue, FunctionExt, JsonAbiExt}; @@ -28,6 +28,8 @@ pub(crate) struct SystemContract { validator_abi_before_luban: JsonAbi, /// The validator contract abi. validator_abi: JsonAbi, + /// The slash abi. + slash_abi: JsonAbi, /// The stake hub abi. stake_hub_abi: JsonAbi, /// The chain spec. @@ -38,8 +40,9 @@ impl SystemContract { pub(crate) fn new(chain_spec: Spec) -> Self { let validator_abi_before_luban = serde_json::from_str(*VALIDATOR_SET_ABI_BEFORE_LUBAN).unwrap(); let validator_abi = serde_json::from_str(*VALIDATOR_SET_ABI).unwrap(); + let slash_abi = serde_json::from_str(*SLASH_INDICATOR_ABI).unwrap(); let stake_hub_abi = serde_json::from_str(*STAKE_HUB_ABI).unwrap(); - Self { validator_abi_before_luban, validator_abi, stake_hub_abi, chain_spec } + Self { validator_abi_before_luban, validator_abi, slash_abi, stake_hub_abi, chain_spec } } /// Return system address and input which is used to query current validators before luban. @@ -175,43 +178,107 @@ impl SystemContract { output[0].as_uint().unwrap().0 } - /// Creates a deposit tx to pay block reward to a validator. - pub fn pay_validator_tx(&self, address: Address, block_reward: u128) -> TransactionSigned { - let function = self.validator_abi.function("deposit").unwrap().first().unwrap(); - let input = function.abi_encode_input(&[DynSolValue::Address(address)]).unwrap(); + /// Return a transaction to slash a validator. + pub fn slash(&self, address: Address) -> Transaction { + let function = self.slash_abi.function("slash").unwrap().first().unwrap(); + let input = function.abi_encode_input(&[DynSolValue::from(address)]).unwrap(); + + Transaction::Legacy(TxLegacy { + chain_id: Some(self.chain_spec.chain().id()), + nonce: 0, + gas_limit: u64::MAX / 2, + gas_price: 0, + value: U256::ZERO, + input: Bytes::from(input), + to: TxKind::Call(SLASH_CONTRACT), + }) + } - let signature = Signature::new(Default::default(), Default::default(), false); + /// Creates a transaction to pay system reward transfering the reward to the system contract. + pub fn distribute_to_system(&self, system_reward: u128) -> Transaction { + Transaction::Legacy(TxLegacy { + chain_id: Some(self.chain_spec.chain().id()), + nonce: 0, + gas_limit: u64::MAX / 2, + gas_price: 0, + value: U256::from(system_reward), + input: Bytes::default(), + to: TxKind::Call(SYSTEM_REWARD_CONTRACT), + }) + } - TransactionSigned::new_unhashed( - Transaction::Legacy(TxLegacy { - chain_id: Some(self.chain_spec.chain().id()), - nonce: 0, - gas_limit: u64::MAX / 2, - gas_price: 0, - value: U256::from(block_reward), - input: Bytes::from(input), - to: TxKind::Call(VALIDATOR_CONTRACT), - }), - signature, - ) + /// Creates a transaction to pay block reward to a validator. + pub fn distribute_to_validator(&self, address: Address, block_reward: u128) -> Transaction { + let function = self.validator_abi.function("deposit").unwrap().first().unwrap(); + let input = function.abi_encode_input(&[DynSolValue::from(address)]).unwrap(); + + Transaction::Legacy(TxLegacy { + chain_id: Some(self.chain_spec.chain().id()), + nonce: 0, + gas_limit: u64::MAX / 2, + gas_price: 0, + value: U256::from(block_reward), + input: Bytes::from(input), + to: TxKind::Call(VALIDATOR_CONTRACT), + }) } - /// Creates a transaction to pay system reward transfering the reward to the system contract. - pub fn pay_system_tx(&self, system_reward: u128) -> TransactionSigned { - let signature = Signature::new(Default::default(), Default::default(), false); + /// Creates a transaction to distribute finality reward to validators. + pub fn distribute_finality_reward( + &self, + validators: Vec
, + weights: Vec, + ) -> Transaction { + let function = + self.validator_abi.function("distributeFinalityReward").unwrap().first().unwrap(); + + let validators = validators.into_iter().map(DynSolValue::from).collect(); + let weights = weights.into_iter().map(DynSolValue::from).collect(); + let input = function + .abi_encode_input(&[DynSolValue::Array(validators), DynSolValue::Array(weights)]) + .unwrap(); + + Transaction::Legacy(TxLegacy { + chain_id: Some(self.chain_spec.chain().id()), + nonce: 0, + gas_limit: u64::MAX / 2, + gas_price: 0, + value: U256::ZERO, + input: Bytes::from(input), + to: TxKind::Call(VALIDATOR_CONTRACT), + }) + } - TransactionSigned::new_unhashed( - Transaction::Legacy(TxLegacy { - chain_id: Some(self.chain_spec.chain().id()), - nonce: 0, - gas_limit: u64::MAX / 2, - gas_price: 0, - value: U256::from(system_reward), - input: Bytes::default(), - to: TxKind::Call(SYSTEM_REWARD_CONTRACT), - }), - signature, - ) + /// Creates a transaction to update validator set v2. + pub fn update_validator_set_v2( + &self, + validators: Vec
, + voting_powers: Vec, + vote_addresses: Vec>, + ) -> Transaction { + let function = + self.validator_abi.function("updateValidatorSetV2").unwrap().first().unwrap(); + + let validators = validators.into_iter().map(DynSolValue::from).collect(); + let voting_powers = voting_powers.into_iter().map(DynSolValue::from).collect(); + let vote_addresses = vote_addresses.into_iter().map(DynSolValue::from).collect(); + let input = function + .abi_encode_input(&[ + DynSolValue::Array(validators), + DynSolValue::Array(voting_powers), + DynSolValue::Array(vote_addresses), + ]) + .unwrap(); + + Transaction::Legacy(TxLegacy { + chain_id: Some(self.chain_spec.chain().id()), + nonce: 0, + gas_limit: u64::MAX / 2, + gas_price: 0, + value: U256::ZERO, + input: Bytes::from(input), + to: TxKind::Call(VALIDATOR_CONTRACT), + }) } pub(crate) fn genesis_contracts_txs(&self) -> Vec {