Remove RuntimeCall from Transaction

I believe this was originally here as we needed to return a reference, not an
owned instance, so this caching enabled returning a reference? Regardless, it
isn't valuable now.
This commit is contained in:
Luke Parker
2025-02-26 05:19:04 -05:00
parent 88c7ae3e7d
commit 820b710928
2 changed files with 42 additions and 73 deletions

View File

@@ -80,7 +80,6 @@ mod substrate {
}; };
use super::*; use super::*;
use crate::Call;
/// The digest for all of the Serai-specific header fields. /// The digest for all of the Serai-specific header fields.
#[derive(Clone, Copy, PartialEq, Eq, BorshSerialize, BorshDeserialize)] #[derive(Clone, Copy, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
@@ -165,14 +164,10 @@ mod substrate {
/// A block, as needed by Substrate. /// A block, as needed by Substrate.
#[derive(Clone, Debug, PartialEq, Eq, Encode, Decode, sp_runtime::Serialize)] #[derive(Clone, Debug, PartialEq, Eq, Encode, Decode, sp_runtime::Serialize)]
#[codec(encode_bound(skip_type_params(RuntimeCall)))] pub struct SubstrateBlock {
#[codec(decode_bound(skip_type_params(RuntimeCall)))]
pub struct SubstrateBlock<
RuntimeCall: 'static + Send + Sync + Clone + PartialEq + Eq + Debug + From<Call>,
> {
header: SubstrateHeader, header: SubstrateHeader,
#[serde(skip)] // This makes this unsafe to deserialize, but we don't impl `Deserialize` #[serde(skip)] // This makes this unsafe to deserialize, but we don't impl `Deserialize`
transactions: Vec<Transaction<RuntimeCall>>, transactions: Vec<Transaction>,
} }
impl HeaderTrait for SubstrateHeader { impl HeaderTrait for SubstrateHeader {
@@ -266,16 +261,12 @@ mod substrate {
} }
} }
impl<RuntimeCall: 'static + Send + Sync + Clone + PartialEq + Eq + Debug + From<Call>> impl HeaderProvider for SubstrateBlock {
HeaderProvider for SubstrateBlock<RuntimeCall>
{
type HeaderT = SubstrateHeader; type HeaderT = SubstrateHeader;
} }
impl<RuntimeCall: 'static + Send + Sync + Clone + PartialEq + Eq + Debug + From<Call>> BlockTrait impl BlockTrait for SubstrateBlock {
for SubstrateBlock<RuntimeCall> type Extrinsic = Transaction;
{
type Extrinsic = Transaction<RuntimeCall>;
type Header = SubstrateHeader; type Header = SubstrateHeader;
type Hash = H256; type Hash = H256;
fn header(&self) -> &Self::Header { fn header(&self) -> &Self::Header {

View File

@@ -104,20 +104,18 @@ pub struct ContextualizedSignature {
/// The Serai transaction type. /// The Serai transaction type.
#[derive(Clone, PartialEq, Eq, Debug)] #[derive(Clone, PartialEq, Eq, Debug)]
pub struct Transaction<RuntimeCall: 'static + From<Call> = Call> { pub struct Transaction {
/// The calls, as defined in Serai's ABI. /// The calls, as defined in Serai's ABI.
/// ///
/// These calls are executed atomically. Either all successfully execute or none do. The /// These calls are executed atomically. Either all successfully execute or none do. The
/// transaction's fee is paid regardless. /// transaction's fee is paid regardless.
// TODO: if this is unsigned, we only allow a single call. Should we serialize that as 0? // TODO: if this is unsigned, we only allow a single call. Should we serialize that as 0?
calls: BoundedVec<Call, ConstU32<{ MAX_CALLS }>>, calls: BoundedVec<Call, ConstU32<{ MAX_CALLS }>>,
/// The calls, as defined by Substrate.
runtime_calls: Vec<RuntimeCall>,
/// The signature, if present. /// The signature, if present.
contextualized_signature: Option<ContextualizedSignature>, contextualized_signature: Option<ContextualizedSignature>,
} }
impl<RuntimeCall: 'static + From<Call>> BorshSerialize for Transaction<RuntimeCall> { impl BorshSerialize for Transaction {
fn serialize<W: io::Write>(&self, writer: &mut W) -> io::Result<()> { fn serialize<W: io::Write>(&self, writer: &mut W) -> io::Result<()> {
// Write the calls // Write the calls
self.calls.serialize(writer)?; self.calls.serialize(writer)?;
@@ -129,16 +127,11 @@ impl<RuntimeCall: 'static + From<Call>> BorshSerialize for Transaction<RuntimeCa
} }
} }
impl<RuntimeCall: 'static + From<Call>> BorshDeserialize for Transaction<RuntimeCall> { impl BorshDeserialize for Transaction {
fn deserialize_reader<R: io::Read>(reader: &mut R) -> io::Result<Self> { fn deserialize_reader<R: io::Read>(reader: &mut R) -> io::Result<Self> {
// Read the calls // Read the calls
let calls = let calls =
serai_primitives::sp_borsh::borsh_deserialize_bounded_vec::<_, Call, MAX_CALLS>(reader)?; serai_primitives::sp_borsh::borsh_deserialize_bounded_vec::<_, Call, MAX_CALLS>(reader)?;
// Populate the runtime calls
let mut runtime_calls = Vec::with_capacity(calls.len());
for call in calls.iter().cloned() {
runtime_calls.push(RuntimeCall::from(call));
}
// Determine if this is signed or unsigned // Determine if this is signed or unsigned
let mut signed = None; let mut signed = None;
@@ -159,11 +152,11 @@ impl<RuntimeCall: 'static + From<Call>> BorshDeserialize for Transaction<Runtime
let contextualized_signature = let contextualized_signature =
if signed { Some(<ContextualizedSignature>::deserialize_reader(reader)?) } else { None }; if signed { Some(<ContextualizedSignature>::deserialize_reader(reader)?) } else { None };
Ok(Transaction { calls, runtime_calls, contextualized_signature }) Ok(Transaction { calls, contextualized_signature })
} }
} }
impl<RuntimeCall: 'static + From<Call>> Transaction<RuntimeCall> { impl Transaction {
/// The message to sign to produce a signature, for calls which may or may not be signed and are /// The message to sign to produce a signature, for calls which may or may not be signed and are
/// unchecked. /// unchecked.
fn signature_message_unchecked( fn signature_message_unchecked(
@@ -198,13 +191,8 @@ impl<RuntimeCall: 'static + From<Call>> Transaction<RuntimeCall> {
signature: Signature, signature: Signature,
) -> Self { ) -> Self {
let calls = calls.0; let calls = calls.0;
let mut runtime_calls = Vec::with_capacity(calls.len());
for call in calls.iter().cloned() {
runtime_calls.push(call.into());
}
Self { Self {
calls, calls,
runtime_calls,
contextualized_signature: Some(ContextualizedSignature { explicit_context, signature }), contextualized_signature: Some(ContextualizedSignature { explicit_context, signature }),
} }
} }
@@ -216,7 +204,6 @@ impl<RuntimeCall: 'static + From<Call>> Transaction<RuntimeCall> {
calls: vec![call.clone()] calls: vec![call.clone()]
.try_into() .try_into()
.expect("couldn't convert a length-1 Vec to a BoundedVec"), .expect("couldn't convert a length-1 Vec to a BoundedVec"),
runtime_calls: vec![call.into()],
contextualized_signature: None, contextualized_signature: None,
} }
} }
@@ -242,12 +229,12 @@ mod substrate {
use super::*; use super::*;
impl<RuntimeCall: 'static + From<Call>> Encode for Transaction<RuntimeCall> { impl Encode for Transaction {
fn encode(&self) -> Vec<u8> { fn encode(&self) -> Vec<u8> {
borsh::to_vec(self).unwrap() borsh::to_vec(self).unwrap()
} }
} }
impl<RuntimeCall: 'static + From<Call>> Decode for Transaction<RuntimeCall> { impl Decode for Transaction {
fn decode<I: scale::Input>(input: &mut I) -> Result<Self, scale::Error> { fn decode<I: scale::Input>(input: &mut I) -> Result<Self, scale::Error> {
struct ScaleRead<'a, I: scale::Input>(&'a mut I, Option<scale::Error>); struct ScaleRead<'a, I: scale::Input>(&'a mut I, Option<scale::Error>);
impl<I: scale::Input> borsh::io::Read for ScaleRead<'_, I> { impl<I: scale::Input> borsh::io::Read for ScaleRead<'_, I> {
@@ -274,12 +261,19 @@ mod substrate {
} }
/// The context which transactions are executed in. /// The context which transactions are executed in.
pub trait TransactionContext<RuntimeCall: 'static + From<Call>>: pub trait TransactionContext: 'static + Send + Sync + Clone + PartialEq + Eq + Debug {
'static + Send + Sync + Clone + PartialEq + Eq + Debug
{
/// The base weight for a signed transaction. /// The base weight for a signed transaction.
const SIGNED_WEIGHT: Weight; const SIGNED_WEIGHT: Weight;
/// The call type for the runtime.
type RuntimeCall: From<Call>
+ GetDispatchInfo
+ Dispatchable<
RuntimeOrigin: From<Option<SeraiAddress>>,
Info = DispatchInfo,
PostInfo = PostDispatchInfo,
>;
/// The implicit context to verify transactions with. /// The implicit context to verify transactions with.
fn implicit_context() -> &'static ImplicitContext; fn implicit_context() -> &'static ImplicitContext;
@@ -297,12 +291,12 @@ mod substrate {
/// A transaction with the context necessary to evaluate it within Substrate. /// A transaction with the context necessary to evaluate it within Substrate.
#[derive(Clone, PartialEq, Eq, Debug, Encode, Decode)] #[derive(Clone, PartialEq, Eq, Debug, Encode, Decode)]
pub struct TransactionWithContext< pub struct TransactionWithContext<Context: TransactionContext>(
RuntimeCall: 'static + From<Call>, Transaction,
Context: TransactionContext<RuntimeCall>, #[codec(skip)] PhantomData<Context>,
>(Transaction<RuntimeCall>, #[codec(skip)] PhantomData<Context>); );
impl<RuntimeCall: 'static + From<Call>> ExtrinsicLike for Transaction<RuntimeCall> { impl ExtrinsicLike for Transaction {
fn is_signed(&self) -> Option<bool> { fn is_signed(&self) -> Option<bool> {
Some(Transaction::is_signed(self)) Some(Transaction::is_signed(self))
} }
@@ -311,11 +305,7 @@ mod substrate {
} }
} }
impl< impl<Context: TransactionContext> GetDispatchInfo for TransactionWithContext<Context> {
RuntimeCall: 'static + From<Call> + GetDispatchInfo,
Context: TransactionContext<RuntimeCall>,
> GetDispatchInfo for TransactionWithContext<RuntimeCall, Context>
{
fn get_dispatch_info(&self) -> DispatchInfo { fn get_dispatch_info(&self) -> DispatchInfo {
let (extension_weight, class, pays_fee) = if Transaction::is_signed(&self.0) { let (extension_weight, class, pays_fee) = if Transaction::is_signed(&self.0) {
(Context::SIGNED_WEIGHT, DispatchClass::Normal, Pays::Yes) (Context::SIGNED_WEIGHT, DispatchClass::Normal, Pays::Yes)
@@ -328,7 +318,7 @@ mod substrate {
.calls .calls
.iter() .iter()
.cloned() .cloned()
.map(|call| RuntimeCall::from(call).get_dispatch_info().call_weight) .map(|call| Context::RuntimeCall::from(call).get_dispatch_info().call_weight)
.fold(Weight::zero(), |accum, item| accum + item), .fold(Weight::zero(), |accum, item| accum + item),
extension_weight, extension_weight,
class, class,
@@ -337,10 +327,8 @@ mod substrate {
} }
} }
impl<RuntimeCall: 'static + From<Call>, Context: TransactionContext<RuntimeCall>> impl<Context: TransactionContext> Checkable<Context> for Transaction {
Checkable<Context> for Transaction<RuntimeCall> type Checked = TransactionWithContext<Context>;
{
type Checked = TransactionWithContext<RuntimeCall, Context>;
fn check(self, context: &Context) -> Result<Self::Checked, TransactionValidityError> { fn check(self, context: &Context) -> Result<Self::Checked, TransactionValidityError> {
if let Some(ContextualizedSignature { explicit_context, signature }) = if let Some(ContextualizedSignature { explicit_context, signature }) =
@@ -373,7 +361,7 @@ mod substrate {
} }
} }
if !sp_core::sr25519::Signature::from(*signature).verify( if !sp_core::sr25519::Signature::from(*signature).verify(
Transaction::<RuntimeCall>::signature_message_unchecked( Transaction::signature_message_unchecked(
&self.calls, &self.calls,
Context::implicit_context(), Context::implicit_context(),
explicit_context, explicit_context,
@@ -400,22 +388,10 @@ mod substrate {
} }
} }
impl< impl<Context: TransactionContext> Applyable for TransactionWithContext<Context> {
RuntimeCall: 'static type Call = Context::RuntimeCall;
+ Send
+ Sync
+ From<Call>
+ Dispatchable<
RuntimeOrigin: From<Option<SeraiAddress>>,
Info = DispatchInfo,
PostInfo = PostDispatchInfo,
>,
Context: TransactionContext<RuntimeCall>,
> Applyable for TransactionWithContext<RuntimeCall, Context>
{
type Call = RuntimeCall;
fn validate<V: ValidateUnsigned<Call = RuntimeCall>>( fn validate<V: ValidateUnsigned<Call = Context::RuntimeCall>>(
&self, &self,
source: sp_runtime::transaction_validity::TransactionSource, source: sp_runtime::transaction_validity::TransactionSource,
info: &DispatchInfo, info: &DispatchInfo,
@@ -423,7 +399,7 @@ mod substrate {
) -> sp_runtime::transaction_validity::TransactionValidity { ) -> sp_runtime::transaction_validity::TransactionValidity {
if !self.0.is_signed() { if !self.0.is_signed() {
let ValidTransaction { priority: _, requires, provides, longevity: _, propagate: _ } = let ValidTransaction { priority: _, requires, provides, longevity: _, propagate: _ } =
V::validate_unsigned(source, &self.0.runtime_calls[0])?; V::validate_unsigned(source, &Context::RuntimeCall::from(self.0.calls[0].clone()))?;
Ok(ValidTransaction { Ok(ValidTransaction {
// We should always try to include unsigned transactions prior to signed // We should always try to include unsigned transactions prior to signed
priority: u64::MAX, priority: u64::MAX,
@@ -459,14 +435,15 @@ mod substrate {
} }
} }
fn apply<V: ValidateUnsigned<Call = RuntimeCall>>( fn apply<V: ValidateUnsigned<Call = Context::RuntimeCall>>(
mut self, mut self,
_info: &DispatchInfo, _info: &DispatchInfo,
_len: usize, _len: usize,
) -> sp_runtime::ApplyExtrinsicResultWithInfo<PostDispatchInfo> { ) -> sp_runtime::ApplyExtrinsicResultWithInfo<PostDispatchInfo> {
if !self.0.is_signed() { if !self.0.is_signed() {
V::pre_dispatch(&self.0.runtime_calls[0])?; let call = Context::RuntimeCall::from(self.0.calls.remove(0));
match self.0.runtime_calls.remove(0).dispatch(None.into()) { V::pre_dispatch(&call)?;
match call.dispatch(None.into()) {
Ok(res) => Ok(Ok(res)), Ok(res) => Ok(Ok(res)),
// Unsigned transactions should only be included if valid in all regards // Unsigned transactions should only be included if valid in all regards
// This isn't actually a "mandatory" but the intent is the same // This isn't actually a "mandatory" but the intent is the same
@@ -474,7 +451,8 @@ mod substrate {
} }
} else { } else {
Ok(frame_support::storage::transactional::with_storage_layer(|| { Ok(frame_support::storage::transactional::with_storage_layer(|| {
for call in self.0.runtime_calls { for call in self.0.calls {
let call = Context::RuntimeCall::from(call);
match call.dispatch( match call.dispatch(
Some(self.0.contextualized_signature.as_ref().unwrap().explicit_context.signer) Some(self.0.contextualized_signature.as_ref().unwrap().explicit_context.signer)
.into(), .into(),