executor/
vm.rs

1use std::collections::BTreeMap;
2
3use consensus_types::{block::Block, vote::Vote};
4use diem_logger::{error as diem_error, prelude::*};
5use diem_state_view::StateView;
6use diem_types::{
7    account_address::from_consensus_public_key,
8    block_info::PivotBlockDecision,
9    contract_event::ContractEvent,
10    epoch_state::EpochState,
11    on_chain_config::{self, new_epoch_event_key, OnChainConfig, ValidatorSet},
12    term_state::pos_state_config::{PosStateConfigTrait, POS_STATE_CONFIG},
13    transaction::{
14        authenticator::TransactionAuthenticator, ConflictSignature,
15        DisputePayload, ElectionPayload, RegisterPayload, RetirePayload,
16        SignatureCheckedTransaction, SignedTransaction, Transaction,
17        TransactionOutput, TransactionPayload, TransactionStatus,
18        UpdateVotingPowerPayload, WriteSetPayload,
19    },
20    validator_verifier::{ValidatorConsensusInfo, ValidatorVerifier},
21    vm_status::{KeptVMStatus, StatusCode, VMStatus},
22    write_set::{WriteOp, WriteSetMut},
23};
24
25/// This trait describes the VM's execution interface.
26pub trait VMExecutor: Send {
27    // NOTE: At the moment there are no persistent caches that live past the end
28    // of a block (that's why execute_block doesn't take &self.)
29    // There are some cache invalidation issues around transactions publishing
30    // code that need to be sorted out before that's possible.
31
32    /// Executes a block of transactions and returns output for each one of
33    /// them.
34    fn execute_block(
35        transactions: Vec<Transaction>, state_view: &dyn StateView,
36        catch_up_mode: bool,
37    ) -> Result<Vec<TransactionOutput>, VMStatus>;
38}
39
40/// A VM for Conflux PoS chain.
41pub struct PosVM;
42
43impl VMExecutor for PosVM {
44    fn execute_block(
45        transactions: Vec<Transaction>, state_view: &dyn StateView,
46        catch_up_mode: bool,
47    ) -> Result<Vec<TransactionOutput>, VMStatus> {
48        let mut vm_outputs = Vec::new();
49        for transaction in transactions {
50            let output = match transaction {
51                Transaction::BlockMetadata(_) => {
52                    Self::process_block_metadata(state_view)?
53                }
54                Transaction::UserTransaction(trans) => {
55                    let tx = Self::check_signature_for_user_tx(trans)?;
56                    let spec = Spec { catch_up_mode };
57                    Self::process_user_transaction(state_view, &tx, &spec)?
58                }
59                Transaction::GenesisTransaction(change_set) => {
60                    Self::process_genesis_transction(&change_set)?
61                }
62            };
63            vm_outputs.push(output);
64        }
65
66        Ok(vm_outputs)
67    }
68}
69
70impl PosVM {
71    fn process_block_metadata(
72        state_view: &dyn StateView,
73    ) -> Result<TransactionOutput, VMStatus> {
74        let mut events = state_view.pos_state().get_unlock_events();
75        diem_debug!("get_unlock_events: {}", events.len());
76
77        let next_view = state_view.pos_state().current_view() + 1;
78        let (term, view_in_term) = POS_STATE_CONFIG.get_term_view(next_view);
79
80        // TODO(lpl): Simplify.
81        if view_in_term == 0 {
82            let (validator_verifier, vrf_seed) =
83                state_view.pos_state().get_committee_at(term).map_err(|e| {
84                    diem_warn!("get_new_committee error: {:?}", e);
85                    VMStatus::Error(StatusCode::CFX_INVALID_TX)
86                })?;
87            let epoch = term + 1;
88            let validator_bytes = bcs::to_bytes(&EpochState::new(
89                epoch,
90                validator_verifier,
91                vrf_seed,
92            ))
93            .unwrap();
94            let contract_event =
95                ContractEvent::new(new_epoch_event_key(), validator_bytes);
96            events.push(contract_event);
97        }
98        Ok(Self::gen_output(events, false))
99    }
100
101    fn check_signature_for_user_tx(
102        trans: SignedTransaction,
103    ) -> Result<SignatureCheckedTransaction, VMStatus> {
104        // TODO(lpl): Parallel verification.
105        trans.check_signature().map_err(|e| {
106            diem_trace!("invalid transactions signature: e={:?}", e);
107            VMStatus::Error(StatusCode::INVALID_SIGNATURE)
108        })
109    }
110
111    fn process_user_transaction(
112        state_view: &dyn StateView, tx: &SignatureCheckedTransaction,
113        spec: &Spec,
114    ) -> Result<TransactionOutput, VMStatus> {
115        let events = match tx.payload() {
116            TransactionPayload::WriteSet(WriteSetPayload::Direct(
117                change_set,
118            )) => change_set.events().to_vec(),
119            TransactionPayload::Election(election_payload) => {
120                election_payload.execute(state_view, tx, spec)?
121            }
122            TransactionPayload::Retire(retire_payload) => {
123                retire_payload.execute(state_view, tx, spec)?
124            }
125            TransactionPayload::PivotDecision(pivot_decision) => {
126                pivot_decision.execute(state_view, tx, spec)?
127            }
128            TransactionPayload::Register(register) => {
129                register.execute(state_view, tx, spec)?
130            }
131            TransactionPayload::UpdateVotingPower(update) => {
132                update.execute(state_view, tx, spec)?
133            }
134            TransactionPayload::Dispute(dispute) => {
135                dispute.execute(state_view, tx, spec)?
136            }
137            _ => return Err(VMStatus::Error(StatusCode::CFX_UNEXPECTED_TX)),
138        };
139
140        Ok(Self::gen_output(events, false))
141    }
142
143    fn process_genesis_transction(
144        change_set: &WriteSetPayload,
145    ) -> Result<TransactionOutput, VMStatus> {
146        let events = match change_set {
147            WriteSetPayload::Direct(change_set) => change_set.events().to_vec(),
148            _ => return Err(VMStatus::Error(StatusCode::CFX_UNEXPECTED_TX)),
149        };
150
151        Ok(Self::gen_output(events, true))
152    }
153
154    fn gen_output(
155        events: Vec<ContractEvent>, record_events_on_state: bool,
156    ) -> TransactionOutput {
157        let new_epoch_event_key = on_chain_config::new_epoch_event_key();
158        let status = TransactionStatus::Keep(KeptVMStatus::Executed);
159        let mut write_set = WriteSetMut::default();
160
161        // TODO(linxi): support other event key
162        if record_events_on_state {
163            for event in &events {
164                if *event.key() == new_epoch_event_key {
165                    write_set.push((
166                        ValidatorSet::CONFIG_ID.access_path(),
167                        WriteOp::Value(event.event_data().to_vec()),
168                    ));
169                }
170            }
171        }
172
173        TransactionOutput::new(write_set.freeze().unwrap(), events, 0, status)
174    }
175}
176
177pub struct Spec {
178    pub catch_up_mode: bool,
179}
180
181pub trait ExecutableBuiltinTx {
182    fn execute(
183        &self, state_view: &dyn StateView, tx: &SignatureCheckedTransaction,
184        spec: &Spec,
185    ) -> Result<Vec<ContractEvent>, VMStatus>;
186}
187
188impl ExecutableBuiltinTx for ElectionPayload {
189    fn execute(
190        &self, state_view: &dyn StateView, _tx: &SignatureCheckedTransaction,
191        spec: &Spec,
192    ) -> Result<Vec<ContractEvent>, VMStatus> {
193        if !spec.catch_up_mode {
194            state_view
195                .pos_state()
196                .validate_election(self)
197                .map_err(|e| {
198                    diem_error!("election tx error: {:?}", e);
199                    VMStatus::Error(StatusCode::CFX_INVALID_TX)
200                })?;
201        }
202        Ok(vec![self.to_event()])
203    }
204}
205
206impl ExecutableBuiltinTx for PivotBlockDecision {
207    fn execute(
208        &self, state_view: &dyn StateView, tx: &SignatureCheckedTransaction,
209        spec: &Spec,
210    ) -> Result<Vec<ContractEvent>, VMStatus> {
211        if !spec.catch_up_mode {
212            let authenticator = tx.authenticator();
213            let signature = match authenticator {
214                TransactionAuthenticator::MultiBLS { signature } => {
215                    Ok(signature)
216                }
217                _ => Err(VMStatus::Error(StatusCode::CFX_INVALID_TX)),
218            }?;
219            state_view
220                .pos_state()
221                .validate_pivot_decision(self, signature)
222                .map_err(|e| {
223                    diem_error!("pivot decision tx error: {:?}", e);
224                    VMStatus::Error(StatusCode::CFX_INVALID_TX)
225                })?;
226        }
227        Ok(vec![self.to_event()])
228    }
229}
230
231impl ExecutableBuiltinTx for DisputePayload {
232    fn execute(
233        &self, state_view: &dyn StateView, _tx: &SignatureCheckedTransaction,
234        _spec: &Spec,
235    ) -> Result<Vec<ContractEvent>, VMStatus> {
236        state_view.pos_state().validate_dispute(self).map_err(|e| {
237            diem_error!("dispute tx error: {:?}", e);
238            VMStatus::Error(StatusCode::CFX_INVALID_TX)
239        })?;
240        if !verify_dispute(self) {
241            return Err(VMStatus::Error(StatusCode::CFX_INVALID_TX));
242        }
243        Ok(vec![self.to_event()])
244    }
245}
246
247macro_rules! impl_builtin_tx_by_gen_events {
248    ( $($name:ident),*  ) => {
249        $(impl ExecutableBuiltinTx for $name {
250            fn execute(&self, _state_view: &dyn StateView,_tx: &SignatureCheckedTransaction,  _spec: &Spec) -> Result<Vec<ContractEvent>, VMStatus> {
251                Ok(vec![self.to_event()])
252            }
253        })*
254    }
255}
256
257// Transactions which just generate events without other process
258impl_builtin_tx_by_gen_events!(
259    RegisterPayload,
260    RetirePayload,
261    UpdateVotingPowerPayload
262);
263
264/// Return true if the dispute is valid.
265/// Return false if the encoding is invalid or the provided signatures are
266/// not from the same round.
267pub fn verify_dispute(dispute: &DisputePayload) -> bool {
268    let computed_address =
269        from_consensus_public_key(&dispute.bls_pub_key, &dispute.vrf_pub_key);
270    if dispute.address != computed_address {
271        diem_trace!("Incorrect address and public keys");
272        return false;
273    }
274    match &dispute.conflicting_votes {
275        ConflictSignature::Proposal((proposal_byte1, proposal_byte2)) => {
276            let proposal1: Block =
277                match bcs::from_bytes(proposal_byte1.as_slice()) {
278                    Ok(proposal) => proposal,
279                    Err(e) => {
280                        diem_trace!("1st proposal encoding error: {:?}", e);
281                        return false;
282                    }
283                };
284            let proposal2: Block =
285                match bcs::from_bytes(proposal_byte2.as_slice()) {
286                    Ok(proposal) => proposal,
287                    Err(e) => {
288                        diem_trace!("2nd proposal encoding error: {:?}", e);
289                        return false;
290                    }
291                };
292            if (proposal1.block_data().epoch()
293                != proposal2.block_data().epoch())
294                || (proposal1.block_data().round()
295                    != proposal2.block_data().round())
296            {
297                diem_trace!("Two proposals are from different rounds");
298                return false;
299            }
300            let mut temp_map = BTreeMap::new();
301            temp_map.insert(
302                dispute.address,
303                ValidatorConsensusInfo::new(
304                    dispute.bls_pub_key.clone(),
305                    Some(dispute.vrf_pub_key.clone()),
306                    1,
307                ),
308            );
309            let temp_verifier = ValidatorVerifier::new(temp_map);
310            if proposal1.validate_signature(&temp_verifier).is_err()
311                || proposal2.validate_signature(&temp_verifier).is_err()
312            {
313                return false;
314            }
315        }
316        ConflictSignature::Vote((vote_byte1, vote_byte2)) => {
317            let vote1: Vote = match bcs::from_bytes(vote_byte1.as_slice()) {
318                Ok(vote) => vote,
319                Err(e) => {
320                    diem_trace!("1st vote encoding error: {:?}", e);
321                    return false;
322                }
323            };
324            let vote2: Vote = match bcs::from_bytes(vote_byte2.as_slice()) {
325                Ok(vote) => vote,
326                Err(e) => {
327                    diem_trace!("2nd vote encoding error: {:?}", e);
328                    return false;
329                }
330            };
331            if (vote1.vote_data().proposed().epoch()
332                != vote2.vote_data().proposed().epoch())
333                || (vote1.vote_data().proposed().round()
334                    != vote2.vote_data().proposed().round())
335            {
336                diem_trace!("Two votes are from different rounds");
337                return false;
338            }
339            let mut temp_map = BTreeMap::new();
340            temp_map.insert(
341                dispute.address,
342                ValidatorConsensusInfo::new(
343                    dispute.bls_pub_key.clone(),
344                    Some(dispute.vrf_pub_key.clone()),
345                    1,
346                ),
347            );
348            let temp_verifier = ValidatorVerifier::new(temp_map);
349            if vote1.verify(&temp_verifier).is_err()
350                || vote2.verify(&temp_verifier).is_err()
351            {
352                diem_trace!("dispute vote verification error: vote1_r={:?} vote2_r={:?}", vote1.verify(&temp_verifier), vote2.verify(&temp_verifier));
353                return false;
354            }
355        }
356    }
357    true
358}