mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-08 12:19:24 +00:00
Clean the transaction definitions in the coordinator
Moves to borsh for serialization. No longer includes nonces anywhere in the TX.
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -8344,7 +8344,6 @@ dependencies = [
|
|||||||
name = "serai-coordinator-substrate"
|
name = "serai-coordinator-substrate"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"blake2",
|
|
||||||
"borsh",
|
"borsh",
|
||||||
"futures",
|
"futures",
|
||||||
"log",
|
"log",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
AGPL-3.0-only license
|
AGPL-3.0-only license
|
||||||
|
|
||||||
Copyright (c) 2023 Luke Parker
|
Copyright (c) 2023-2024 Luke Parker
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License Version 3 as
|
it under the terms of the GNU Affero General Public License Version 3 as
|
||||||
|
|||||||
@@ -1,7 +1,19 @@
|
|||||||
# Coordinator
|
# Coordinator
|
||||||
|
|
||||||
The Serai coordinator communicates with other coordinators to prepare batches
|
- [`tendermint`](/tributary/tendermint) is an implementation of the Tendermint BFT algorithm.
|
||||||
for Serai and sign transactions.
|
|
||||||
|
|
||||||
In order to achieve consensus over gossip, and order certain events, a
|
- [`tributary`](./tributary) is a micro-blockchain framework. Instead of a producing a blockchain
|
||||||
micro-blockchain is instantiated.
|
daemon like the Polkadot SDK or Cosmos SDK intend to, `tributary` is solely intended to be an
|
||||||
|
embedded asynchronous task within an application.
|
||||||
|
|
||||||
|
The Serai coordinator spawns a tributary for each validator set it's coordinating. This allows
|
||||||
|
the participating validators to communicate in a byzantine-fault-tolerant manner (relying on
|
||||||
|
Tendermint for consensus).
|
||||||
|
|
||||||
|
- [`cosign`](./cosign) contains a library to decide which Substrate blocks should be cosigned and
|
||||||
|
to evaluate cosigns.
|
||||||
|
|
||||||
|
- [`substrate`](./substrate) contains a library to index the Substrate blockchain and handle its
|
||||||
|
events.
|
||||||
|
|
||||||
|
- [`src`](./src) contains the source code for the Coordinator binary itself.
|
||||||
|
|||||||
@@ -29,5 +29,5 @@ log = { version = "0.4", default-features = false, features = ["std"] }
|
|||||||
|
|
||||||
tokio = { version = "1", default-features = false }
|
tokio = { version = "1", default-features = false }
|
||||||
|
|
||||||
serai-db = { version = "0.1.1", path = "../../common/db" }
|
serai-db = { path = "../../common/db", version = "0.1.1" }
|
||||||
serai-task = { version = "0.1", path = "../../common/task" }
|
serai-task = { path = "../../common/task", version = "0.1" }
|
||||||
|
|||||||
@@ -4,9 +4,7 @@ use std::io;
|
|||||||
use zeroize::Zeroizing;
|
use zeroize::Zeroizing;
|
||||||
use rand_core::{RngCore, CryptoRng};
|
use rand_core::{RngCore, CryptoRng};
|
||||||
|
|
||||||
use blake2::{Digest, Blake2s256};
|
use blake2::{digest::typenum::U32, Digest, Blake2b};
|
||||||
use transcript::{Transcript, RecommendedTranscript};
|
|
||||||
|
|
||||||
use ciphersuite::{
|
use ciphersuite::{
|
||||||
group::{ff::Field, GroupEncoding},
|
group::{ff::Field, GroupEncoding},
|
||||||
Ciphersuite, Ristretto,
|
Ciphersuite, Ristretto,
|
||||||
@@ -14,22 +12,30 @@ use ciphersuite::{
|
|||||||
use schnorr::SchnorrSignature;
|
use schnorr::SchnorrSignature;
|
||||||
|
|
||||||
use scale::{Encode, Decode};
|
use scale::{Encode, Decode};
|
||||||
use processor_messages::coordinator::SubstrateSignableId;
|
use borsh::{BorshSerialize, BorshDeserialize};
|
||||||
|
|
||||||
|
use serai_client::primitives::PublicKey;
|
||||||
|
|
||||||
|
use processor_messages::sign::VariantSignId;
|
||||||
|
|
||||||
use tributary::{
|
use tributary::{
|
||||||
TRANSACTION_SIZE_LIMIT, ReadWrite,
|
ReadWrite,
|
||||||
transaction::{Signed, TransactionError, TransactionKind, Transaction as TransactionTrait},
|
transaction::{
|
||||||
|
Signed as TributarySigned, TransactionError, TransactionKind, Transaction as TransactionTrait,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Encode)]
|
/// The label for data from a signing protocol.
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, Debug, Encode, BorshSerialize, BorshDeserialize)]
|
||||||
pub enum Label {
|
pub enum Label {
|
||||||
|
/// A preprocess.
|
||||||
Preprocess,
|
Preprocess,
|
||||||
|
/// A signature share.
|
||||||
Share,
|
Share,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Label {
|
impl Label {
|
||||||
// TODO: Should nonces be u8 thanks to our use of topics?
|
fn nonce(&self) -> u32 {
|
||||||
pub fn nonce(&self) -> u32 {
|
|
||||||
match self {
|
match self {
|
||||||
Label::Preprocess => 0,
|
Label::Preprocess => 0,
|
||||||
Label::Share => 1,
|
Label::Share => 1,
|
||||||
@@ -37,474 +43,202 @@ impl Label {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Eq)]
|
fn borsh_serialize_public<W: io::Write>(
|
||||||
pub struct SignData<Id: Clone + PartialEq + Eq + Debug + Encode + Decode> {
|
public: &PublicKey,
|
||||||
pub plan: Id,
|
writer: &mut W,
|
||||||
pub attempt: u32,
|
) -> Result<(), io::Error> {
|
||||||
pub label: Label,
|
// This doesn't use `encode_to` as `encode_to` panics if the writer returns an error
|
||||||
|
writer.write_all(&public.encode())
|
||||||
pub data: Vec<Vec<u8>>,
|
}
|
||||||
|
fn borsh_deserialize_public<R: io::Read>(reader: &mut R) -> Result<PublicKey, io::Error> {
|
||||||
pub signed: Signed,
|
Decode::decode(&mut scale::IoReader(reader)).map_err(io::Error::other)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Id: Clone + PartialEq + Eq + Debug + Encode + Decode> Debug for SignData<Id> {
|
/// `tributary::Signed` without the nonce.
|
||||||
fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
|
///
|
||||||
fmt
|
/// All of our nonces are deterministic to the type of transaction and fields within.
|
||||||
.debug_struct("SignData")
|
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||||
.field("id", &hex::encode(self.plan.encode()))
|
pub struct Signed {
|
||||||
.field("attempt", &self.attempt)
|
pub signer: <Ristretto as Ciphersuite>::G,
|
||||||
.field("label", &self.label)
|
pub signature: SchnorrSignature<Ristretto>,
|
||||||
.field("signer", &hex::encode(self.signed.signer.to_bytes()))
|
}
|
||||||
.finish_non_exhaustive()
|
|
||||||
|
impl BorshSerialize for Signed {
|
||||||
|
fn serialize<W: io::Write>(&self, writer: &mut W) -> Result<(), io::Error> {
|
||||||
|
writer.write_all(self.signer.to_bytes().as_ref())?;
|
||||||
|
self.signature.write(writer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl BorshDeserialize for Signed {
|
||||||
|
fn deserialize_reader<R: io::Read>(reader: &mut R) -> Result<Self, io::Error> {
|
||||||
|
let signer = Ristretto::read_G(reader)?;
|
||||||
|
let signature = SchnorrSignature::read(reader)?;
|
||||||
|
Ok(Self { signer, signature })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Id: Clone + PartialEq + Eq + Debug + Encode + Decode> SignData<Id> {
|
impl Signed {
|
||||||
pub(crate) fn read<R: io::Read>(reader: &mut R) -> io::Result<Self> {
|
/// Provide a nonce to convert a `Signed` into a `tributary::Signed`.
|
||||||
let plan = Id::decode(&mut scale::IoReader(&mut *reader))
|
fn nonce(&self, nonce: u32) -> TributarySigned {
|
||||||
.map_err(|_| io::Error::other("invalid plan in SignData"))?;
|
TributarySigned { signer: self.signer, nonce, signature: self.signature }
|
||||||
|
|
||||||
let mut attempt = [0; 4];
|
|
||||||
reader.read_exact(&mut attempt)?;
|
|
||||||
let attempt = u32::from_le_bytes(attempt);
|
|
||||||
|
|
||||||
let mut label = [0; 1];
|
|
||||||
reader.read_exact(&mut label)?;
|
|
||||||
let label = match label[0] {
|
|
||||||
0 => Label::Preprocess,
|
|
||||||
1 => Label::Share,
|
|
||||||
_ => Err(io::Error::other("invalid label in SignData"))?,
|
|
||||||
};
|
|
||||||
|
|
||||||
let data = {
|
|
||||||
let mut data_pieces = [0];
|
|
||||||
reader.read_exact(&mut data_pieces)?;
|
|
||||||
if data_pieces[0] == 0 {
|
|
||||||
Err(io::Error::other("zero pieces of data in SignData"))?;
|
|
||||||
}
|
|
||||||
let mut all_data = vec![];
|
|
||||||
for _ in 0 .. data_pieces[0] {
|
|
||||||
let mut data_len = [0; 2];
|
|
||||||
reader.read_exact(&mut data_len)?;
|
|
||||||
let mut data = vec![0; usize::from(u16::from_le_bytes(data_len))];
|
|
||||||
reader.read_exact(&mut data)?;
|
|
||||||
all_data.push(data);
|
|
||||||
}
|
|
||||||
all_data
|
|
||||||
};
|
|
||||||
|
|
||||||
let signed = Signed::read_without_nonce(reader, label.nonce())?;
|
|
||||||
|
|
||||||
Ok(SignData { plan, attempt, label, data, signed })
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn write<W: io::Write>(&self, writer: &mut W) -> io::Result<()> {
|
|
||||||
writer.write_all(&self.plan.encode())?;
|
|
||||||
writer.write_all(&self.attempt.to_le_bytes())?;
|
|
||||||
writer.write_all(&[match self.label {
|
|
||||||
Label::Preprocess => 0,
|
|
||||||
Label::Share => 1,
|
|
||||||
}])?;
|
|
||||||
|
|
||||||
writer.write_all(&[u8::try_from(self.data.len()).unwrap()])?;
|
|
||||||
for data in &self.data {
|
|
||||||
if data.len() > u16::MAX.into() {
|
|
||||||
// Currently, the largest individual preprocess is a Monero transaction
|
|
||||||
// It provides 4 commitments per input (128 bytes), a 64-byte proof for them, along with a
|
|
||||||
// key image and proof (96 bytes)
|
|
||||||
// Even with all of that, we could support 227 inputs in a single TX
|
|
||||||
// Monero is limited to ~120 inputs per TX
|
|
||||||
//
|
|
||||||
// Bitcoin has a much higher input count of 520, yet it only uses 64 bytes per preprocess
|
|
||||||
Err(io::Error::other("signing data exceeded 65535 bytes"))?;
|
|
||||||
}
|
|
||||||
writer.write_all(&u16::try_from(data.len()).unwrap().to_le_bytes())?;
|
|
||||||
writer.write_all(data)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.signed.write_without_nonce(writer)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Eq)]
|
/// The Tributary transaction definition used by Serai
|
||||||
|
#[derive(Clone, PartialEq, Eq, Debug, BorshSerialize, BorshDeserialize)]
|
||||||
pub enum Transaction {
|
pub enum Transaction {
|
||||||
|
/// A vote to remove a participant for invalid behavior
|
||||||
RemoveParticipant {
|
RemoveParticipant {
|
||||||
participant: <Ristretto as Ciphersuite>::G,
|
/// The participant to remove
|
||||||
|
#[borsh(
|
||||||
|
serialize_with = "borsh_serialize_public",
|
||||||
|
deserialize_with = "borsh_deserialize_public"
|
||||||
|
)]
|
||||||
|
participant: PublicKey,
|
||||||
|
/// The transaction's signer and signature
|
||||||
signed: Signed,
|
signed: Signed,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/// A participation in the DKG
|
||||||
DkgParticipation {
|
DkgParticipation {
|
||||||
participation: Vec<u8>,
|
participation: Vec<u8>,
|
||||||
|
/// The transaction's signer and signature
|
||||||
signed: Signed,
|
signed: Signed,
|
||||||
},
|
},
|
||||||
DkgConfirmationNonces {
|
/// The preprocess to confirm the DKG results on-chain
|
||||||
// The confirmation attempt
|
DkgConfirmationPreprocess {
|
||||||
|
/// The attempt number of this signing protocol
|
||||||
attempt: u32,
|
attempt: u32,
|
||||||
// The nonces for DKG confirmation attempt #attempt
|
// The preprocess
|
||||||
confirmation_nonces: [u8; 64],
|
preprocess: [u8; 64],
|
||||||
|
/// The transaction's signer and signature
|
||||||
signed: Signed,
|
signed: Signed,
|
||||||
},
|
},
|
||||||
|
/// The signature share to confirm the DKG results on-chain
|
||||||
DkgConfirmationShare {
|
DkgConfirmationShare {
|
||||||
// The confirmation attempt
|
/// The attempt number of this signing protocol
|
||||||
attempt: u32,
|
attempt: u32,
|
||||||
// The share for DKG confirmation attempt #attempt
|
// The signature share
|
||||||
confirmation_share: [u8; 32],
|
confirmation_share: [u8; 32],
|
||||||
|
/// The transaction's signer and signature
|
||||||
signed: Signed,
|
signed: Signed,
|
||||||
},
|
},
|
||||||
|
|
||||||
// Co-sign a Substrate block.
|
/// Intend to co-sign a finalized Substrate block
|
||||||
CosignSubstrateBlock([u8; 32]),
|
///
|
||||||
|
/// When the time comes to start a new co-signing protocol, the most recent Substrate block will
|
||||||
|
/// be the one selected to be cosigned.
|
||||||
|
CosignSubstrateBlock {
|
||||||
|
/// THe hash of the Substrate block to sign
|
||||||
|
hash: [u8; 32],
|
||||||
|
},
|
||||||
|
|
||||||
// When we have synchrony on a batch, we can allow signing it
|
/// Acknowledge a Substrate block
|
||||||
// TODO (never?): This is less efficient compared to an ExternalBlock provided transaction,
|
///
|
||||||
// which would be binding over the block hash and automatically achieve synchrony on all
|
/// This is provided after the block has been cosigned.
|
||||||
// relevant batches. ExternalBlock was removed for this due to complexity around the pipeline
|
///
|
||||||
// with the current processor, yet it would still be an improvement.
|
/// With the acknowledgement of a Substrate block, we can whitelist all the `VariantSignId`s
|
||||||
|
/// resulting from its handling.
|
||||||
|
SubstrateBlock {
|
||||||
|
/// The hash of the Substrate block
|
||||||
|
hash: [u8; 32],
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Acknowledge a Batch
|
||||||
|
///
|
||||||
|
/// Once everyone has acknowledged the Batch, we can begin signing it.
|
||||||
Batch {
|
Batch {
|
||||||
block: [u8; 32],
|
/// The hash of the Batch's serialization.
|
||||||
batch: u32,
|
///
|
||||||
},
|
/// Generally, we refer to a Batch by its ID/the hash of its instructions. Here, we want to
|
||||||
// When a Serai block is finalized, with the contained batches, we can allow the associated plan
|
/// ensure consensus on the Batch, and achieving consensus on its hash is the most effective
|
||||||
// IDs
|
/// way to do that.
|
||||||
SubstrateBlock(u64),
|
hash: [u8; 32],
|
||||||
|
|
||||||
SubstrateSign(SignData<SubstrateSignableId>),
|
|
||||||
Sign(SignData<[u8; 32]>),
|
|
||||||
// This is defined as an Unsigned transaction in order to de-duplicate SignCompleted amongst
|
|
||||||
// reporters (who should all report the same thing)
|
|
||||||
// We do still track the signer in order to prevent a single signer from publishing arbitrarily
|
|
||||||
// many TXs without penalty
|
|
||||||
// Here, they're denoted as the first_signer, as only the signer of the first TX to be included
|
|
||||||
// with this pairing will be remembered on-chain
|
|
||||||
SignCompleted {
|
|
||||||
plan: [u8; 32],
|
|
||||||
tx_hash: Vec<u8>,
|
|
||||||
first_signer: <Ristretto as Ciphersuite>::G,
|
|
||||||
signature: SchnorrSignature<Ristretto>,
|
|
||||||
},
|
},
|
||||||
|
|
||||||
SlashReport(Vec<u32>, Signed),
|
/// The local view of slashes observed by the transaction's sender
|
||||||
}
|
SlashReport {
|
||||||
|
/// The slash points accrued by each validator
|
||||||
|
slash_points: Vec<u32>,
|
||||||
|
/// The transaction's signer and signature
|
||||||
|
signed: Signed,
|
||||||
|
},
|
||||||
|
|
||||||
impl Debug for Transaction {
|
Sign {
|
||||||
fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
|
/// The ID of the object being signed
|
||||||
match self {
|
id: VariantSignId,
|
||||||
Transaction::RemoveParticipant { participant, signed } => fmt
|
/// The attempt number of this signing protocol
|
||||||
.debug_struct("Transaction::RemoveParticipant")
|
attempt: u32,
|
||||||
.field("participant", &hex::encode(participant.to_bytes()))
|
/// The label for this data within the signing protocol
|
||||||
.field("signer", &hex::encode(signed.signer.to_bytes()))
|
label: Label,
|
||||||
.finish_non_exhaustive(),
|
/// The data itself
|
||||||
Transaction::DkgParticipation { signed, .. } => fmt
|
///
|
||||||
.debug_struct("Transaction::DkgParticipation")
|
/// There will be `n` blobs of data where `n` is the amount of key shares the validator sending
|
||||||
.field("signer", &hex::encode(signed.signer.to_bytes()))
|
/// this transaction has.
|
||||||
.finish_non_exhaustive(),
|
data: Vec<Vec<u8>>,
|
||||||
Transaction::DkgConfirmationNonces { attempt, signed, .. } => fmt
|
/// The transaction's signer and signature
|
||||||
.debug_struct("Transaction::DkgConfirmationNonces")
|
signed: Signed,
|
||||||
.field("attempt", attempt)
|
},
|
||||||
.field("signer", &hex::encode(signed.signer.to_bytes()))
|
|
||||||
.finish_non_exhaustive(),
|
|
||||||
Transaction::DkgConfirmationShare { attempt, signed, .. } => fmt
|
|
||||||
.debug_struct("Transaction::DkgConfirmationShare")
|
|
||||||
.field("attempt", attempt)
|
|
||||||
.field("signer", &hex::encode(signed.signer.to_bytes()))
|
|
||||||
.finish_non_exhaustive(),
|
|
||||||
Transaction::CosignSubstrateBlock(block) => fmt
|
|
||||||
.debug_struct("Transaction::CosignSubstrateBlock")
|
|
||||||
.field("block", &hex::encode(block))
|
|
||||||
.finish(),
|
|
||||||
Transaction::Batch { block, batch } => fmt
|
|
||||||
.debug_struct("Transaction::Batch")
|
|
||||||
.field("block", &hex::encode(block))
|
|
||||||
.field("batch", &batch)
|
|
||||||
.finish(),
|
|
||||||
Transaction::SubstrateBlock(block) => {
|
|
||||||
fmt.debug_struct("Transaction::SubstrateBlock").field("block", block).finish()
|
|
||||||
}
|
|
||||||
Transaction::SubstrateSign(sign_data) => {
|
|
||||||
fmt.debug_struct("Transaction::SubstrateSign").field("sign_data", sign_data).finish()
|
|
||||||
}
|
|
||||||
Transaction::Sign(sign_data) => {
|
|
||||||
fmt.debug_struct("Transaction::Sign").field("sign_data", sign_data).finish()
|
|
||||||
}
|
|
||||||
Transaction::SignCompleted { plan, tx_hash, .. } => fmt
|
|
||||||
.debug_struct("Transaction::SignCompleted")
|
|
||||||
.field("plan", &hex::encode(plan))
|
|
||||||
.field("tx_hash", &hex::encode(tx_hash))
|
|
||||||
.finish_non_exhaustive(),
|
|
||||||
Transaction::SlashReport(points, signed) => fmt
|
|
||||||
.debug_struct("Transaction::SignCompleted")
|
|
||||||
.field("points", points)
|
|
||||||
.field("signed", signed)
|
|
||||||
.finish(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ReadWrite for Transaction {
|
impl ReadWrite for Transaction {
|
||||||
fn read<R: io::Read>(reader: &mut R) -> io::Result<Self> {
|
fn read<R: io::Read>(reader: &mut R) -> io::Result<Self> {
|
||||||
let mut kind = [0];
|
borsh::from_reader(reader)
|
||||||
reader.read_exact(&mut kind)?;
|
|
||||||
|
|
||||||
match kind[0] {
|
|
||||||
0 => Ok(Transaction::RemoveParticipant {
|
|
||||||
participant: Ristretto::read_G(reader)?,
|
|
||||||
signed: Signed::read_without_nonce(reader, 0)?,
|
|
||||||
}),
|
|
||||||
|
|
||||||
1 => {
|
|
||||||
let participation = {
|
|
||||||
let mut participation_len = [0; 4];
|
|
||||||
reader.read_exact(&mut participation_len)?;
|
|
||||||
let participation_len = u32::from_le_bytes(participation_len);
|
|
||||||
|
|
||||||
if participation_len > u32::try_from(TRANSACTION_SIZE_LIMIT).unwrap() {
|
|
||||||
Err(io::Error::other(
|
|
||||||
"participation present in transaction exceeded transaction size limit",
|
|
||||||
))?;
|
|
||||||
}
|
|
||||||
let participation_len = usize::try_from(participation_len).unwrap();
|
|
||||||
|
|
||||||
let mut participation = vec![0; participation_len];
|
|
||||||
reader.read_exact(&mut participation)?;
|
|
||||||
participation
|
|
||||||
};
|
|
||||||
|
|
||||||
let signed = Signed::read_without_nonce(reader, 0)?;
|
|
||||||
|
|
||||||
Ok(Transaction::DkgParticipation { participation, signed })
|
|
||||||
}
|
|
||||||
|
|
||||||
2 => {
|
|
||||||
let mut attempt = [0; 4];
|
|
||||||
reader.read_exact(&mut attempt)?;
|
|
||||||
let attempt = u32::from_le_bytes(attempt);
|
|
||||||
|
|
||||||
let mut confirmation_nonces = [0; 64];
|
|
||||||
reader.read_exact(&mut confirmation_nonces)?;
|
|
||||||
|
|
||||||
let signed = Signed::read_without_nonce(reader, 0)?;
|
|
||||||
|
|
||||||
Ok(Transaction::DkgConfirmationNonces { attempt, confirmation_nonces, signed })
|
|
||||||
}
|
|
||||||
|
|
||||||
3 => {
|
|
||||||
let mut attempt = [0; 4];
|
|
||||||
reader.read_exact(&mut attempt)?;
|
|
||||||
let attempt = u32::from_le_bytes(attempt);
|
|
||||||
|
|
||||||
let mut confirmation_share = [0; 32];
|
|
||||||
reader.read_exact(&mut confirmation_share)?;
|
|
||||||
|
|
||||||
let signed = Signed::read_without_nonce(reader, 1)?;
|
|
||||||
|
|
||||||
Ok(Transaction::DkgConfirmationShare { attempt, confirmation_share, signed })
|
|
||||||
}
|
|
||||||
|
|
||||||
4 => {
|
|
||||||
let mut block = [0; 32];
|
|
||||||
reader.read_exact(&mut block)?;
|
|
||||||
Ok(Transaction::CosignSubstrateBlock(block))
|
|
||||||
}
|
|
||||||
|
|
||||||
5 => {
|
|
||||||
let mut block = [0; 32];
|
|
||||||
reader.read_exact(&mut block)?;
|
|
||||||
let mut batch = [0; 4];
|
|
||||||
reader.read_exact(&mut batch)?;
|
|
||||||
Ok(Transaction::Batch { block, batch: u32::from_le_bytes(batch) })
|
|
||||||
}
|
|
||||||
|
|
||||||
6 => {
|
|
||||||
let mut block = [0; 8];
|
|
||||||
reader.read_exact(&mut block)?;
|
|
||||||
Ok(Transaction::SubstrateBlock(u64::from_le_bytes(block)))
|
|
||||||
}
|
|
||||||
|
|
||||||
7 => SignData::read(reader).map(Transaction::SubstrateSign),
|
|
||||||
8 => SignData::read(reader).map(Transaction::Sign),
|
|
||||||
|
|
||||||
9 => {
|
|
||||||
let mut plan = [0; 32];
|
|
||||||
reader.read_exact(&mut plan)?;
|
|
||||||
|
|
||||||
let mut tx_hash_len = [0];
|
|
||||||
reader.read_exact(&mut tx_hash_len)?;
|
|
||||||
let mut tx_hash = vec![0; usize::from(tx_hash_len[0])];
|
|
||||||
reader.read_exact(&mut tx_hash)?;
|
|
||||||
|
|
||||||
let first_signer = Ristretto::read_G(reader)?;
|
|
||||||
let signature = SchnorrSignature::<Ristretto>::read(reader)?;
|
|
||||||
|
|
||||||
Ok(Transaction::SignCompleted { plan, tx_hash, first_signer, signature })
|
|
||||||
}
|
|
||||||
|
|
||||||
10 => {
|
|
||||||
let mut len = [0];
|
|
||||||
reader.read_exact(&mut len)?;
|
|
||||||
let len = len[0];
|
|
||||||
// If the set has as many validators as MAX_KEY_SHARES_PER_SET, then the amount of distinct
|
|
||||||
// validators (the amount of validators reported on) will be at most
|
|
||||||
// `MAX_KEY_SHARES_PER_SET - 1`
|
|
||||||
if u32::from(len) > (serai_client::validator_sets::primitives::MAX_KEY_SHARES_PER_SET - 1) {
|
|
||||||
Err(io::Error::other("more points reported than allowed validator"))?;
|
|
||||||
}
|
|
||||||
let mut points = vec![0u32; len.into()];
|
|
||||||
for points in &mut points {
|
|
||||||
let mut these_points = [0; 4];
|
|
||||||
reader.read_exact(&mut these_points)?;
|
|
||||||
*points = u32::from_le_bytes(these_points);
|
|
||||||
}
|
|
||||||
Ok(Transaction::SlashReport(points, Signed::read_without_nonce(reader, 0)?))
|
|
||||||
}
|
|
||||||
|
|
||||||
_ => Err(io::Error::other("invalid transaction type")),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write<W: io::Write>(&self, writer: &mut W) -> io::Result<()> {
|
fn write<W: io::Write>(&self, writer: &mut W) -> io::Result<()> {
|
||||||
match self {
|
borsh::to_writer(writer, self)
|
||||||
Transaction::RemoveParticipant { participant, signed } => {
|
|
||||||
writer.write_all(&[0])?;
|
|
||||||
writer.write_all(&participant.to_bytes())?;
|
|
||||||
signed.write_without_nonce(writer)
|
|
||||||
}
|
|
||||||
|
|
||||||
Transaction::DkgParticipation { participation, signed } => {
|
|
||||||
writer.write_all(&[1])?;
|
|
||||||
writer.write_all(&u32::try_from(participation.len()).unwrap().to_le_bytes())?;
|
|
||||||
writer.write_all(participation)?;
|
|
||||||
signed.write_without_nonce(writer)
|
|
||||||
}
|
|
||||||
|
|
||||||
Transaction::DkgConfirmationNonces { attempt, confirmation_nonces, signed } => {
|
|
||||||
writer.write_all(&[2])?;
|
|
||||||
writer.write_all(&attempt.to_le_bytes())?;
|
|
||||||
writer.write_all(confirmation_nonces)?;
|
|
||||||
signed.write_without_nonce(writer)
|
|
||||||
}
|
|
||||||
|
|
||||||
Transaction::DkgConfirmationShare { attempt, confirmation_share, signed } => {
|
|
||||||
writer.write_all(&[3])?;
|
|
||||||
writer.write_all(&attempt.to_le_bytes())?;
|
|
||||||
writer.write_all(confirmation_share)?;
|
|
||||||
signed.write_without_nonce(writer)
|
|
||||||
}
|
|
||||||
|
|
||||||
Transaction::CosignSubstrateBlock(block) => {
|
|
||||||
writer.write_all(&[4])?;
|
|
||||||
writer.write_all(block)
|
|
||||||
}
|
|
||||||
|
|
||||||
Transaction::Batch { block, batch } => {
|
|
||||||
writer.write_all(&[5])?;
|
|
||||||
writer.write_all(block)?;
|
|
||||||
writer.write_all(&batch.to_le_bytes())
|
|
||||||
}
|
|
||||||
|
|
||||||
Transaction::SubstrateBlock(block) => {
|
|
||||||
writer.write_all(&[6])?;
|
|
||||||
writer.write_all(&block.to_le_bytes())
|
|
||||||
}
|
|
||||||
|
|
||||||
Transaction::SubstrateSign(data) => {
|
|
||||||
writer.write_all(&[7])?;
|
|
||||||
data.write(writer)
|
|
||||||
}
|
|
||||||
Transaction::Sign(data) => {
|
|
||||||
writer.write_all(&[8])?;
|
|
||||||
data.write(writer)
|
|
||||||
}
|
|
||||||
Transaction::SignCompleted { plan, tx_hash, first_signer, signature } => {
|
|
||||||
writer.write_all(&[9])?;
|
|
||||||
writer.write_all(plan)?;
|
|
||||||
writer
|
|
||||||
.write_all(&[u8::try_from(tx_hash.len()).expect("tx hash length exceed 255 bytes")])?;
|
|
||||||
writer.write_all(tx_hash)?;
|
|
||||||
writer.write_all(&first_signer.to_bytes())?;
|
|
||||||
signature.write(writer)
|
|
||||||
}
|
|
||||||
Transaction::SlashReport(points, signed) => {
|
|
||||||
writer.write_all(&[10])?;
|
|
||||||
writer.write_all(&[u8::try_from(points.len()).unwrap()])?;
|
|
||||||
for points in points {
|
|
||||||
writer.write_all(&points.to_le_bytes())?;
|
|
||||||
}
|
|
||||||
signed.write_without_nonce(writer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TransactionTrait for Transaction {
|
impl TransactionTrait for Transaction {
|
||||||
fn kind(&self) -> TransactionKind<'_> {
|
fn kind(&self) -> TransactionKind {
|
||||||
match self {
|
match self {
|
||||||
Transaction::RemoveParticipant { participant, signed } => {
|
Transaction::RemoveParticipant { participant, signed } => {
|
||||||
TransactionKind::Signed((b"remove", participant.to_bytes()).encode(), signed)
|
TransactionKind::Signed((b"RemoveParticipant", participant).encode(), signed.nonce(0))
|
||||||
}
|
}
|
||||||
|
|
||||||
Transaction::DkgParticipation { signed, .. } => {
|
Transaction::DkgParticipation { signed, .. } => {
|
||||||
TransactionKind::Signed(b"dkg".to_vec(), signed)
|
TransactionKind::Signed(b"DkgParticipation".encode(), signed.nonce(0))
|
||||||
|
}
|
||||||
|
Transaction::DkgConfirmationPreprocess { attempt, signed, .. } => {
|
||||||
|
TransactionKind::Signed((b"DkgConfirmation", attempt).encode(), signed.nonce(0))
|
||||||
}
|
}
|
||||||
Transaction::DkgConfirmationNonces { attempt, signed, .. } |
|
|
||||||
Transaction::DkgConfirmationShare { attempt, signed, .. } => {
|
Transaction::DkgConfirmationShare { attempt, signed, .. } => {
|
||||||
TransactionKind::Signed((b"dkg_confirmation", attempt).encode(), signed)
|
TransactionKind::Signed((b"DkgConfirmation", attempt).encode(), signed.nonce(1))
|
||||||
}
|
}
|
||||||
|
|
||||||
Transaction::CosignSubstrateBlock(_) => TransactionKind::Provided("cosign"),
|
Transaction::CosignSubstrateBlock { .. } => TransactionKind::Provided("CosignSubstrateBlock"),
|
||||||
|
Transaction::SubstrateBlock { .. } => TransactionKind::Provided("SubstrateBlock"),
|
||||||
|
Transaction::Batch { .. } => TransactionKind::Provided("Batch"),
|
||||||
|
|
||||||
Transaction::Batch { .. } => TransactionKind::Provided("batch"),
|
Transaction::Sign { id, attempt, label, signed, .. } => {
|
||||||
Transaction::SubstrateBlock(_) => TransactionKind::Provided("serai"),
|
TransactionKind::Signed((b"Sign", id, attempt).encode(), signed.nonce(label.nonce()))
|
||||||
|
|
||||||
Transaction::SubstrateSign(data) => {
|
|
||||||
TransactionKind::Signed((b"substrate", data.plan, data.attempt).encode(), &data.signed)
|
|
||||||
}
|
}
|
||||||
Transaction::Sign(data) => {
|
|
||||||
TransactionKind::Signed((b"sign", data.plan, data.attempt).encode(), &data.signed)
|
|
||||||
}
|
|
||||||
Transaction::SignCompleted { .. } => TransactionKind::Unsigned,
|
|
||||||
|
|
||||||
Transaction::SlashReport(_, signed) => {
|
Transaction::SlashReport { signed, .. } => {
|
||||||
TransactionKind::Signed(b"slash_report".to_vec(), signed)
|
TransactionKind::Signed(b"SlashReport".encode(), signed.nonce(0))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hash(&self) -> [u8; 32] {
|
fn hash(&self) -> [u8; 32] {
|
||||||
let mut tx = self.serialize();
|
let mut tx = ReadWrite::serialize(self);
|
||||||
if let TransactionKind::Signed(_, signed) = self.kind() {
|
if let TransactionKind::Signed(_, signed) = self.kind() {
|
||||||
// Make sure the part we're cutting off is the signature
|
// Make sure the part we're cutting off is the signature
|
||||||
assert_eq!(tx.drain((tx.len() - 64) ..).collect::<Vec<_>>(), signed.signature.serialize());
|
assert_eq!(tx.drain((tx.len() - 64) ..).collect::<Vec<_>>(), signed.signature.serialize());
|
||||||
}
|
}
|
||||||
Blake2s256::digest([b"Coordinator Tributary Transaction".as_slice(), &tx].concat()).into()
|
Blake2b::<U32>::digest(&tx).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We don't have any verification logic embedded into the transaction. We just slash anyone who
|
||||||
|
// publishes an invalid transaction.
|
||||||
fn verify(&self) -> Result<(), TransactionError> {
|
fn verify(&self) -> Result<(), TransactionError> {
|
||||||
// TODO: Check SubstrateSign's lengths here
|
|
||||||
|
|
||||||
if let Transaction::SignCompleted { first_signer, signature, .. } = self {
|
|
||||||
if !signature.verify(*first_signer, self.sign_completed_challenge()) {
|
|
||||||
Err(TransactionError::InvalidContent)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Transaction {
|
impl Transaction {
|
||||||
// Used to initially construct transactions so we can then get sig hashes and perform signing
|
|
||||||
pub fn empty_signed() -> Signed {
|
|
||||||
Signed {
|
|
||||||
signer: Ristretto::generator(),
|
|
||||||
nonce: 0,
|
|
||||||
signature: SchnorrSignature::<Ristretto> {
|
|
||||||
R: Ristretto::generator(),
|
|
||||||
s: <Ristretto as Ciphersuite>::F::ZERO,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sign a transaction
|
// Sign a transaction
|
||||||
pub fn sign<R: RngCore + CryptoRng>(
|
pub fn sign<R: RngCore + CryptoRng>(
|
||||||
&mut self,
|
&mut self,
|
||||||
@@ -512,76 +246,38 @@ impl Transaction {
|
|||||||
genesis: [u8; 32],
|
genesis: [u8; 32],
|
||||||
key: &Zeroizing<<Ristretto as Ciphersuite>::F>,
|
key: &Zeroizing<<Ristretto as Ciphersuite>::F>,
|
||||||
) {
|
) {
|
||||||
fn signed(tx: &mut Transaction) -> (u32, &mut Signed) {
|
fn signed(tx: &mut Transaction) -> &mut Signed {
|
||||||
#[allow(clippy::match_same_arms)] // Doesn't make semantic sense here
|
#[allow(clippy::match_same_arms)] // This doesn't make semantic sense here
|
||||||
let nonce = match tx {
|
|
||||||
Transaction::RemoveParticipant { .. } => 0,
|
|
||||||
|
|
||||||
Transaction::DkgParticipation { .. } => 0,
|
|
||||||
// Uses a nonce of 0 as it has an internal attempt counter we distinguish by
|
|
||||||
Transaction::DkgConfirmationNonces { .. } => 0,
|
|
||||||
// Uses a nonce of 1 due to internal attempt counter and due to following
|
|
||||||
// DkgConfirmationNonces
|
|
||||||
Transaction::DkgConfirmationShare { .. } => 1,
|
|
||||||
|
|
||||||
Transaction::CosignSubstrateBlock(_) => panic!("signing CosignSubstrateBlock"),
|
|
||||||
|
|
||||||
Transaction::Batch { .. } => panic!("signing Batch"),
|
|
||||||
Transaction::SubstrateBlock(_) => panic!("signing SubstrateBlock"),
|
|
||||||
|
|
||||||
Transaction::SubstrateSign(data) => data.label.nonce(),
|
|
||||||
Transaction::Sign(data) => data.label.nonce(),
|
|
||||||
|
|
||||||
Transaction::SignCompleted { .. } => panic!("signing SignCompleted"),
|
|
||||||
|
|
||||||
Transaction::SlashReport(_, _) => 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
(
|
|
||||||
nonce,
|
|
||||||
#[allow(clippy::match_same_arms)]
|
|
||||||
match tx {
|
match tx {
|
||||||
Transaction::RemoveParticipant { ref mut signed, .. } |
|
Transaction::RemoveParticipant { ref mut signed, .. } |
|
||||||
Transaction::DkgParticipation { ref mut signed, .. } |
|
Transaction::DkgParticipation { ref mut signed, .. } |
|
||||||
Transaction::DkgConfirmationNonces { ref mut signed, .. } => signed,
|
Transaction::DkgConfirmationPreprocess { ref mut signed, .. } => signed,
|
||||||
Transaction::DkgConfirmationShare { ref mut signed, .. } => signed,
|
Transaction::DkgConfirmationShare { ref mut signed, .. } => signed,
|
||||||
|
|
||||||
Transaction::CosignSubstrateBlock(_) => panic!("signing CosignSubstrateBlock"),
|
Transaction::CosignSubstrateBlock { .. } => panic!("signing CosignSubstrateBlock"),
|
||||||
|
Transaction::SubstrateBlock { .. } => panic!("signing SubstrateBlock"),
|
||||||
Transaction::Batch { .. } => panic!("signing Batch"),
|
Transaction::Batch { .. } => panic!("signing Batch"),
|
||||||
Transaction::SubstrateBlock(_) => panic!("signing SubstrateBlock"),
|
|
||||||
|
|
||||||
Transaction::SubstrateSign(ref mut data) => &mut data.signed,
|
Transaction::Sign { ref mut signed, .. } => signed,
|
||||||
Transaction::Sign(ref mut data) => &mut data.signed,
|
|
||||||
|
|
||||||
Transaction::SignCompleted { .. } => panic!("signing SignCompleted"),
|
Transaction::SlashReport { ref mut signed, .. } => signed,
|
||||||
|
}
|
||||||
Transaction::SlashReport(_, ref mut signed) => signed,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let (nonce, signed_ref) = signed(self);
|
// Decide the nonce to sign with
|
||||||
signed_ref.signer = Ristretto::generator() * key.deref();
|
|
||||||
signed_ref.nonce = nonce;
|
|
||||||
|
|
||||||
let sig_nonce = Zeroizing::new(<Ristretto as Ciphersuite>::F::random(rng));
|
let sig_nonce = Zeroizing::new(<Ristretto as Ciphersuite>::F::random(rng));
|
||||||
signed(self).1.signature.R = <Ristretto as Ciphersuite>::generator() * sig_nonce.deref();
|
|
||||||
let sig_hash = self.sig_hash(genesis);
|
{
|
||||||
signed(self).1.signature = SchnorrSignature::<Ristretto>::sign(key, sig_nonce, sig_hash);
|
// Set the signer and the nonce
|
||||||
|
let signed = signed(self);
|
||||||
|
signed.signer = Ristretto::generator() * key.deref();
|
||||||
|
signed.signature.R = <Ristretto as Ciphersuite>::generator() * sig_nonce.deref();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn sign_completed_challenge(&self) -> <Ristretto as Ciphersuite>::F {
|
// Get the signature hash (which now includes `R || A` making it valid as the challenge)
|
||||||
if let Transaction::SignCompleted { plan, tx_hash, first_signer, signature } = self {
|
let sig_hash = self.sig_hash(genesis);
|
||||||
let mut transcript =
|
|
||||||
RecommendedTranscript::new(b"Coordinator Tributary Transaction SignCompleted");
|
// Sign the signature
|
||||||
transcript.append_message(b"plan", plan);
|
signed(self).signature = SchnorrSignature::<Ristretto>::sign(key, sig_nonce, sig_hash);
|
||||||
transcript.append_message(b"tx_hash", tx_hash);
|
|
||||||
transcript.append_message(b"signer", first_signer.to_bytes());
|
|
||||||
transcript.append_message(b"nonce", signature.R.to_bytes());
|
|
||||||
Ristretto::hash_to_F(b"SignCompleted signature", &transcript.challenge(b"challenge"))
|
|
||||||
} else {
|
|
||||||
panic!("sign_completed_challenge called on transaction which wasn't SignCompleted")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,16 +20,16 @@ workspace = true
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["std", "derive"] }
|
scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["std", "derive"] }
|
||||||
borsh = { version = "1", default-features = false, features = ["std", "derive", "de_strict_order"] }
|
borsh = { version = "1", default-features = false, features = ["std", "derive", "de_strict_order"] }
|
||||||
serai-client = { path = "../../substrate/client", default-features = false, features = ["serai", "borsh"] }
|
serai-client = { path = "../../substrate/client", version = "0.1", default-features = false, features = ["serai", "borsh"] }
|
||||||
|
|
||||||
log = { version = "0.4", default-features = false, features = ["std"] }
|
log = { version = "0.4", default-features = false, features = ["std"] }
|
||||||
|
|
||||||
futures = { version = "0.3", default-features = false, features = ["std"] }
|
futures = { version = "0.3", default-features = false, features = ["std"] }
|
||||||
tokio = { version = "1", default-features = false }
|
tokio = { version = "1", default-features = false }
|
||||||
|
|
||||||
serai-db = { version = "0.1.1", path = "../../common/db" }
|
serai-db = { path = "../../common/db", version = "0.1.1" }
|
||||||
serai-task = { version = "0.1", path = "../../common/task" }
|
serai-task = { path = "../../common/task", version = "0.1" }
|
||||||
|
|
||||||
serai-cosign = { path = "../cosign" }
|
serai-cosign = { path = "../cosign", version = "0.1" }
|
||||||
|
|
||||||
messages = { package = "serai-processor-messages", path = "../../processor/messages" }
|
messages = { package = "serai-processor-messages", version = "0.1", path = "../../processor/messages" }
|
||||||
|
|||||||
@@ -96,6 +96,9 @@ impl NewSet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The channel for notifications to sign a slash report, as emitted by an ephemeral event stream.
|
/// The channel for notifications to sign a slash report, as emitted by an ephemeral event stream.
|
||||||
|
///
|
||||||
|
/// These notifications MAY be for irrelevant validator sets. The only guarantee is the
|
||||||
|
/// notifications for all relevant validator sets will be included.
|
||||||
pub struct SignSlashReport;
|
pub struct SignSlashReport;
|
||||||
impl SignSlashReport {
|
impl SignSlashReport {
|
||||||
pub(crate) fn send(txn: &mut impl DbTxn, set: &ValidatorSet) {
|
pub(crate) fn send(txn: &mut impl DbTxn, set: &ValidatorSet) {
|
||||||
|
|||||||
@@ -135,7 +135,7 @@ impl<T: TransactionTrait> Block<T> {
|
|||||||
// Check TXs are sorted by nonce.
|
// Check TXs are sorted by nonce.
|
||||||
let nonce = |tx: &Transaction<T>| {
|
let nonce = |tx: &Transaction<T>| {
|
||||||
if let TransactionKind::Signed(_, Signed { nonce, .. }) = tx.kind() {
|
if let TransactionKind::Signed(_, Signed { nonce, .. }) = tx.kind() {
|
||||||
*nonce
|
nonce
|
||||||
} else {
|
} else {
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -323,7 +323,7 @@ impl<D: Db, T: TransactionTrait> Blockchain<D, T> {
|
|||||||
}
|
}
|
||||||
TransactionKind::Signed(order, Signed { signer, nonce, .. }) => {
|
TransactionKind::Signed(order, Signed { signer, nonce, .. }) => {
|
||||||
let next_nonce = nonce + 1;
|
let next_nonce = nonce + 1;
|
||||||
txn.put(Self::next_nonce_key(&self.genesis, signer, &order), next_nonce.to_le_bytes());
|
txn.put(Self::next_nonce_key(&self.genesis, &signer, &order), next_nonce.to_le_bytes());
|
||||||
self.mempool.remove(&tx.hash());
|
self.mempool.remove(&tx.hash());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ impl<T: TransactionTrait> Transaction<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn kind(&self) -> TransactionKind<'_> {
|
pub fn kind(&self) -> TransactionKind {
|
||||||
match self {
|
match self {
|
||||||
Transaction::Tendermint(tx) => tx.kind(),
|
Transaction::Tendermint(tx) => tx.kind(),
|
||||||
Transaction::Application(tx) => tx.kind(),
|
Transaction::Application(tx) => tx.kind(),
|
||||||
|
|||||||
@@ -81,11 +81,11 @@ impl<D: Db, T: TransactionTrait> Mempool<D, T> {
|
|||||||
}
|
}
|
||||||
Transaction::Application(tx) => match tx.kind() {
|
Transaction::Application(tx) => match tx.kind() {
|
||||||
TransactionKind::Signed(order, Signed { signer, nonce, .. }) => {
|
TransactionKind::Signed(order, Signed { signer, nonce, .. }) => {
|
||||||
let amount = *res.txs_per_signer.get(signer).unwrap_or(&0) + 1;
|
let amount = *res.txs_per_signer.get(&signer).unwrap_or(&0) + 1;
|
||||||
res.txs_per_signer.insert(*signer, amount);
|
res.txs_per_signer.insert(signer, amount);
|
||||||
|
|
||||||
if let Some(prior_nonce) =
|
if let Some(prior_nonce) =
|
||||||
res.last_nonce_in_mempool.insert((*signer, order.clone()), *nonce)
|
res.last_nonce_in_mempool.insert((signer, order.clone()), nonce)
|
||||||
{
|
{
|
||||||
assert_eq!(prior_nonce, nonce - 1);
|
assert_eq!(prior_nonce, nonce - 1);
|
||||||
}
|
}
|
||||||
@@ -133,14 +133,14 @@ impl<D: Db, T: TransactionTrait> Mempool<D, T> {
|
|||||||
match app_tx.kind() {
|
match app_tx.kind() {
|
||||||
TransactionKind::Signed(order, Signed { signer, .. }) => {
|
TransactionKind::Signed(order, Signed { signer, .. }) => {
|
||||||
// Get the nonce from the blockchain
|
// Get the nonce from the blockchain
|
||||||
let Some(blockchain_next_nonce) = blockchain_next_nonce(*signer, order.clone()) else {
|
let Some(blockchain_next_nonce) = blockchain_next_nonce(signer, order.clone()) else {
|
||||||
// Not a participant
|
// Not a participant
|
||||||
Err(TransactionError::InvalidSigner)?
|
Err(TransactionError::InvalidSigner)?
|
||||||
};
|
};
|
||||||
let mut next_nonce = blockchain_next_nonce;
|
let mut next_nonce = blockchain_next_nonce;
|
||||||
|
|
||||||
if let Some(mempool_last_nonce) =
|
if let Some(mempool_last_nonce) =
|
||||||
self.last_nonce_in_mempool.get(&(*signer, order.clone()))
|
self.last_nonce_in_mempool.get(&(signer, order.clone()))
|
||||||
{
|
{
|
||||||
assert!(*mempool_last_nonce >= blockchain_next_nonce);
|
assert!(*mempool_last_nonce >= blockchain_next_nonce);
|
||||||
next_nonce = *mempool_last_nonce + 1;
|
next_nonce = *mempool_last_nonce + 1;
|
||||||
@@ -148,14 +148,14 @@ impl<D: Db, T: TransactionTrait> Mempool<D, T> {
|
|||||||
|
|
||||||
// If we have too many transactions from this sender, don't add this yet UNLESS we are
|
// If we have too many transactions from this sender, don't add this yet UNLESS we are
|
||||||
// this sender
|
// this sender
|
||||||
let amount_in_pool = *self.txs_per_signer.get(signer).unwrap_or(&0) + 1;
|
let amount_in_pool = *self.txs_per_signer.get(&signer).unwrap_or(&0) + 1;
|
||||||
if !internal && (amount_in_pool > ACCOUNT_MEMPOOL_LIMIT) {
|
if !internal && (amount_in_pool > ACCOUNT_MEMPOOL_LIMIT) {
|
||||||
Err(TransactionError::TooManyInMempool)?;
|
Err(TransactionError::TooManyInMempool)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
verify_transaction(app_tx, self.genesis, &mut |_, _| Some(next_nonce))?;
|
verify_transaction(app_tx, self.genesis, &mut |_, _| Some(next_nonce))?;
|
||||||
self.last_nonce_in_mempool.insert((*signer, order.clone()), next_nonce);
|
self.last_nonce_in_mempool.insert((signer, order.clone()), next_nonce);
|
||||||
self.txs_per_signer.insert(*signer, amount_in_pool);
|
self.txs_per_signer.insert(signer, amount_in_pool);
|
||||||
}
|
}
|
||||||
TransactionKind::Unsigned => {
|
TransactionKind::Unsigned => {
|
||||||
// check we have the tx in the pool/chain
|
// check we have the tx in the pool/chain
|
||||||
@@ -205,7 +205,7 @@ impl<D: Db, T: TransactionTrait> Mempool<D, T> {
|
|||||||
// Sort signed by nonce
|
// Sort signed by nonce
|
||||||
let nonce = |tx: &Transaction<T>| {
|
let nonce = |tx: &Transaction<T>| {
|
||||||
if let TransactionKind::Signed(_, Signed { nonce, .. }) = tx.kind() {
|
if let TransactionKind::Signed(_, Signed { nonce, .. }) = tx.kind() {
|
||||||
*nonce
|
nonce
|
||||||
} else {
|
} else {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
}
|
}
|
||||||
@@ -242,11 +242,11 @@ impl<D: Db, T: TransactionTrait> Mempool<D, T> {
|
|||||||
|
|
||||||
if let Some(tx) = self.txs.remove(tx) {
|
if let Some(tx) = self.txs.remove(tx) {
|
||||||
if let TransactionKind::Signed(order, Signed { signer, nonce, .. }) = tx.kind() {
|
if let TransactionKind::Signed(order, Signed { signer, nonce, .. }) = tx.kind() {
|
||||||
let amount = *self.txs_per_signer.get(signer).unwrap() - 1;
|
let amount = *self.txs_per_signer.get(&signer).unwrap() - 1;
|
||||||
self.txs_per_signer.insert(*signer, amount);
|
self.txs_per_signer.insert(signer, amount);
|
||||||
|
|
||||||
if self.last_nonce_in_mempool.get(&(*signer, order.clone())) == Some(nonce) {
|
if self.last_nonce_in_mempool.get(&(signer, order.clone())) == Some(&nonce) {
|
||||||
self.last_nonce_in_mempool.remove(&(*signer, order));
|
self.last_nonce_in_mempool.remove(&(signer, order));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ impl ReadWrite for TendermintTx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Transaction for TendermintTx {
|
impl Transaction for TendermintTx {
|
||||||
fn kind(&self) -> TransactionKind<'_> {
|
fn kind(&self) -> TransactionKind {
|
||||||
// There's an assert elsewhere in the codebase expecting this behavior
|
// There's an assert elsewhere in the codebase expecting this behavior
|
||||||
// If we do want to add Provided/Signed TendermintTxs, review the implications carefully
|
// If we do want to add Provided/Signed TendermintTxs, review the implications carefully
|
||||||
TransactionKind::Unsigned
|
TransactionKind::Unsigned
|
||||||
|
|||||||
@@ -60,8 +60,8 @@ impl ReadWrite for NonceTransaction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl TransactionTrait for NonceTransaction {
|
impl TransactionTrait for NonceTransaction {
|
||||||
fn kind(&self) -> TransactionKind<'_> {
|
fn kind(&self) -> TransactionKind {
|
||||||
TransactionKind::Signed(vec![], &self.2)
|
TransactionKind::Signed(vec![], self.2.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hash(&self) -> [u8; 32] {
|
fn hash(&self) -> [u8; 32] {
|
||||||
|
|||||||
@@ -425,7 +425,7 @@ async fn block_tx_ordering() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl TransactionTrait for SignedTx {
|
impl TransactionTrait for SignedTx {
|
||||||
fn kind(&self) -> TransactionKind<'_> {
|
fn kind(&self) -> TransactionKind {
|
||||||
match self {
|
match self {
|
||||||
SignedTx::Signed(signed) => signed.kind(),
|
SignedTx::Signed(signed) => signed.kind(),
|
||||||
SignedTx::Provided(pro) => pro.kind(),
|
SignedTx::Provided(pro) => pro.kind(),
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ impl ReadWrite for ProvidedTransaction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Transaction for ProvidedTransaction {
|
impl Transaction for ProvidedTransaction {
|
||||||
fn kind(&self) -> TransactionKind<'_> {
|
fn kind(&self) -> TransactionKind {
|
||||||
match self.0[0] {
|
match self.0[0] {
|
||||||
1 => TransactionKind::Provided("order1"),
|
1 => TransactionKind::Provided("order1"),
|
||||||
2 => TransactionKind::Provided("order2"),
|
2 => TransactionKind::Provided("order2"),
|
||||||
@@ -119,8 +119,8 @@ impl ReadWrite for SignedTransaction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Transaction for SignedTransaction {
|
impl Transaction for SignedTransaction {
|
||||||
fn kind(&self) -> TransactionKind<'_> {
|
fn kind(&self) -> TransactionKind {
|
||||||
TransactionKind::Signed(vec![], &self.1)
|
TransactionKind::Signed(vec![], self.1.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hash(&self) -> [u8; 32] {
|
fn hash(&self) -> [u8; 32] {
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ impl Signed {
|
|||||||
|
|
||||||
#[allow(clippy::large_enum_variant)]
|
#[allow(clippy::large_enum_variant)]
|
||||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||||
pub enum TransactionKind<'a> {
|
pub enum TransactionKind {
|
||||||
/// This transaction should be provided by every validator, in an exact order.
|
/// This transaction should be provided by every validator, in an exact order.
|
||||||
///
|
///
|
||||||
/// The contained static string names the orderer to use. This allows two distinct provided
|
/// The contained static string names the orderer to use. This allows two distinct provided
|
||||||
@@ -137,14 +137,14 @@ pub enum TransactionKind<'a> {
|
|||||||
Unsigned,
|
Unsigned,
|
||||||
|
|
||||||
/// A signed transaction.
|
/// A signed transaction.
|
||||||
Signed(Vec<u8>, &'a Signed),
|
Signed(Vec<u8>, Signed),
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Should this be renamed TransactionTrait now that a literal Transaction exists?
|
// TODO: Should this be renamed TransactionTrait now that a literal Transaction exists?
|
||||||
// Or should the literal Transaction be renamed to Event?
|
// Or should the literal Transaction be renamed to Event?
|
||||||
pub trait Transaction: 'static + Send + Sync + Clone + Eq + Debug + ReadWrite {
|
pub trait Transaction: 'static + Send + Sync + Clone + Eq + Debug + ReadWrite {
|
||||||
/// Return what type of transaction this is.
|
/// Return what type of transaction this is.
|
||||||
fn kind(&self) -> TransactionKind<'_>;
|
fn kind(&self) -> TransactionKind;
|
||||||
|
|
||||||
/// Return the hash of this transaction.
|
/// Return the hash of this transaction.
|
||||||
///
|
///
|
||||||
@@ -198,8 +198,8 @@ pub(crate) fn verify_transaction<F: GAIN, T: Transaction>(
|
|||||||
match tx.kind() {
|
match tx.kind() {
|
||||||
TransactionKind::Provided(_) | TransactionKind::Unsigned => {}
|
TransactionKind::Provided(_) | TransactionKind::Unsigned => {}
|
||||||
TransactionKind::Signed(order, Signed { signer, nonce, signature }) => {
|
TransactionKind::Signed(order, Signed { signer, nonce, signature }) => {
|
||||||
if let Some(next_nonce) = get_and_increment_nonce(signer, &order) {
|
if let Some(next_nonce) = get_and_increment_nonce(&signer, &order) {
|
||||||
if *nonce != next_nonce {
|
if nonce != next_nonce {
|
||||||
Err(TransactionError::InvalidNonce)?;
|
Err(TransactionError::InvalidNonce)?;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -208,7 +208,7 @@ pub(crate) fn verify_transaction<F: GAIN, T: Transaction>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Use a batch verification here
|
// TODO: Use a batch verification here
|
||||||
if !signature.verify(*signer, tx.sig_hash(genesis)) {
|
if !signature.verify(signer, tx.sig_hash(genesis)) {
|
||||||
Err(TransactionError::InvalidSignature)?;
|
Err(TransactionError::InvalidSignature)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user