complete various todos in tributary (#520)

* complete various todos

* fix pr comments

* Document bounds on unique hashes in TransactionKind

---------

Co-authored-by: Luke Parker <lukeparker5132@gmail.com>
This commit is contained in:
akildemir 2024-02-05 11:50:55 +03:00 committed by GitHub
parent af12cec3b9
commit ad0ecc5185
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 184 additions and 165 deletions

View file

@ -175,9 +175,8 @@ impl<T: TransactionTrait> Block<T> {
mut locally_provided: HashMap<&'static str, VecDeque<T>>,
get_and_increment_nonce: &mut G,
schema: &N::SignatureScheme,
commit: impl Fn(u32) -> Option<Commit<N::SignatureScheme>>,
unsigned_in_chain: impl Fn([u8; 32]) -> bool,
provided_in_chain: impl Fn([u8; 32]) -> bool, // TODO: merge this with unsigned_on_chain?
commit: impl Fn(u64) -> Option<Commit<N::SignatureScheme>>,
provided_or_unsigned_in_chain: impl Fn([u8; 32]) -> bool,
allow_non_local_provided: bool,
) -> Result<(), BlockError> {
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
@ -213,7 +212,7 @@ impl<T: TransactionTrait> Block<T> {
let current_tx_order = match tx.kind() {
TransactionKind::Provided(order) => {
if provided_in_chain(tx_hash) {
if provided_or_unsigned_in_chain(tx_hash) {
Err(BlockError::ProvidedAlreadyIncluded)?;
}
@ -233,7 +232,7 @@ impl<T: TransactionTrait> Block<T> {
}
TransactionKind::Unsigned => {
// check we don't already have the tx in the chain
if unsigned_in_chain(tx_hash) || included_in_block.contains(&tx_hash) {
if provided_or_unsigned_in_chain(tx_hash) || included_in_block.contains(&tx_hash) {
Err(BlockError::UnsignedAlreadyIncluded)?;
}
included_in_block.insert(tx_hash);

View file

@ -18,7 +18,7 @@ pub(crate) struct Blockchain<D: Db, T: TransactionTrait> {
db: Option<D>,
genesis: [u8; 32],
block_number: u32,
block_number: u64,
tip: [u8; 32],
participants: HashSet<<Ristretto as Ciphersuite>::G>,
@ -38,7 +38,7 @@ impl<D: Db, T: TransactionTrait> Blockchain<D, T> {
fn block_key(genesis: &[u8], hash: &[u8; 32]) -> Vec<u8> {
D::key(b"tributary_blockchain", b"block", [genesis, hash].concat())
}
fn block_hash_key(genesis: &[u8], block_number: u32) -> Vec<u8> {
fn block_hash_key(genesis: &[u8], block_number: u64) -> Vec<u8> {
D::key(b"tributary_blockchain", b"block_hash", [genesis, &block_number.to_le_bytes()].concat())
}
fn commit_key(genesis: &[u8], hash: &[u8; 32]) -> Vec<u8> {
@ -88,7 +88,7 @@ impl<D: Db, T: TransactionTrait> Blockchain<D, T> {
let db = res.db.as_ref().unwrap();
db.get(res.block_number_key()).map(|number| (number, db.get(Self::tip_key(genesis)).unwrap()))
} {
res.block_number = u32::from_le_bytes(block_number.try_into().unwrap());
res.block_number = u64::from_le_bytes(block_number.try_into().unwrap());
res.tip.copy_from_slice(&tip);
}
@ -99,7 +99,7 @@ impl<D: Db, T: TransactionTrait> Blockchain<D, T> {
self.tip
}
pub(crate) fn block_number(&self) -> u32 {
pub(crate) fn block_number(&self) -> u64 {
self.block_number
}
@ -112,7 +112,7 @@ impl<D: Db, T: TransactionTrait> Blockchain<D, T> {
db.get(Self::commit_key(&genesis, block))
}
pub(crate) fn block_hash_from_db(db: &D, genesis: [u8; 32], block: u32) -> Option<[u8; 32]> {
pub(crate) fn block_hash_from_db(db: &D, genesis: [u8; 32], block: u64) -> Option<[u8; 32]> {
db.get(Self::block_hash_key(&genesis, block)).map(|h| h.try_into().unwrap())
}
@ -120,11 +120,11 @@ impl<D: Db, T: TransactionTrait> Blockchain<D, T> {
Self::commit_from_db(self.db.as_ref().unwrap(), self.genesis, block)
}
pub(crate) fn block_hash(&self, block: u32) -> Option<[u8; 32]> {
pub(crate) fn block_hash(&self, block: u64) -> Option<[u8; 32]> {
Self::block_hash_from_db(self.db.as_ref().unwrap(), self.genesis, block)
}
pub(crate) fn commit_by_block_number(&self, block: u32) -> Option<Vec<u8>> {
pub(crate) fn commit_by_block_number(&self, block: u64) -> Option<Vec<u8>> {
self.commit(&self.block_hash(block)?)
}
@ -160,16 +160,16 @@ impl<D: Db, T: TransactionTrait> Blockchain<D, T> {
let db = self.db.as_ref().unwrap();
let genesis = self.genesis;
let commit = |block: u32| -> Option<Commit<N::SignatureScheme>> {
let commit = |block: u64| -> Option<Commit<N::SignatureScheme>> {
let hash = Self::block_hash_from_db(db, genesis, block)?;
// we must have a commit per valid hash
let commit = Self::commit_from_db(db, genesis, &hash).unwrap();
// commit has to be valid if it is coming from our db
Some(Commit::<N::SignatureScheme>::decode(&mut commit.as_ref()).unwrap())
};
let unsigned_in_chain =
|hash: [u8; 32]| db.get(Self::unsigned_included_key(&self.genesis, &hash)).is_some();
self.mempool.add::<N, _>(
|signer, order| {
if self.participants.contains(&signer) {
@ -233,11 +233,11 @@ impl<D: Db, T: TransactionTrait> Blockchain<D, T> {
allow_non_local_provided: bool,
) -> Result<(), BlockError> {
let db = self.db.as_ref().unwrap();
let unsigned_in_chain =
|hash: [u8; 32]| db.get(Self::unsigned_included_key(&self.genesis, &hash)).is_some();
let provided_in_chain =
|hash: [u8; 32]| db.get(Self::provided_included_key(&self.genesis, &hash)).is_some();
let commit = |block: u32| -> Option<Commit<N::SignatureScheme>> {
let provided_or_unsigned_in_chain = |hash: [u8; 32]| {
db.get(Self::unsigned_included_key(&self.genesis, &hash)).is_some() ||
db.get(Self::provided_included_key(&self.genesis, &hash)).is_some()
};
let commit = |block: u64| -> Option<Commit<N::SignatureScheme>> {
let commit = self.commit_by_block_number(block)?;
// commit has to be valid if it is coming from our db
Some(Commit::<N::SignatureScheme>::decode(&mut commit.as_ref()).unwrap())
@ -263,8 +263,7 @@ impl<D: Db, T: TransactionTrait> Blockchain<D, T> {
},
schema,
&commit,
unsigned_in_chain,
provided_in_chain,
provided_or_unsigned_in_chain,
allow_non_local_provided,
);
// Drop this TXN's changes as we're solely verifying the block

View file

@ -182,7 +182,7 @@ impl<D: Db, T: TransactionTrait, P: P2p> Tributary<D, T, P> {
let validators = Arc::new(Validators::new(genesis, validators)?);
let mut blockchain = Blockchain::new(db.clone(), genesis, &validators_vec);
let block_number = BlockNumber(blockchain.block_number().into());
let block_number = BlockNumber(blockchain.block_number());
let start_time = if let Some(commit) = blockchain.commit(&blockchain.tip()) {
Commit::<Validators>::decode(&mut commit.as_ref()).unwrap().end_time
@ -240,7 +240,7 @@ impl<D: Db, T: TransactionTrait, P: P2p> Tributary<D, T, P> {
self.genesis
}
pub async fn block_number(&self) -> u32 {
pub async fn block_number(&self) -> u64 {
self.network.blockchain.read().await.block_number()
}
pub async fn tip(&self) -> [u8; 32] {
@ -314,7 +314,7 @@ impl<D: Db, T: TransactionTrait, P: P2p> Tributary<D, T, P> {
return false;
}
let number = BlockNumber((block_number + 1).into());
let number = BlockNumber(block_number + 1);
self.synced_block.write().await.send(SyncedBlock { number, block, commit }).await.unwrap();
result.next().await.unwrap()
}

View file

@ -114,7 +114,7 @@ impl<D: Db, T: TransactionTrait> Mempool<D, T> {
tx: Transaction<T>,
schema: &N::SignatureScheme,
unsigned_in_chain: impl Fn([u8; 32]) -> bool,
commit: impl Fn(u32) -> Option<Commit<N::SignatureScheme>>,
commit: impl Fn(u64) -> Option<Commit<N::SignatureScheme>>,
) -> Result<bool, TransactionError> {
match &tx {
Transaction::Tendermint(tendermint_tx) => {

View file

@ -275,9 +275,31 @@ pub struct TendermintNetwork<D: Db, T: TransactionTrait, P: P2p> {
pub const BLOCK_PROCESSING_TIME: u32 = 999;
pub const LATENCY_TIME: u32 = 1667;
// TODO: Add test asserting this
pub const TARGET_BLOCK_TIME: u32 = BLOCK_PROCESSING_TIME + (3 * LATENCY_TIME);
#[test]
fn assert_target_block_time() {
use serai_db::MemDb;
#[derive(Clone, Debug)]
pub struct DummyP2p;
#[async_trait::async_trait]
impl P2p for DummyP2p {
async fn broadcast(&self, _: [u8; 32], _: Vec<u8>) {
unimplemented!()
}
}
// Type paremeters don't matter here since we only need to call the block_time()
// and it only relies on the constants of the trait implementation. block_time() is in seconds,
// TARGET_BLOCK_TIME is in milliseconds.
assert_eq!(
<TendermintNetwork<MemDb, TendermintTx, DummyP2p> as Network>::block_time(),
TARGET_BLOCK_TIME / 1000
)
}
#[async_trait]
impl<D: Db, T: TransactionTrait, P: P2p> Network for TendermintNetwork<D, T, P> {
type ValidatorId = [u8; 32];
@ -342,7 +364,6 @@ impl<D: Db, T: TransactionTrait, P: P2p> Network for TendermintNetwork<D, T, P>
};
// add tx to blockchain and broadcast to peers
// TODO: Make a function out of this following block
let mut to_broadcast = vec![TRANSACTION_MESSAGE];
tx.write(&mut to_broadcast).unwrap();
if self.blockchain.write().await.add_transaction::<Self>(

View file

@ -12,14 +12,11 @@ use crate::{
};
use tendermint::{
SignedMessageFor, Data,
round::RoundData,
time::CanonicalInstant,
commit_msg,
ext::{Network, Commit, RoundNumber, SignatureScheme},
verify_tendermint_evience,
ext::{Network, Commit},
};
pub use tendermint::Evidence;
pub use tendermint::{Evidence, decode_signed_message};
#[allow(clippy::large_enum_variant)]
#[derive(Clone, PartialEq, Eq, Debug)]
@ -63,127 +60,16 @@ impl Transaction for TendermintTx {
}
}
pub fn decode_signed_message<N: Network>(
mut data: &[u8],
) -> Result<SignedMessageFor<N>, TransactionError> {
SignedMessageFor::<N>::decode(&mut data).map_err(|_| TransactionError::InvalidContent)
}
fn decode_and_verify_signed_message<N: Network>(
data: &[u8],
schema: &N::SignatureScheme,
) -> Result<SignedMessageFor<N>, TransactionError> {
let msg = decode_signed_message::<N>(data)?;
// verify that evidence messages are signed correctly
if !msg.verify_signature(schema) {
Err(TransactionError::InvalidSignature)?
}
Ok(msg)
}
// TODO: Move this into tendermint-machine
// TODO: Strongly type Evidence, instead of having two messages and no idea what's supposedly
// wrong with them. Doing so will massively simplify the auditability of this (as this
// re-implements an entire foreign library's checks for malicious behavior).
pub(crate) fn verify_tendermint_tx<N: Network>(
tx: &TendermintTx,
schema: &N::SignatureScheme,
commit: impl Fn(u32) -> Option<Commit<N::SignatureScheme>>,
commit: impl Fn(u64) -> Option<Commit<N::SignatureScheme>>,
) -> Result<(), TransactionError> {
tx.verify()?;
match tx {
// TODO: Only allow one evidence per validator, since evidence is fatal
TendermintTx::SlashEvidence(ev) => {
match ev {
Evidence::ConflictingMessages(first, second) => {
let first = decode_and_verify_signed_message::<N>(first, schema)?.msg;
let second = decode_and_verify_signed_message::<N>(second, schema)?.msg;
// Make sure they're distinct messages, from the same sender, within the same block
if (first == second) || (first.sender != second.sender) || (first.block != second.block) {
Err(TransactionError::InvalidContent)?;
}
// Distinct messages within the same step
if !((first.round == second.round) && (first.data.step() == second.data.step())) {
Err(TransactionError::InvalidContent)?;
}
}
Evidence::ConflictingPrecommit(first, second) => {
let first = decode_and_verify_signed_message::<N>(first, schema)?.msg;
let second = decode_and_verify_signed_message::<N>(second, schema)?.msg;
if (first.sender != second.sender) || (first.block != second.block) {
Err(TransactionError::InvalidContent)?;
}
// check whether messages are precommits to different blocks
// The inner signatures don't need to be verified since the outer signatures were
// While the inner signatures may be invalid, that would've yielded a invalid precommit
// signature slash instead of distinct precommit slash
if let Data::Precommit(Some((h1, _))) = first.data {
if let Data::Precommit(Some((h2, _))) = second.data {
if h1 == h2 {
Err(TransactionError::InvalidContent)?;
}
return Ok(());
}
}
// No fault identified
Err(TransactionError::InvalidContent)?
}
Evidence::InvalidPrecommit(msg) => {
let msg = decode_and_verify_signed_message::<N>(msg, schema)?.msg;
let Data::Precommit(Some((id, sig))) = &msg.data else {
Err(TransactionError::InvalidContent)?
};
// TODO: We need to be passed in the genesis time to handle this edge case
if msg.block.0 == 0 {
todo!("invalid precommit signature on first block")
}
// get the last commit
// TODO: Why do we use u32 when Tendermint uses u64?
let prior_commit = match u32::try_from(msg.block.0 - 1) {
Ok(n) => match commit(n) {
Some(c) => c,
// If we have yet to sync the block in question, we will return InvalidContent based
// on our own temporal ambiguity
// This will also cause an InvalidContent for anything using a non-existent block,
// yet that's valid behavior
// TODO: Double check the ramifications of this
_ => Err(TransactionError::InvalidContent)?,
},
_ => Err(TransactionError::InvalidContent)?,
};
// calculate the end time till the msg round
let mut last_end_time = CanonicalInstant::new(prior_commit.end_time);
for r in 0 ..= msg.round.0 {
last_end_time = RoundData::<N>::new(RoundNumber(r), last_end_time).end_time();
}
// verify that the commit was actually invalid
if schema.verify(msg.sender, &commit_msg(last_end_time.canonical(), id.as_ref()), sig) {
Err(TransactionError::InvalidContent)?
}
}
Evidence::InvalidValidRound(msg) => {
let msg = decode_and_verify_signed_message::<N>(msg, schema)?.msg;
let Data::Proposal(Some(vr), _) = &msg.data else {
Err(TransactionError::InvalidContent)?
};
if vr.0 < msg.round.0 {
Err(TransactionError::InvalidContent)?
}
}
}
}
TendermintTx::SlashEvidence(ev) => verify_tendermint_evience::<N>(ev, schema, commit)
.map_err(|_| TransactionError::InvalidContent)?,
}
Ok(())

View file

@ -78,11 +78,10 @@ fn empty_block() {
const GENESIS: [u8; 32] = [0xff; 32];
const LAST: [u8; 32] = [0x01; 32];
let validators = Arc::new(Validators::new(GENESIS, vec![]).unwrap());
let commit = |_: u32| -> Option<Commit<Arc<Validators>>> {
let commit = |_: u64| -> Option<Commit<Arc<Validators>>> {
Some(Commit::<Arc<Validators>> { end_time: 0, validators: vec![], signature: vec![] })
};
let unsigned_in_chain = |_: [u8; 32]| false;
let provided_in_chain = |_: [u8; 32]| false;
let provided_or_unsigned_in_chain = |_: [u8; 32]| false;
Block::<NonceTransaction>::new(LAST, vec![], vec![])
.verify::<N, _>(
GENESIS,
@ -91,8 +90,7 @@ fn empty_block() {
&mut |_, _| None,
&validators,
commit,
unsigned_in_chain,
provided_in_chain,
provided_or_unsigned_in_chain,
false,
)
.unwrap();
@ -113,11 +111,10 @@ fn duplicate_nonces() {
insert(NonceTransaction::new(0, 0));
insert(NonceTransaction::new(i, 1));
let commit = |_: u32| -> Option<Commit<Arc<Validators>>> {
let commit = |_: u64| -> Option<Commit<Arc<Validators>>> {
Some(Commit::<Arc<Validators>> { end_time: 0, validators: vec![], signature: vec![] })
};
let unsigned_in_chain = |_: [u8; 32]| false;
let provided_in_chain = |_: [u8; 32]| false;
let provided_or_unsigned_in_chain = |_: [u8; 32]| false;
let mut last_nonce = 0;
let res = Block::new(LAST, vec![], mempool).verify::<N, _>(
@ -131,8 +128,7 @@ fn duplicate_nonces() {
},
&validators,
commit,
unsigned_in_chain,
provided_in_chain,
provided_or_unsigned_in_chain,
false,
);
if i == 1 {

View file

@ -28,7 +28,7 @@ fn new_mempool<T: TransactionTrait>() -> ([u8; 32], MemDb, Mempool<MemDb, T>) {
#[tokio::test]
async fn mempool_addition() {
let (genesis, db, mut mempool) = new_mempool::<SignedTransaction>();
let commit = |_: u32| -> Option<Commit<Arc<Validators>>> {
let commit = |_: u64| -> Option<Commit<Arc<Validators>>> {
Some(Commit::<Arc<Validators>> { end_time: 0, validators: vec![], signature: vec![] })
};
let unsigned_in_chain = |_: [u8; 32]| false;
@ -160,7 +160,7 @@ async fn mempool_addition() {
fn too_many_mempool() {
let (genesis, _, mut mempool) = new_mempool::<SignedTransaction>();
let validators = Arc::new(Validators::new(genesis, vec![]).unwrap());
let commit = |_: u32| -> Option<Commit<Arc<Validators>>> {
let commit = |_: u64| -> Option<Commit<Arc<Validators>>> {
Some(Commit::<Arc<Validators>> { end_time: 0, validators: vec![], signature: vec![] })
};
let unsigned_in_chain = |_: [u8; 32]| false;

View file

@ -42,7 +42,7 @@ async fn serialize_tendermint() {
async fn invalid_valid_round() {
// signer
let (_, signer, signer_id, validators) = tendermint_meta().await;
let commit = |_: u32| -> Option<Commit<Arc<Validators>>> {
let commit = |_: u64| -> Option<Commit<Arc<Validators>>> {
Some(Commit::<Arc<Validators>> { end_time: 0, validators: vec![], signature: vec![] })
};
@ -78,7 +78,7 @@ async fn invalid_valid_round() {
#[tokio::test]
async fn invalid_precommit_signature() {
let (_, signer, signer_id, validators) = tendermint_meta().await;
let commit = |i: u32| -> Option<Commit<Arc<Validators>>> {
let commit = |i: u64| -> Option<Commit<Arc<Validators>>> {
assert_eq!(i, 0);
Some(Commit::<Arc<Validators>> { end_time: 0, validators: vec![], signature: vec![] })
};
@ -127,7 +127,7 @@ async fn invalid_precommit_signature() {
#[tokio::test]
async fn evidence_with_prevote() {
let (_, signer, signer_id, validators) = tendermint_meta().await;
let commit = |_: u32| -> Option<Commit<Arc<Validators>>> {
let commit = |_: u64| -> Option<Commit<Arc<Validators>>> {
Some(Commit::<Arc<Validators>> { end_time: 0, validators: vec![], signature: vec![] })
};
@ -180,7 +180,7 @@ async fn evidence_with_prevote() {
#[tokio::test]
async fn conflicting_msgs_evidence_tx() {
let (genesis, signer, signer_id, validators) = tendermint_meta().await;
let commit = |i: u32| -> Option<Commit<Arc<Validators>>> {
let commit = |i: u64| -> Option<Commit<Arc<Validators>>> {
assert_eq!(i, 0);
Some(Commit::<Arc<Validators>> { end_time: 0, validators: vec![], signature: vec![] })
};

View file

@ -122,6 +122,9 @@ pub enum TransactionKind<'a> {
/// If a supermajority of validators produce a commit for a block with a provided transaction
/// which isn't locally held, the block will be added to the local chain. When the transaction is
/// locally provided, it will be compared for correctness to the on-chain version
///
/// In order to ensure TXs aren't accidentally provided multiple times, all provided transactions
/// must have a unique hash which is also unique to all Unsigned transactions.
Provided(&'static str),
/// An unsigned transaction, only able to be included by the block producer.
@ -129,6 +132,8 @@ pub enum TransactionKind<'a> {
/// Once an Unsigned transaction is included on-chain, it may not be included again. In order to
/// have multiple Unsigned transactions with the same values included on-chain, some distinct
/// nonce must be included in order to cause a distinct hash.
///
/// The hash must also be unique with all Provided transactions.
Unsigned,
/// A signed transaction.

View file

@ -19,6 +19,7 @@ pub mod time;
use time::{sys_time, CanonicalInstant};
pub mod round;
use round::RoundData;
mod block;
use block::BlockData;
@ -103,10 +104,11 @@ impl<V: ValidatorId, B: Block, S: Signature> SignedMessage<V, B, S> {
}
#[derive(Clone, PartialEq, Eq, Debug)]
enum TendermintError<N: Network> {
pub enum TendermintError<N: Network> {
Malicious(N::ValidatorId, Option<Evidence>),
Temporal,
AlreadyHandled,
InvalidEvidence,
}
// Type aliases to abstract over generic hell
@ -139,6 +141,113 @@ pub enum Evidence {
InvalidValidRound(Vec<u8>),
}
pub fn decode_signed_message<N: Network>(mut data: &[u8]) -> Option<SignedMessageFor<N>> {
SignedMessageFor::<N>::decode(&mut data).ok()
}
fn decode_and_verify_signed_message<N: Network>(
data: &[u8],
schema: &N::SignatureScheme,
) -> Result<SignedMessageFor<N>, TendermintError<N>> {
let msg = decode_signed_message::<N>(data).ok_or(TendermintError::InvalidEvidence)?;
// verify that evidence messages are signed correctly
if !msg.verify_signature(schema) {
Err(TendermintError::InvalidEvidence)?;
}
Ok(msg)
}
pub fn verify_tendermint_evience<N: Network>(
evidence: &Evidence,
schema: &N::SignatureScheme,
commit: impl Fn(u64) -> Option<Commit<N::SignatureScheme>>,
) -> Result<(), TendermintError<N>> {
match evidence {
Evidence::ConflictingMessages(first, second) => {
let first = decode_and_verify_signed_message::<N>(first, schema)?.msg;
let second = decode_and_verify_signed_message::<N>(second, schema)?.msg;
// Make sure they're distinct messages, from the same sender, within the same block
if (first == second) || (first.sender != second.sender) || (first.block != second.block) {
Err(TendermintError::InvalidEvidence)?;
}
// Distinct messages within the same step
if !((first.round == second.round) && (first.data.step() == second.data.step())) {
Err(TendermintError::InvalidEvidence)?;
}
}
Evidence::ConflictingPrecommit(first, second) => {
let first = decode_and_verify_signed_message::<N>(first, schema)?.msg;
let second = decode_and_verify_signed_message::<N>(second, schema)?.msg;
if (first.sender != second.sender) || (first.block != second.block) {
Err(TendermintError::InvalidEvidence)?;
}
// check whether messages are precommits to different blocks
// The inner signatures don't need to be verified since the outer signatures were
// While the inner signatures may be invalid, that would've yielded a invalid precommit
// signature slash instead of distinct precommit slash
if let Data::Precommit(Some((h1, _))) = first.data {
if let Data::Precommit(Some((h2, _))) = second.data {
if h1 == h2 {
Err(TendermintError::InvalidEvidence)?;
}
return Ok(());
}
}
// No fault identified
Err(TendermintError::InvalidEvidence)?;
}
Evidence::InvalidPrecommit(msg) => {
let msg = decode_and_verify_signed_message::<N>(msg, schema)?.msg;
let Data::Precommit(Some((id, sig))) = &msg.data else {
Err(TendermintError::InvalidEvidence)?
};
// TODO: We need to be passed in the genesis time to handle this edge case
if msg.block.0 == 0 {
todo!("invalid precommit signature on first block")
}
// get the last commit
let prior_commit = match commit(msg.block.0 - 1) {
Some(c) => c,
// If we have yet to sync the block in question, we will return InvalidContent based
// on our own temporal ambiguity
// This will also cause an InvalidContent for anything using a non-existent block,
// yet that's valid behavior
// TODO: Double check the ramifications of this
_ => Err(TendermintError::InvalidEvidence)?,
};
// calculate the end time till the msg round
let mut last_end_time = CanonicalInstant::new(prior_commit.end_time);
for r in 0 ..= msg.round.0 {
last_end_time = RoundData::<N>::new(RoundNumber(r), last_end_time).end_time();
}
// verify that the commit was actually invalid
if schema.verify(msg.sender, &commit_msg(last_end_time.canonical(), id.as_ref()), sig) {
Err(TendermintError::InvalidEvidence)?
}
}
Evidence::InvalidValidRound(msg) => {
let msg = decode_and_verify_signed_message::<N>(msg, schema)?.msg;
let Data::Proposal(Some(vr), _) = &msg.data else { Err(TendermintError::InvalidEvidence)? };
if vr.0 < msg.round.0 {
Err(TendermintError::InvalidEvidence)?
}
}
}
Ok(())
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub enum SlashEvent {
Id(SlashReason, u64, u32),
@ -543,7 +652,11 @@ impl<N: Network + 'static> TendermintMachine<N> {
self.slash(sender, slash).await
}
Err(TendermintError::Temporal | TendermintError::AlreadyHandled) => (),
Err(
TendermintError::Temporal |
TendermintError::AlreadyHandled |
TendermintError::InvalidEvidence,
) => (),
}
}
}