2025-02-12 03:41:50 -05:00
|
|
|
use core::num::NonZero;
|
2025-02-26 06:54:42 -05:00
|
|
|
use alloc::vec::Vec;
|
2025-02-12 03:41:50 -05:00
|
|
|
|
|
|
|
|
use borsh::{io, BorshSerialize, BorshDeserialize};
|
|
|
|
|
|
2025-02-26 05:05:35 -05:00
|
|
|
use sp_core::{ConstU32, bounded::BoundedVec};
|
|
|
|
|
use serai_primitives::{BlockHash, address::SeraiAddress, balance::Amount, crypto::Signature};
|
2025-02-12 03:41:50 -05:00
|
|
|
use crate::Call;
|
|
|
|
|
|
2025-02-26 05:05:35 -05:00
|
|
|
/// The maximum amount of calls allowed in a transaction.
|
|
|
|
|
pub const MAX_CALLS: u32 = 8;
|
2025-02-12 03:41:50 -05:00
|
|
|
|
|
|
|
|
/// An error regarding `SignedCalls`.
|
|
|
|
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
|
|
|
|
pub enum SignedCallsError {
|
|
|
|
|
/// No calls were included.
|
|
|
|
|
NoCalls,
|
2025-02-26 05:05:35 -05:00
|
|
|
/// Too many calls were included.
|
|
|
|
|
TooManyCalls,
|
2025-02-12 03:41:50 -05:00
|
|
|
/// An unsigned call was included.
|
|
|
|
|
IncludedUnsignedCall,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// A `Vec` of signed calls.
|
2025-02-26 06:54:42 -05:00
|
|
|
// We don't implement BorshDeserialize due to to maintained invariants on this struct.
|
|
|
|
|
#[derive(Clone, PartialEq, Eq, Debug, BorshSerialize)]
|
|
|
|
|
pub struct SignedCalls(
|
|
|
|
|
#[borsh(serialize_with = "serai_primitives::sp_borsh::borsh_serialize_bounded_vec")]
|
|
|
|
|
BoundedVec<Call, ConstU32<{ MAX_CALLS }>>,
|
|
|
|
|
);
|
2025-02-12 03:41:50 -05:00
|
|
|
impl TryFrom<Vec<Call>> for SignedCalls {
|
|
|
|
|
type Error = SignedCallsError;
|
|
|
|
|
fn try_from(calls: Vec<Call>) -> Result<Self, Self::Error> {
|
|
|
|
|
if calls.is_empty() {
|
|
|
|
|
Err(SignedCallsError::NoCalls)?;
|
|
|
|
|
}
|
|
|
|
|
for call in &calls {
|
|
|
|
|
if !call.is_signed() {
|
|
|
|
|
Err(SignedCallsError::IncludedUnsignedCall)?;
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-02-26 05:05:35 -05:00
|
|
|
calls.try_into().map_err(|_| SignedCallsError::TooManyCalls).map(SignedCalls)
|
2025-02-12 03:41:50 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// An error regarding `UnsignedCall`.
|
|
|
|
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
|
|
|
|
pub enum UnsignedCallError {
|
|
|
|
|
/// A signed call was specified.
|
|
|
|
|
SignedCall,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// An unsigned call.
|
2025-02-26 06:54:42 -05:00
|
|
|
// We don't implement BorshDeserialize due to to maintained invariants on this struct.
|
|
|
|
|
#[derive(Clone, PartialEq, Eq, Debug, BorshSerialize)]
|
2025-02-12 03:41:50 -05:00
|
|
|
pub struct UnsignedCall(Call);
|
|
|
|
|
impl TryFrom<Call> for UnsignedCall {
|
|
|
|
|
type Error = UnsignedCallError;
|
|
|
|
|
fn try_from(call: Call) -> Result<Self, Self::Error> {
|
|
|
|
|
if call.is_signed() {
|
|
|
|
|
Err(UnsignedCallError::SignedCall)?;
|
|
|
|
|
}
|
|
|
|
|
Ok(UnsignedCall(call))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Part of the context used to sign with, from the protocol.
|
|
|
|
|
#[derive(Clone, PartialEq, Eq, Debug, BorshSerialize, BorshDeserialize)]
|
|
|
|
|
pub struct ImplicitContext {
|
|
|
|
|
/// The genesis hash of the blockchain.
|
2025-02-26 05:05:35 -05:00
|
|
|
pub genesis: BlockHash,
|
|
|
|
|
/// The ID of the current protocol.
|
|
|
|
|
pub protocol_id: [u8; 32],
|
2025-02-12 03:41:50 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Part of the context used to sign with, specified within the transaction itself.
|
|
|
|
|
#[derive(Clone, PartialEq, Eq, Debug, BorshSerialize, BorshDeserialize)]
|
|
|
|
|
pub struct ExplicitContext {
|
|
|
|
|
/// The historic block this transaction builds upon.
|
|
|
|
|
///
|
|
|
|
|
/// This transaction can not be included in a blockchain which does not include this block.
|
2025-02-26 05:05:35 -05:00
|
|
|
pub historic_block: BlockHash,
|
2025-02-12 03:41:50 -05:00
|
|
|
|
2025-02-26 05:05:35 -05:00
|
|
|
/// The UNIX time this transaction must be included by (and expires after).
|
2025-02-12 03:41:50 -05:00
|
|
|
///
|
2025-02-26 05:05:35 -05:00
|
|
|
/// This transaction can not be included in a block whose time is equal or greater to this value.
|
|
|
|
|
pub include_by: Option<NonZero<u64>>,
|
2025-02-12 03:41:50 -05:00
|
|
|
|
|
|
|
|
/// The signer.
|
|
|
|
|
pub signer: SeraiAddress,
|
|
|
|
|
|
|
|
|
|
/// The signer's nonce.
|
|
|
|
|
pub nonce: u32,
|
|
|
|
|
|
2025-02-26 05:05:35 -05:00
|
|
|
/// The fee, in SRI, paid to the network for inclusion.
|
2025-02-12 03:41:50 -05:00
|
|
|
///
|
|
|
|
|
/// This fee is paid regardless of the success of any of the calls.
|
2025-02-26 05:05:35 -05:00
|
|
|
pub fee: Amount,
|
2025-02-12 03:41:50 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// A signature, with context.
|
|
|
|
|
#[derive(Clone, PartialEq, Eq, Debug, BorshSerialize, BorshDeserialize)]
|
|
|
|
|
pub struct ContextualizedSignature {
|
|
|
|
|
/// The explicit context.
|
|
|
|
|
explicit_context: ExplicitContext,
|
|
|
|
|
/// The signature.
|
|
|
|
|
signature: Signature,
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-26 06:54:42 -05:00
|
|
|
/// A Serai transaction.
|
2025-02-12 03:41:50 -05:00
|
|
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
2025-02-26 06:54:42 -05:00
|
|
|
pub enum Transaction {
|
|
|
|
|
/// An unsigned transaction.
|
|
|
|
|
Unsigned {
|
|
|
|
|
/// The contained call.
|
|
|
|
|
call: UnsignedCall,
|
|
|
|
|
},
|
|
|
|
|
/// A signed transaction.
|
|
|
|
|
Signed {
|
|
|
|
|
/// The calls.
|
|
|
|
|
///
|
|
|
|
|
/// These calls are executed atomically. Either all successfully execute or none do. The
|
|
|
|
|
/// transaction's fee is paid regardless.
|
|
|
|
|
calls: SignedCalls,
|
|
|
|
|
/// The signature for this transaction.
|
|
|
|
|
///
|
|
|
|
|
/// This is not checked on deserializtion and may be invalid.
|
|
|
|
|
contextualized_signature: ContextualizedSignature,
|
|
|
|
|
},
|
2025-02-12 03:41:50 -05:00
|
|
|
}
|
|
|
|
|
|
2025-02-26 05:19:04 -05:00
|
|
|
impl BorshSerialize for Transaction {
|
2025-02-12 03:41:50 -05:00
|
|
|
fn serialize<W: io::Write>(&self, writer: &mut W) -> io::Result<()> {
|
2025-02-26 06:54:42 -05:00
|
|
|
match self {
|
|
|
|
|
Transaction::Unsigned { call } => {
|
|
|
|
|
/*
|
|
|
|
|
`Signed` `Transaction`s encode the length of their `Vec<Call>` here. Since that `Vec` is
|
|
|
|
|
bound to be non-empty, it will never write `0`, enabling `Unsigned` to use it.
|
|
|
|
|
|
|
|
|
|
The benefit to these not overlapping is in the ability to determine if the `Transaction`
|
|
|
|
|
has a signature or not. If this wrote a `1`, for the amount of `Call`s present in the
|
|
|
|
|
`Transaction`, that `Call` would have to be introspected for if its signed or not. With
|
|
|
|
|
the usage of `0`, given how low `MAX_CALLS` is, this `Transaction` can technically be
|
|
|
|
|
defined as an enum of
|
|
|
|
|
`0 Call, 1 Call ContextualizedSignature, 2 Call Call ContextualizedSignature ...`, to
|
|
|
|
|
maintain compatbility with the borsh specification without wrapper functions. The checks
|
|
|
|
|
here on `Call` types/quantity could be moved to later validation functions.
|
|
|
|
|
*/
|
|
|
|
|
writer.write_all(&[0])?;
|
|
|
|
|
call.serialize(writer)
|
|
|
|
|
}
|
|
|
|
|
Transaction::Signed { calls, contextualized_signature } => {
|
|
|
|
|
serai_primitives::sp_borsh::borsh_serialize_bounded_vec(&calls.0, writer)?;
|
|
|
|
|
contextualized_signature.serialize(writer)
|
|
|
|
|
}
|
2025-02-12 03:41:50 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-26 05:19:04 -05:00
|
|
|
impl BorshDeserialize for Transaction {
|
2025-02-12 03:41:50 -05:00
|
|
|
fn deserialize_reader<R: io::Read>(reader: &mut R) -> io::Result<Self> {
|
2025-02-26 06:54:42 -05:00
|
|
|
let mut len = [0xff];
|
|
|
|
|
reader.read_exact(&mut len)?;
|
|
|
|
|
let len = len[0];
|
|
|
|
|
|
|
|
|
|
if len == 0 {
|
|
|
|
|
let call = Call::deserialize_reader(reader)?;
|
|
|
|
|
if call.is_signed() {
|
|
|
|
|
Err(io::Error::new(io::ErrorKind::Other, "call was signed but marked unsigned"))?;
|
|
|
|
|
}
|
|
|
|
|
Ok(Transaction::Unsigned { call: UnsignedCall(call) })
|
|
|
|
|
} else {
|
|
|
|
|
if u32::from(len) > MAX_CALLS {
|
|
|
|
|
Err(io::Error::new(io::ErrorKind::Other, "too many calls"))?;
|
2025-02-12 03:41:50 -05:00
|
|
|
}
|
2025-02-26 06:54:42 -05:00
|
|
|
let mut calls = BoundedVec::with_bounded_capacity(len.into());
|
|
|
|
|
for _ in 0 .. len {
|
|
|
|
|
let call = Call::deserialize_reader(reader)?;
|
|
|
|
|
if !call.is_signed() {
|
|
|
|
|
Err(io::Error::new(io::ErrorKind::Other, "call was unsigned but included as signed"))?;
|
|
|
|
|
}
|
|
|
|
|
calls.try_push(call).unwrap();
|
|
|
|
|
}
|
|
|
|
|
let contextualized_signature = ContextualizedSignature::deserialize_reader(reader)?;
|
|
|
|
|
Ok(Transaction::Signed { calls: SignedCalls(calls), contextualized_signature })
|
2025-02-12 03:41:50 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-26 05:19:04 -05:00
|
|
|
impl Transaction {
|
2025-02-26 06:54:42 -05:00
|
|
|
/// The message to sign to produce a signature.
|
|
|
|
|
pub fn signature_message(
|
|
|
|
|
calls: &SignedCalls,
|
2025-02-12 03:41:50 -05:00
|
|
|
implicit_context: &ImplicitContext,
|
|
|
|
|
explicit_context: &ExplicitContext,
|
|
|
|
|
) -> Vec<u8> {
|
|
|
|
|
let mut message = Vec::with_capacity(
|
2025-02-26 06:54:42 -05:00
|
|
|
(calls.0.len() * 64) +
|
2025-02-12 03:41:50 -05:00
|
|
|
core::mem::size_of::<ImplicitContext>() +
|
|
|
|
|
core::mem::size_of::<ExplicitContext>(),
|
|
|
|
|
);
|
2025-02-26 05:05:35 -05:00
|
|
|
calls.serialize(&mut message).unwrap();
|
2025-02-12 03:41:50 -05:00
|
|
|
implicit_context.serialize(&mut message).unwrap();
|
|
|
|
|
explicit_context.serialize(&mut message).unwrap();
|
|
|
|
|
message
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(feature = "substrate")]
|
|
|
|
|
mod substrate {
|
2025-02-26 07:24:58 -05:00
|
|
|
use core::fmt::Debug;
|
2025-02-26 06:54:42 -05:00
|
|
|
use alloc::vec;
|
2025-02-26 05:05:35 -05:00
|
|
|
|
|
|
|
|
use scale::{Encode, Decode};
|
|
|
|
|
use sp_runtime::{
|
|
|
|
|
transaction_validity::*,
|
|
|
|
|
traits::{Verify, ExtrinsicLike, Dispatchable, ValidateUnsigned, Checkable, Applyable},
|
|
|
|
|
Weight,
|
|
|
|
|
};
|
|
|
|
|
#[rustfmt::skip]
|
|
|
|
|
use frame_support::dispatch::{DispatchClass, Pays, DispatchInfo, GetDispatchInfo, PostDispatchInfo};
|
|
|
|
|
|
2025-02-12 03:41:50 -05:00
|
|
|
use super::*;
|
|
|
|
|
|
2025-02-26 05:19:04 -05:00
|
|
|
impl Encode for Transaction {
|
2025-02-12 03:41:50 -05:00
|
|
|
fn encode(&self) -> Vec<u8> {
|
|
|
|
|
borsh::to_vec(self).unwrap()
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-02-26 05:19:04 -05:00
|
|
|
impl Decode for Transaction {
|
2025-02-12 03:41:50 -05:00
|
|
|
fn decode<I: scale::Input>(input: &mut I) -> Result<Self, scale::Error> {
|
2025-02-26 05:05:35 -05:00
|
|
|
struct ScaleRead<'a, I: scale::Input>(&'a mut I, Option<scale::Error>);
|
2025-02-12 03:41:50 -05:00
|
|
|
impl<I: scale::Input> borsh::io::Read for ScaleRead<'_, I> {
|
|
|
|
|
fn read(&mut self, buf: &mut [u8]) -> borsh::io::Result<usize> {
|
2025-02-26 05:05:35 -05:00
|
|
|
let remaining_len = self.0.remaining_len().map_err(|err| {
|
|
|
|
|
self.1 = Some(err);
|
|
|
|
|
borsh::io::Error::new(borsh::io::ErrorKind::Other, "")
|
|
|
|
|
})?;
|
2025-02-12 03:41:50 -05:00
|
|
|
// If we're still calling `read`, we try to read at least one more byte
|
|
|
|
|
let to_read = buf.len().min(remaining_len.unwrap_or(1));
|
2025-02-26 05:05:35 -05:00
|
|
|
self.0.read(&mut buf[.. to_read]).map_err(|err| {
|
|
|
|
|
self.1 = Some(err);
|
|
|
|
|
borsh::io::Error::new(borsh::io::ErrorKind::Other, "")
|
|
|
|
|
})?;
|
2025-02-12 03:41:50 -05:00
|
|
|
Ok(to_read)
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-02-26 05:05:35 -05:00
|
|
|
let mut input = ScaleRead(input, None);
|
|
|
|
|
match Self::deserialize_reader(&mut input) {
|
|
|
|
|
Ok(res) => Ok(res),
|
|
|
|
|
Err(_) => Err(input.1.unwrap()),
|
|
|
|
|
}
|
2025-02-12 03:41:50 -05:00
|
|
|
}
|
|
|
|
|
}
|
2025-02-26 05:05:35 -05:00
|
|
|
|
|
|
|
|
/// The context which transactions are executed in.
|
2025-02-26 05:19:04 -05:00
|
|
|
pub trait TransactionContext: 'static + Send + Sync + Clone + PartialEq + Eq + Debug {
|
2025-02-26 05:05:35 -05:00
|
|
|
/// The base weight for a signed transaction.
|
|
|
|
|
const SIGNED_WEIGHT: Weight;
|
|
|
|
|
|
2025-02-26 05:19:04 -05:00
|
|
|
/// The call type for the runtime.
|
|
|
|
|
type RuntimeCall: From<Call>
|
|
|
|
|
+ GetDispatchInfo
|
|
|
|
|
+ Dispatchable<
|
|
|
|
|
RuntimeOrigin: From<Option<SeraiAddress>>,
|
|
|
|
|
Info = DispatchInfo,
|
|
|
|
|
PostInfo = PostDispatchInfo,
|
|
|
|
|
>;
|
|
|
|
|
|
2025-02-26 05:05:35 -05:00
|
|
|
/// The implicit context to verify transactions with.
|
|
|
|
|
fn implicit_context() -> &'static ImplicitContext;
|
|
|
|
|
|
|
|
|
|
/// If a block is present in the blockchain.
|
|
|
|
|
fn block_is_present_in_blockchain(&self, hash: &BlockHash) -> bool;
|
|
|
|
|
/// The time embedded into the current block.
|
|
|
|
|
///
|
|
|
|
|
/// Returns `None` if the time has yet to be set.
|
|
|
|
|
fn current_time(&self) -> Option<u64>;
|
|
|
|
|
/// The next nonce for an account.
|
|
|
|
|
fn next_nonce(&self, signer: &SeraiAddress) -> u32;
|
2025-02-26 07:24:58 -05:00
|
|
|
/// If the signer can pay the SRI fee.
|
|
|
|
|
fn can_pay_fee(
|
|
|
|
|
&self,
|
|
|
|
|
signer: &SeraiAddress,
|
|
|
|
|
fee: Amount,
|
|
|
|
|
) -> Result<(), TransactionValidityError>;
|
2025-02-26 05:05:35 -05:00
|
|
|
/// Have the transaction pay its SRI fee.
|
|
|
|
|
fn pay_fee(&self, signer: &SeraiAddress, fee: Amount) -> Result<(), TransactionValidityError>;
|
2025-02-12 03:41:50 -05:00
|
|
|
}
|
2025-02-26 05:05:35 -05:00
|
|
|
|
|
|
|
|
/// A transaction with the context necessary to evaluate it within Substrate.
|
|
|
|
|
#[derive(Clone, PartialEq, Eq, Debug, Encode, Decode)]
|
2025-02-26 05:19:04 -05:00
|
|
|
pub struct TransactionWithContext<Context: TransactionContext>(
|
|
|
|
|
Transaction,
|
2025-02-26 07:24:58 -05:00
|
|
|
#[codec(skip)] Context,
|
2025-02-26 05:19:04 -05:00
|
|
|
);
|
2025-02-26 05:05:35 -05:00
|
|
|
|
2025-02-26 05:19:04 -05:00
|
|
|
impl ExtrinsicLike for Transaction {
|
2025-02-12 03:41:50 -05:00
|
|
|
fn is_signed(&self) -> Option<bool> {
|
2025-02-26 06:54:42 -05:00
|
|
|
Some(matches!(self, Transaction::Signed { .. }))
|
2025-02-12 03:41:50 -05:00
|
|
|
}
|
2025-02-26 05:05:35 -05:00
|
|
|
fn is_bare(&self) -> bool {
|
2025-02-26 06:54:42 -05:00
|
|
|
matches!(self, Transaction::Unsigned { .. })
|
2025-02-12 03:41:50 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-26 05:19:04 -05:00
|
|
|
impl<Context: TransactionContext> GetDispatchInfo for TransactionWithContext<Context> {
|
2025-02-26 05:05:35 -05:00
|
|
|
fn get_dispatch_info(&self) -> DispatchInfo {
|
2025-02-26 06:54:42 -05:00
|
|
|
match &self.0 {
|
|
|
|
|
Transaction::Unsigned { call } => DispatchInfo {
|
|
|
|
|
call_weight: Context::RuntimeCall::from(call.0.clone()).get_dispatch_info().call_weight,
|
|
|
|
|
extension_weight: Weight::zero(),
|
|
|
|
|
class: DispatchClass::Operational,
|
|
|
|
|
pays_fee: Pays::No,
|
|
|
|
|
},
|
|
|
|
|
Transaction::Signed { calls, .. } => DispatchInfo {
|
|
|
|
|
call_weight: calls
|
|
|
|
|
.0
|
|
|
|
|
.iter()
|
|
|
|
|
.cloned()
|
|
|
|
|
.map(|call| Context::RuntimeCall::from(call).get_dispatch_info().call_weight)
|
|
|
|
|
.fold(Weight::zero(), |accum, item| accum + item),
|
|
|
|
|
extension_weight: Context::SIGNED_WEIGHT,
|
|
|
|
|
class: DispatchClass::Normal,
|
|
|
|
|
pays_fee: Pays::Yes,
|
|
|
|
|
},
|
2025-02-26 05:05:35 -05:00
|
|
|
}
|
2025-02-12 03:41:50 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-26 05:19:04 -05:00
|
|
|
impl<Context: TransactionContext> Checkable<Context> for Transaction {
|
|
|
|
|
type Checked = TransactionWithContext<Context>;
|
2025-02-26 05:05:35 -05:00
|
|
|
|
|
|
|
|
fn check(self, context: &Context) -> Result<Self::Checked, TransactionValidityError> {
|
2025-02-26 06:54:42 -05:00
|
|
|
match &self {
|
|
|
|
|
Transaction::Unsigned { .. } => {}
|
|
|
|
|
Transaction::Signed {
|
|
|
|
|
calls,
|
|
|
|
|
contextualized_signature: ContextualizedSignature { explicit_context, signature },
|
|
|
|
|
} => {
|
|
|
|
|
if !sp_core::sr25519::Signature::from(*signature).verify(
|
|
|
|
|
Transaction::signature_message(calls, Context::implicit_context(), explicit_context)
|
|
|
|
|
.as_slice(),
|
|
|
|
|
&sp_core::sr25519::Public::from(explicit_context.signer),
|
|
|
|
|
) {
|
2025-02-26 07:24:58 -05:00
|
|
|
Err(InvalidTransaction::BadProof)?;
|
2025-02-26 06:54:42 -05:00
|
|
|
}
|
2025-02-26 05:05:35 -05:00
|
|
|
}
|
|
|
|
|
}
|
2025-02-12 03:41:50 -05:00
|
|
|
|
2025-02-26 07:24:58 -05:00
|
|
|
Ok(TransactionWithContext(self, context.clone()))
|
2025-02-26 05:05:35 -05:00
|
|
|
}
|
2025-02-12 03:41:50 -05:00
|
|
|
|
2025-02-26 05:05:35 -05:00
|
|
|
#[cfg(feature = "try-runtime")]
|
|
|
|
|
fn unchecked_into_checked_i_know_what_i_am_doing(
|
|
|
|
|
self,
|
|
|
|
|
c: &Context,
|
|
|
|
|
) -> Result<Self::Checked, TransactionValidityError> {
|
|
|
|
|
// This satisfies the API, not necessarily the intent, yet this fn is only intended to be used
|
|
|
|
|
// within tests. Accordingly, it's fine to be stricter than necessarily
|
|
|
|
|
self.check(c)
|
2025-02-12 03:41:50 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-26 07:24:58 -05:00
|
|
|
impl<Context: TransactionContext> TransactionWithContext<Context> {
|
|
|
|
|
fn validate_except_fee<V: ValidateUnsigned<Call = Context::RuntimeCall>>(
|
2025-02-26 05:05:35 -05:00
|
|
|
&self,
|
2025-02-26 07:24:58 -05:00
|
|
|
source: TransactionSource,
|
|
|
|
|
mempool_priority_if_signed: u64,
|
|
|
|
|
) -> TransactionValidity {
|
2025-02-26 06:54:42 -05:00
|
|
|
match &self.0 {
|
|
|
|
|
Transaction::Unsigned { call } => {
|
|
|
|
|
let ValidTransaction { priority: _, requires, provides, longevity: _, propagate: _ } =
|
|
|
|
|
V::validate_unsigned(source, &Context::RuntimeCall::from(call.0.clone()))?;
|
|
|
|
|
Ok(ValidTransaction {
|
|
|
|
|
// We should always try to include unsigned transactions prior to signed
|
|
|
|
|
priority: u64::MAX,
|
|
|
|
|
requires,
|
|
|
|
|
provides,
|
|
|
|
|
// This is valid until included
|
|
|
|
|
longevity: u64::MAX,
|
|
|
|
|
// Ensure this is propagated
|
|
|
|
|
propagate: true,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
Transaction::Signed { calls: _, contextualized_signature } => {
|
2025-02-26 07:24:58 -05:00
|
|
|
let ExplicitContext { historic_block, include_by, signer, nonce, fee: _ } =
|
|
|
|
|
&contextualized_signature.explicit_context;
|
|
|
|
|
if !self.1.block_is_present_in_blockchain(historic_block) {
|
|
|
|
|
// We don't know if this is a block from a fundamentally distinct blockchain or a
|
|
|
|
|
// continuation of this blockchain we have yet to sync (which would be `Future`)
|
|
|
|
|
Err(TransactionValidityError::Unknown(UnknownTransaction::CannotLookup))?;
|
|
|
|
|
}
|
|
|
|
|
if let Some(include_by) = *include_by {
|
|
|
|
|
if let Some(current_time) = self.1.current_time() {
|
|
|
|
|
if current_time >= u64::from(include_by) {
|
|
|
|
|
// Since this transaction has a time bound which has passed, error
|
|
|
|
|
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale))?;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// Since this transaction has a time bound, yet we don't know the time, error
|
|
|
|
|
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale))?;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
match self.1.next_nonce(signer).cmp(nonce) {
|
|
|
|
|
core::cmp::Ordering::Less => {
|
|
|
|
|
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale))?
|
|
|
|
|
}
|
|
|
|
|
core::cmp::Ordering::Equal => {}
|
|
|
|
|
core::cmp::Ordering::Greater => {
|
|
|
|
|
Err(TransactionValidityError::Invalid(InvalidTransaction::Future))?
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let requires = if let Some(prior_nonce) = nonce.checked_sub(1) {
|
|
|
|
|
vec![borsh::to_vec(&(signer, prior_nonce)).unwrap()]
|
2025-02-26 06:54:42 -05:00
|
|
|
} else {
|
|
|
|
|
vec![]
|
|
|
|
|
};
|
2025-02-26 07:24:58 -05:00
|
|
|
let provides = vec![borsh::to_vec(&(signer, nonce)).unwrap()];
|
2025-02-26 06:54:42 -05:00
|
|
|
Ok(ValidTransaction {
|
2025-02-26 07:24:58 -05:00
|
|
|
priority: mempool_priority_if_signed,
|
2025-02-26 06:54:42 -05:00
|
|
|
requires,
|
|
|
|
|
provides,
|
|
|
|
|
// This revalidates the transaction every block. This is required due to this being
|
|
|
|
|
// denominated in blocks, and our transaction expiration being denominated in seconds.
|
|
|
|
|
longevity: 1,
|
|
|
|
|
propagate: true,
|
|
|
|
|
})
|
|
|
|
|
}
|
2025-02-26 05:05:35 -05:00
|
|
|
}
|
|
|
|
|
}
|
2025-02-26 07:24:58 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<Context: TransactionContext> Applyable for TransactionWithContext<Context> {
|
|
|
|
|
type Call = Context::RuntimeCall;
|
|
|
|
|
|
|
|
|
|
fn validate<V: ValidateUnsigned<Call = Context::RuntimeCall>>(
|
|
|
|
|
&self,
|
|
|
|
|
source: TransactionSource,
|
|
|
|
|
info: &DispatchInfo,
|
|
|
|
|
_len: usize,
|
|
|
|
|
) -> TransactionValidity {
|
|
|
|
|
let mempool_priority_if_signed = match &self.0 {
|
|
|
|
|
Transaction::Unsigned { .. } => {
|
|
|
|
|
// Since this is the priority if signed, and this isn't signed, we return 0
|
|
|
|
|
0
|
|
|
|
|
}
|
|
|
|
|
Transaction::Signed {
|
|
|
|
|
calls: _,
|
|
|
|
|
contextualized_signature:
|
|
|
|
|
ContextualizedSignature { explicit_context: ExplicitContext { signer, fee, .. }, .. },
|
|
|
|
|
} => {
|
|
|
|
|
self.1.can_pay_fee(signer, *fee)?;
|
|
|
|
|
|
|
|
|
|
// Prioritize transactions by their fees
|
|
|
|
|
{
|
|
|
|
|
let fee = fee.0;
|
|
|
|
|
Weight::from_all(fee).checked_div_per_component(&info.call_weight).unwrap_or(0)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
self.validate_except_fee::<V>(source, mempool_priority_if_signed)
|
|
|
|
|
}
|
2025-02-12 03:41:50 -05:00
|
|
|
|
2025-02-26 05:19:04 -05:00
|
|
|
fn apply<V: ValidateUnsigned<Call = Context::RuntimeCall>>(
|
2025-02-26 06:54:42 -05:00
|
|
|
self,
|
2025-02-26 05:05:35 -05:00
|
|
|
_info: &DispatchInfo,
|
|
|
|
|
_len: usize,
|
|
|
|
|
) -> sp_runtime::ApplyExtrinsicResultWithInfo<PostDispatchInfo> {
|
2025-02-26 07:24:58 -05:00
|
|
|
// We use 0 for the mempool priority, as this is no longer in the mempool so it's irrelevant
|
|
|
|
|
self.validate_except_fee::<V>(TransactionSource::InBlock, 0)?;
|
|
|
|
|
|
2025-02-26 06:54:42 -05:00
|
|
|
match self.0 {
|
|
|
|
|
Transaction::Unsigned { call } => {
|
|
|
|
|
let call = Context::RuntimeCall::from(call.0);
|
|
|
|
|
V::pre_dispatch(&call)?;
|
|
|
|
|
match call.dispatch(None.into()) {
|
|
|
|
|
Ok(res) => Ok(Ok(res)),
|
|
|
|
|
// Unsigned transactions should only be included if valid in all regards
|
|
|
|
|
// This isn't actually a "mandatory" but the intent is the same
|
|
|
|
|
Err(_err) => Err(TransactionValidityError::Invalid(InvalidTransaction::BadMandatory)),
|
|
|
|
|
}
|
2025-02-12 03:41:50 -05:00
|
|
|
}
|
2025-02-26 07:24:58 -05:00
|
|
|
Transaction::Signed {
|
|
|
|
|
calls,
|
|
|
|
|
contextualized_signature:
|
|
|
|
|
ContextualizedSignature { explicit_context: ExplicitContext { signer, fee, .. }, .. },
|
|
|
|
|
} => {
|
|
|
|
|
// Start by paying the fee
|
|
|
|
|
self.1.pay_fee(&signer, fee)?;
|
|
|
|
|
|
2025-02-26 06:54:42 -05:00
|
|
|
Ok(frame_support::storage::transactional::with_storage_layer(|| {
|
|
|
|
|
for call in calls.0 {
|
|
|
|
|
let call = Context::RuntimeCall::from(call);
|
2025-02-26 07:24:58 -05:00
|
|
|
match call.dispatch(Some(signer).into()) {
|
2025-02-26 06:54:42 -05:00
|
|
|
Ok(_res) => {}
|
|
|
|
|
// Because this call errored, don't continue and revert all prior calls
|
|
|
|
|
Err(e) => Err(e)?,
|
|
|
|
|
}
|
2025-02-26 05:05:35 -05:00
|
|
|
}
|
2025-02-26 07:24:58 -05:00
|
|
|
// Since all calls succeeded, return Ok
|
2025-02-26 06:54:42 -05:00
|
|
|
Ok(PostDispatchInfo {
|
|
|
|
|
// `None` stands for the worst case, which is what we want
|
|
|
|
|
actual_weight: None,
|
|
|
|
|
// Signed transactions always pay their fee
|
|
|
|
|
// TODO: Do we want to handle this so we can not charge fees on removing genesis
|
|
|
|
|
// liquidity?
|
|
|
|
|
pays_fee: Pays::Yes,
|
|
|
|
|
})
|
|
|
|
|
}))
|
|
|
|
|
}
|
2025-02-26 05:05:35 -05:00
|
|
|
}
|
2025-02-12 03:41:50 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-02-26 05:05:35 -05:00
|
|
|
#[cfg(feature = "substrate")]
|
|
|
|
|
pub use substrate::*;
|