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

@@ -5,7 +5,7 @@ use ciphersuite::{group::GroupEncoding, Ciphersuite};
use serai_db::{Get, DbTxn, Db};
use scale::Decode;
use borsh::BorshDeserialize;
use tendermint::ext::{Network, Commit};
@@ -62,7 +62,7 @@ impl<D: Db, T: TransactionTrait> Blockchain<D, T> {
D::key(
b"tributary_blockchain",
b"next_nonce",
[genesis.as_ref(), signer.to_bytes().as_ref(), order].concat(),
[genesis.as_slice(), signer.to_bytes().as_slice(), order].concat(),
)
}
@@ -106,7 +106,7 @@ impl<D: Db, T: TransactionTrait> Blockchain<D, T> {
pub(crate) fn block_from_db(db: &D, genesis: [u8; 32], block: &[u8; 32]) -> Option<Block<T>> {
db.get(Self::block_key(&genesis, block))
.map(|bytes| Block::<T>::read::<&[u8]>(&mut bytes.as_ref()).unwrap())
.map(|bytes| Block::<T>::read::<&[u8]>(&mut bytes.as_slice()).unwrap())
}
pub(crate) fn commit_from_db(db: &D, genesis: [u8; 32], block: &[u8; 32]) -> Option<Vec<u8>> {
@@ -166,7 +166,7 @@ impl<D: Db, T: TransactionTrait> Blockchain<D, T> {
// 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())
Some(Commit::<N::SignatureScheme>::deserialize_reader(&mut commit.as_slice()).unwrap())
};
let unsigned_in_chain =
|hash: [u8; 32]| db.get(Self::unsigned_included_key(&self.genesis, &hash)).is_some();
@@ -241,7 +241,7 @@ impl<D: Db, T: TransactionTrait> Blockchain<D, T> {
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())
Some(Commit::<N::SignatureScheme>::deserialize_reader(&mut commit.as_slice()).unwrap())
};
let mut txn_db = db.clone();

View File

@@ -3,10 +3,11 @@ use std::{sync::Arc, io};
use zeroize::Zeroizing;
use borsh::BorshDeserialize;
use ciphersuite::Ciphersuite;
use dalek_ff_group::Ristretto;
use scale::Decode;
use futures_channel::mpsc::UnboundedReceiver;
use futures_util::{StreamExt, SinkExt};
use ::tendermint::{
@@ -177,7 +178,7 @@ impl<D: Db, T: TransactionTrait, P: P2p> Tributary<D, T, P> {
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
Commit::<Validators>::deserialize_reader(&mut commit.as_slice()).unwrap().end_time
} else {
start_time
};
@@ -276,8 +277,8 @@ impl<D: Db, T: TransactionTrait, P: P2p> Tributary<D, T, P> {
}
let block = TendermintBlock(block.serialize());
let mut commit_ref = commit.as_ref();
let Ok(commit) = Commit::<Arc<Validators>>::decode(&mut commit_ref) else {
let mut commit_ref = commit.as_slice();
let Ok(commit) = Commit::<Arc<Validators>>::deserialize_reader(&mut commit_ref) else {
log::error!("sent an invalidly serialized commit");
return false;
};
@@ -327,7 +328,7 @@ impl<D: Db, T: TransactionTrait, P: P2p> Tributary<D, T, P> {
Some(&TENDERMINT_MESSAGE) => {
let Ok(msg) =
SignedMessageFor::<TendermintNetwork<D, T, P>>::decode::<&[u8]>(&mut &msg[1 ..])
SignedMessageFor::<TendermintNetwork<D, T, P>>::deserialize_reader(&mut &msg[1 ..])
else {
log::error!("received invalid tendermint message");
return false;
@@ -367,15 +368,17 @@ impl<D: Db, T: TransactionTrait> TributaryReader<D, T> {
Blockchain::<D, T>::commit_from_db(&self.0, self.1, hash)
}
pub fn parsed_commit(&self, hash: &[u8; 32]) -> Option<Commit<Validators>> {
self.commit(hash).map(|commit| Commit::<Validators>::decode(&mut commit.as_ref()).unwrap())
self
.commit(hash)
.map(|commit| Commit::<Validators>::deserialize_reader(&mut commit.as_slice()).unwrap())
}
pub fn block_after(&self, hash: &[u8; 32]) -> Option<[u8; 32]> {
Blockchain::<D, T>::block_after(&self.0, self.1, hash)
}
pub fn time_of_block(&self, hash: &[u8; 32]) -> Option<u64> {
self
.commit(hash)
.map(|commit| Commit::<Validators>::decode(&mut commit.as_ref()).unwrap().end_time)
self.commit(hash).map(|commit| {
Commit::<Validators>::deserialize_reader(&mut commit.as_slice()).unwrap().end_time
})
}
pub fn locally_provided_txs_in_block(&self, hash: &[u8; 32], order: &str) -> bool {

View File

@@ -24,7 +24,7 @@ use schnorr::{
use serai_db::Db;
use scale::{Encode, Decode};
use borsh::{BorshSerialize, BorshDeserialize};
use tendermint::{
SignedMessageFor,
ext::{
@@ -249,7 +249,7 @@ impl Weights for Validators {
}
}
#[derive(Clone, PartialEq, Eq, Debug, Encode, Decode)]
#[derive(Clone, PartialEq, Eq, Debug, BorshSerialize, BorshDeserialize)]
pub struct TendermintBlock(pub Vec<u8>);
impl BlockTrait for TendermintBlock {
type Id = [u8; 32];
@@ -301,7 +301,7 @@ impl<D: Db, T: TransactionTrait, P: P2p> Network for TendermintNetwork<D, T, P>
fn broadcast(&mut self, msg: SignedMessageFor<Self>) -> impl Send + Future<Output = ()> {
async move {
let mut to_broadcast = vec![TENDERMINT_MESSAGE];
to_broadcast.extend(msg.encode());
msg.serialize(&mut to_broadcast).unwrap();
self.p2p.broadcast(self.genesis, to_broadcast).await
}
}
@@ -391,7 +391,7 @@ impl<D: Db, T: TransactionTrait, P: P2p> Network for TendermintNetwork<D, T, P>
return invalid_block();
};
let encoded_commit = commit.encode();
let encoded_commit = borsh::to_vec(&commit).unwrap();
loop {
let block_res = self.blockchain.write().await.add_block::<Self>(
&block,

View File

@@ -1,6 +1,6 @@
use std::io;
use scale::{Encode, Decode, IoReader};
use borsh::BorshDeserialize;
use blake2::{Digest, Blake2s256};
@@ -27,14 +27,14 @@ pub enum TendermintTx {
impl ReadWrite for TendermintTx {
fn read<R: io::Read>(reader: &mut R) -> io::Result<Self> {
Evidence::decode(&mut IoReader(reader))
Evidence::deserialize_reader(reader)
.map(TendermintTx::SlashEvidence)
.map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "invalid evidence format"))
}
fn write<W: io::Write>(&self, writer: &mut W) -> io::Result<()> {
match self {
TendermintTx::SlashEvidence(ev) => writer.write_all(&ev.encode()),
TendermintTx::SlashEvidence(ev) => writer.write_all(&borsh::to_vec(&ev).unwrap()),
}
}
}

View File

@@ -13,8 +13,6 @@ use ciphersuite::{
};
use schnorr::SchnorrSignature;
use scale::Encode;
use ::tendermint::{
ext::{Network, Signer as SignerTrait, SignatureScheme, BlockNumber, RoundNumber},
SignedMessageFor, DataFor, Message, SignedMessage, Data, Evidence,
@@ -204,7 +202,7 @@ pub async fn signed_from_data<N: Network>(
round: RoundNumber(round_number),
data,
};
let sig = signer.sign(&msg.encode()).await;
let sig = signer.sign(&borsh::to_vec(&msg).unwrap()).await;
SignedMessage { msg, sig }
}
@@ -217,5 +215,5 @@ pub async fn random_evidence_tx<N: Network>(
let data = Data::Proposal(Some(RoundNumber(0)), b);
let signer_id = signer.validator_id().await.unwrap();
let signed = signed_from_data::<N>(signer, signer_id, 0, 0, data).await;
TendermintTx::SlashEvidence(Evidence::InvalidValidRound(signed.encode()))
TendermintTx::SlashEvidence(Evidence::InvalidValidRound(borsh::to_vec(&signed).unwrap()))
}

View File

@@ -6,8 +6,6 @@ use rand::{RngCore, rngs::OsRng};
use dalek_ff_group::Ristretto;
use ciphersuite::{Ciphersuite, group::ff::Field};
use scale::Encode;
use tendermint::{
time::CanonicalInstant,
round::RoundData,
@@ -52,7 +50,10 @@ async fn invalid_valid_round() {
async move {
let data = Data::Proposal(valid_round, TendermintBlock(vec![]));
let signed = signed_from_data::<N>(signer.clone().into(), signer_id, 0, 0, data).await;
(signed.clone(), TendermintTx::SlashEvidence(Evidence::InvalidValidRound(signed.encode())))
(
signed.clone(),
TendermintTx::SlashEvidence(Evidence::InvalidValidRound(borsh::to_vec(&signed).unwrap())),
)
}
};
@@ -70,7 +71,8 @@ async fn invalid_valid_round() {
let mut random_sig = [0u8; 64];
OsRng.fill_bytes(&mut random_sig);
signed.sig = random_sig;
let tx = TendermintTx::SlashEvidence(Evidence::InvalidValidRound(signed.encode()));
let tx =
TendermintTx::SlashEvidence(Evidence::InvalidValidRound(borsh::to_vec(&signed).unwrap()));
// should fail
assert!(verify_tendermint_tx::<N>(&tx, &validators, commit).is_err());
@@ -90,7 +92,10 @@ async fn invalid_precommit_signature() {
let signed =
signed_from_data::<N>(signer.clone().into(), signer_id, 1, 0, Data::Precommit(precommit))
.await;
(signed.clone(), TendermintTx::SlashEvidence(Evidence::InvalidPrecommit(signed.encode())))
(
signed.clone(),
TendermintTx::SlashEvidence(Evidence::InvalidPrecommit(borsh::to_vec(&signed).unwrap())),
)
}
};
@@ -120,7 +125,8 @@ async fn invalid_precommit_signature() {
let mut random_sig = [0u8; 64];
OsRng.fill_bytes(&mut random_sig);
signed.sig = random_sig;
let tx = TendermintTx::SlashEvidence(Evidence::InvalidPrecommit(signed.encode()));
let tx =
TendermintTx::SlashEvidence(Evidence::InvalidPrecommit(borsh::to_vec(&signed).unwrap()));
assert!(verify_tendermint_tx::<N>(&tx, &validators, commit).is_err());
}
}
@@ -138,24 +144,32 @@ async fn evidence_with_prevote() {
// it should fail for all reasons.
let mut txs = vec![];
txs.push(TendermintTx::SlashEvidence(Evidence::InvalidPrecommit(
signed_from_data::<N>(signer.clone().into(), signer_id, 0, 0, Data::Prevote(block_id))
.await
.encode(),
borsh::to_vec(
&&signed_from_data::<N>(signer.clone().into(), signer_id, 0, 0, Data::Prevote(block_id))
.await,
)
.unwrap(),
)));
txs.push(TendermintTx::SlashEvidence(Evidence::InvalidValidRound(
signed_from_data::<N>(signer.clone().into(), signer_id, 0, 0, Data::Prevote(block_id))
.await
.encode(),
borsh::to_vec(
&signed_from_data::<N>(signer.clone().into(), signer_id, 0, 0, Data::Prevote(block_id))
.await,
)
.unwrap(),
)));
// Since these require a second message, provide this one again
// ConflictingMessages can be fired for actually conflicting Prevotes however
txs.push(TendermintTx::SlashEvidence(Evidence::ConflictingMessages(
signed_from_data::<N>(signer.clone().into(), signer_id, 0, 0, Data::Prevote(block_id))
.await
.encode(),
signed_from_data::<N>(signer.clone().into(), signer_id, 0, 0, Data::Prevote(block_id))
.await
.encode(),
borsh::to_vec(
&signed_from_data::<N>(signer.clone().into(), signer_id, 0, 0, Data::Prevote(block_id))
.await,
)
.unwrap(),
borsh::to_vec(
&signed_from_data::<N>(signer.clone().into(), signer_id, 0, 0, Data::Prevote(block_id))
.await,
)
.unwrap(),
)));
txs
}
@@ -189,16 +203,16 @@ async fn conflicting_msgs_evidence_tx() {
// non-conflicting data should fail
let signed_1 = signed_for_b_r(0, 0, Data::Proposal(None, TendermintBlock(vec![0x11]))).await;
let tx = TendermintTx::SlashEvidence(Evidence::ConflictingMessages(
signed_1.encode(),
signed_1.encode(),
borsh::to_vec(&signed_1).unwrap(),
borsh::to_vec(&signed_1).unwrap(),
));
assert!(verify_tendermint_tx::<N>(&tx, &validators, commit).is_err());
// conflicting data should pass
let signed_2 = signed_for_b_r(0, 0, Data::Proposal(None, TendermintBlock(vec![0x22]))).await;
let tx = TendermintTx::SlashEvidence(Evidence::ConflictingMessages(
signed_1.encode(),
signed_2.encode(),
borsh::to_vec(&signed_1).unwrap(),
borsh::to_vec(&signed_2).unwrap(),
));
verify_tendermint_tx::<N>(&tx, &validators, commit).unwrap();
@@ -206,16 +220,16 @@ async fn conflicting_msgs_evidence_tx() {
// (except for Precommit)
let signed_2 = signed_for_b_r(0, 1, Data::Proposal(None, TendermintBlock(vec![0x22]))).await;
let tx = TendermintTx::SlashEvidence(Evidence::ConflictingMessages(
signed_1.encode(),
signed_2.encode(),
borsh::to_vec(&signed_1).unwrap(),
borsh::to_vec(&signed_2).unwrap(),
));
verify_tendermint_tx::<N>(&tx, &validators, commit).unwrap_err();
// Proposals for different block numbers should also fail as evidence
let signed_2 = signed_for_b_r(1, 0, Data::Proposal(None, TendermintBlock(vec![0x22]))).await;
let tx = TendermintTx::SlashEvidence(Evidence::ConflictingMessages(
signed_1.encode(),
signed_2.encode(),
borsh::to_vec(&signed_1).unwrap(),
borsh::to_vec(&signed_2).unwrap(),
));
verify_tendermint_tx::<N>(&tx, &validators, commit).unwrap_err();
}
@@ -225,16 +239,16 @@ async fn conflicting_msgs_evidence_tx() {
// non-conflicting data should fail
let signed_1 = signed_for_b_r(0, 0, Data::Prevote(Some([0x11; 32]))).await;
let tx = TendermintTx::SlashEvidence(Evidence::ConflictingMessages(
signed_1.encode(),
signed_1.encode(),
borsh::to_vec(&signed_1).unwrap(),
borsh::to_vec(&signed_1).unwrap(),
));
assert!(verify_tendermint_tx::<N>(&tx, &validators, commit).is_err());
// conflicting data should pass
let signed_2 = signed_for_b_r(0, 0, Data::Prevote(Some([0x22; 32]))).await;
let tx = TendermintTx::SlashEvidence(Evidence::ConflictingMessages(
signed_1.encode(),
signed_2.encode(),
borsh::to_vec(&signed_1).unwrap(),
borsh::to_vec(&signed_2).unwrap(),
));
verify_tendermint_tx::<N>(&tx, &validators, commit).unwrap();
@@ -242,16 +256,16 @@ async fn conflicting_msgs_evidence_tx() {
// (except for Precommit)
let signed_2 = signed_for_b_r(0, 1, Data::Prevote(Some([0x22; 32]))).await;
let tx = TendermintTx::SlashEvidence(Evidence::ConflictingMessages(
signed_1.encode(),
signed_2.encode(),
borsh::to_vec(&signed_1).unwrap(),
borsh::to_vec(&signed_2).unwrap(),
));
verify_tendermint_tx::<N>(&tx, &validators, commit).unwrap_err();
// Proposals for different block numbers should also fail as evidence
let signed_2 = signed_for_b_r(1, 0, Data::Prevote(Some([0x22; 32]))).await;
let tx = TendermintTx::SlashEvidence(Evidence::ConflictingMessages(
signed_1.encode(),
signed_2.encode(),
borsh::to_vec(&signed_1).unwrap(),
borsh::to_vec(&signed_2).unwrap(),
));
verify_tendermint_tx::<N>(&tx, &validators, commit).unwrap_err();
}
@@ -273,8 +287,8 @@ async fn conflicting_msgs_evidence_tx() {
.await;
let tx = TendermintTx::SlashEvidence(Evidence::ConflictingMessages(
signed_1.encode(),
signed_2.encode(),
borsh::to_vec(&signed_1).unwrap(),
borsh::to_vec(&signed_2).unwrap(),
));
// update schema so that we don't fail due to invalid signature
@@ -293,8 +307,8 @@ async fn conflicting_msgs_evidence_tx() {
let signed_1 = signed_for_b_r(0, 0, Data::Proposal(None, TendermintBlock(vec![]))).await;
let signed_2 = signed_for_b_r(0, 0, Data::Prevote(None)).await;
let tx = TendermintTx::SlashEvidence(Evidence::ConflictingMessages(
signed_1.encode(),
signed_2.encode(),
borsh::to_vec(&signed_1).unwrap(),
borsh::to_vec(&signed_2).unwrap(),
));
assert!(verify_tendermint_tx::<N>(&tx, &validators, commit).is_err());
}