mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-10 05:09:22 +00:00
Add verification of SignedBatch to the in-instructions pallet
This commit is contained in:
2
.github/workflows/daily-deny.yml
vendored
2
.github/workflows/daily-deny.yml
vendored
@@ -12,7 +12,7 @@ jobs:
|
|||||||
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # 6.0.0
|
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # 6.0.0
|
||||||
|
|
||||||
- name: Install cargo deny
|
- name: Install cargo deny
|
||||||
run: cargo +1.91.1 install cargo-deny --version =0.18.8
|
run: cargo +1.91.1 install cargo-deny --version =0.18.9
|
||||||
|
|
||||||
- name: Run cargo deny
|
- name: Run cargo deny
|
||||||
run: cargo deny -L error --all-features check --hide-inclusion-graph
|
run: cargo deny -L error --all-features check --hide-inclusion-graph
|
||||||
|
|||||||
2
.github/workflows/lint.yml
vendored
2
.github/workflows/lint.yml
vendored
@@ -46,7 +46,7 @@ jobs:
|
|||||||
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # 6.0.0
|
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # 6.0.0
|
||||||
|
|
||||||
- name: Install cargo deny
|
- name: Install cargo deny
|
||||||
run: cargo +1.91.1 install cargo-deny --version =0.18.8
|
run: cargo +1.91.1 install cargo-deny --version =0.18.9
|
||||||
|
|
||||||
- name: Run cargo deny
|
- name: Run cargo deny
|
||||||
run: cargo deny -L error --all-features check --hide-inclusion-graph
|
run: cargo deny -L error --all-features check --hide-inclusion-graph
|
||||||
|
|||||||
10
Cargo.lock
generated
10
Cargo.lock
generated
@@ -92,9 +92,9 @@ checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "alloy-chains"
|
name = "alloy-chains"
|
||||||
version = "0.2.21"
|
version = "0.2.23"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1b9ebac8ff9c2f07667e1803dc777304337e160ce5153335beb45e8ec0751808"
|
checksum = "35d744058a9daa51a8cf22a3009607498fcf82d3cf4c5444dd8056cdf651f471"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"alloy-primitives",
|
"alloy-primitives",
|
||||||
"num_enum",
|
"num_enum",
|
||||||
@@ -4321,9 +4321,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libp2p-identity"
|
name = "libp2p-identity"
|
||||||
version = "0.2.12"
|
version = "0.2.13"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3104e13b51e4711ff5738caa1fb54467c8604c2e94d607e27745bcf709068774"
|
checksum = "f0c7892c221730ba55f7196e98b0b8ba5e04b4155651736036628e9f73ed6fc3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bs58",
|
"bs58",
|
||||||
"ed25519-dalek",
|
"ed25519-dalek",
|
||||||
@@ -8441,7 +8441,9 @@ dependencies = [
|
|||||||
"serai-core-pallet",
|
"serai-core-pallet",
|
||||||
"serai-dex-pallet",
|
"serai-dex-pallet",
|
||||||
"serai-genesis-liquidity-pallet",
|
"serai-genesis-liquidity-pallet",
|
||||||
|
"serai-signals-pallet",
|
||||||
"serai-validator-sets-pallet",
|
"serai-validator-sets-pallet",
|
||||||
|
"sp-application-crypto",
|
||||||
"sp-core",
|
"sp-core",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ workspace = true
|
|||||||
scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] }
|
scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] }
|
||||||
|
|
||||||
sp-core = { git = "https://github.com/serai-dex/patch-polkadot-sdk", default-features = false }
|
sp-core = { git = "https://github.com/serai-dex/patch-polkadot-sdk", default-features = false }
|
||||||
|
sp-application-crypto = { git = "https://github.com/serai-dex/patch-polkadot-sdk", default-features = false }
|
||||||
|
|
||||||
frame-system = { git = "https://github.com/serai-dex/patch-polkadot-sdk", default-features = false }
|
frame-system = { git = "https://github.com/serai-dex/patch-polkadot-sdk", default-features = false }
|
||||||
frame-support = { git = "https://github.com/serai-dex/patch-polkadot-sdk", default-features = false }
|
frame-support = { git = "https://github.com/serai-dex/patch-polkadot-sdk", default-features = false }
|
||||||
@@ -32,6 +33,7 @@ serai-abi = { path = "../abi", default-features = false, features = ["substrate"
|
|||||||
serai-core-pallet = { path = "../core", default-features = false }
|
serai-core-pallet = { path = "../core", default-features = false }
|
||||||
serai-coins-pallet = { path = "../coins", default-features = false }
|
serai-coins-pallet = { path = "../coins", default-features = false }
|
||||||
serai-validator-sets-pallet = { path = "../validator-sets", default-features = false }
|
serai-validator-sets-pallet = { path = "../validator-sets", default-features = false }
|
||||||
|
serai-signals-pallet = { path = "../signals", default-features = false }
|
||||||
serai-dex-pallet = { path = "../dex", default-features = false }
|
serai-dex-pallet = { path = "../dex", default-features = false }
|
||||||
serai-genesis-liquidity-pallet = { path = "../genesis-liquidity", default-features = false }
|
serai-genesis-liquidity-pallet = { path = "../genesis-liquidity", default-features = false }
|
||||||
|
|
||||||
@@ -40,6 +42,7 @@ std = [
|
|||||||
"scale/std",
|
"scale/std",
|
||||||
|
|
||||||
"sp-core/std",
|
"sp-core/std",
|
||||||
|
"sp-application-crypto/std",
|
||||||
|
|
||||||
"frame-system/std",
|
"frame-system/std",
|
||||||
"frame-support/std",
|
"frame-support/std",
|
||||||
@@ -49,6 +52,7 @@ std = [
|
|||||||
"serai-core-pallet/std",
|
"serai-core-pallet/std",
|
||||||
"serai-coins-pallet/std",
|
"serai-coins-pallet/std",
|
||||||
"serai-validator-sets-pallet/std",
|
"serai-validator-sets-pallet/std",
|
||||||
|
"serai-signals-pallet/std",
|
||||||
"serai-dex-pallet/std",
|
"serai-dex-pallet/std",
|
||||||
"serai-genesis-liquidity-pallet/std",
|
"serai-genesis-liquidity-pallet/std",
|
||||||
]
|
]
|
||||||
@@ -61,6 +65,7 @@ try-runtime = [
|
|||||||
"serai-core-pallet/try-runtime",
|
"serai-core-pallet/try-runtime",
|
||||||
"serai-coins-pallet/try-runtime",
|
"serai-coins-pallet/try-runtime",
|
||||||
"serai-validator-sets-pallet/try-runtime",
|
"serai-validator-sets-pallet/try-runtime",
|
||||||
|
"serai-signals-pallet/try-runtime",
|
||||||
"serai-dex-pallet/try-runtime",
|
"serai-dex-pallet/try-runtime",
|
||||||
"serai-genesis-liquidity-pallet/try-runtime",
|
"serai-genesis-liquidity-pallet/try-runtime",
|
||||||
]
|
]
|
||||||
@@ -72,6 +77,7 @@ runtime-benchmarks = [
|
|||||||
"serai-core-pallet/runtime-benchmarks",
|
"serai-core-pallet/runtime-benchmarks",
|
||||||
"serai-coins-pallet/runtime-benchmarks",
|
"serai-coins-pallet/runtime-benchmarks",
|
||||||
"serai-validator-sets-pallet/runtime-benchmarks",
|
"serai-validator-sets-pallet/runtime-benchmarks",
|
||||||
|
"serai-signals-pallet/runtime-benchmarks",
|
||||||
"serai-dex-pallet/runtime-benchmarks",
|
"serai-dex-pallet/runtime-benchmarks",
|
||||||
"serai-genesis-liquidity-pallet/runtime-benchmarks",
|
"serai-genesis-liquidity-pallet/runtime-benchmarks",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ extern crate alloc;
|
|||||||
#[expect(clippy::cast_possible_truncation)]
|
#[expect(clippy::cast_possible_truncation)]
|
||||||
#[frame_support::pallet]
|
#[frame_support::pallet]
|
||||||
mod pallet {
|
mod pallet {
|
||||||
|
use sp_application_crypto::RuntimePublic;
|
||||||
|
|
||||||
use frame_system::pallet_prelude::*;
|
use frame_system::pallet_prelude::*;
|
||||||
use frame_support::pallet_prelude::*;
|
use frame_support::pallet_prelude::*;
|
||||||
|
|
||||||
@@ -26,6 +28,7 @@ mod pallet {
|
|||||||
+ serai_core_pallet::Config
|
+ serai_core_pallet::Config
|
||||||
+ serai_coins_pallet::Config<serai_coins_pallet::CoinsInstance>
|
+ serai_coins_pallet::Config<serai_coins_pallet::CoinsInstance>
|
||||||
+ serai_validator_sets_pallet::Config
|
+ serai_validator_sets_pallet::Config
|
||||||
|
+ serai_signals_pallet::Config
|
||||||
+ serai_coins_pallet::Config<serai_coins_pallet::LiquidityTokensInstance>
|
+ serai_coins_pallet::Config<serai_coins_pallet::LiquidityTokensInstance>
|
||||||
+ serai_dex_pallet::Config
|
+ serai_dex_pallet::Config
|
||||||
+ serai_genesis_liquidity_pallet::Config
|
+ serai_genesis_liquidity_pallet::Config
|
||||||
@@ -36,6 +39,18 @@ mod pallet {
|
|||||||
#[pallet::error]
|
#[pallet::error]
|
||||||
pub enum Error<T> {}
|
pub enum Error<T> {}
|
||||||
|
|
||||||
|
/// The block the last batch was published during.
|
||||||
|
///
|
||||||
|
/// This is used to restrict publication of batches to once-per-block, limiting the impact of a
|
||||||
|
/// compromised publisher from bloating the Serai blockchain with spam.
|
||||||
|
#[pallet::storage]
|
||||||
|
type BlockOfLastBatch<T: Config> =
|
||||||
|
StorageMap<_, Identity, ExternalNetworkId, BlockNumberFor<T>, OptionQuery>;
|
||||||
|
|
||||||
|
/// The ID of the last batch which was published.
|
||||||
|
#[pallet::storage]
|
||||||
|
type LastBatch<T: Config> = StorageMap<_, Identity, ExternalNetworkId, u32, OptionQuery>;
|
||||||
|
|
||||||
/// The Pallet struct.
|
/// The Pallet struct.
|
||||||
#[pallet::pallet]
|
#[pallet::pallet]
|
||||||
pub struct Pallet<T>(_);
|
pub struct Pallet<T>(_);
|
||||||
@@ -55,6 +70,113 @@ mod pallet {
|
|||||||
todo!("TODO")
|
todo!("TODO")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[pallet::validate_unsigned]
|
||||||
|
impl<T: Config> ValidateUnsigned for Pallet<T> {
|
||||||
|
type Call = Call<T>;
|
||||||
|
|
||||||
|
fn validate_unsigned(_: TransactionSource, call: &Self::Call) -> TransactionValidity {
|
||||||
|
let batch = match call {
|
||||||
|
Call::execute_batch { batch } => batch,
|
||||||
|
Call::__Ignore(_, _) => Err(InvalidTransaction::Call)?,
|
||||||
|
};
|
||||||
|
|
||||||
|
let network = batch.batch.network();
|
||||||
|
|
||||||
|
// Verify the network isn't halted
|
||||||
|
if serai_signals_pallet::Pallet::<T>::halted(network) {
|
||||||
|
Err(InvalidTransaction::Custom(1))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the `Batch`'s signature
|
||||||
|
let mut signed_by_latest_decided_session = false;
|
||||||
|
{
|
||||||
|
let message = batch.batch.publish_batch_message();
|
||||||
|
let signed_by_session = |session| {
|
||||||
|
let Some(key) =
|
||||||
|
serai_validator_sets_pallet::Pallet::<T>::oraclization_key(ExternalValidatorSet {
|
||||||
|
network,
|
||||||
|
session,
|
||||||
|
})
|
||||||
|
else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
key.verify(&message, &batch.signature.into())
|
||||||
|
};
|
||||||
|
let Some(current_session) =
|
||||||
|
serai_validator_sets_pallet::Pallet::<T>::current_session(NetworkId::from(network))
|
||||||
|
else {
|
||||||
|
Err(InvalidTransaction::BadProof)?
|
||||||
|
};
|
||||||
|
if !signed_by_session(current_session) {
|
||||||
|
let latest_decided_session =
|
||||||
|
serai_validator_sets_pallet::Pallet::<T>::latest_decided_session(NetworkId::from(
|
||||||
|
network,
|
||||||
|
))
|
||||||
|
.expect("current session yet never one decided?");
|
||||||
|
if !signed_by_session(latest_decided_session) {
|
||||||
|
Err(InvalidTransaction::BadProof)?;
|
||||||
|
}
|
||||||
|
signed_by_latest_decided_session = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify this is the first `Batch` for this block
|
||||||
|
let current_block_number = frame_system::Pallet::<T>::block_number();
|
||||||
|
if BlockOfLastBatch::<T>::get(network) == Some(current_block_number) {
|
||||||
|
// This transaction is valid in the future, the next block, but not now
|
||||||
|
Err(InvalidTransaction::Future)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify this `Batch` descends immediately from the prior `Batch`
|
||||||
|
let required_last_batch = batch.batch.id().checked_sub(1);
|
||||||
|
{
|
||||||
|
let last_batch = LastBatch::<T>::get(network);
|
||||||
|
if last_batch < required_last_batch {
|
||||||
|
Err(InvalidTransaction::Future)?;
|
||||||
|
}
|
||||||
|
if last_batch > required_last_batch {
|
||||||
|
Err(InvalidTransaction::Stale)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Set the metadata fields as necessary for further batches to be verified. While this is
|
||||||
|
mutating the state within the verification function, it's necessary for the verification of
|
||||||
|
the following transactions.
|
||||||
|
|
||||||
|
Additionally, we know these state changes occur as tests verify we can publish `Batch`es
|
||||||
|
and have the `LastBatch` field be incremented.
|
||||||
|
*/
|
||||||
|
BlockOfLastBatch::<T>::set(network, Some(current_block_number));
|
||||||
|
LastBatch::<T>::set(network, Some(batch.batch.id()));
|
||||||
|
if signed_by_latest_decided_session {
|
||||||
|
// Because the latest decided session, which is not the current session, has taken over
|
||||||
|
// for publishing `Batch`es, it has agreed to become the current session
|
||||||
|
serai_validator_sets_pallet::Pallet::<T>::accept_handover(network);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut builder = ValidTransaction::with_tag_prefix("InInstructions");
|
||||||
|
if let Some(required_last_batch) = required_last_batch {
|
||||||
|
// TODO: Should this replace the DB mutations present within this verification function?
|
||||||
|
builder = builder.and_requires((network, required_last_batch));
|
||||||
|
}
|
||||||
|
builder
|
||||||
|
.and_provides((network, batch.batch.id()))
|
||||||
|
// Set a 10 block longevity, though this should be included in the next block
|
||||||
|
.longevity(10)
|
||||||
|
.propagate(true)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Explicitly provide a `pre_dispatch` which calls `validate_unsigned`.
|
||||||
|
///
|
||||||
|
/// This is reasonably assumed, and the current provided implementation, but not guaranteed by
|
||||||
|
/// the documentation.
|
||||||
|
fn pre_dispatch(call: &Self::Call) -> Result<(), TransactionValidityError> {
|
||||||
|
Self::validate_unsigned(TransactionSource::InBlock, call).map(|_| ())
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub use pallet::*;
|
pub use pallet::*;
|
||||||
|
|||||||
@@ -234,6 +234,11 @@ pub mod pallet {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Check if an external network was halted.
|
||||||
|
pub fn halted(network: ExternalNetworkId) -> bool {
|
||||||
|
Halted::<T>::contains_key(network)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An error from the `signals` pallet.
|
/// An error from the `signals` pallet.
|
||||||
|
|||||||
@@ -345,6 +345,14 @@ mod pallet {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Have the latest decided session become the current session.
|
||||||
|
///
|
||||||
|
/// This is restricted to `ExternalNetworkId` as this process happens internally for
|
||||||
|
/// `NetworkId::Serai`.
|
||||||
|
pub fn accept_handover(network: ExternalNetworkId) {
|
||||||
|
Abstractions::<T>::accept_handover(network.into());
|
||||||
|
}
|
||||||
|
|
||||||
/* TODO
|
/* TODO
|
||||||
pub fn distribute_block_rewards(
|
pub fn distribute_block_rewards(
|
||||||
network: NetworkId,
|
network: NetworkId,
|
||||||
|
|||||||
Reference in New Issue
Block a user