diff --git a/core/executor/src/state/state_object/collateral.rs b/core/executor/src/state/state_object/collateral.rs index 212d5eaaab..b533a08d63 100644 --- a/core/executor/src/state/state_object/collateral.rs +++ b/core/executor/src/state/state_object/collateral.rs @@ -100,6 +100,7 @@ impl State { let prop: U256 = self.storage_point_prop()?; let mut account = self.write_account_or_new_lock(&address.with_native_space())?; + return_if!(!account.is_contract()); return_if!(account.is_cip_107_initialized()); let (from_balance, from_collateral) = account.initialize_cip107(prop); diff --git a/core/src/pos/consensus/consensus_provider.rs b/core/src/pos/consensus/consensus_provider.rs index b97ff893fc..22719a6ab5 100644 --- a/core/src/pos/consensus/consensus_provider.rs +++ b/core/src/pos/consensus/consensus_provider.rs @@ -19,7 +19,7 @@ use diem_types::{ account_address::AccountAddress, on_chain_config::OnChainConfigPayload, transaction::SignedTransaction, }; -use executor::{vm::FakeVM, Executor}; +use executor::{vm::PosVM, Executor}; use storage_interface::DbReader; use crate::pos::{ @@ -74,7 +74,7 @@ pub fn start_consensus( runtime.handle().clone(), consensus_db.clone(), )); - let executor = Box::new(Executor::::new( + let executor = Box::new(Executor::::new( db_with_cache, pow_handler.clone(), consensus_db.clone() as Arc, diff --git a/core/src/pos/consensus/executor/src/vm.rs b/core/src/pos/consensus/executor/src/vm.rs index 49eaceb035..31c0b1d483 100644 --- a/core/src/pos/consensus/executor/src/vm.rs +++ b/core/src/pos/consensus/executor/src/vm.rs @@ -5,14 +5,17 @@ use diem_logger::{error as diem_error, prelude::*}; use diem_state_view::StateView; use diem_types::{ account_address::from_consensus_public_key, + block_info::PivotBlockDecision, contract_event::ContractEvent, epoch_state::EpochState, on_chain_config::{self, new_epoch_event_key, OnChainConfig, ValidatorSet}, term_state::pos_state_config::{PosStateConfigTrait, POS_STATE_CONFIG}, transaction::{ authenticator::TransactionAuthenticator, ConflictSignature, - DisputePayload, Transaction, TransactionOutput, TransactionPayload, - TransactionStatus, WriteSetPayload, + DisputePayload, ElectionPayload, RegisterPayload, RetirePayload, + SignatureCheckedTransaction, SignedTransaction, Transaction, + TransactionOutput, TransactionPayload, TransactionStatus, + UpdateVotingPowerPayload, WriteSetPayload, }, validator_verifier::{ValidatorConsensusInfo, ValidatorVerifier}, vm_status::{KeptVMStatus, StatusCode, VMStatus}, @@ -34,213 +37,130 @@ pub trait VMExecutor: Send { ) -> Result, VMStatus>; } -/// A fake VM implementing VMExecutor -pub struct FakeVM; +/// A VM for Conflux PoS chain. +pub struct PosVM; -impl VMExecutor for FakeVM { +impl VMExecutor for PosVM { fn execute_block( transactions: Vec, state_view: &dyn StateView, catch_up_mode: bool, ) -> Result, VMStatus> { let mut vm_outputs = Vec::new(); for transaction in transactions { - // Execute the transaction - match transaction { + let output = match transaction { Transaction::BlockMetadata(_) => { - let mut events = state_view.pos_state().get_unlock_events(); - diem_debug!("get_unlock_events: {}", events.len()); - // TODO(lpl): Simplify. - if (state_view.pos_state().current_view() + 1) - % POS_STATE_CONFIG.round_per_term() - == 0 - { - let term = (state_view.pos_state().current_view() + 1) - / POS_STATE_CONFIG.round_per_term(); - let (validator_verifier, vrf_seed) = state_view - .pos_state() - .get_committee_at(term) - .map_err(|e| { - diem_warn!("get_new_committee error: {:?}", e); - VMStatus::Error(StatusCode::CFX_INVALID_TX) - })?; - let epoch = (state_view.pos_state().current_view() + 1) - / POS_STATE_CONFIG.round_per_term() - + 1; - let validator_bytes = bcs::to_bytes(&EpochState::new( - epoch, - validator_verifier, - vrf_seed, - )) - .unwrap(); - let contract_event = ContractEvent::new( - new_epoch_event_key(), - validator_bytes, - ); - events.push(contract_event); - } - let output = Self::gen_output(events, false); - vm_outputs.push(output); + Self::process_block_metadata(state_view)? } Transaction::UserTransaction(trans) => { - // TODO(lpl): Parallel verification. - let trans = trans.check_signature().map_err(|e| { - diem_trace!( - "invalid transactions signature: e={:?}", - e - ); - VMStatus::Error(StatusCode::INVALID_SIGNATURE) - })?; - /* TODO(lpl): Handle pos epoch change. - if verify_admin_transaction && trans.is_admin_type() { - info!("executing admin trans"); - // Check the voting power of signers in administrators. - let admins = self.validators.read(); - if admins.is_none() { - bail!("Administrators are not set."); - } - let admins = admins.as_ref().unwrap(); - let signers = trans.pubkey_account_addresses(); - match admins.check_voting_power(signers.iter()) { - Ok(_) => {} - Err(VerifyError::TooLittleVotingPower { - .. - }) => { - bail!("Not enough voting power in administrators."); - } - Err(_) => { - bail!( - "There are signers not in administrators." - ); - } - } - } - */ - let payload = trans.payload(); - let events = match payload { - TransactionPayload::WriteSet( - WriteSetPayload::Direct(change_set), - ) => change_set.events().to_vec(), - TransactionPayload::Election(election_payload) => { - if !catch_up_mode { - state_view - .pos_state() - .validate_election(election_payload) - .map_err(|e| { - diem_error!( - "election tx error: {:?}", - e - ); - VMStatus::Error( - StatusCode::CFX_INVALID_TX, - ) - })?; - } - vec![election_payload.to_event()] - } - TransactionPayload::Retire(retire_payload) => { - vec![retire_payload.to_event()] - } - TransactionPayload::PivotDecision(pivot_decision) => { - if !catch_up_mode { - let authenticator = trans.authenticator(); - let signature = match authenticator { - TransactionAuthenticator::MultiBLS { - signature, - } => Ok(signature), - _ => Err(VMStatus::Error( - StatusCode::CFX_INVALID_TX, - )), - }?; - state_view - .pos_state() - .validate_pivot_decision( - pivot_decision, - signature, - ) - .map_err(|e| { - diem_error!( - "pivot decision tx error: {:?}", - e - ); - VMStatus::Error( - StatusCode::CFX_INVALID_TX, - ) - })?; - } - vec![pivot_decision.to_event()] - } - TransactionPayload::Register(register) => { - vec![register.to_event()] - } - TransactionPayload::UpdateVotingPower(update) => { - vec![update.to_event()] - } - TransactionPayload::Dispute(dispute) => { - state_view - .pos_state() - .validate_dispute(dispute) - .map_err(|e| { - diem_error!("dispute tx error: {:?}", e); - VMStatus::Error(StatusCode::CFX_INVALID_TX) - })?; - if !Self::verify_dispute(dispute) { - return Err(VMStatus::Error( - StatusCode::CFX_INVALID_TX, - )); - } - vec![dispute.to_event()] - } - _ => { - return Err(VMStatus::Error( - StatusCode::CFX_UNEXPECTED_TX, - )) - } - }; - - // ensure!( - // events.len() == 1, - // "One transaction can contain exactly 1 event." - // ); - - let output = Self::gen_output(events, false); - vm_outputs.push(output); + let tx = Self::check_signature_for_user_tx(trans)?; + let spec = Spec { catch_up_mode }; + Self::process_user_transaction(state_view, &tx, &spec)? } Transaction::GenesisTransaction(change_set) => { - let events = match change_set { - WriteSetPayload::Direct(change_set) => { - change_set.events().to_vec() - } - _ => { - return Err(VMStatus::Error( - StatusCode::CFX_UNEXPECTED_TX, - )) - } - }; - // ensure!( - // events.len() == 1, - // "One transaction can contain exactly 1 event." - // ); - - let output = Self::gen_output(events, true); - vm_outputs.push(output); + Self::process_genesis_transction(&change_set)? } - } + }; + vm_outputs.push(output); } Ok(vm_outputs) } } -impl FakeVM { +impl PosVM { + fn process_block_metadata( + state_view: &dyn StateView, + ) -> Result { + let mut events = state_view.pos_state().get_unlock_events(); + diem_debug!("get_unlock_events: {}", events.len()); + + let next_view = state_view.pos_state().current_view() + 1; + let round_per_term = POS_STATE_CONFIG.round_per_term(); + + // TODO(lpl): Simplify. + if next_view % round_per_term == 0 { + let term = next_view / round_per_term; + let (validator_verifier, vrf_seed) = + state_view.pos_state().get_committee_at(term).map_err(|e| { + diem_warn!("get_new_committee error: {:?}", e); + VMStatus::Error(StatusCode::CFX_INVALID_TX) + })?; + let epoch = next_view / round_per_term + 1; + let validator_bytes = bcs::to_bytes(&EpochState::new( + epoch, + validator_verifier, + vrf_seed, + )) + .unwrap(); + let contract_event = + ContractEvent::new(new_epoch_event_key(), validator_bytes); + events.push(contract_event); + } + Ok(Self::gen_output(events, false)) + } + + fn check_signature_for_user_tx( + trans: SignedTransaction, + ) -> Result { + // TODO(lpl): Parallel verification. + trans.check_signature().map_err(|e| { + diem_trace!("invalid transactions signature: e={:?}", e); + VMStatus::Error(StatusCode::INVALID_SIGNATURE) + }) + } + + fn process_user_transaction( + state_view: &dyn StateView, tx: &SignatureCheckedTransaction, + spec: &Spec, + ) -> Result { + let events = match tx.payload() { + TransactionPayload::WriteSet(WriteSetPayload::Direct( + change_set, + )) => change_set.events().to_vec(), + TransactionPayload::Election(election_payload) => { + election_payload.execute(state_view, tx, spec)? + } + TransactionPayload::Retire(retire_payload) => { + retire_payload.execute(state_view, tx, spec)? + } + TransactionPayload::PivotDecision(pivot_decision) => { + pivot_decision.execute(state_view, tx, spec)? + } + TransactionPayload::Register(register) => { + register.execute(state_view, tx, spec)? + } + TransactionPayload::UpdateVotingPower(update) => { + update.execute(state_view, tx, spec)? + } + TransactionPayload::Dispute(dispute) => { + dispute.execute(state_view, tx, spec)? + } + _ => return Err(VMStatus::Error(StatusCode::CFX_UNEXPECTED_TX)), + }; + + Ok(Self::gen_output(events, false)) + } + + fn process_genesis_transction( + change_set: &WriteSetPayload, + ) -> Result { + let events = match change_set { + WriteSetPayload::Direct(change_set) => change_set.events().to_vec(), + _ => return Err(VMStatus::Error(StatusCode::CFX_UNEXPECTED_TX)), + }; + + Ok(Self::gen_output(events, true)) + } + fn gen_output( - events: Vec, write: bool, + events: Vec, record_events_on_state: bool, ) -> TransactionOutput { let new_epoch_event_key = on_chain_config::new_epoch_event_key(); let status = TransactionStatus::Keep(KeptVMStatus::Executed); let mut write_set = WriteSetMut::default(); // TODO(linxi): support other event key - if write { + if record_events_on_state { for event in &events { if *event.key() == new_epoch_event_key { write_set.push(( @@ -253,112 +173,197 @@ impl FakeVM { TransactionOutput::new(write_set.freeze().unwrap(), events, 0, status) } +} - /// Return true if the dispute is valid. - /// Return false if the encoding is invalid or the provided signatures are - /// not from the same round. - pub fn verify_dispute(dispute: &DisputePayload) -> bool { - let computed_address = from_consensus_public_key( - &dispute.bls_pub_key, - &dispute.vrf_pub_key, - ); - if dispute.address != computed_address { - diem_trace!("Incorrect address and public keys"); - return false; +pub struct Spec { + pub catch_up_mode: bool, +} + +pub trait ExecutableBuiltinTx { + fn execute( + &self, state_view: &dyn StateView, tx: &SignatureCheckedTransaction, + spec: &Spec, + ) -> Result, VMStatus>; +} + +impl ExecutableBuiltinTx for ElectionPayload { + fn execute( + &self, state_view: &dyn StateView, _tx: &SignatureCheckedTransaction, + spec: &Spec, + ) -> Result, VMStatus> { + if !spec.catch_up_mode { + state_view + .pos_state() + .validate_election(self) + .map_err(|e| { + diem_error!("election tx error: {:?}", e); + VMStatus::Error(StatusCode::CFX_INVALID_TX) + })?; } - match &dispute.conflicting_votes { - ConflictSignature::Proposal((proposal_byte1, proposal_byte2)) => { - let proposal1: Block = - match bcs::from_bytes(proposal_byte1.as_slice()) { - Ok(proposal) => proposal, - Err(e) => { - diem_trace!("1st proposal encoding error: {:?}", e); - return false; - } - }; - let proposal2: Block = - match bcs::from_bytes(proposal_byte2.as_slice()) { - Ok(proposal) => proposal, - Err(e) => { - diem_trace!("2nd proposal encoding error: {:?}", e); - return false; - } - }; - if proposal1 == proposal2 { - diem_trace!( - "Two same proposals are claimed to be conflict" - ); - return false; - } - if (proposal1.block_data().epoch() - != proposal2.block_data().epoch()) - || (proposal1.block_data().round() - != proposal2.block_data().round()) - { - diem_trace!("Two proposals are from different rounds"); - return false; - } - let mut temp_map = BTreeMap::new(); - temp_map.insert( - dispute.address, - ValidatorConsensusInfo::new( - dispute.bls_pub_key.clone(), - Some(dispute.vrf_pub_key.clone()), - 1, - ), - ); - let temp_verifier = ValidatorVerifier::new(temp_map); - if proposal1.validate_signature(&temp_verifier).is_err() - || proposal2.validate_signature(&temp_verifier).is_err() - { - return false; + Ok(vec![self.to_event()]) + } +} + +impl ExecutableBuiltinTx for PivotBlockDecision { + fn execute( + &self, state_view: &dyn StateView, tx: &SignatureCheckedTransaction, + spec: &Spec, + ) -> Result, VMStatus> { + if !spec.catch_up_mode { + let authenticator = tx.authenticator(); + let signature = match authenticator { + TransactionAuthenticator::MultiBLS { signature } => { + Ok(signature) } + _ => Err(VMStatus::Error(StatusCode::CFX_INVALID_TX)), + }?; + state_view + .pos_state() + .validate_pivot_decision(self, signature) + .map_err(|e| { + diem_error!("pivot decision tx error: {:?}", e); + VMStatus::Error(StatusCode::CFX_INVALID_TX) + })?; + } + Ok(vec![self.to_event()]) + } +} + +impl ExecutableBuiltinTx for DisputePayload { + fn execute( + &self, state_view: &dyn StateView, _tx: &SignatureCheckedTransaction, + _spec: &Spec, + ) -> Result, VMStatus> { + state_view.pos_state().validate_dispute(self).map_err(|e| { + diem_error!("dispute tx error: {:?}", e); + VMStatus::Error(StatusCode::CFX_INVALID_TX) + })?; + if !verify_dispute(self) { + return Err(VMStatus::Error(StatusCode::CFX_INVALID_TX)); + } + Ok(vec![self.to_event()]) + } +} + +macro_rules! impl_builtin_tx_by_gen_events { + ( $($name:ident),* ) => { + $(impl ExecutableBuiltinTx for $name { + fn execute(&self, _state_view: &dyn StateView,_tx: &SignatureCheckedTransaction, _spec: &Spec) -> Result, VMStatus> { + Ok(vec![self.to_event()]) } - ConflictSignature::Vote((vote_byte1, vote_byte2)) => { - let vote1: Vote = match bcs::from_bytes(vote_byte1.as_slice()) { - Ok(vote) => vote, + })* + } +} + +// Transactions which just generate events without other process +impl_builtin_tx_by_gen_events!( + RegisterPayload, + RetirePayload, + UpdateVotingPowerPayload +); + +/// Return true if the dispute is valid. +/// Return false if the encoding is invalid or the provided signatures are +/// not from the same round. +pub fn verify_dispute(dispute: &DisputePayload) -> bool { + let computed_address = + from_consensus_public_key(&dispute.bls_pub_key, &dispute.vrf_pub_key); + if dispute.address != computed_address { + diem_trace!("Incorrect address and public keys"); + return false; + } + match &dispute.conflicting_votes { + ConflictSignature::Proposal((proposal_byte1, proposal_byte2)) => { + let proposal1: Block = + match bcs::from_bytes(proposal_byte1.as_slice()) { + Ok(proposal) => proposal, Err(e) => { - diem_trace!("1st vote encoding error: {:?}", e); + diem_trace!("1st proposal encoding error: {:?}", e); return false; } }; - let vote2: Vote = match bcs::from_bytes(vote_byte2.as_slice()) { - Ok(vote) => vote, + let proposal2: Block = + match bcs::from_bytes(proposal_byte2.as_slice()) { + Ok(proposal) => proposal, Err(e) => { - diem_trace!("2nd vote encoding error: {:?}", e); + diem_trace!("2nd proposal encoding error: {:?}", e); return false; } }; - if vote1 == vote2 { - diem_trace!("Two same votes are claimed to be conflict"); + if proposal1 == proposal2 { + diem_trace!( + "Two same proposals are claimed to be conflict" + ); return false; } - if (vote1.vote_data().proposed().epoch() - != vote2.vote_data().proposed().epoch()) - || (vote1.vote_data().proposed().round() - != vote2.vote_data().proposed().round()) - { - diem_trace!("Two votes are from different rounds"); + if (proposal1.block_data().epoch() + != proposal2.block_data().epoch()) + || (proposal1.block_data().round() + != proposal2.block_data().round()) + { + diem_trace!("Two proposals are from different rounds"); + return false; + } + let mut temp_map = BTreeMap::new(); + temp_map.insert( + dispute.address, + ValidatorConsensusInfo::new( + dispute.bls_pub_key.clone(), + Some(dispute.vrf_pub_key.clone()), + 1, + ), + ); + let temp_verifier = ValidatorVerifier::new(temp_map); + if proposal1.validate_signature(&temp_verifier).is_err() + || proposal2.validate_signature(&temp_verifier).is_err() + { + return false; + } + } + ConflictSignature::Vote((vote_byte1, vote_byte2)) => { + let vote1: Vote = match bcs::from_bytes(vote_byte1.as_slice()) { + Ok(vote) => vote, + Err(e) => { + diem_trace!("1st vote encoding error: {:?}", e); return false; } - let mut temp_map = BTreeMap::new(); - temp_map.insert( - dispute.address, - ValidatorConsensusInfo::new( - dispute.bls_pub_key.clone(), - Some(dispute.vrf_pub_key.clone()), - 1, - ), - ); - let temp_verifier = ValidatorVerifier::new(temp_map); - if vote1.verify(&temp_verifier).is_err() - || vote2.verify(&temp_verifier).is_err() - { - diem_trace!("dispute vote verification error: vote1_r={:?} vote2_r={:?}", vote1.verify(&temp_verifier), vote2.verify(&temp_verifier)); + }; + let vote2: Vote = match bcs::from_bytes(vote_byte2.as_slice()) { + Ok(vote) => vote, + Err(e) => { + diem_trace!("2nd vote encoding error: {:?}", e); return false; } + }; + if vote1 == vote2 { + diem_trace!("Two same votes are claimed to be conflict"); + return false; + } + if (vote1.vote_data().proposed().epoch() + != vote2.vote_data().proposed().epoch()) + || (vote1.vote_data().proposed().round() + != vote2.vote_data().proposed().round()) + { + diem_trace!("Two votes are from different rounds"); + return false; + } + let mut temp_map = BTreeMap::new(); + temp_map.insert( + dispute.address, + ValidatorConsensusInfo::new( + dispute.bls_pub_key.clone(), + Some(dispute.vrf_pub_key.clone()), + 1, + ), + ); + let temp_verifier = ValidatorVerifier::new(temp_map); + if vote1.verify(&temp_verifier).is_err() + || vote2.verify(&temp_verifier).is_err() + { + diem_trace!("dispute vote verification error: vote1_r={:?} vote2_r={:?}", vote1.verify(&temp_verifier), vote2.verify(&temp_verifier)); + return false; } } - true } + true } diff --git a/core/src/pos/mempool/core_mempool/mempool.rs b/core/src/pos/mempool/core_mempool/mempool.rs index 7633c7440a..b1463e0183 100644 --- a/core/src/pos/mempool/core_mempool/mempool.rs +++ b/core/src/pos/mempool/core_mempool/mempool.rs @@ -30,7 +30,7 @@ use diem_types::{ }, validator_verifier::ValidatorVerifier, }; -use executor::vm::FakeVM; +use executor::vm::verify_dispute; use std::{ collections::HashSet, time::{Duration, SystemTime}, @@ -164,8 +164,8 @@ impl Mempool { TransactionPayload::Dispute(dispute_payload) => { // TODO(lpl): Only dispute a node once. pos_state.validate_dispute(dispute_payload).and( - FakeVM::verify_dispute(dispute_payload) - .then(|| ()) + verify_dispute(dispute_payload) + .then_some(()) .ok_or(anyhow::anyhow!("invalid dispute")), ) } diff --git a/core/src/pos/pos.rs b/core/src/pos/pos.rs index 9e451dc90f..189f6fdfb5 100644 --- a/core/src/pos/pos.rs +++ b/core/src/pos/pos.rs @@ -42,7 +42,7 @@ use diem_types::{ validator_config::{ConsensusPublicKey, ConsensusVRFPublicKey}, PeerId, }; -use executor::{db_bootstrapper::maybe_bootstrap, vm::FakeVM, Executor}; +use executor::{db_bootstrapper::maybe_bootstrap, vm::PosVM, Executor}; use executor_types::ChunkExecutor; use futures::{ channel::{ @@ -173,7 +173,7 @@ fn setup_metrics(peer_id: PeerId, config: &NodeConfig) { } fn setup_chunk_executor(db: DbReaderWriter) -> Box { - Box::new(Executor::::new( + Box::new(Executor::::new( Arc::new(CachedPosLedgerDB::new(db)), Arc::new(FakePowHandler {}), Arc::new(FakeLedgerBlockDB {}), @@ -221,7 +221,7 @@ pub fn setup_pos_environment( let genesis_waypoint = node_config.base.waypoint.genesis_waypoint(); // if there's genesis txn and waypoint, commit it if the result matches. if let Some(genesis) = get_genesis_txn(&node_config) { - maybe_bootstrap::( + maybe_bootstrap::( &db_rw, genesis, genesis_waypoint, diff --git a/tools/pos-genesis-tool/main.rs b/tools/pos-genesis-tool/main.rs index 938f643f08..2bf5c83094 100644 --- a/tools/pos-genesis-tool/main.rs +++ b/tools/pos-genesis-tool/main.rs @@ -54,7 +54,7 @@ use diem_types::{ waypoint::Waypoint, write_set::WriteSet, }; -use executor::{db_bootstrapper::generate_waypoint, vm::FakeVM}; +use executor::{db_bootstrapper::generate_waypoint, vm::PosVM}; use pos_ledger_db::PosLedgerDB; use std::{ collections::{BTreeMap, BinaryHeap, HashMap}, @@ -162,7 +162,7 @@ fn execute_genesis_transaction(genesis_txn: Transaction) -> Waypoint { ) .expect("DB should open."), ); - generate_waypoint::(&db, &genesis_txn).unwrap() + generate_waypoint::(&db, &genesis_txn).unwrap() } fn generate_genesis_from_public_keys(public_keys: Vec<(NodeID, u64)>) {