Use borsh instead of SCALE within tendermint-machine, tributary-sdk

Not only does this follow our general practice, the latest SCALE has a
possibly-lossy truncation in its current implementation for `enum`s I'd like to
avoid without simply silencing.
This commit is contained in:
Luke Parker
2025-09-01 20:07:18 -04:00
parent 53a64bc7e2
commit e8094523ff
12 changed files with 140 additions and 98 deletions

View File

@@ -21,7 +21,7 @@ thiserror = { version = "2", default-features = false, features = ["std"] }
hex = { version = "0.4", default-features = false, features = ["std"] }
log = { version = "0.4", default-features = false, features = ["std"] }
parity-scale-codec = { version = "3", default-features = false, features = ["std", "derive"] }
borsh = { version = "1", default-features = false, features = ["std", "derive", "de_strict_order"] }
futures-util = { version = "0.3", default-features = false, features = ["std", "async-await-macro", "sink", "channel"] }
futures-channel = { version = "0.3", default-features = false, features = ["std", "sink"] }

View File

@@ -3,33 +3,41 @@ use std::{sync::Arc, collections::HashSet};
use thiserror::Error;
use parity_scale_codec::{Encode, Decode};
use borsh::{BorshSerialize, BorshDeserialize};
use crate::{SignedMessageFor, SlashEvent, commit_msg};
/// An alias for a series of traits required for a type to be usable as a validator ID,
/// automatically implemented for all types satisfying those traits.
pub trait ValidatorId:
Send + Sync + Clone + Copy + PartialEq + Eq + Hash + Debug + Encode + Decode
Send + Sync + Clone + Copy + PartialEq + Eq + Hash + Debug + BorshSerialize + BorshDeserialize
{
}
impl<V: Send + Sync + Clone + Copy + PartialEq + Eq + Hash + Debug + Encode + Decode> ValidatorId
for V
#[rustfmt::skip]
impl<
V: Send + Sync + Clone + Copy + PartialEq + Eq + Hash + Debug + BorshSerialize + BorshDeserialize,
> ValidatorId for V
{
}
/// An alias for a series of traits required for a type to be usable as a signature,
/// automatically implemented for all types satisfying those traits.
pub trait Signature: Send + Sync + Clone + PartialEq + Eq + Debug + Encode + Decode {}
impl<S: Send + Sync + Clone + PartialEq + Eq + Debug + Encode + Decode> Signature for S {}
pub trait Signature:
Send + Sync + Clone + PartialEq + Eq + Debug + BorshSerialize + BorshDeserialize
{
}
impl<S: Send + Sync + Clone + PartialEq + Eq + Debug + BorshSerialize + BorshDeserialize> Signature
for S
{
}
// Type aliases which are distinct according to the type system
/// A struct containing a Block Number, wrapped to have a distinct type.
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Encode, Decode)]
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, BorshSerialize, BorshDeserialize)]
pub struct BlockNumber(pub u64);
/// A struct containing a round number, wrapped to have a distinct type.
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Encode, Decode)]
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, BorshSerialize, BorshDeserialize)]
pub struct RoundNumber(pub u32);
/// A signer for a validator.
@@ -127,7 +135,7 @@ impl<S: SignatureScheme> SignatureScheme for Arc<S> {
/// A commit for a specific block.
///
/// The list of validators have weight exceeding the threshold for a valid commit.
#[derive(PartialEq, Debug, Encode, Decode)]
#[derive(PartialEq, Debug, BorshSerialize, BorshDeserialize)]
pub struct Commit<S: SignatureScheme> {
/// End time of the round which created this commit, used as the start time of the next block.
pub end_time: u64,
@@ -185,7 +193,7 @@ impl<W: Weights> Weights for Arc<W> {
}
/// Simplified error enum representing a block's validity.
#[derive(Clone, Copy, PartialEq, Eq, Debug, Error, Encode, Decode)]
#[derive(Clone, Copy, PartialEq, Eq, Debug, Error, BorshSerialize, BorshDeserialize)]
pub enum BlockError {
/// Malformed block which is wholly invalid.
#[error("invalid block")]
@@ -197,9 +205,20 @@ pub enum BlockError {
}
/// Trait representing a Block.
pub trait Block: Send + Sync + Clone + PartialEq + Eq + Debug + Encode + Decode {
pub trait Block:
Send + Sync + Clone + PartialEq + Eq + Debug + BorshSerialize + BorshDeserialize
{
// Type used to identify blocks. Presumably a cryptographic hash of the block.
type Id: Send + Sync + Copy + Clone + PartialEq + Eq + AsRef<[u8]> + Debug + Encode + Decode;
type Id: Send
+ Sync
+ Copy
+ Clone
+ PartialEq
+ Eq
+ AsRef<[u8]>
+ Debug
+ BorshSerialize
+ BorshDeserialize;
/// Return the deterministic, unique ID for this block.
fn id(&self) -> Self::Id;

View File

@@ -6,7 +6,7 @@ use std::{
collections::{VecDeque, HashMap},
};
use parity_scale_codec::{Encode, Decode, IoReader};
use borsh::{BorshSerialize, BorshDeserialize};
use futures_channel::mpsc;
use futures_util::{
@@ -41,14 +41,14 @@ pub fn commit_msg(end_time: u64, id: &[u8]) -> Vec<u8> {
[&end_time.to_le_bytes(), id].concat()
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Encode, Decode)]
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, BorshSerialize, BorshDeserialize)]
pub enum Step {
Propose,
Prevote,
Precommit,
}
#[derive(Clone, Eq, Debug, Encode, Decode)]
#[derive(Clone, Eq, Debug, BorshSerialize, BorshDeserialize)]
pub enum Data<B: Block, S: Signature> {
Proposal(Option<RoundNumber>, B),
Prevote(Option<B::Id>),
@@ -90,7 +90,7 @@ impl<B: Block, S: Signature> Data<B, S> {
}
}
#[derive(Clone, PartialEq, Eq, Debug, Encode, Decode)]
#[derive(Clone, PartialEq, Eq, Debug, BorshSerialize, BorshDeserialize)]
pub struct Message<V: ValidatorId, B: Block, S: Signature> {
pub sender: V,
pub block: BlockNumber,
@@ -100,7 +100,7 @@ pub struct Message<V: ValidatorId, B: Block, S: Signature> {
}
/// A signed Tendermint consensus message to be broadcast to the other validators.
#[derive(Clone, PartialEq, Eq, Debug, Encode, Decode)]
#[derive(Clone, PartialEq, Eq, Debug, BorshSerialize, BorshDeserialize)]
pub struct SignedMessage<V: ValidatorId, B: Block, S: Signature> {
pub msg: Message<V, B, S>,
pub sig: S,
@@ -117,18 +117,18 @@ impl<V: ValidatorId, B: Block, S: Signature> SignedMessage<V, B, S> {
&self,
signer: &Scheme,
) -> bool {
signer.verify(self.msg.sender, &self.msg.encode(), &self.sig)
signer.verify(self.msg.sender, &borsh::to_vec(&self.msg).unwrap(), &self.sig)
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug, Encode, Decode)]
#[derive(Clone, Copy, PartialEq, Eq, Debug, BorshSerialize, BorshDeserialize)]
pub enum SlashReason {
FailToPropose,
InvalidBlock,
InvalidProposer,
}
#[derive(Clone, PartialEq, Eq, Debug, Encode, Decode)]
#[derive(Clone, PartialEq, Eq, Debug, BorshSerialize, BorshDeserialize)]
pub enum Evidence {
ConflictingMessages(Vec<u8>, Vec<u8>),
InvalidPrecommit(Vec<u8>),
@@ -159,7 +159,7 @@ pub type SignedMessageFor<N> = SignedMessage<
>;
pub fn decode_signed_message<N: Network>(mut data: &[u8]) -> Option<SignedMessageFor<N>> {
SignedMessageFor::<N>::decode(&mut data).ok()
SignedMessageFor::<N>::deserialize_reader(&mut data).ok()
}
fn decode_and_verify_signed_message<N: Network>(
@@ -339,7 +339,7 @@ impl<N: Network + 'static> TendermintMachine<N> {
target: "tendermint",
"proposer for block {}, round {round:?} was {} (me: {res})",
self.block.number.0,
hex::encode(proposer.encode()),
hex::encode(borsh::to_vec(&proposer).unwrap()),
);
res
}
@@ -420,7 +420,11 @@ impl<N: Network + 'static> TendermintMachine<N> {
// TODO: If the new slash event has evidence, emit to prevent a low-importance slash from
// cancelling emission of high-importance slashes
if !self.block.slashes.contains(&validator) {
log::info!(target: "tendermint", "Slashing validator {}", hex::encode(validator.encode()));
log::info!(
target: "tendermint",
"Slashing validator {}",
hex::encode(borsh::to_vec(&validator).unwrap()),
);
self.block.slashes.insert(validator);
self.network.slash(validator, slash_event).await;
}
@@ -670,7 +674,7 @@ impl<N: Network + 'static> TendermintMachine<N> {
self
.slash(
msg.sender,
SlashEvent::WithEvidence(Evidence::InvalidPrecommit(signed.encode())),
SlashEvent::WithEvidence(Evidence::InvalidPrecommit(borsh::to_vec(&signed).unwrap())),
)
.await;
Err(TendermintError::Malicious)?;
@@ -741,7 +745,10 @@ impl<N: Network + 'static> TendermintMachine<N> {
self.broadcast(Data::Prevote(None));
}
self
.slash(msg.sender, SlashEvent::WithEvidence(Evidence::InvalidValidRound(msg.encode())))
.slash(
msg.sender,
SlashEvent::WithEvidence(Evidence::InvalidValidRound(borsh::to_vec(&msg).unwrap())),
)
.await;
Err(TendermintError::Malicious)?;
}
@@ -1032,7 +1039,7 @@ impl<N: Network + 'static> TendermintMachine<N> {
while !messages.is_empty() {
self.network.broadcast(
SignedMessageFor::<N>::decode(&mut IoReader(&mut messages))
SignedMessageFor::<N>::deserialize_reader(&mut messages)
.expect("saved invalid message to DB")
).await;
}
@@ -1057,7 +1064,7 @@ impl<N: Network + 'static> TendermintMachine<N> {
} {
if our_message {
assert!(sig.is_none());
sig = Some(self.signer.sign(&msg.encode()).await);
sig = Some(self.signer.sign(&borsh::to_vec(&msg).unwrap()).await);
}
let sig = sig.unwrap();
@@ -1077,7 +1084,7 @@ impl<N: Network + 'static> TendermintMachine<N> {
let message_tape_key = message_tape_key(self.genesis);
let mut txn = self.db.txn();
let mut message_tape = txn.get(&message_tape_key).unwrap_or(vec![]);
message_tape.extend(signed_msg.encode());
signed_msg.serialize(&mut message_tape).unwrap();
txn.put(&message_tape_key, message_tape);
txn.commit();
}

View File

@@ -1,7 +1,5 @@
use std::{sync::Arc, collections::HashMap};
use parity_scale_codec::Encode;
use crate::{ext::*, RoundNumber, Step, DataFor, SignedMessageFor, Evidence};
type RoundLog<N> = HashMap<<N as Network>::ValidatorId, HashMap<Step, SignedMessageFor<N>>>;
@@ -39,7 +37,10 @@ impl<N: Network> MessageLog<N> {
target: "tendermint",
"Validator sent multiple messages for the same block + round + step"
);
Err(Evidence::ConflictingMessages(existing.encode(), signed.encode()))?;
Err(Evidence::ConflictingMessages(
borsh::to_vec(&existing).unwrap(),
borsh::to_vec(&signed).unwrap(),
))?;
}
return Ok(false);
}

View File

@@ -4,7 +4,7 @@ use std::{
time::{UNIX_EPOCH, SystemTime, Duration},
};
use parity_scale_codec::{Encode, Decode};
use borsh::{BorshSerialize, BorshDeserialize};
use futures_util::sink::SinkExt;
use tokio::{sync::RwLock, time::sleep};
@@ -89,7 +89,7 @@ impl Weights for TestWeights {
}
}
#[derive(Clone, PartialEq, Eq, Debug, Encode, Decode)]
#[derive(Clone, PartialEq, Eq, Debug, BorshSerialize, BorshDeserialize)]
struct TestBlock {
id: TestBlockId,
valid: Result<(), BlockError>,