Improve batch handling (#316)

* restrict batch size to ~25kb

* add batch size check to node

* rate limit batches to 1 per serai block

* add support for multiple batches for block

* fix review comments

* Misc fixes

Doesn't yet update tests/processor until data flow is inspected.

* Move the block from SignId to ProcessorMessage::BatchPreprocesses

* Misc clean up

---------

Co-authored-by: Luke Parker <lukeparker5132@gmail.com>
This commit is contained in:
akildemir
2023-08-14 18:57:38 +03:00
committed by GitHub
parent a3441a6871
commit e680eabb62
17 changed files with 234 additions and 155 deletions

View File

@@ -19,6 +19,7 @@ scale-info = { version = "2", default-features = false, features = ["derive"] }
sp-application-crypto = { git = "https://github.com/serai-dex/substrate", default-features = false }
sp-runtime = { git = "https://github.com/serai-dex/substrate", default-features = false }
sp-core = { git = "https://github.com/serai-dex/substrate", default-features = false }
frame-system = { git = "https://github.com/serai-dex/substrate", default-features = false }
frame-support = { git = "https://github.com/serai-dex/substrate", default-features = false }

View File

@@ -23,6 +23,8 @@ pub enum PalletError {
#[frame_support::pallet]
pub mod pallet {
use sp_application_crypto::RuntimePublic;
use sp_runtime::traits::Zero;
use sp_core::sr25519::Public;
use frame_support::pallet_prelude::*;
use frame_system::pallet_prelude::*;
@@ -55,10 +57,16 @@ pub mod pallet {
#[pallet::getter(fn batches)]
pub(crate) type Batches<T: Config> = StorageMap<_, Blake2_256, NetworkId, u32, OptionQuery>;
// The last Serai block in which this validator set included a batch
#[pallet::storage]
#[pallet::getter(fn last_batch_block)]
pub(crate) type LastBatchBlock<T: Config> =
StorageMap<_, Blake2_256, NetworkId, BlockNumberFor<T>, OptionQuery>;
// The latest block a network has acknowledged as finalized
#[pallet::storage]
#[pallet::getter(fn last_block)]
pub(crate) type LatestBlock<T: Config> =
#[pallet::getter(fn latest_network_block)]
pub(crate) type LatestNetworkBlock<T: Config> =
StorageMap<_, Blake2_256, NetworkId, BlockHash, OptionQuery>;
impl<T: Config> Pallet<T> {
@@ -71,6 +79,31 @@ pub mod pallet {
}
}
fn key_for_network<T: Config>(network: NetworkId) -> Result<Public, InvalidTransaction> {
// TODO: Get the latest session
let session = Session(0);
let mut set = ValidatorSet { session, network };
// TODO: If this session just set their keys, it'll invalidate any batches in the mempool
// Should there be a transitory period/future-set cut off?
if let Some(keys) = ValidatorSets::<T>::keys(set) {
Ok(keys.0)
} else {
// If this set hasn't set their keys yet, use the previous set's
if set.session.0 == 0 {
// Since there haven't been any keys set, no signature can legitimately exist
Err(InvalidTransaction::BadProof)?;
}
set.session.0 -= 1;
if let Some(keys) = ValidatorSets::<T>::keys(set) {
Ok(keys.0)
} else {
Err(InvalidTransaction::BadProof)?
}
}
}
#[pallet::call]
impl<T: Config> Pallet<T> {
#[pallet::call_index(0)]
@@ -78,16 +111,20 @@ pub mod pallet {
pub fn execute_batch(origin: OriginFor<T>, batch: SignedBatch) -> DispatchResult {
ensure_none(origin)?;
let mut batch = batch.batch;
let batch = batch.batch;
// TODO: Test validate_unsigned is actually called prior to execution, which is required for
// this to be safe
LastBatchBlock::<T>::insert(batch.network, frame_system::Pallet::<T>::block_number());
Batches::<T>::insert(batch.network, batch.id);
LatestBlock::<T>::insert(batch.network, batch.block);
LatestNetworkBlock::<T>::insert(batch.network, batch.block);
Self::deposit_event(Event::Batch {
network: batch.network,
id: batch.id,
block: batch.block,
});
for (i, instruction) in batch.instructions.drain(..).enumerate() {
for (i, instruction) in batch.instructions.into_iter().enumerate() {
// TODO: Check this balance's coin belongs to this network
// If they don't, the validator set should be completely slashed, without question
@@ -116,37 +153,30 @@ pub mod pallet {
};
let network = batch.batch.network;
let key = key_for_network::<T>(network)?;
// TODO: Get the latest session
let session = Session(0);
let mut set = ValidatorSet { session, network };
// TODO: If this session just set their keys, it'll invalidate anything in the mempool
// Should there be a transitory period/future-set cut off?
let key = if let Some(keys) = ValidatorSets::<T>::keys(set) {
keys.0
} else {
// If this set hasn't set their keys yet, use the previous set's
if set.session.0 == 0 {
Err(InvalidTransaction::BadProof)?;
}
set.session.0 -= 1;
if let Some(keys) = ValidatorSets::<T>::keys(set) {
keys.0
} else {
Err(InvalidTransaction::BadProof)?
}
};
// verify the batch size
// TODO: Merge this encode with the one done by batch_message
if batch.batch.encode().len() > MAX_BATCH_SIZE {
Err(InvalidTransaction::ExhaustsResources)?;
}
// verify the signature
if !key.verify(&batch_message(&batch.batch), &batch.signature) {
Err(InvalidTransaction::BadProof)?;
}
// check that this validator set isn't publishing a batch more than once per block
let current_block = <frame_system::Pallet<T>>::block_number();
let last_block = LastBatchBlock::<T>::get(network).unwrap_or(Zero::zero());
if last_block >= current_block {
Err(InvalidTransaction::Future)?;
}
// Verify the batch is sequential
// Batches has the last ID set. The next ID should be it + 1
// If there's no ID, the next ID should be 0
let expected = Batches::<T>::get(network).map(|prev| prev + 1).unwrap_or(0);
let expected = Batches::<T>::get(network).map_or(0, |prev| prev + 1);
if batch.batch.id < expected {
Err(InvalidTransaction::Stale)?;
}

View File

@@ -21,6 +21,8 @@ use serai_primitives::{BlockHash, Balance, NetworkId, SeraiAddress, ExternalAddr
mod shorthand;
pub use shorthand::*;
pub const MAX_BATCH_SIZE: usize = 25_000; // ~25kb
#[derive(
Clone, PartialEq, Eq, Debug, Serialize, Deserialize, Encode, Decode, MaxEncodedLen, TypeInfo,
)]