mirror of
https://github.com/serai-dex/serai.git
synced 2025-03-12 09:26:51 +00:00
Remove SlashVote per #349
This commit is contained in:
parent
718c44d50e
commit
c65bb70741
9 changed files with 59 additions and 369 deletions
|
@ -76,20 +76,6 @@ async fn handle_block<
|
|||
|
||||
// TODO: disconnect the node from network/ban from further participation in Tributary
|
||||
}
|
||||
TributaryTransaction::Tendermint(TendermintTx::SlashVote(vote)) => {
|
||||
// TODO: make sure same signer doesn't vote twice
|
||||
|
||||
// increment the counter for this vote
|
||||
let vote_key = TributaryDb::<D>::slash_vote_key(genesis, vote.id, vote.target);
|
||||
let mut count = txn.get(&vote_key).map_or(0, |c| u32::from_le_bytes(c.try_into().unwrap()));
|
||||
count += 1; // TODO: Increase by weight, not by 1
|
||||
txn.put(vote_key, count.to_le_bytes());
|
||||
|
||||
// TODO: Check if a supermajority of validators, by weight, voted, and increment slash
|
||||
// points if so
|
||||
// If a node has a certain number more than the median slash points, the node should be
|
||||
// removed
|
||||
}
|
||||
TributaryTransaction::Application(tx) => {
|
||||
handle_application_tx::<D, _, _, _>(
|
||||
tx,
|
||||
|
|
|
@ -251,7 +251,7 @@ impl<T: TransactionTrait> Block<T> {
|
|||
// use this pattern of verifying tendermint Txs and app txs differently?
|
||||
match tx {
|
||||
Transaction::Tendermint(tx) => {
|
||||
match verify_tendermint_tx::<N>(tx, genesis, schema.clone(), &commit) {
|
||||
match verify_tendermint_tx::<N>(tx, schema.clone(), &commit) {
|
||||
Ok(()) => {}
|
||||
Err(e) => Err(BlockError::TransactionError(e))?,
|
||||
}
|
||||
|
|
|
@ -112,7 +112,7 @@ impl<D: Db, T: TransactionTrait> Mempool<D, T> {
|
|||
}
|
||||
|
||||
// verify the tx
|
||||
if verify_tendermint_tx::<N>(tendermint_tx, self.genesis, schema, commit).is_err() {
|
||||
if verify_tendermint_tx::<N>(tendermint_tx, schema, commit).is_err() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ use async_trait::async_trait;
|
|||
use subtle::ConstantTimeEq;
|
||||
use zeroize::{Zeroize, Zeroizing};
|
||||
|
||||
use rand::{SeedableRng, seq::SliceRandom, rngs::OsRng};
|
||||
use rand::{SeedableRng, seq::SliceRandom};
|
||||
use rand_chacha::ChaCha12Rng;
|
||||
|
||||
use transcript::{Transcript, RecommendedTranscript};
|
||||
|
@ -43,11 +43,11 @@ use tokio::{
|
|||
use crate::{
|
||||
TENDERMINT_MESSAGE, TRANSACTION_MESSAGE, BLOCK_MESSAGE, ReadWrite,
|
||||
transaction::Transaction as TransactionTrait, Transaction, BlockHeader, Block, BlockError,
|
||||
Blockchain, P2p, tendermint::tx::SlashVote,
|
||||
Blockchain, P2p,
|
||||
};
|
||||
|
||||
pub mod tx;
|
||||
use tx::{TendermintTx, VoteSignature};
|
||||
use tx::TendermintTx;
|
||||
|
||||
const DST: &[u8] = b"Tributary Tendermint Commit Aggregator";
|
||||
|
||||
|
@ -312,21 +312,17 @@ impl<D: Db, T: TransactionTrait, P: P2p> Network for TendermintNetwork<D, T, P>
|
|||
);
|
||||
|
||||
let signer = self.signer();
|
||||
let tx = match slash_event {
|
||||
let Some(tx) = (match slash_event {
|
||||
SlashEvent::WithEvidence(m1, m2) => {
|
||||
// create an unsigned evidence tx
|
||||
TendermintTx::SlashEvidence((m1, m2).encode())
|
||||
Some(TendermintTx::SlashEvidence((m1, m2).encode()))
|
||||
}
|
||||
SlashEvent::Id(reason, block, round) => {
|
||||
// create a signed vote tx
|
||||
let mut tx = TendermintTx::SlashVote(SlashVote {
|
||||
id: (reason, block, round).encode().try_into().unwrap(),
|
||||
target: validator.encode().try_into().unwrap(),
|
||||
sig: VoteSignature::default(),
|
||||
});
|
||||
tx.sign(&mut OsRng, signer.genesis, &signer.key);
|
||||
tx
|
||||
SlashEvent::Id(_reason, _block, _round) => {
|
||||
// TODO: Increase locally observed slash points
|
||||
None
|
||||
}
|
||||
}) else {
|
||||
return;
|
||||
};
|
||||
|
||||
// add tx to blockchain and broadcast to peers
|
||||
|
|
|
@ -1,19 +1,10 @@
|
|||
use core::ops::Deref;
|
||||
use std::{io, vec, default::Default};
|
||||
use std::io;
|
||||
|
||||
use scale::Decode;
|
||||
|
||||
use zeroize::Zeroizing;
|
||||
use blake2::{Digest, Blake2s256};
|
||||
|
||||
use blake2::{Digest, Blake2s256, Blake2b512};
|
||||
|
||||
use rand::{RngCore, CryptoRng};
|
||||
|
||||
use ciphersuite::{
|
||||
group::{GroupEncoding, ff::Field},
|
||||
Ciphersuite, Ristretto,
|
||||
};
|
||||
use schnorr::SchnorrSignature;
|
||||
use ciphersuite::{Ciphersuite, Ristretto};
|
||||
|
||||
use crate::{
|
||||
transaction::{Transaction, TransactionKind, TransactionError},
|
||||
|
@ -28,72 +19,10 @@ use tendermint::{
|
|||
ext::{Network, Commit, RoundNumber, SignatureScheme},
|
||||
};
|
||||
|
||||
/// Signing data for a slash vote.
|
||||
///
|
||||
/// The traditional Signed uses a nonce, whereas votes aren't required/expected to be ordered.
|
||||
/// Accordingly, a simple uniqueness check works instead.
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
pub struct VoteSignature {
|
||||
pub signer: <Ristretto as Ciphersuite>::G,
|
||||
pub signature: SchnorrSignature<Ristretto>,
|
||||
}
|
||||
|
||||
impl ReadWrite for VoteSignature {
|
||||
fn read<R: io::Read>(reader: &mut R) -> io::Result<Self> {
|
||||
let signer = Ristretto::read_G(reader)?;
|
||||
let signature = SchnorrSignature::<Ristretto>::read(reader)?;
|
||||
|
||||
Ok(VoteSignature { signer, signature })
|
||||
}
|
||||
|
||||
fn write<W: io::Write>(&self, writer: &mut W) -> io::Result<()> {
|
||||
writer.write_all(&self.signer.to_bytes())?;
|
||||
self.signature.write(writer)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for VoteSignature {
|
||||
fn default() -> Self {
|
||||
VoteSignature {
|
||||
signer: Ristretto::generator(),
|
||||
signature: SchnorrSignature::<Ristretto>::read(&mut [0; 64].as_slice()).unwrap(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A vote to slash a malicious validator.
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
pub struct SlashVote {
|
||||
pub id: [u8; 13], // vote id(slash event id)
|
||||
pub target: [u8; 32], // who to slash
|
||||
pub sig: VoteSignature, // signature
|
||||
}
|
||||
|
||||
impl ReadWrite for SlashVote {
|
||||
fn read<R: io::Read>(reader: &mut R) -> io::Result<Self> {
|
||||
let mut id = [0; 13];
|
||||
let mut target = [0; 32];
|
||||
reader.read_exact(&mut id)?;
|
||||
reader.read_exact(&mut target)?;
|
||||
let sig = VoteSignature::read(reader)?;
|
||||
|
||||
Ok(SlashVote { id, target, sig })
|
||||
}
|
||||
|
||||
fn write<W: io::Write>(&self, writer: &mut W) -> io::Result<()> {
|
||||
writer.write_all(&self.id)?;
|
||||
writer.write_all(&self.target)?;
|
||||
self.sig.write(writer)
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
pub enum TendermintTx {
|
||||
SlashEvidence(Vec<u8>),
|
||||
// TODO: should the SlashVote.sig be directly in the enum
|
||||
// like as in (SlashVote, sig) since the sig is sig of the tx.
|
||||
SlashVote(SlashVote),
|
||||
}
|
||||
|
||||
impl ReadWrite for TendermintTx {
|
||||
|
@ -126,10 +55,6 @@ impl ReadWrite for TendermintTx {
|
|||
}
|
||||
Ok(TendermintTx::SlashEvidence(data))
|
||||
}
|
||||
1 => {
|
||||
let vote = SlashVote::read(reader)?;
|
||||
Ok(TendermintTx::SlashVote(vote))
|
||||
}
|
||||
_ => Err(io::Error::new(io::ErrorKind::Other, "invalid transaction type")),
|
||||
}
|
||||
}
|
||||
|
@ -141,10 +66,6 @@ impl ReadWrite for TendermintTx {
|
|||
writer.write_all(&u32::try_from(ev.len()).unwrap().to_le_bytes())?;
|
||||
writer.write_all(ev)
|
||||
}
|
||||
TendermintTx::SlashVote(vote) => {
|
||||
writer.write_all(&[1])?;
|
||||
vote.write(writer)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -157,32 +78,12 @@ impl Transaction for TendermintTx {
|
|||
}
|
||||
|
||||
fn hash(&self) -> [u8; 32] {
|
||||
let mut tx = self.serialize();
|
||||
if let TendermintTx::SlashVote(vote) = self {
|
||||
// Make sure the part we're cutting off is the signature
|
||||
assert_eq!(tx.drain((tx.len() - 64) ..).collect::<Vec<_>>(), vote.sig.signature.serialize());
|
||||
}
|
||||
Blake2s256::digest(tx).into()
|
||||
Blake2s256::digest(self.serialize()).into()
|
||||
}
|
||||
|
||||
fn sig_hash(&self, genesis: [u8; 32]) -> <Ristretto as Ciphersuite>::F {
|
||||
fn sig_hash(&self, _genesis: [u8; 32]) -> <Ristretto as Ciphersuite>::F {
|
||||
match self {
|
||||
TendermintTx::SlashEvidence(_) => panic!("sig_hash called on slash evidence transaction"),
|
||||
TendermintTx::SlashVote(vote) => {
|
||||
let signature = &vote.sig.signature;
|
||||
<Ristretto as Ciphersuite>::F::from_bytes_mod_order_wide(
|
||||
&Blake2b512::digest(
|
||||
[
|
||||
b"Tributary Slash Vote",
|
||||
genesis.as_ref(),
|
||||
&self.hash(),
|
||||
signature.R.to_bytes().as_ref(),
|
||||
]
|
||||
.concat(),
|
||||
)
|
||||
.into(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -191,34 +92,6 @@ impl Transaction for TendermintTx {
|
|||
}
|
||||
}
|
||||
|
||||
impl TendermintTx {
|
||||
// Sign a transaction
|
||||
pub fn sign<R: RngCore + CryptoRng>(
|
||||
&mut self,
|
||||
rng: &mut R,
|
||||
genesis: [u8; 32],
|
||||
key: &Zeroizing<<Ristretto as Ciphersuite>::F>,
|
||||
) {
|
||||
fn signature(tx: &mut TendermintTx) -> Option<&mut VoteSignature> {
|
||||
match tx {
|
||||
TendermintTx::SlashVote(vote) => Some(&mut vote.sig),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
signature(self).unwrap().signer = Ristretto::generator() * key.deref();
|
||||
|
||||
let sig_nonce = Zeroizing::new(<Ristretto as Ciphersuite>::F::random(rng));
|
||||
signature(self).unwrap().signature.R =
|
||||
<Ristretto as Ciphersuite>::generator() * sig_nonce.deref();
|
||||
|
||||
let sig_hash = self.sig_hash(genesis);
|
||||
|
||||
signature(self).unwrap().signature =
|
||||
SchnorrSignature::<Ristretto>::sign(key, sig_nonce, sig_hash);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn decode_evidence<N: Network>(
|
||||
mut ev: &[u8],
|
||||
) -> Result<(SignedMessageFor<N>, Option<SignedMessageFor<N>>), TransactionError> {
|
||||
|
@ -234,13 +107,13 @@ pub fn decode_evidence<N: Network>(
|
|||
// re-implements an entire foreign library's checks for malicious behavior).
|
||||
pub(crate) fn verify_tendermint_tx<N: Network>(
|
||||
tx: &TendermintTx,
|
||||
genesis: [u8; 32],
|
||||
schema: N::SignatureScheme,
|
||||
commit: impl Fn(u32) -> Option<Commit<N::SignatureScheme>>,
|
||||
) -> Result<(), TransactionError> {
|
||||
tx.verify()?;
|
||||
|
||||
match tx {
|
||||
// TODO: Only allow one evidence per validator, since evidence is fatal
|
||||
TendermintTx::SlashEvidence(ev) => {
|
||||
let (first, second) = decode_evidence::<N>(ev)?;
|
||||
|
||||
|
@ -332,21 +205,6 @@ pub(crate) fn verify_tendermint_tx<N: Network>(
|
|||
_ => Err(TransactionError::InvalidContent)?,
|
||||
}
|
||||
}
|
||||
TendermintTx::SlashVote(vote) => {
|
||||
// TODO: verify the target is actually one of our validators?
|
||||
// this shouldn't be a problem because if the target isn't valid, no one else
|
||||
// gonna vote on it. But we still have to think about spam votes.
|
||||
|
||||
// TODO: we need to check signer is a participant
|
||||
|
||||
// TODO: Move this into the standalone TendermintTx verify
|
||||
let sig = &vote.sig;
|
||||
// verify the tx signature
|
||||
// TODO: Use Schnorr half-aggregation and a batch verification here
|
||||
if !sig.signature.verify(sig.signer, tx.sig_hash(genesis)) {
|
||||
Err(TransactionError::InvalidSignature)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
|
@ -19,10 +19,10 @@ use crate::{
|
|||
transaction::Transaction as TransactionTrait,
|
||||
TransactionError, Transaction, ProvidedError, ProvidedTransactions, merkle, BlockError, Block,
|
||||
Blockchain,
|
||||
tendermint::{TendermintNetwork, Validators, tx::TendermintTx, Signer, TendermintBlock},
|
||||
tendermint::{TendermintNetwork, Validators, Signer, TendermintBlock},
|
||||
tests::{
|
||||
ProvidedTransaction, SignedTransaction, random_provided_transaction, p2p::DummyP2p,
|
||||
new_genesis, random_vote_tx, random_evidence_tx,
|
||||
new_genesis, random_evidence_tx,
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -149,34 +149,6 @@ fn invalid_block() {
|
|||
// signature (which it explicitly isn't allowed to anyways)
|
||||
assert_eq!(block.header.transactions, merkle(&[block.transactions[0].hash()]));
|
||||
}
|
||||
|
||||
{
|
||||
// Invalid vote signature
|
||||
let mut blockchain = blockchain.clone();
|
||||
let vote_tx = random_vote_tx(&mut OsRng, genesis);
|
||||
assert!(blockchain.add_transaction::<N>(
|
||||
true,
|
||||
Transaction::Tendermint(vote_tx),
|
||||
validators.clone()
|
||||
));
|
||||
let mut block = blockchain.build_block::<N>(validators.clone());
|
||||
blockchain.verify_block::<N>(&block, validators.clone()).unwrap();
|
||||
match &mut block.transactions[0] {
|
||||
Transaction::Tendermint(tx) => match tx {
|
||||
TendermintTx::SlashVote(vote) => {
|
||||
vote.sig.signature.s += <Ristretto as Ciphersuite>::F::ONE;
|
||||
}
|
||||
_ => panic!("non-vote tx found"),
|
||||
},
|
||||
_ => panic!("non-tendermint tx found"),
|
||||
}
|
||||
|
||||
assert!(blockchain.verify_block::<N>(&block, validators.clone()).is_err());
|
||||
|
||||
// Make sure this isn't because the merkle changed due to the transaction hash including the
|
||||
// signature (which it explicitly isn't allowed to anyways)
|
||||
assert_eq!(block.header.transactions, merkle(&[block.transactions[0].hash()]));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -281,61 +253,6 @@ fn provided_transaction() {
|
|||
assert!(blockchain.add_block::<N>(&block, vec![], validators.clone()).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tendermint_vote_tx() {
|
||||
let genesis = new_genesis();
|
||||
let validators = Arc::new(Validators::new(genesis, vec![]).unwrap());
|
||||
|
||||
let (_, mut blockchain) = new_blockchain::<SignedTransaction>(genesis, &[]);
|
||||
|
||||
let test = |blockchain: &mut Blockchain<MemDb, SignedTransaction>,
|
||||
mempool: Vec<Transaction<SignedTransaction>>| {
|
||||
let tip = blockchain.tip();
|
||||
for tx in mempool.clone() {
|
||||
let Transaction::Tendermint(tx) = tx else {
|
||||
panic!("non-tendermint tx found");
|
||||
};
|
||||
assert!(blockchain.add_transaction::<N>(
|
||||
true,
|
||||
Transaction::Tendermint(tx),
|
||||
validators.clone()
|
||||
));
|
||||
}
|
||||
let block = blockchain.build_block::<N>(validators.clone());
|
||||
|
||||
assert_eq!(blockchain.tip(), tip);
|
||||
assert_eq!(block.header.parent, tip);
|
||||
|
||||
// Make sure all transactions were included
|
||||
for bt in &block.transactions {
|
||||
assert!(mempool.contains(bt));
|
||||
}
|
||||
|
||||
// Make sure the merkle was correct
|
||||
// Uses block.transactions instead of mempool as order may differ between the two
|
||||
assert_eq!(
|
||||
block.header.transactions,
|
||||
merkle(&block.transactions.iter().map(Transaction::hash).collect::<Vec<_>>()),
|
||||
);
|
||||
|
||||
// Verify and add the block
|
||||
blockchain.verify_block::<N>(&block, validators.clone()).unwrap();
|
||||
assert!(blockchain.add_block::<N>(&block, vec![], validators.clone()).is_ok());
|
||||
assert_eq!(blockchain.tip(), block.hash());
|
||||
};
|
||||
|
||||
// test with single tx
|
||||
let tx = random_vote_tx(&mut OsRng, genesis);
|
||||
test(&mut blockchain, vec![Transaction::Tendermint(tx)]);
|
||||
|
||||
// test with multiple txs
|
||||
let mut mempool: Vec<Transaction<SignedTransaction>> = vec![];
|
||||
for _ in 0 .. 5 {
|
||||
mempool.push(Transaction::Tendermint(random_vote_tx(&mut OsRng, genesis)));
|
||||
}
|
||||
test(&mut blockchain, mempool);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn tendermint_evidence_tx() {
|
||||
let genesis = new_genesis();
|
||||
|
@ -397,8 +314,8 @@ async fn tendermint_evidence_tx() {
|
|||
test(&mut blockchain, mempool, validators);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn block_tx_ordering() {
|
||||
#[tokio::test]
|
||||
async fn block_tx_ordering() {
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
enum SignedTx {
|
||||
Signed(Box<SignedTransaction>),
|
||||
|
@ -451,10 +368,10 @@ fn block_tx_ordering() {
|
|||
|
||||
let genesis = new_genesis();
|
||||
let key = Zeroizing::new(<Ristretto as Ciphersuite>::F::random(&mut OsRng));
|
||||
let validators = Arc::new(Validators::new(genesis, vec![]).unwrap());
|
||||
|
||||
// signer
|
||||
let signer = crate::tests::signed_transaction(&mut OsRng, genesis, &key, 0).1.signer;
|
||||
let validators = Arc::new(Validators::new(genesis, vec![(signer, 1)]).unwrap());
|
||||
|
||||
let (_, mut blockchain) = new_blockchain::<SignedTx>(genesis, &[signer]);
|
||||
let tip = blockchain.tip();
|
||||
|
@ -469,7 +386,13 @@ fn block_tx_ordering() {
|
|||
assert!(blockchain.add_transaction::<N>(true, signed_tx.clone(), validators.clone()));
|
||||
mempool.push(signed_tx);
|
||||
|
||||
let unsigned_tx = Transaction::Tendermint(random_vote_tx(&mut OsRng, genesis));
|
||||
let unsigned_tx = Transaction::Tendermint(
|
||||
random_evidence_tx::<N>(
|
||||
Signer::new(genesis, key.clone()).into(),
|
||||
TendermintBlock(vec![u8::try_from(i).unwrap()]),
|
||||
)
|
||||
.await,
|
||||
);
|
||||
assert!(blockchain.add_transaction::<N>(true, unsigned_tx.clone(), validators.clone()));
|
||||
mempool.push(unsigned_tx);
|
||||
|
||||
|
|
|
@ -13,9 +13,7 @@ use crate::{
|
|||
transaction::Transaction as TransactionTrait,
|
||||
tendermint::{TendermintBlock, Validators, Signer, TendermintNetwork},
|
||||
ACCOUNT_MEMPOOL_LIMIT, Transaction, Mempool,
|
||||
tests::{
|
||||
SignedTransaction, signed_transaction, p2p::DummyP2p, random_vote_tx, random_evidence_tx,
|
||||
},
|
||||
tests::{SignedTransaction, signed_transaction, p2p::DummyP2p, random_evidence_tx},
|
||||
};
|
||||
|
||||
type N = TendermintNetwork<MemDb, SignedTransaction, DummyP2p>;
|
||||
|
@ -55,17 +53,6 @@ async fn mempool_addition() {
|
|||
));
|
||||
assert_eq!(mempool.next_nonce(&signer), Some(1));
|
||||
|
||||
// add a tendermint vote tx
|
||||
let vote_tx = random_vote_tx(&mut OsRng, genesis);
|
||||
assert!(mempool.add::<N>(
|
||||
&blockchain_next_nonces,
|
||||
true,
|
||||
Transaction::Tendermint(vote_tx.clone()),
|
||||
validators.clone(),
|
||||
unsigned_in_chain,
|
||||
commit,
|
||||
));
|
||||
|
||||
// add a tendermint evidence tx
|
||||
let evidence_tx =
|
||||
random_evidence_tx::<N>(Signer::new(genesis, key.clone()).into(), TendermintBlock(vec![]))
|
||||
|
@ -91,14 +78,6 @@ async fn mempool_addition() {
|
|||
unsigned_in_chain,
|
||||
commit,
|
||||
));
|
||||
assert!(!mempool.add::<N>(
|
||||
&blockchain_next_nonces,
|
||||
true,
|
||||
Transaction::Tendermint(vote_tx.clone()),
|
||||
validators.clone(),
|
||||
unsigned_in_chain,
|
||||
commit,
|
||||
));
|
||||
assert!(!mempool.add::<N>(
|
||||
&blockchain_next_nonces,
|
||||
true,
|
||||
|
@ -146,18 +125,17 @@ async fn mempool_addition() {
|
|||
assert_eq!(mempool.next_nonce(&second_signer), Some(3));
|
||||
|
||||
// Getting a block should work
|
||||
assert_eq!(mempool.block(&blockchain_next_nonces, unsigned_in_chain).len(), 5);
|
||||
assert_eq!(mempool.block(&blockchain_next_nonces, unsigned_in_chain).len(), 4);
|
||||
|
||||
// If the blockchain says an account had its nonce updated, it should cause a prune
|
||||
blockchain_next_nonces.insert(signer, 1);
|
||||
let mut block = mempool.block(&blockchain_next_nonces, unsigned_in_chain);
|
||||
assert_eq!(block.len(), 4);
|
||||
assert_eq!(block.len(), 3);
|
||||
assert!(!block.iter().any(|tx| tx.hash() == first_tx.hash()));
|
||||
assert_eq!(mempool.txs(), &block.drain(..).map(|tx| (tx.hash(), tx)).collect::<HashMap<_, _>>());
|
||||
|
||||
// Removing should also successfully prune
|
||||
mempool.remove(&tx.hash());
|
||||
mempool.remove(&vote_tx.hash());
|
||||
|
||||
assert_eq!(
|
||||
mempool.txs(),
|
||||
|
|
|
@ -22,10 +22,7 @@ use ::tendermint::{
|
|||
use crate::{
|
||||
transaction::{Signed, TransactionError, TransactionKind, Transaction, verify_transaction},
|
||||
ReadWrite,
|
||||
tendermint::{
|
||||
tx::{SlashVote, VoteSignature, TendermintTx},
|
||||
Validators, Signer,
|
||||
},
|
||||
tendermint::{tx::TendermintTx, Validators, Signer},
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -205,19 +202,3 @@ pub async fn random_evidence_tx<N: Network>(
|
|||
let signed = signed_from_data::<N>(signer, signer_id, 0, 0, data).await;
|
||||
TendermintTx::SlashEvidence((signed, None::<SignedMessageFor<N>>).encode())
|
||||
}
|
||||
|
||||
pub fn random_vote_tx<R: RngCore + CryptoRng>(rng: &mut R, genesis: [u8; 32]) -> TendermintTx {
|
||||
// private key
|
||||
let key = Zeroizing::new(<Ristretto as Ciphersuite>::F::random(&mut *rng));
|
||||
|
||||
// vote data
|
||||
let mut id = [0u8; 13];
|
||||
let mut target = [0u8; 32];
|
||||
rng.fill_bytes(&mut id);
|
||||
rng.fill_bytes(&mut target);
|
||||
|
||||
let mut tx = TendermintTx::SlashVote(SlashVote { id, target, sig: VoteSignature::default() });
|
||||
tx.sign(rng, genesis, &key);
|
||||
|
||||
tx
|
||||
}
|
||||
|
|
|
@ -4,7 +4,6 @@ use zeroize::Zeroizing;
|
|||
use rand::{RngCore, rngs::OsRng};
|
||||
|
||||
use ciphersuite::{Ristretto, Ciphersuite, group::ff::Field};
|
||||
use schnorr::SchnorrSignature;
|
||||
|
||||
use scale::Encode;
|
||||
|
||||
|
@ -24,54 +23,25 @@ use crate::{
|
|||
TendermintBlock, Signer, Validators, TendermintNetwork,
|
||||
},
|
||||
tests::{
|
||||
p2p::DummyP2p, SignedTransaction, new_genesis, random_evidence_tx, random_vote_tx,
|
||||
tendermint_meta, signed_from_data,
|
||||
p2p::DummyP2p, SignedTransaction, random_evidence_tx, tendermint_meta, signed_from_data,
|
||||
},
|
||||
};
|
||||
|
||||
type N = TendermintNetwork<MemDb, SignedTransaction, DummyP2p>;
|
||||
|
||||
#[test]
|
||||
fn vote_tx() {
|
||||
let genesis = new_genesis();
|
||||
let mut tx = random_vote_tx(&mut OsRng, genesis);
|
||||
|
||||
let commit = |_: u32| -> Option<Commit<Arc<Validators>>> {
|
||||
Some(Commit::<Arc<Validators>> { end_time: 0, validators: vec![], signature: vec![] })
|
||||
};
|
||||
let validators = Arc::new(Validators::new(genesis, vec![]).unwrap());
|
||||
|
||||
// should pass
|
||||
verify_tendermint_tx::<N>(&tx, genesis, validators.clone(), commit).unwrap();
|
||||
|
||||
if let TendermintTx::SlashVote(vote) = &mut tx {
|
||||
vote.sig.signature = SchnorrSignature::read(&mut [0; 64].as_slice()).unwrap();
|
||||
} else {
|
||||
panic!("SlashVote TX wasn't SlashVote");
|
||||
}
|
||||
|
||||
// should fail
|
||||
assert!(verify_tendermint_tx::<N>(&tx, genesis, validators, commit).is_err());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn serialize_tendermint() {
|
||||
// make a tendermint tx with random evidence
|
||||
let (genesis, signer, _, _) = tendermint_meta().await;
|
||||
let (_, signer, _, _) = tendermint_meta().await;
|
||||
let tx = random_evidence_tx::<N>(signer.into(), TendermintBlock(vec![])).await;
|
||||
let res = TendermintTx::read::<&[u8]>(&mut tx.serialize().as_ref()).unwrap();
|
||||
assert_eq!(res, tx);
|
||||
|
||||
// with vote tx
|
||||
let vote_tx = random_vote_tx(&mut OsRng, genesis);
|
||||
let vote_res = TendermintTx::read::<&[u8]>(&mut vote_tx.serialize().as_ref()).unwrap();
|
||||
assert_eq!(vote_res, vote_tx);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn invalid_valid_round() {
|
||||
// signer
|
||||
let (genesis, signer, signer_id, validators) = tendermint_meta().await;
|
||||
let (_, signer, signer_id, validators) = tendermint_meta().await;
|
||||
let commit = |_: u32| -> Option<Commit<Arc<Validators>>> {
|
||||
Some(Commit::<Arc<Validators>> { end_time: 0, validators: vec![], signature: vec![] })
|
||||
};
|
||||
|
@ -87,13 +57,13 @@ async fn invalid_valid_round() {
|
|||
|
||||
// This should be invalid evidence if a valid valid round is specified
|
||||
let (_, tx) = valid_round_tx(None).await;
|
||||
assert!(verify_tendermint_tx::<N>(&tx, genesis, validators.clone(), commit).is_err());
|
||||
assert!(verify_tendermint_tx::<N>(&tx, validators.clone(), commit).is_err());
|
||||
|
||||
// If an invalid valid round is specified (>= current), this should be invalid evidence
|
||||
let (mut signed, tx) = valid_round_tx(Some(RoundNumber(0))).await;
|
||||
|
||||
// should pass
|
||||
verify_tendermint_tx::<N>(&tx, genesis, validators.clone(), commit).unwrap();
|
||||
verify_tendermint_tx::<N>(&tx, validators.clone(), commit).unwrap();
|
||||
|
||||
// change the signature
|
||||
let mut random_sig = [0u8; 64];
|
||||
|
@ -102,12 +72,12 @@ async fn invalid_valid_round() {
|
|||
let tx = TendermintTx::SlashEvidence((signed.clone(), None::<SignedMessageFor<N>>).encode());
|
||||
|
||||
// should fail
|
||||
assert!(verify_tendermint_tx::<N>(&tx, genesis, validators, commit).is_err());
|
||||
assert!(verify_tendermint_tx::<N>(&tx, validators, commit).is_err());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn invalid_precommit_signature() {
|
||||
let (genesis, signer, signer_id, validators) = tendermint_meta().await;
|
||||
let (_, signer, signer_id, validators) = tendermint_meta().await;
|
||||
let commit = |i: u32| -> Option<Commit<Arc<Validators>>> {
|
||||
assert_eq!(i, 0);
|
||||
Some(Commit::<Arc<Validators>> { end_time: 0, validators: vec![], signature: vec![] })
|
||||
|
@ -124,8 +94,7 @@ async fn invalid_precommit_signature() {
|
|||
};
|
||||
|
||||
// Empty Precommit should fail.
|
||||
assert!(verify_tendermint_tx::<N>(&precommit(None).await.1, genesis, validators.clone(), commit)
|
||||
.is_err());
|
||||
assert!(verify_tendermint_tx::<N>(&precommit(None).await.1, validators.clone(), commit).is_err());
|
||||
|
||||
// valid precommit signature should fail.
|
||||
let block_id = [0x22u8; 32];
|
||||
|
@ -136,7 +105,6 @@ async fn invalid_precommit_signature() {
|
|||
|
||||
assert!(verify_tendermint_tx::<N>(
|
||||
&precommit(Some((block_id, signer.clone().sign(&commit_msg).await))).await.1,
|
||||
genesis,
|
||||
validators.clone(),
|
||||
commit
|
||||
)
|
||||
|
@ -145,20 +113,20 @@ async fn invalid_precommit_signature() {
|
|||
// any other signature can be used as evidence.
|
||||
{
|
||||
let (mut signed, tx) = precommit(Some((block_id, signer.sign(&[]).await))).await;
|
||||
verify_tendermint_tx::<N>(&tx, genesis, validators.clone(), commit).unwrap();
|
||||
verify_tendermint_tx::<N>(&tx, validators.clone(), commit).unwrap();
|
||||
|
||||
// So long as we can authenticate where it came from
|
||||
let mut random_sig = [0u8; 64];
|
||||
OsRng.fill_bytes(&mut random_sig);
|
||||
signed.sig = random_sig;
|
||||
let tx = TendermintTx::SlashEvidence((signed.clone(), None::<SignedMessageFor<N>>).encode());
|
||||
assert!(verify_tendermint_tx::<N>(&tx, genesis, validators, commit).is_err());
|
||||
assert!(verify_tendermint_tx::<N>(&tx, validators, commit).is_err());
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn evidence_with_prevote() {
|
||||
let (genesis, signer, signer_id, validators) = tendermint_meta().await;
|
||||
let (_, signer, signer_id, validators) = tendermint_meta().await;
|
||||
let commit = |_: u32| -> Option<Commit<Arc<Validators>>> {
|
||||
Some(Commit::<Arc<Validators>> { end_time: 0, validators: vec![], signature: vec![] })
|
||||
};
|
||||
|
@ -179,7 +147,7 @@ async fn evidence_with_prevote() {
|
|||
|
||||
// No prevote message should be valid as slash evidence at this time
|
||||
for prevote in [prevote(None).await, prevote(Some([0x22u8; 32])).await] {
|
||||
assert!(verify_tendermint_tx::<N>(&prevote, genesis, validators.clone(), commit).is_err());
|
||||
assert!(verify_tendermint_tx::<N>(&prevote, validators.clone(), commit).is_err());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -202,23 +170,23 @@ 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((&signed_1, Some(&signed_1)).encode());
|
||||
assert!(verify_tendermint_tx::<N>(&tx, genesis, validators.clone(), commit).is_err());
|
||||
assert!(verify_tendermint_tx::<N>(&tx, validators.clone(), 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((&signed_1, Some(signed_2)).encode());
|
||||
verify_tendermint_tx::<N>(&tx, genesis, validators.clone(), commit).unwrap();
|
||||
verify_tendermint_tx::<N>(&tx, validators.clone(), commit).unwrap();
|
||||
|
||||
// Except if it has a distinct round number, as we don't check cross-round conflicts
|
||||
// (except for Precommit)
|
||||
let signed_2 = signed_for_b_r(0, 1, Data::Proposal(None, TendermintBlock(vec![0x22]))).await;
|
||||
let tx = TendermintTx::SlashEvidence((&signed_1, Some(signed_2)).encode());
|
||||
verify_tendermint_tx::<N>(&tx, genesis, validators.clone(), commit).unwrap_err();
|
||||
verify_tendermint_tx::<N>(&tx, validators.clone(), 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((&signed_1, Some(signed_2)).encode());
|
||||
verify_tendermint_tx::<N>(&tx, genesis, validators.clone(), commit).unwrap_err();
|
||||
verify_tendermint_tx::<N>(&tx, validators.clone(), commit).unwrap_err();
|
||||
}
|
||||
|
||||
// Prevote
|
||||
|
@ -226,23 +194,23 @@ 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((&signed_1, Some(&signed_1)).encode());
|
||||
assert!(verify_tendermint_tx::<N>(&tx, genesis, validators.clone(), commit).is_err());
|
||||
assert!(verify_tendermint_tx::<N>(&tx, validators.clone(), 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((&signed_1, Some(signed_2)).encode());
|
||||
verify_tendermint_tx::<N>(&tx, genesis, validators.clone(), commit).unwrap();
|
||||
verify_tendermint_tx::<N>(&tx, validators.clone(), commit).unwrap();
|
||||
|
||||
// Except if it has a distinct round number, as we don't check cross-round conflicts
|
||||
// (except for Precommit)
|
||||
let signed_2 = signed_for_b_r(0, 1, Data::Prevote(Some([0x22; 32]))).await;
|
||||
let tx = TendermintTx::SlashEvidence((&signed_1, Some(signed_2)).encode());
|
||||
verify_tendermint_tx::<N>(&tx, genesis, validators.clone(), commit).unwrap_err();
|
||||
verify_tendermint_tx::<N>(&tx, validators.clone(), 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((&signed_1, Some(signed_2)).encode());
|
||||
verify_tendermint_tx::<N>(&tx, genesis, validators.clone(), commit).unwrap_err();
|
||||
verify_tendermint_tx::<N>(&tx, validators.clone(), commit).unwrap_err();
|
||||
}
|
||||
|
||||
// Precommit
|
||||
|
@ -251,17 +219,17 @@ async fn conflicting_msgs_evidence_tx() {
|
|||
|
||||
let signed_1 = signed_for_b_r(0, 0, Data::Precommit(Some(([0x11; 32], sig)))).await;
|
||||
let tx = TendermintTx::SlashEvidence((&signed_1, Some(&signed_1)).encode());
|
||||
assert!(verify_tendermint_tx::<N>(&tx, genesis, validators.clone(), commit).is_err());
|
||||
assert!(verify_tendermint_tx::<N>(&tx, validators.clone(), commit).is_err());
|
||||
|
||||
// For precommit, the round number is ignored
|
||||
let signed_2 = signed_for_b_r(0, 1, Data::Precommit(Some(([0x22; 32], sig)))).await;
|
||||
let tx = TendermintTx::SlashEvidence((&signed_1, Some(signed_2)).encode());
|
||||
verify_tendermint_tx::<N>(&tx, genesis, validators.clone(), commit).unwrap();
|
||||
verify_tendermint_tx::<N>(&tx, validators.clone(), commit).unwrap();
|
||||
|
||||
// Yet the block number isn't
|
||||
let signed_2 = signed_for_b_r(1, 0, Data::Precommit(Some(([0x22; 32], sig)))).await;
|
||||
let tx = TendermintTx::SlashEvidence((&signed_1, Some(signed_2)).encode());
|
||||
assert!(verify_tendermint_tx::<N>(&tx, genesis, validators.clone(), commit).is_err());
|
||||
assert!(verify_tendermint_tx::<N>(&tx, validators.clone(), commit).is_err());
|
||||
}
|
||||
|
||||
// msgs from different senders should fail
|
||||
|
@ -290,7 +258,7 @@ async fn conflicting_msgs_evidence_tx() {
|
|||
let validators =
|
||||
Arc::new(Validators::new(genesis, vec![(signer_pub, 1), (signer_pub_2, 1)]).unwrap());
|
||||
|
||||
assert!(verify_tendermint_tx::<N>(&tx, genesis, validators, commit).is_err());
|
||||
assert!(verify_tendermint_tx::<N>(&tx, validators, commit).is_err());
|
||||
}
|
||||
|
||||
// msgs with different steps should fail
|
||||
|
@ -298,6 +266,6 @@ 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((signed_1, Some(signed_2)).encode());
|
||||
assert!(verify_tendermint_tx::<N>(&tx, genesis, validators.clone(), commit).is_err());
|
||||
assert!(verify_tendermint_tx::<N>(&tx, validators.clone(), commit).is_err());
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue