Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement double proposal detection #3007

Open
wants to merge 7 commits into
base: albatross
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 18 additions & 7 deletions blockchain/tests/history_store.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use nimiq_block::{
Block, DoubleProposalProof, DoubleVoteProof, EquivocationProof, ForkProof, MacroHeader,
MicroHeader,
Block, DoubleProposalProof, DoubleVoteProof, EquivocationProof, ForkProof, MicroHeader,
};
use nimiq_blockchain::interface::HistoryInterface;
use nimiq_blockchain_interface::{AbstractBlockchain, PushResult};
Expand All @@ -9,7 +8,9 @@ use nimiq_database::traits::WriteTransaction;
use nimiq_genesis::NetworkId;
use nimiq_hash::{Blake2sHash, HashOutput};
use nimiq_keys::{KeyPair, PrivateKey};
use nimiq_primitives::{policy::Policy, TendermintIdentifier, TendermintStep, TendermintVote};
use nimiq_primitives::{
policy::Policy, TendermintIdentifier, TendermintProposal, TendermintStep, TendermintVote,
};
use nimiq_serde::Deserialize;
use nimiq_test_log::test;
use nimiq_test_utils::{
Expand Down Expand Up @@ -135,15 +136,25 @@ fn do_double_proposal(
.clone()
.unwrap_macro()
.header;
let justification1 = signing_key.sign(MacroHeader::hash(&header1).as_bytes());
let justification2 = signing_key.sign(MacroHeader::hash(&header2).as_bytes());
let proposal1 = TendermintProposal {
proposal: header1,
round: 0,
valid_round: None,
};
let proposal2 = TendermintProposal {
proposal: header2,
round: 0,
valid_round: None,
};
let justification1 = signing_key.sign(proposal1.hash().as_bytes());
let justification2 = signing_key.sign(proposal2.hash().as_bytes());

// Produce the double proposal proof.
EquivocationProof::DoubleProposal(DoubleProposalProof::new(
validator_address(),
header1.clone(),
proposal1,
justification1,
header2,
proposal2,
justification2,
))
}
Expand Down
32 changes: 22 additions & 10 deletions blockchain/tests/inherents.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use nimiq_primitives::{
networks::NetworkId,
policy::Policy,
slots_allocation::{JailedValidator, PenalizedSlot},
TendermintIdentifier, TendermintStep, TendermintVote,
TendermintIdentifier, TendermintProposal, TendermintStep, TendermintVote,
};
use nimiq_test_log::test;
use nimiq_test_utils::{
Expand Down Expand Up @@ -513,17 +513,29 @@ fn it_correctly_creates_inherents_from_double_proposal_proof() {
.next_block(vec![], false)
.unwrap_macro()
.header;
let header1_hash: Blake2bHash = header1.hash();
let header2_hash: Blake2bHash = header2.hash();
let justification1 = signing_key.sign(header1_hash.as_bytes());
let justification2 = signing_key.sign(header2_hash.as_bytes());

let block_number = header1.block_number;
let round = header1.round;

let proposal1 = TendermintProposal {
proposal: header1,
round: 0,
valid_round: None,
};
let proposal2 = TendermintProposal {
proposal: header2,
round: 0,
valid_round: None,
};
let justification1 = signing_key.sign(proposal1.hash().as_bytes());
let justification2 = signing_key.sign(proposal2.hash().as_bytes());

// Produce the double proposal proof.
let double_proposal_proof = DoubleProposalProof::new(
validator_address(),
header1.clone(),
proposal1,
justification1,
header2,
proposal2,
justification2,
);

Expand All @@ -546,12 +558,12 @@ fn it_correctly_creates_inherents_from_double_proposal_proof() {

let blockchain = temp_producer.blockchain.read();
let slot = blockchain
.get_proposer_at(header1.block_number, header1.round, None)
.get_proposer_at(block_number, round, None)
.unwrap();

// Create the inherents from the double proposal proof.
let inherents = blockchain.create_punishment_inherents(
header1.block_number + 1,
block_number + 1,
&[double_proposal_proof.into()],
None,
None,
Expand All @@ -564,7 +576,7 @@ fn it_correctly_creates_inherents_from_double_proposal_proof() {
jailed_validator: JailedValidator {
slots: slot.validator.slots,
validator_address: slot.validator.address,
offense_event_block: header1.block_number,
offense_event_block: block_number,
},
new_epoch_slot_range: None
}]
Expand Down
22 changes: 15 additions & 7 deletions blockchain/tests/push.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use nimiq_hash::{Blake2bHash, Blake2sHash, HashOutput};
use nimiq_keys::KeyPair;
use nimiq_primitives::{
key_nibbles::KeyNibbles, networks::NetworkId, policy::Policy, TendermintIdentifier,
TendermintStep,
TendermintProposal, TendermintStep,
};
use nimiq_test_log::test;
use nimiq_test_utils::{
Expand Down Expand Up @@ -484,18 +484,26 @@ fn it_validates_double_proposal_proofs() {
.header;
let mut header2 = header1.clone();
header2.timestamp += 1;
let header1_hash: Blake2bHash = header1.hash();
let header2_hash: Blake2bHash = header2.hash();
let justification1 = signing_key.sign(header1_hash.as_bytes());
let justification2 = signing_key.sign(header2_hash.as_bytes());
let proposal1 = TendermintProposal {
proposal: header1,
round: 0,
valid_round: None,
};
let proposal2 = TendermintProposal {
proposal: header2,
round: 0,
valid_round: None,
};
let justification1 = signing_key.sign(proposal1.hash().as_bytes());
let justification2 = signing_key.sign(proposal2.hash().as_bytes());

expect_push_micro_block(
BlockConfig {
equivocation_proofs: vec![DoubleProposalProof::new(
validator_address(),
header1,
proposal1,
justification1,
header2,
proposal2,
justification2,
)
.into()],
Expand Down
114 changes: 66 additions & 48 deletions primitives/block/src/equivocation_proof.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use nimiq_primitives::{
networks::NetworkId,
policy::Policy,
slots_allocation::{Validator, Validators},
TendermintIdentifier, TendermintStep, TendermintVote,
TendermintIdentifier, TendermintProposal, TendermintStep, TendermintVote,
};
use nimiq_serde::{Deserialize, Serialize, SerializedMaxSize};
use nimiq_transaction::{
Expand Down Expand Up @@ -314,9 +314,9 @@ pub struct DoubleProposalProof {
/// Address of the offending validator.
validator_address: Address,
/// Header number 1.
header1: MacroHeader,
proposal1: TendermintProposal<MacroHeader>,
/// Header number 2.
header2: MacroHeader,
proposal2: TendermintProposal<MacroHeader>,
/// Justification for header number 1.
justification1: SchnorrSignature,
/// Justification for header number 2.
Expand All @@ -326,21 +326,21 @@ pub struct DoubleProposalProof {
impl DoubleProposalProof {
pub fn new(
validator_address: Address,
mut header1: MacroHeader,
mut proposal1: TendermintProposal<MacroHeader>,
mut justification1: SchnorrSignature,
mut header2: MacroHeader,
mut proposal2: TendermintProposal<MacroHeader>,
mut justification2: SchnorrSignature,
) -> DoubleProposalProof {
let hash1: Blake2bHash = header1.hash();
let hash2: Blake2bHash = header2.hash();
let hash1: Blake2sHash = proposal1.hash();
let hash2: Blake2sHash = proposal2.hash();
if hash1 > hash2 {
mem::swap(&mut header1, &mut header2);
mem::swap(&mut proposal1, &mut proposal2);
mem::swap(&mut justification1, &mut justification2);
}
DoubleProposalProof {
validator_address,
header1,
header2,
proposal1,
proposal2,
justification1,
justification2,
}
Expand All @@ -360,36 +360,36 @@ impl DoubleProposalProof {

/// Network ID this equivocation happened on.
pub fn network(&self) -> NetworkId {
self.header1.network
self.proposal1.proposal.network
}
/// Address of the offending validator.
pub fn validator_address(&self) -> &Address {
&self.validator_address
}
/// Block number at which the offense occurred.
pub fn block_number(&self) -> u32 {
self.header1.block_number
self.proposal1.proposal.block_number
}
/// Round of the proposals.
pub fn round(&self) -> u32 {
self.header1.round
self.proposal1.round
}
/// Hash of header number 1.
pub fn header1_hash(&self) -> Blake2bHash {
self.header1.hash()
self.proposal1.proposal.hash()
}
/// Hash of header number 2.
pub fn header2_hash(&self) -> Blake2bHash {
self.header2.hash()
self.proposal2.proposal.hash()
}

/// Verify the validity of a double proposal proof.
pub fn verify_excluding_address_and_network(
&self,
signing_key: &SchnorrPublicKey,
) -> Result<(), EquivocationProofError> {
let hash1: Blake2bHash = self.header1.hash();
let hash2: Blake2bHash = self.header2.hash();
let hash1: Blake2sHash = self.proposal1.hash();
let hash2: Blake2sHash = self.proposal2.hash();

// Check that the headers are not equal and in the right order:
match hash1.cmp(&hash2) {
Expand All @@ -399,12 +399,12 @@ impl DoubleProposalProof {
}

// Check that the headers have equal network IDs.
if self.header1.network != self.header2.network {
if self.proposal1.proposal.network != self.proposal2.proposal.network {
return Err(EquivocationProofError::NetworkMismatch);
}

if self.header1.block_number != self.header2.block_number
|| self.header1.round != self.header2.round
if self.proposal1.proposal.block_number != self.proposal2.proposal.block_number
|| self.proposal1.round != self.proposal2.round
{
return Err(EquivocationProofError::SlotMismatch);
}
Expand Down Expand Up @@ -596,7 +596,8 @@ mod test {
use nimiq_hash::{Blake2bHash, Blake2sHash, Hash, HashOutput};
use nimiq_keys::{Address, KeyPair, PrivateKey};
use nimiq_primitives::{
networks::NetworkId, policy::Policy, TendermintIdentifier, TendermintStep, TendermintVote,
networks::NetworkId, policy::Policy, TendermintIdentifier, TendermintProposal,
TendermintStep, TendermintVote,
};
use nimiq_serde::Deserialize;
use nimiq_vrf::VrfSeed;
Expand Down Expand Up @@ -794,54 +795,71 @@ mod test {
let mut header7 = header2.clone();
header7.round += 1;

let justification1 = key.sign(header1.hash().as_bytes());
let justification2 = key.sign(header2.hash().as_bytes());
let justification3 = key.sign(header3.hash().as_bytes());
let justification4 = key.sign(header4.hash().as_bytes());
let justification5 = key.sign(header5.hash().as_bytes());
let justification6 = key.sign(header6.hash().as_bytes());
let justification7 = key.sign(header7.hash().as_bytes());
let headers = vec![
Default::default(), // keep indices
header1,
header2,
header3,
header4,
header5,
header6,
header7,
];

let proposals: Vec<_> = headers
.into_iter()
.map(|header| TendermintProposal {
round: header.round,
proposal: header,
valid_round: None,
})
.collect();

let justifications: Vec<_> = proposals
.iter()
.map(|proposal| key.sign(proposal.hash().as_bytes()))
.collect();

let proof1: EquivocationProof = DoubleProposalProof::new(
Address::burn_address(),
header1.clone(),
justification1.clone(),
header2.clone(),
justification2.clone(),
proposals[1].clone(),
justifications[1].clone(),
proposals[2].clone(),
justifications[2].clone(),
)
.into();
let proof2: EquivocationProof = DoubleProposalProof::new(
Address::burn_address(),
header2.clone(),
justification2.clone(),
header3.clone(),
justification3.clone(),
proposals[2].clone(),
justifications[2].clone(),
proposals[3].clone(),
justifications[3].clone(),
)
.into();
let proof3: EquivocationProof = DoubleProposalProof::new(
Address::burn_address(),
header3.clone(),
justification3.clone(),
header1.clone(),
justification1.clone(),
proposals[3].clone(),
justifications[3].clone(),
proposals[1].clone(),
justifications[1].clone(),
)
.into();
// Different block height.
let proof4: EquivocationProof = DoubleProposalProof::new(
Address::burn_address(),
header4.clone(),
justification4.clone(),
header5.clone(),
justification5.clone(),
proposals[4].clone(),
justifications[4].clone(),
proposals[5].clone(),
justifications[5].clone(),
)
.into();
// Different round.
let proof5: EquivocationProof = DoubleProposalProof::new(
Address::burn_address(),
header6.clone(),
justification6.clone(),
header7.clone(),
justification7.clone(),
proposals[6].clone(),
justifications[6].clone(),
proposals[7].clone(),
justifications[7].clone(),
)
.into();

Expand Down
Loading
Loading