mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-08 12:19:24 +00:00
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:
@@ -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 }
|
||||
|
||||
@@ -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)?;
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
)]
|
||||
|
||||
Reference in New Issue
Block a user