diff --git a/Cargo.lock b/Cargo.lock index bbfb2ef..f0cc02e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9775,6 +9775,7 @@ dependencies = [ "tokio", "tokio-stream", "tracing", + "uuid", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index f817142..4cd02ef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -183,3 +183,6 @@ codegen-units = 16 inherits = "release" lto = "fat" codegen-units = 1 + +[dev-dependencies] +uuid = { version = "1", features = ["v4"] } diff --git a/src/node/evm/executor.rs b/src/node/evm/executor.rs index f97fe21..02e1ec6 100644 --- a/src/node/evm/executor.rs +++ b/src/node/evm/executor.rs @@ -49,9 +49,9 @@ where Spec: EthChainSpec, { /// Reference to the specification object. - spec: Spec, + pub(super) spec: Spec, /// Inner EVM. - evm: EVM, + pub(super) evm: EVM, /// Gas used in the block. gas_used: u64, /// Receipts of executed transactions. @@ -61,7 +61,7 @@ where /// Receipt builder. receipt_builder: R, /// System contracts used to trigger fork specific logic. - system_contracts: SystemContract, + pub(super) system_contracts: SystemContract, /// Hertz patch manager for mainnet compatibility /// TODO: refine later. #[allow(dead_code)] @@ -192,7 +192,7 @@ where tx: &TransactionSigned, sender: Address, ) -> Result<(), BlockExecutionError> { - trace!("⚙️ [BSC] transact_system_tx: sender={:?}, tx_hash={:?}, to={:?}, value={}, gas_limit={}", + 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) @@ -205,7 +205,7 @@ where .map_err(BlockExecutionError::other)? .unwrap_or_default(); - trace!("⚙️ [BSC] transact_system_tx: sender account balance={}, nonce={}", account.balance, account.nonce); + trace!("transact_system_tx: sender account balance={}, nonce={}", account.balance, account.nonce); let tx_env = BscTxEnv { base: TxEnv { @@ -234,7 +234,7 @@ where is_system_transaction: true, }; - trace!("⚙️ [BSC] transact_system_tx: TxEnv gas_price={}, gas_limit={}, is_system_transaction={}", + 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)?; diff --git a/src/node/evm/pre_execution.rs b/src/node/evm/pre_execution.rs index e63da84..bc32426 100644 --- a/src/node/evm/pre_execution.rs +++ b/src/node/evm/pre_execution.rs @@ -4,8 +4,14 @@ 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_revm::State; -use revm::context::BlockEnv; +use revm::{ + context::{BlockEnv, TxEnv}, + primitives::{Address, Bytes, TxKind, U256}, +}; use alloy_consensus::TxReceipt; +//use alloy_primitives::Address; +use crate::consensus::parlia::VoteAddress; +use crate::system_contracts::feynman_fork::ValidatorElectionInfo; // use consensus trait object for cascading validation impl<'a, DB, EVM, Spec, R: ReceiptBuilder> BscBlockExecutor<'a, EVM, Spec, R> @@ -59,6 +65,7 @@ where .unwrap() .verify_cascading_fields(&header, &parent_header, None, &snap); + // TODO: remove this part, just for debug. if let Err(err) = verify_res { let proposer = header.beneficiary; let is_inturn = snap.is_inturn(proposer); @@ -89,8 +96,91 @@ where return Err(err); } - // TODO: query finalise input from parlia consensus object. + // TODO: query validator-related info from system contract. + let (validator_set, vote_address) = self.get_current_validators(block_number)?; + tracing::info!("validator_set: {:?}, vote_address: {:?}", validator_set, vote_address); + + // TODO: query election info from system contract. + if self.spec.is_feynman_active_at_timestamp(header.timestamp) && + !self.spec.is_feynman_transition_at_timestamp(header.timestamp, parent_header.timestamp) + { + let (to, data) = self.system_contracts.get_max_elected_validators(); + let bz = self.eth_call(to, data)?; + let max_elected_validators = self.system_contracts.unpack_data_into_max_elected_validators(bz.as_ref()); + tracing::info!("max_elected_validators: {:?}", max_elected_validators); + + let (to, data) = self.system_contracts.get_validator_election_info(); + let bz = self.eth_call(to, data)?; + + let (validators, voting_powers, vote_addrs, total_length) = + self.system_contracts.unpack_data_into_validator_election_info(bz.as_ref()); + + let total_length = total_length.to::() as usize; + if validators.len() != total_length || + voting_powers.len() != total_length || + vote_addrs.len() != total_length + { + return Err(BlockExecutionError::msg("Failed to get top validators")); + } + + let validator_election_info: Vec = validators + .into_iter() + .zip(voting_powers) + .zip(vote_addrs) + .map(|((validator, voting_power), vote_addr)| ValidatorElectionInfo { + address: validator, + voting_power, + vote_address: vote_addr, + }) + .collect(); + tracing::info!("validator_election_info: {:?}", validator_election_info); + } Ok(()) } + + fn get_current_validators(&mut self, block_number: u64) -> Result<(Vec
, Vec), BlockExecutionError> { + if self.spec.is_luban_active_at_block(block_number) { + let (to, data) = self.system_contracts.get_current_validators(); + let output = self.eth_call(to, data)?; + Ok(self.system_contracts.unpack_data_into_validator_set(&output)) + } else { + let (to, data) = self.system_contracts.get_current_validators_before_luban(block_number); + let output = self.eth_call(to, data)?; + let validator_set = self.system_contracts.unpack_data_into_validator_set_before_luban(&output); + Ok((validator_set, Vec::new())) + } + } + + fn eth_call(&mut self, to: Address, data: Bytes) -> Result { + let tx_env = BscTxEnv { + base: TxEnv { + caller: Address::default(), + kind: TxKind::Call(to), + nonce: 0, + gas_limit: self.evm.block().gas_limit, + value: U256::ZERO, + data: data.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: false, + }; + + let result_and_state = self.evm.transact(tx_env).map_err(|err| BlockExecutionError::other(err))?; + if !result_and_state.result.is_success() { + tracing::error!("Failed to eth call, to: {:?}, data: {:?}", to, data); + return Err(BlockExecutionError::msg("ETH call failed")); + } + let output = result_and_state.result.output().ok_or(BlockExecutionError::msg("ETH call output is None"))?; + Ok(output.clone()) + } + + } \ No newline at end of file diff --git a/src/system_contracts/feynman_fork.rs b/src/system_contracts/feynman_fork.rs new file mode 100644 index 0000000..7f768be --- /dev/null +++ b/src/system_contracts/feynman_fork.rs @@ -0,0 +1,219 @@ +use std::{cmp::Ordering, collections::BinaryHeap}; + +use alloy_primitives::{Address, U256}; + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct ValidatorElectionInfo { + pub address: Address, + pub voting_power: U256, + pub vote_address: Vec, +} + +/// Helper type for the output of `get_top_validators_by_voting_power` +#[derive(Clone, Debug, Default)] +#[allow(dead_code)] +pub struct ElectedValidators { + pub validators: Vec
, + pub voting_powers: Vec, + pub vote_addrs: Vec>, +} + +impl Ord for ValidatorElectionInfo { + fn cmp(&self, other: &Self) -> Ordering { + match self.voting_power.cmp(&other.voting_power) { + // If the voting power is the same, we compare the address as string. + Ordering::Equal => other.address.to_string().cmp(&self.address.to_string()), + other => other, + } + } +} + +impl PartialOrd for ValidatorElectionInfo { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +#[allow(dead_code)] +pub fn get_top_validators_by_voting_power( + validators: Vec, + max_elected_validators: U256, +) -> ElectedValidators { + let mut validator_heap: BinaryHeap = BinaryHeap::new(); + for info in validators { + if info.voting_power > U256::ZERO { + validator_heap.push(info); + } + } + + let top_n = max_elected_validators.to::() as usize; + let top_n = if top_n > validator_heap.len() { validator_heap.len() } else { top_n }; + + let mut e_validators = Vec::with_capacity(top_n); + let mut e_voting_powers = Vec::with_capacity(top_n); + let mut e_vote_addrs = Vec::with_capacity(top_n); + + for _ in 0..top_n { + if let Some(item) = validator_heap.pop() { + e_validators.push(item.address); + // as the decimal in BNB Beacon Chain is 1e8 and in BNB Smart Chain is 1e18, we need to + // divide it by 1e10 + e_voting_powers.push((item.voting_power / U256::from(10u64.pow(10))).to::()); + e_vote_addrs.push(item.vote_address); + } + } + + ElectedValidators { + validators: e_validators, + voting_powers: e_voting_powers, + vote_addrs: e_vote_addrs, + } +} + +#[cfg(test)] +mod tests { + use alloy_primitives::hex; + + use super::*; + + #[test] + fn validator_heap() { + let test_cases = vec![ + ( + "normal case", + 2, + vec![ + ValidatorElectionInfo { + address: Address::with_last_byte(1), + voting_power: U256::from(300) * U256::from(10u64.pow(10)), + vote_address: hex::decode("0x01").unwrap(), + }, + ValidatorElectionInfo { + address: Address::with_last_byte(2), + voting_power: U256::from(200) * U256::from(10u64.pow(10)), + vote_address: hex::decode("0x02").unwrap(), + }, + ValidatorElectionInfo { + address: Address::with_last_byte(3), + voting_power: U256::from(100) * U256::from(10u64.pow(10)), + vote_address: hex::decode("0x03").unwrap(), + }, + ], + vec![Address::with_last_byte(1), Address::with_last_byte(2)], + ), + ( + "same voting power", + 2, + vec![ + ValidatorElectionInfo { + address: Address::with_last_byte(1), + voting_power: U256::from(300) * U256::from(10u64.pow(10)), + vote_address: hex::decode("0x01").unwrap(), + }, + ValidatorElectionInfo { + address: Address::with_last_byte(2), + voting_power: U256::from(100) * U256::from(10u64.pow(10)), + vote_address: hex::decode("0x02").unwrap(), + }, + ValidatorElectionInfo { + address: Address::with_last_byte(3), + voting_power: U256::from(100) * U256::from(10u64.pow(10)), + vote_address: hex::decode("0x03").unwrap(), + }, + ], + vec![Address::with_last_byte(1), Address::with_last_byte(2)], + ), + ( + "zero voting power and k > len(validators)", + 5, + vec![ + ValidatorElectionInfo { + address: Address::with_last_byte(1), + voting_power: U256::from(300) * U256::from(10u64.pow(10)), + vote_address: hex::decode("0x01").unwrap(), + }, + ValidatorElectionInfo { + address: Address::with_last_byte(2), + voting_power: U256::ZERO, + vote_address: hex::decode("0x02").unwrap(), + }, + ValidatorElectionInfo { + address: Address::with_last_byte(3), + voting_power: U256::ZERO, + vote_address: hex::decode("0x03").unwrap(), + }, + ValidatorElectionInfo { + address: Address::with_last_byte(4), + voting_power: U256::ZERO, + vote_address: hex::decode("0x04").unwrap(), + }, + ], + vec![Address::with_last_byte(1)], + ), + ( + "zero voting power and k < len(validators)", + 5, + vec![ + ValidatorElectionInfo { + address: Address::with_last_byte(1), + voting_power: U256::from(300) * U256::from(10u64.pow(10)), + vote_address: hex::decode("0x01").unwrap(), + }, + ValidatorElectionInfo { + address: Address::with_last_byte(2), + voting_power: U256::ZERO, + vote_address: hex::decode("0x02").unwrap(), + }, + ValidatorElectionInfo { + address: Address::with_last_byte(3), + voting_power: U256::ZERO, + vote_address: hex::decode("0x03").unwrap(), + }, + ValidatorElectionInfo { + address: Address::with_last_byte(4), + voting_power: U256::ZERO, + vote_address: hex::decode("0x04").unwrap(), + }, + ], + vec![Address::with_last_byte(1)], + ), + ( + "all zero voting power", + 2, + vec![ + ValidatorElectionInfo { + address: Address::with_last_byte(1), + voting_power: U256::ZERO, + vote_address: hex::decode("0x01").unwrap(), + }, + ValidatorElectionInfo { + address: Address::with_last_byte(2), + voting_power: U256::ZERO, + vote_address: hex::decode("0x02").unwrap(), + }, + ValidatorElectionInfo { + address: Address::with_last_byte(3), + voting_power: U256::ZERO, + vote_address: hex::decode("0x03").unwrap(), + }, + ValidatorElectionInfo { + address: Address::with_last_byte(4), + voting_power: U256::ZERO, + vote_address: hex::decode("0x04").unwrap(), + }, + ], + vec![], + ), + ]; + + for (description, k, validators, expected) in test_cases { + let eligible_validators = + get_top_validators_by_voting_power(validators, U256::from(k)).validators; + + assert_eq!(eligible_validators.len(), expected.len(), "case: {}", description); + for i in 0..expected.len() { + assert_eq!(eligible_validators[i], expected[i], "case: {}", description); + } + } + } +} \ No newline at end of file diff --git a/src/system_contracts/mod.rs b/src/system_contracts/mod.rs index 5ca8916..b892bee 100644 --- a/src/system_contracts/mod.rs +++ b/src/system_contracts/mod.rs @@ -2,12 +2,13 @@ //! Credits to use crate::{ chainspec::{bsc::bsc_mainnet, bsc_chapel::bsc_testnet}, + consensus::parlia::VoteAddress, hardforks::{bsc::BscHardfork, BscHardforks}, }; -use abi::{STAKE_HUB_ABI, VALIDATOR_SET_ABI}; +use abi::{STAKE_HUB_ABI, VALIDATOR_SET_ABI, VALIDATOR_SET_ABI_BEFORE_LUBAN}; use alloy_chains::Chain; use alloy_consensus::TxLegacy; -use alloy_dyn_abi::{DynSolValue, JsonAbiExt}; +use alloy_dyn_abi::{DynSolValue, FunctionExt, JsonAbiExt}; use alloy_json_abi::JsonAbi; use alloy_primitives::{address, hex, Address, BlockNumber, Bytes, Signature, TxKind, U256}; use lazy_static::lazy_static; @@ -20,21 +21,143 @@ use thiserror::Error; mod abi; mod embedded_contracts; +pub mod feynman_fork; pub(crate) struct SystemContract { - /// The validator contract abi + /// The validator set abi before luban. + validator_abi_before_luban: JsonAbi, + /// The validator contract abi. validator_abi: JsonAbi, - /// The stake hub abi + /// The stake hub abi. stake_hub_abi: JsonAbi, - /// The chain spec + /// The chain spec. chain_spec: Spec, } -impl SystemContract { +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 stake_hub_abi = serde_json::from_str(*STAKE_HUB_ABI).unwrap(); - Self { validator_abi, stake_hub_abi, chain_spec } + Self { validator_abi_before_luban, validator_abi, stake_hub_abi, chain_spec } + } + + /// Return system address and input which is used to query current validators before luban. + pub fn get_current_validators_before_luban(&self, block_number: BlockNumber) -> (Address, Bytes) { + let function = if self.chain_spec.is_euler_active_at_block(block_number) { + self.validator_abi_before_luban + .function("getMiningValidators") + .unwrap() + .first() + .unwrap() + } else { + self.validator_abi_before_luban.function("getValidators").unwrap().first().unwrap() + }; + (VALIDATOR_CONTRACT, Bytes::from(function.abi_encode_input(&[]).unwrap())) + } + + /// Unpack the data into validator set before luban. + pub fn unpack_data_into_validator_set_before_luban(&self, data: &[u8]) -> Vec
{ + let function = + self.validator_abi_before_luban.function("getValidators").unwrap().first().unwrap(); + let output = function.abi_decode_output(data).unwrap(); + + output + .first() + .unwrap() + .as_array() + .unwrap() + .iter() + .map(|val| val.as_address().unwrap()) + .collect() + } + + /// Return system address and input which is used to query current validators. + pub fn get_current_validators(&self) -> (Address, Bytes) { + let function = self.validator_abi.function("getMiningValidators").unwrap().first().unwrap(); + (VALIDATOR_CONTRACT, Bytes::from(function.abi_encode_input(&[]).unwrap())) + } + + /// Unpack the data into validator set. + pub fn unpack_data_into_validator_set(&self, data: &[u8]) -> (Vec
, Vec) { + let function = self.validator_abi.function("getMiningValidators").unwrap().first().unwrap(); + let output = function.abi_decode_output(data).unwrap(); + + let consensus_addresses = + output[0].as_array().unwrap().iter().map(|val| val.as_address().unwrap()).collect(); + let vote_address = output[1] + .as_array() + .unwrap() + .iter() + .map(|val| { + if val.as_bytes().unwrap().is_empty() { + VoteAddress::default() + } else { + VoteAddress::from_slice(val.as_bytes().unwrap()) + } + }) + .collect(); + + (consensus_addresses, vote_address) + } + + /// Return system address and input which is used to query max elected validators. + pub fn get_max_elected_validators(&self) -> (Address, Bytes) { + let function = + self.stake_hub_abi.function("maxElectedValidators").unwrap().first().unwrap(); + + (STAKE_HUB_CONTRACT, Bytes::from(function.abi_encode_input(&[]).unwrap())) + } + + /// Unpack the data into max elected validators. + pub fn unpack_data_into_max_elected_validators(&self, data: &[u8]) -> U256 { + let function = + self.stake_hub_abi.function("maxElectedValidators").unwrap().first().unwrap(); + let output = function.abi_decode_output(data).unwrap(); + + output[0].as_uint().unwrap().0 + } + + /// Return system address and input which is used to query validator election info. + pub fn get_validator_election_info(&self) -> (Address, Bytes) { + let function = + self.stake_hub_abi.function("getValidatorElectionInfo").unwrap().first().unwrap(); + + ( + STAKE_HUB_CONTRACT, + Bytes::from( + function + .abi_encode_input(&[ + DynSolValue::from(U256::from(0)), + DynSolValue::from(U256::from(0)), + ]) + .unwrap(), + ), + ) + } + + /// Unpack the data into validator election info. + pub fn unpack_data_into_validator_election_info( + &self, + data: &[u8], + ) -> (Vec
, Vec, Vec>, U256) { + let function = + self.stake_hub_abi.function("getValidatorElectionInfo").unwrap().first().unwrap(); + let output = function.abi_decode_output(data).unwrap(); + + let consensus_address = + output[0].as_array().unwrap().iter().map(|val| val.as_address().unwrap()).collect(); + let voting_powers = + output[1].as_array().unwrap().iter().map(|val| val.as_uint().unwrap().0).collect(); + let vote_addresses = output[2] + .as_array() + .unwrap() + .iter() + .map(|val| val.as_bytes().unwrap().to_vec()) + .collect(); + let total_length = output[3].as_uint().unwrap().0; + + (consensus_address, voting_powers, vote_addresses, total_length) } /// Creates a deposit tx to pay block reward to a validator.