Skip to content

Commit

Permalink
Validate signer to be in validator set (BFT-384) (#45)
Browse files Browse the repository at this point in the history
Add validation for messages sent by replica to have their signer be in
the validator set.
  • Loading branch information
moshababo committed Dec 5, 2023
1 parent a7c953d commit 9644bf1
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 37 deletions.
14 changes: 14 additions & 0 deletions node/actors/bft/src/leader/replica_commit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ pub(crate) enum Error {
/// Local version.
local_version: ProtocolVersion,
},
/// Message signer isn't part of the validator set.
#[error("Message signer isn't part of the validator set (signer: {signer:?})")]
NonValidatorSigner {
/// Signer of the message.
signer: validator::PublicKey,
},
/// Unexpected proposal.
#[error("unexpected proposal")]
UnexpectedProposal,
Expand Down Expand Up @@ -77,6 +83,14 @@ impl StateMachine {
});
}

// Check that the message signer is in the validator set.
consensus
.validator_set
.index(author)
.ok_or(Error::NonValidatorSigner {
signer: author.clone(),
})?;

// If the message is from the "past", we discard it.
if (message.view, validator::Phase::Commit) < (self.view, self.phase) {
return Err(Error::Old {
Expand Down
14 changes: 14 additions & 0 deletions node/actors/bft/src/leader/replica_prepare.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ pub(crate) enum Error {
/// Local version.
local_version: ProtocolVersion,
},
/// Message signer isn't part of the validator set.
#[error("Message signer isn't part of the validator set (signer: {signer:?})")]
NonValidatorSigner {
/// Signer of the message.
signer: validator::PublicKey,
},
/// Past view or phase.
#[error("past view/phase (current view: {current_view:?}, current phase: {current_phase:?})")]
Old {
Expand Down Expand Up @@ -89,6 +95,14 @@ impl StateMachine {
});
}

// Check that the message signer is in the validator set.
consensus
.validator_set
.index(author)
.ok_or(Error::NonValidatorSigner {
signer: author.clone(),
})?;

// If the message is from the "past", we discard it.
if (message.view, validator::Phase::Prepare) < (self.view, self.phase) {
return Err(Error::Old {
Expand Down
89 changes: 52 additions & 37 deletions node/actors/bft/src/leader/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,24 +56,6 @@ async fn replica_prepare_sanity_yield_leader_prepare() {
);
}

#[tokio::test]
async fn replica_prepare_incompatible_protocol_version() {
let mut util = UTHarness::new_one().await;

let incompatible_protocol_version = util.incompatible_protocol_version();
let replica_prepare = util.new_current_replica_prepare(|msg| {
msg.protocol_version = incompatible_protocol_version;
});
let res = util.dispatch_replica_prepare_one(replica_prepare);
assert_matches!(
res,
Err(ReplicaPrepareError::IncompatibleProtocolVersion { message_version, local_version }) => {
assert_eq!(message_version, incompatible_protocol_version);
assert_eq!(local_version, util.protocol_version());
}
)
}

#[tokio::test]
async fn replica_prepare_sanity_yield_leader_prepare_reproposal() {
let mut util = UTHarness::new_many().await;
Expand Down Expand Up @@ -119,6 +101,41 @@ async fn replica_prepare_sanity_yield_leader_prepare_reproposal() {
);
}

#[tokio::test]
async fn replica_prepare_incompatible_protocol_version() {
let mut util = UTHarness::new_one().await;

let incompatible_protocol_version = util.incompatible_protocol_version();
let replica_prepare = util.new_current_replica_prepare(|msg| {
msg.protocol_version = incompatible_protocol_version;
});
let res = util.dispatch_replica_prepare_one(replica_prepare);
assert_matches!(
res,
Err(ReplicaPrepareError::IncompatibleProtocolVersion { message_version, local_version }) => {
assert_eq!(message_version, incompatible_protocol_version);
assert_eq!(local_version, util.protocol_version());
}
)
}

#[tokio::test]
async fn replica_prepare_non_validator_signer() {
let mut util = UTHarness::new_one().await;

let replica_prepare = util.new_current_replica_prepare(|_| {}).cast().unwrap().msg;
let non_validator_key: validator::SecretKey = util.rng().gen();
let signed = non_validator_key.sign_msg(ConsensusMsg::ReplicaPrepare(replica_prepare));

let res = util.dispatch_replica_prepare_one(signed);
assert_matches!(
res,
Err(ReplicaPrepareError::NonValidatorSigner { signer }) => {
assert_eq!(signer, non_validator_key.public());
}
);
}

#[tokio::test]
async fn replica_prepare_old_view() {
let mut util = UTHarness::new_one().await;
Expand Down Expand Up @@ -272,25 +289,6 @@ async fn replica_prepare_high_qc_of_future_view() {
);
}

#[ignore = "fails/unsupported"]
#[tokio::test]
async fn replica_prepare_non_validator_signer() {
let mut util = UTHarness::new_with(2).await;

let view = ViewNumber(2);
util.set_view(view);
assert_eq!(util.view_leader(view), util.key_at(0).public());

let replica_prepare = util.new_current_replica_prepare(|_| {});
let _ = util.dispatch_replica_prepare_one(replica_prepare.clone());

let non_validator: validator::SecretKey = util.rng().gen();
let replica_prepare = non_validator.sign_msg(replica_prepare.msg);
util.dispatch_replica_prepare_one(replica_prepare).unwrap();
// PANICS:
// "Couldn't create justification from valid replica messages!: Message signer isn't in the validator set"
}

#[tokio::test]
async fn replica_commit_sanity() {
let mut util = UTHarness::new_many().await;
Expand Down Expand Up @@ -356,6 +354,23 @@ async fn replica_commit_incompatible_protocol_version() {
)
}

#[tokio::test]
async fn replica_commit_non_validator_signer() {
let mut util = UTHarness::new_one().await;

let replica_commit = util.new_current_replica_commit(|_| {}).cast().unwrap().msg;
let non_validator_key: validator::SecretKey = util.rng().gen();
let signed = non_validator_key.sign_msg(ConsensusMsg::ReplicaCommit(replica_commit));

let res = util.dispatch_replica_commit_one(signed);
assert_matches!(
res,
Err(ReplicaCommitError::NonValidatorSigner { signer }) => {
assert_eq!(signer, non_validator_key.public());
}
);
}

#[tokio::test]
async fn replica_commit_old() {
let mut util = UTHarness::new_one().await;
Expand Down
2 changes: 2 additions & 0 deletions node/actors/bft/src/testonly/ut_harness.rs
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,7 @@ impl UTHarness {
.unwrap()
}

#[allow(clippy::result_large_err)]
pub(crate) fn dispatch_replica_commit_one(
&mut self,
msg: Signed<ConsensusMsg>,
Expand All @@ -334,6 +335,7 @@ impl UTHarness {
)
}

#[allow(clippy::result_large_err)]
pub(crate) fn dispatch_replica_commit_many(
&mut self,
messages: Vec<ReplicaCommit>,
Expand Down

0 comments on commit 9644bf1

Please sign in to comment.