1use consensus_types::{block::Block, block_data::BlockType, vote::Vote};
2use diem_crypto::hash::CryptoHash;
3use diem_logger::{error as diem_error, prelude::*};
4use diem_state_view::StateView;
5use diem_types::{
6 account_address::{from_consensus_public_key, AccountAddress},
7 block_info::PivotBlockDecision,
8 contract_event::ContractEvent,
9 epoch_state::EpochState,
10 on_chain_config::new_epoch_event_key,
11 term_state::pos_state_config::{PosStateConfigTrait, POS_STATE_CONFIG},
12 transaction::{
13 authenticator::TransactionAuthenticator, ConflictSignature,
14 DisputePayload, ElectionPayload, RegisterPayload, RetirePayload,
15 SignatureCheckedTransaction, SignedTransaction, Transaction,
16 TransactionOutput, TransactionPayload, TransactionStatus,
17 UpdateVotingPowerPayload,
18 },
19 validator_verifier::ValidatorVerifier,
20 vm_status::{KeptVMStatus, StatusCode, VMStatus},
21};
22
23pub struct PosVM;
25
26impl PosVM {
27 pub fn execute_block(
30 transactions: Vec<Transaction>, state_view: &dyn StateView,
31 catch_up_mode: bool,
32 ) -> Result<Vec<TransactionOutput>, VMStatus> {
33 let mut vm_outputs = Vec::new();
34 for transaction in transactions {
35 let output = match transaction {
36 Transaction::BlockMetadata(_) => {
37 Self::process_block_metadata(state_view)?
38 }
39 Transaction::UserTransaction(trans) => {
40 let tx = Self::check_signature_for_user_tx(trans)?;
41 let spec = Spec { catch_up_mode };
42 Self::process_user_transaction(state_view, &tx, &spec)?
43 }
44 Transaction::GenesisTransaction(events) => {
45 Self::process_genesis_transaction(events)?
46 }
47 };
48 vm_outputs.push(output);
49 }
50
51 Ok(vm_outputs)
52 }
53}
54
55impl PosVM {
56 fn process_block_metadata(
57 state_view: &dyn StateView,
58 ) -> Result<TransactionOutput, VMStatus> {
59 let mut events = state_view.pos_state().get_unlock_events();
60 diem_debug!("get_unlock_events: {}", events.len());
61
62 let next_view = state_view.pos_state().current_view() + 1;
63 let (term, view_in_term) = POS_STATE_CONFIG.get_term_view(next_view);
64
65 if view_in_term == 0 {
67 let (validator_verifier, vrf_seed) =
68 state_view.pos_state().get_committee_at(term).map_err(|e| {
69 diem_warn!("get_new_committee error: {:?}", e);
70 VMStatus::Error(StatusCode::CFX_INVALID_TX)
71 })?;
72 let epoch = term + 1;
73 let validator_bytes = bcs::to_bytes(&EpochState::new(
74 epoch,
75 validator_verifier,
76 vrf_seed,
77 ))
78 .unwrap();
79 let contract_event =
80 ContractEvent::new(new_epoch_event_key(), validator_bytes);
81 events.push(contract_event);
82 }
83 Ok(Self::gen_output(events))
84 }
85
86 fn check_signature_for_user_tx(
87 trans: SignedTransaction,
88 ) -> Result<SignatureCheckedTransaction, VMStatus> {
89 trans.check_signature().map_err(|e| {
91 diem_trace!("invalid transactions signature: e={:?}", e);
92 VMStatus::Error(StatusCode::INVALID_SIGNATURE)
93 })
94 }
95
96 fn process_user_transaction(
97 state_view: &dyn StateView, tx: &SignatureCheckedTransaction,
98 spec: &Spec,
99 ) -> Result<TransactionOutput, VMStatus> {
100 let events = match tx.payload() {
101 TransactionPayload::Election(election_payload) => {
102 election_payload.execute(state_view, tx, spec)?
103 }
104 TransactionPayload::Retire(retire_payload) => {
105 retire_payload.execute(state_view, tx, spec)?
106 }
107 TransactionPayload::PivotDecision(pivot_decision) => {
108 pivot_decision.execute(state_view, tx, spec)?
109 }
110 TransactionPayload::Register(register) => {
111 register.execute(state_view, tx, spec)?
112 }
113 TransactionPayload::UpdateVotingPower(update) => {
114 update.execute(state_view, tx, spec)?
115 }
116 TransactionPayload::Dispute(dispute) => {
117 dispute.execute(state_view, tx, spec)?
118 }
119 _ => return Err(VMStatus::Error(StatusCode::CFX_UNEXPECTED_TX)),
120 };
121
122 Ok(Self::gen_output(events))
123 }
124
125 fn process_genesis_transaction(
126 events: Vec<ContractEvent>,
127 ) -> Result<TransactionOutput, VMStatus> {
128 Ok(Self::gen_output(events))
129 }
130
131 fn gen_output(events: Vec<ContractEvent>) -> TransactionOutput {
132 let status = TransactionStatus::Keep(KeptVMStatus::Executed);
133 TransactionOutput::new(events, 0, status)
134 }
135}
136
137pub struct Spec {
138 pub catch_up_mode: bool,
139}
140
141pub trait ExecutableBuiltinTx {
142 fn execute(
143 &self, state_view: &dyn StateView, tx: &SignatureCheckedTransaction,
144 spec: &Spec,
145 ) -> Result<Vec<ContractEvent>, VMStatus>;
146}
147
148impl ExecutableBuiltinTx for ElectionPayload {
149 fn execute(
150 &self, state_view: &dyn StateView, _tx: &SignatureCheckedTransaction,
151 spec: &Spec,
152 ) -> Result<Vec<ContractEvent>, VMStatus> {
153 if !spec.catch_up_mode {
154 state_view
155 .pos_state()
156 .validate_election(self)
157 .map_err(|e| {
158 diem_error!("election tx error: {:?}", e);
159 VMStatus::Error(StatusCode::CFX_INVALID_TX)
160 })?;
161 }
162 Ok(vec![self.to_event()])
163 }
164}
165
166impl ExecutableBuiltinTx for PivotBlockDecision {
167 fn execute(
168 &self, state_view: &dyn StateView, tx: &SignatureCheckedTransaction,
169 spec: &Spec,
170 ) -> Result<Vec<ContractEvent>, VMStatus> {
171 if !spec.catch_up_mode {
172 let authenticator = tx.authenticator();
173 let signature = match authenticator {
174 TransactionAuthenticator::MultiBLS { signature } => {
175 Ok(signature)
176 }
177 _ => Err(VMStatus::Error(StatusCode::CFX_INVALID_TX)),
178 }?;
179 state_view
180 .pos_state()
181 .validate_pivot_decision(self, signature)
182 .map_err(|e| {
183 diem_error!("pivot decision tx error: {:?}", e);
184 VMStatus::Error(StatusCode::CFX_INVALID_TX)
185 })?;
186 }
187 Ok(vec![self.to_event()])
188 }
189}
190
191impl ExecutableBuiltinTx for DisputePayload {
192 fn execute(
193 &self, state_view: &dyn StateView, _tx: &SignatureCheckedTransaction,
194 _spec: &Spec,
195 ) -> Result<Vec<ContractEvent>, VMStatus> {
196 let view = state_view.pos_state().current_view();
197 let offense_epoch = verify_dispute(self, view)
198 .ok_or(VMStatus::Error(StatusCode::CFX_INVALID_TX))?;
199 state_view
200 .pos_state()
201 .validate_dispute(self, offense_epoch)
202 .map_err(|e| {
203 diem_error!("dispute tx error: {:?}", e);
204 VMStatus::Error(StatusCode::CFX_INVALID_TX)
205 })?;
206 Ok(vec![
207 if POS_STATE_CONFIG.cip173_active(view) {
208 self.to_event_v2(offense_epoch)
209 } else {
210 self.to_event()
211 },
212 ])
213 }
214}
215
216macro_rules! impl_builtin_tx_by_gen_events {
217 ( $($name:ident),* ) => {
218 $(impl ExecutableBuiltinTx for $name {
219 fn execute(&self, _state_view: &dyn StateView,_tx: &SignatureCheckedTransaction, _spec: &Spec) -> Result<Vec<ContractEvent>, VMStatus> {
220 Ok(vec![self.to_event()])
221 }
222 })*
223 }
224}
225
226impl_builtin_tx_by_gen_events!(
228 RegisterPayload,
229 RetirePayload,
230 UpdateVotingPowerPayload
231);
232
233fn verify_dispute_proposal(
237 block: &Block, address: AccountAddress, verifier: &ValidatorVerifier,
238) -> bool {
239 match block.block_data().block_type() {
240 BlockType::Proposal { author, .. } => {
241 if *author != address {
242 diem_trace!("Dispute proposal authored by another validator");
243 return false;
244 }
245 match block.signature() {
246 Some(signature) => verifier
247 .verify(*author, block.block_data(), signature)
248 .is_ok(),
249 None => {
250 diem_trace!("Dispute proposal missing proposer signature");
251 false
252 }
253 }
254 }
255 _ => {
256 diem_trace!("Dispute proposal is not a Proposal block");
257 false
258 }
259 }
260}
261
262pub fn verify_dispute(dispute: &DisputePayload, view: u64) -> Option<u64> {
264 let computed_address =
265 from_consensus_public_key(&dispute.bls_pub_key, &dispute.vrf_pub_key);
266 if dispute.address != computed_address {
267 diem_trace!("Incorrect address and public keys");
268 return None;
269 }
270 let enforce_conflict = POS_STATE_CONFIG.cip173_active(view);
271 match &dispute.conflicting_votes {
272 ConflictSignature::Proposal((proposal_byte1, proposal_byte2)) => {
273 let proposal1: Block =
274 match bcs::from_bytes(proposal_byte1.as_slice()) {
275 Ok(proposal) => proposal,
276 Err(e) => {
277 diem_trace!("1st proposal encoding error: {:?}", e);
278 return None;
279 }
280 };
281 let proposal2: Block =
282 match bcs::from_bytes(proposal_byte2.as_slice()) {
283 Ok(proposal) => proposal,
284 Err(e) => {
285 diem_trace!("2nd proposal encoding error: {:?}", e);
286 return None;
287 }
288 };
289 if (proposal1.block_data().epoch()
290 != proposal2.block_data().epoch())
291 || (proposal1.block_data().round()
292 != proposal2.block_data().round())
293 {
294 diem_trace!("Two proposals are from different rounds");
295 return None;
296 }
297 let temp_verifier = ValidatorVerifier::new_single(
298 dispute.address,
299 dispute.bls_pub_key.clone(),
300 Some(dispute.vrf_pub_key.clone()),
301 );
302 if enforce_conflict {
303 if !verify_dispute_proposal(
304 &proposal1,
305 dispute.address,
306 &temp_verifier,
307 ) || !verify_dispute_proposal(
308 &proposal2,
309 dispute.address,
310 &temp_verifier,
311 ) {
312 return None;
313 }
314 if proposal1.id() == proposal2.id() {
317 diem_trace!("Two proposals are identical");
318 return None;
319 }
320 } else if proposal1.validate_signature(&temp_verifier).is_err()
321 || proposal2.validate_signature(&temp_verifier).is_err()
322 {
323 return None;
324 }
325 return Some(proposal1.block_data().epoch());
326 }
327 ConflictSignature::Vote((vote_byte1, vote_byte2)) => {
328 let vote1: Vote = match bcs::from_bytes(vote_byte1.as_slice()) {
329 Ok(vote) => vote,
330 Err(e) => {
331 diem_trace!("1st vote encoding error: {:?}", e);
332 return None;
333 }
334 };
335 let vote2: Vote = match bcs::from_bytes(vote_byte2.as_slice()) {
336 Ok(vote) => vote,
337 Err(e) => {
338 diem_trace!("2nd vote encoding error: {:?}", e);
339 return None;
340 }
341 };
342 if (vote1.vote_data().proposed().epoch()
343 != vote2.vote_data().proposed().epoch())
344 || (vote1.vote_data().proposed().round()
345 != vote2.vote_data().proposed().round())
346 {
347 diem_trace!("Two votes are from different rounds");
348 return None;
349 }
350 if enforce_conflict
354 && (vote1.author() != dispute.address
355 || vote2.author() != dispute.address)
356 {
357 diem_trace!("Dispute vote authored by another validator");
358 return None;
359 }
360 let temp_verifier = ValidatorVerifier::new_single(
361 dispute.address,
362 dispute.bls_pub_key.clone(),
363 Some(dispute.vrf_pub_key.clone()),
364 );
365 if vote1.verify(&temp_verifier).is_err()
366 || vote2.verify(&temp_verifier).is_err()
367 {
368 diem_trace!("dispute vote verification error: vote1_r={:?} vote2_r={:?}", vote1.verify(&temp_verifier), vote2.verify(&temp_verifier));
369 return None;
370 }
371 if enforce_conflict
374 && vote1.ledger_info().hash() == vote2.ledger_info().hash()
375 {
376 diem_trace!("Two votes share the same ledger info");
377 return None;
378 }
379 return Some(vote1.vote_data().proposed().epoch());
380 }
381 }
382}
383
384#[cfg(test)]
385mod tests {
386 use super::verify_dispute;
398 use consensus_types::{
399 block::Block, block_data::BlockData, quorum_cert::QuorumCert,
400 vote::Vote, vote_data::VoteData,
401 };
402 use diem_crypto::{hash::CryptoHash, HashValue, ValidCryptoMaterial};
403 use diem_types::{
404 account_address::{from_consensus_public_key, AccountAddress},
405 block_info::BlockInfo,
406 ledger_info::{LedgerInfo, LedgerInfoWithSignatures},
407 term_state::pos_state_config::{PosStateConfig, POS_STATE_CONFIG},
408 transaction::{ConflictSignature, DisputePayload},
409 validator_config::{
410 ConsensusPublicKey, ConsensusSignature, ConsensusVRFProof,
411 ConsensusVRFPublicKey,
412 },
413 validator_signer::ValidatorSigner,
414 };
415 use std::{collections::BTreeMap, convert::TryFrom};
416
417 const TRANSITION: u64 = 2 * 60;
419 const BEFORE: u64 = 0;
420
421 const EPOCH: u64 = 1;
424 const ROUND: u64 = 2;
425 const TIMESTAMP: u64 = 1000;
426
427 #[track_caller]
428 fn accepted(evidence: &DisputePayload) {
429 assert_eq!(verify_dispute(evidence, BEFORE), Some(EPOCH));
430 assert_eq!(verify_dispute(evidence, TRANSITION), Some(EPOCH));
431 }
432
433 #[track_caller]
434 fn gated(evidence: &DisputePayload) {
435 assert_eq!(verify_dispute(evidence, BEFORE), Some(EPOCH));
436 assert_eq!(verify_dispute(evidence, TRANSITION), None);
437 }
438
439 #[track_caller]
442 fn enabled(evidence: &DisputePayload) {
443 assert_eq!(verify_dispute(evidence, BEFORE), None);
444 assert_eq!(verify_dispute(evidence, TRANSITION), Some(EPOCH));
445 }
446
447 #[track_caller]
448 fn rejected(evidence: &DisputePayload) {
449 assert_eq!(verify_dispute(evidence, BEFORE), None);
450 assert_eq!(verify_dispute(evidence, TRANSITION), None);
451 }
452
453 fn install_config() {
456 const M: u64 = u64::MAX;
457 POS_STATE_CONFIG
458 .set(PosStateConfig::new(
459 60, 1, 1, 0, 0, M, 0, 0, M, M, M, 0, 0, 60, M, M, TRANSITION,
460 ))
461 .expect("POS_STATE_CONFIG already set");
462 }
463
464 struct Target {
467 address: AccountAddress,
468 bls: ConsensusPublicKey,
469 vrf: ConsensusVRFPublicKey,
470 }
471
472 impl Target {
473 fn of(signer: &ValidatorSigner) -> Self {
474 let bls = signer.public_key();
475 let vrf = signer.vrf_public_key().unwrap();
476 let address = from_consensus_public_key(&bls, &vrf);
477 Self { address, bls, vrf }
478 }
479
480 fn dispute(&self, conflict: ConflictSignature) -> DisputePayload {
481 DisputePayload {
482 address: self.address,
483 bls_pub_key: self.bls.clone(),
484 vrf_pub_key: self.vrf.clone(),
485 conflicting_votes: conflict,
486 }
487 }
488
489 fn votes(&self, v1: &Vote, v2: &Vote) -> DisputePayload {
490 self.dispute(ConflictSignature::Vote((
491 bcs::to_bytes(v1).unwrap(),
492 bcs::to_bytes(v2).unwrap(),
493 )))
494 }
495
496 fn blocks(&self, b1: &Block, b2: &Block) -> DisputePayload {
497 self.raw_blocks(
498 bcs::to_bytes(b1).unwrap(),
499 bcs::to_bytes(b2).unwrap(),
500 )
501 }
502
503 fn raw_blocks(&self, b1: Vec<u8>, b2: Vec<u8>) -> DisputePayload {
504 self.dispute(ConflictSignature::Proposal((b1, b2)))
505 }
506 }
507
508 fn block_info(round: u64, id: HashValue) -> BlockInfo {
509 BlockInfo::new(EPOCH, round, id, HashValue::zero(), 0, 0, None, None)
510 }
511
512 fn genesis_qc(parent: u8) -> QuorumCert {
515 let bi = block_info(0, HashValue::new([parent; 32]));
516 let vote_data = VoteData::new(bi.clone(), bi.clone());
517 let li = LedgerInfo::new(bi, vote_data.hash());
518 QuorumCert::new(
519 vote_data,
520 LedgerInfoWithSignatures::new(li, BTreeMap::new()),
521 )
522 }
523
524 fn live_qc(signer: &ValidatorSigner, parent: u8) -> QuorumCert {
528 let certified = block_info(ROUND - 1, HashValue::new([parent; 32]));
529 let vote_data =
530 VoteData::new(certified.clone(), block_info(0, HashValue::zero()));
531 let li = LedgerInfo::new(certified, vote_data.hash());
532 let mut signatures = BTreeMap::new();
533 signatures.insert(Target::of(signer).address, signer.sign(&li));
534 QuorumCert::new(
535 vote_data,
536 LedgerInfoWithSignatures::new(li, signatures),
537 )
538 }
539
540 fn make_vote(
544 signer: &ValidatorSigner, author: AccountAddress, parent: u8,
545 ) -> Vote {
546 let vote_data = VoteData::new(
547 block_info(ROUND, HashValue::new([0xAA; 32])),
548 block_info(ROUND - 1, HashValue::new([parent; 32])),
549 );
550 let li = LedgerInfo::new(BlockInfo::empty(), HashValue::zero());
551 Vote::new(vote_data, author, li, signer)
552 }
553
554 fn proposal_data(author: AccountAddress, parent: u8) -> BlockData {
555 proposal_data_with_qc(author, genesis_qc(parent))
556 }
557
558 fn proposal_data_with_qc(
559 author: AccountAddress, qc: QuorumCert,
560 ) -> BlockData {
561 BlockData::new_proposal(vec![], author, ROUND, TIMESTAMP, qc)
562 }
563
564 fn make_proposal(
565 signer: &ValidatorSigner, author: AccountAddress, parent: u8,
566 ) -> Block {
567 Block::new_proposal_from_block_data(
568 proposal_data(author, parent),
569 signer,
570 )
571 }
572
573 fn proposal_wire_bytes(
577 data: &BlockData, signature: Option<&[u8]>,
578 ) -> Vec<u8> {
579 bcs::to_bytes(&(
580 data,
581 signature.map(<[u8]>::to_vec),
582 None::<(u64, ConsensusVRFProof)>,
583 ))
584 .unwrap()
585 }
586
587 fn compressed_signature_bytes(signature: &ConsensusSignature) -> Vec<u8> {
590 let uncompressed = ValidCryptoMaterial::to_bytes(signature);
591 for sort_flag in &[0x00u8, 0x20u8] {
592 let mut candidate = uncompressed[..96].to_vec();
593 candidate[0] |= 0x80 | sort_flag;
594 if let Ok(decoded) =
595 ConsensusSignature::try_from(candidate.as_slice())
596 {
597 if decoded == *signature {
598 return candidate;
599 }
600 }
601 }
602 panic!("no compressed encoding decodes back to the same signature");
603 }
604
605 fn dummy_vrf_proof() -> ConsensusVRFProof {
607 ConsensusVRFProof::try_from(&[][..]).unwrap()
608 }
609
610 #[test]
611 fn verify_dispute_conflict_gating() {
612 install_config();
613
614 let signer = ValidatorSigner::random([7u8; 32]);
615 let other = ValidatorSigner::random([9u8; 32]);
616 let target = Target::of(&signer);
617
618 vote_conflict_gating(&signer, &target);
619 proposal_conflict_gating(&signer, &target);
620 vote_identity_is_bound_to_the_accused(&signer, &other, &target);
621 proposal_identity_is_bound_to_the_accused(&signer, &other, &target);
622 unsigned_proposal_is_not_evidence(&signer, &target);
623 fields_outside_block_data_cannot_forge_a_conflict(&signer, &target);
624 live_qc_evidence_only_works_after_the_gate(&signer, &other, &target);
625 }
626
627 fn vote_conflict_gating(signer: &ValidatorSigner, target: &Target) {
628 let va = make_vote(signer, target.address, 1);
629 let vb = make_vote(signer, target.address, 2);
630 assert_eq!(
633 va.vote_data().proposed().id(),
634 vb.vote_data().proposed().id()
635 );
636 assert_ne!(va.ledger_info().hash(), vb.ledger_info().hash());
637 accepted(&target.votes(&va, &vb));
638 gated(&target.votes(&va, &va));
639
640 let mut timeout = va.clone();
643 timeout.add_timeout_signature(signer.sign(&timeout.timeout()));
644 assert_ne!(
645 bcs::to_bytes(&va).unwrap(),
646 bcs::to_bytes(&timeout).unwrap()
647 );
648 assert_eq!(va.ledger_info().hash(), timeout.ledger_info().hash());
649 gated(&target.votes(&va, &timeout));
650 }
651
652 fn proposal_conflict_gating(signer: &ValidatorSigner, target: &Target) {
653 let pa = make_proposal(signer, target.address, 1);
654 let pb = make_proposal(signer, target.address, 2);
655 assert_eq!(pa.timestamp_usecs(), pb.timestamp_usecs());
657 assert_ne!(pa.id(), pb.id());
658 accepted(&target.blocks(&pa, &pb));
659 gated(&target.blocks(&pa, &pa));
660
661 let na = Block::new_nil(ROUND, genesis_qc(1));
663 let nb = Block::new_nil(ROUND, genesis_qc(2));
664 gated(&target.blocks(&na, &nb));
665 }
666
667 fn vote_identity_is_bound_to_the_accused(
668 signer: &ValidatorSigner, other: &ValidatorSigner, target: &Target,
669 ) {
670 let impostor = Target::of(other).address;
671 let genuine = make_vote(signer, target.address, 1);
672 let foreign_a = make_vote(other, impostor, 1);
674 let foreign_b = make_vote(other, impostor, 2);
675 let forged = make_vote(other, target.address, 2);
677 let misnamed = make_vote(signer, impostor, 2);
678
679 rejected(&target.votes(&foreign_a, &foreign_b));
680 rejected(&target.votes(&genuine, &foreign_b));
681 rejected(&target.votes(&forged, &genuine));
682 rejected(&target.votes(&genuine, &forged));
683 rejected(&target.votes(&misnamed, &genuine));
684 rejected(&target.votes(&genuine, &misnamed));
685 }
686
687 fn proposal_identity_is_bound_to_the_accused(
688 signer: &ValidatorSigner, other: &ValidatorSigner, target: &Target,
689 ) {
690 let impostor = Target::of(other).address;
691 let genuine = make_proposal(signer, target.address, 1);
692 let foreign_a = make_proposal(other, impostor, 1);
694 let foreign_b = make_proposal(other, impostor, 2);
695 let forged = make_proposal(other, target.address, 2);
697 let misnamed = make_proposal(signer, impostor, 2);
700
701 rejected(&target.blocks(&foreign_a, &foreign_b));
702 rejected(&target.blocks(&genuine, &foreign_b));
703 rejected(&target.blocks(&forged, &genuine));
704 rejected(&target.blocks(&genuine, &forged));
705 rejected(&target.blocks(&misnamed, &genuine));
706 rejected(&target.blocks(&genuine, &misnamed));
707 }
708
709 fn unsigned_proposal_is_not_evidence(
710 signer: &ValidatorSigner, target: &Target,
711 ) {
712 let unsigned =
713 proposal_wire_bytes(&proposal_data(target.address, 2), None);
714 let decoded: Block = bcs::from_bytes(&unsigned).unwrap();
715 assert!(decoded.signature().is_none());
716 assert_eq!(decoded.author(), Some(target.address));
717
718 let genuine =
721 bcs::to_bytes(&make_proposal(signer, target.address, 1)).unwrap();
722 rejected(&target.raw_blocks(unsigned.clone(), genuine.clone()));
723 rejected(&target.raw_blocks(genuine, unsigned));
724 }
725
726 fn fields_outside_block_data_cannot_forge_a_conflict(
732 signer: &ValidatorSigner, target: &Target,
733 ) {
734 let data = proposal_data(target.address, 1);
735 let signature = signer.sign(&data);
736 let copy = |vrf: Option<(u64, ConsensusVRFProof)>| {
737 Block::new_proposal_from_block_data_and_signature(
738 data.clone(),
739 signature.clone(),
740 vrf,
741 )
742 };
743 let plain = copy(None);
744 let with_proof = copy(Some((1, dummy_vrf_proof())));
745 let other_nonce = copy(Some((2, dummy_vrf_proof())));
746
747 assert_ne!(
750 bcs::to_bytes(&plain).unwrap(),
751 bcs::to_bytes(&with_proof).unwrap()
752 );
753 assert_eq!(plain.id(), with_proof.id());
754 assert_eq!(with_proof.id(), other_nonce.id());
755 gated(&target.blocks(&plain, &with_proof));
756 gated(&target.blocks(&with_proof, &other_nonce));
757
758 let canonical = ValidCryptoMaterial::to_bytes(&signature);
762 let compressed = compressed_signature_bytes(&signature);
763 assert_ne!(canonical, compressed);
764 let uncompressed = proposal_wire_bytes(&data, Some(&canonical));
765 assert_eq!(uncompressed, bcs::to_bytes(&plain).unwrap());
766 let alternative = proposal_wire_bytes(&data, Some(&compressed));
767 let decoded: Block = bcs::from_bytes(&alternative).unwrap();
768 assert_eq!(decoded.id(), plain.id());
769 gated(&target.raw_blocks(uncompressed, alternative));
770 }
771
772 fn live_qc_evidence_only_works_after_the_gate(
779 signer: &ValidatorSigner, other: &ValidatorSigner, target: &Target,
780 ) {
781 let proposal = |parent: u8| {
782 Block::new_proposal_from_block_data(
783 proposal_data_with_qc(target.address, live_qc(other, parent)),
784 signer,
785 )
786 };
787 let pa = proposal(1);
788 let pb = proposal(2);
789 assert_ne!(pa.id(), pb.id());
790 enabled(&target.blocks(&pa, &pb));
791 }
792}