Skip to content

Commit

Permalink
passkey check weights (#2059)
Browse files Browse the repository at this point in the history
# Goal
The goal of this PR is <!-- insert goal here -->

Related to #2032 

# Discussion
- Checks the blocks limits
- The `post_dispatch` is not added to the extrinsic yet due to
constraint complications but it will be done a different PR related
ticket #2063

# Checklist
- [x] Chain spec updated
- [x] Tests added
- [x] Weights updated
  • Loading branch information
aramikm authored Jul 10, 2024
1 parent 551688f commit d2bef1b
Show file tree
Hide file tree
Showing 4 changed files with 180 additions and 17 deletions.
60 changes: 55 additions & 5 deletions pallets/passkey/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ pub use weights::*;

#[cfg(feature = "runtime-benchmarks")]
use frame_support::traits::tokens::fungible::Mutate;
use frame_system::CheckWeight;

#[cfg(feature = "runtime-benchmarks")]
mod benchmarking;
Expand All @@ -71,7 +72,9 @@ pub mod module {
pub const STORAGE_VERSION: StorageVersion = StorageVersion::new(0);

#[pallet::config]
pub trait Config: frame_system::Config + pallet_transaction_payment::Config {
pub trait Config:
frame_system::Config + pallet_transaction_payment::Config + Sync + Send
{
/// The overarching event type.
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;

Expand Down Expand Up @@ -137,7 +140,8 @@ pub mod module {
transaction_account_id.clone(),
));
let result = payload.passkey_call.call.dispatch(main_origin);
if result.is_ok() {
if let Ok(_inner) = result {
// all post-dispatch logic should be included in here
Self::deposit_event(Event::TransactionExecutionSuccess {
account_id: transaction_account_id,
});
Expand Down Expand Up @@ -170,10 +174,14 @@ pub mod module {
);
let tx_payment_validity = tx_charge.validate()?;

let weight_check = PasskeyWeightCheck::new(call.clone());
let weight_validity = weight_check.validate()?;

let valid_tx = valid_tx
.combine_with(signature_validity)
.combine_with(nonce_validity)
.combine_with(tx_payment_validity);
.combine_with(tx_payment_validity)
.combine_with(weight_validity);
Ok(valid_tx)
}

Expand All @@ -190,15 +198,19 @@ pub mod module {
payload.passkey_call.account_id.clone(),
call.clone(),
);
tx_charge.pre_dispatch()
tx_charge.pre_dispatch()?;

let weight_check = PasskeyWeightCheck::new(call.clone());
weight_check.pre_dispatch()
}
}
}

impl<T: Config> Pallet<T>
where
BalanceOf<T>: Send + Sync + From<u64>,
<T as frame_system::Config>::RuntimeCall: From<Call<T>> + Dispatchable<Info = DispatchInfo>,
<T as frame_system::Config>::RuntimeCall:
From<Call<T>> + Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>,
{
fn filter_valid_calls(call: &Call<T>) -> Result<PasskeyPayload<T>, TransactionValidityError> {
match call {
Expand Down Expand Up @@ -350,3 +362,41 @@ where
)
}
}

/// Block resource (weight) limit check.
#[derive(Encode, Decode, Clone, TypeInfo)]
#[scale_info(skip_type_params(T))]
pub struct PasskeyWeightCheck<T: Config>(pub Call<T>);

impl<T: Config> PasskeyWeightCheck<T>
where
<T as frame_system::Config>::RuntimeCall:
From<Call<T>> + Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>,
{
/// creating a new instance
pub fn new(call: Call<T>) -> Self {
Self(call)
}

/// Validate the transaction
pub fn validate(&self) -> TransactionValidity {
let len = self.0.encode().len();

CheckWeight::<T>::validate_unsigned(
&self.0.clone().into(),
&self.0.get_dispatch_info(),
len,
)
}

/// Pre-dispatch transaction checks
pub fn pre_dispatch(&self) -> Result<(), TransactionValidityError> {
let len = self.0.encode().len();

CheckWeight::<T>::pre_dispatch_unsigned(
&self.0.clone().into(),
&self.0.get_dispatch_info(),
len,
)
}
}
115 changes: 114 additions & 1 deletion pallets/passkey/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use super::*;
use crate::mock::Passkey;
use common_primitives::utils::wrap_binary_data;
use frame_support::{assert_err, assert_noop, assert_ok};
use frame_system::{Call as SystemCall, RawOrigin};
use frame_system::{limits::BlockLength, Call as SystemCall, RawOrigin};
use mock::*;

use crate::test_common::{constants::*, utilities::*};
Expand Down Expand Up @@ -670,6 +670,64 @@ fn validate_unsigned_with_correct_nonce_should_work() {
});
}

#[test]
fn validate_unsigned_with_exceeding_weights_should_fail() {
new_test_ext().execute_with(|| {
// arrange
let (test_account_1_key_pair, _) = sr25519::Pair::generate();
// Fund the account
assert_ok!(Balances::force_set_balance(
RawOrigin::Root.into(),
test_account_1_key_pair.public().into(),
10000000000
));
let who: <Test as frame_system::Config>::AccountId =
test_account_1_key_pair.public().into();
let mut account = frame_system::Account::<Test>::get(&who);
account.nonce += 1;
frame_system::Account::<Test>::insert(who.clone(), account);

let secret = p256::SecretKey::from_slice(&[
1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8,
])
.unwrap();
let passkey_public_key = get_p256_public_key(&secret).unwrap();
let wrapped_binary = wrap_binary_data(passkey_public_key.inner().to_vec());
let signature: MultiSignature = test_account_1_key_pair.sign(&wrapped_binary).into();
let client_data = base64_url::decode(REPLACED_CLIENT_DATA_JSON).unwrap();
let authenticator = base64_url::decode(AUTHENTICATOR_DATA).unwrap();
let block_length = BlockLength::default();
let max = block_length.max.get(DispatchClass::Normal);

let call: PasskeyCall<Test> = PasskeyCall {
account_id: test_account_1_key_pair.public().into(),
account_nonce: 2,
account_ownership_proof: signature,
call: Box::new(RuntimeCall::System(SystemCall::remark {
remark: vec![1u8; *max as usize],
})),
};
let passkey_signature =
passkey_sign(&secret, &call.encode(), &client_data, &authenticator).unwrap();
let payload = PasskeyPayload {
passkey_public_key,
verifiable_passkey_signature: VerifiablePasskeySignature {
signature: passkey_signature,
client_data_json: client_data.try_into().unwrap(),
authenticator_data: authenticator.try_into().unwrap(),
},
passkey_call: call,
};

// act
let v = Passkey::validate_unsigned(TransactionSource::InBlock, &Call::proxy { payload });

// assert
let err: TransactionValidityError = InvalidTransaction::ExhaustsResources.into();
assert_err!(v, err);
});
}

#[test]
fn pre_dispatch_unsigned_with_used_nonce_should_fail_with_stale() {
new_test_ext().execute_with(|| {
Expand Down Expand Up @@ -833,3 +891,58 @@ fn pre_dispatch_unsigned_should_increment_nonce_on_success() {
assert_eq!(account.nonce, <Test as frame_system::Config>::Nonce::one());
});
}

#[test]
fn pre_dispatch_with_exceeding_weight_should_fail() {
new_test_ext().execute_with(|| {
// arrange
let (test_account_1_key_pair, _) = sr25519::Pair::generate();
let account_1_pk: <Test as frame_system::Config>::AccountId =
test_account_1_key_pair.public().into();
// Fund
assert_ok!(Balances::force_set_balance(
RawOrigin::Root.into(),
account_1_pk.clone(),
10000000000
));

let secret = p256::SecretKey::from_slice(&[
1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8,
])
.unwrap();
let passkey_public_key = get_p256_public_key(&secret).unwrap();
let wrapped_binary = wrap_binary_data(passkey_public_key.inner().to_vec());
let signature: MultiSignature = test_account_1_key_pair.sign(&wrapped_binary).into();
let client_data = base64_url::decode(REPLACED_CLIENT_DATA_JSON).unwrap();
let authenticator = base64_url::decode(AUTHENTICATOR_DATA).unwrap();
let block_length = BlockLength::default();
let max = block_length.max.get(DispatchClass::Normal);

let call: PasskeyCall<Test> = PasskeyCall {
account_id: account_1_pk.clone(),
account_nonce: 0,
account_ownership_proof: signature,
call: Box::new(RuntimeCall::System(SystemCall::remark {
remark: vec![1u8; *max as usize],
})),
};
let passkey_signature =
passkey_sign(&secret, &call.encode(), &client_data, &authenticator).unwrap();
let payload = PasskeyPayload {
passkey_public_key,
verifiable_passkey_signature: VerifiablePasskeySignature {
signature: passkey_signature,
client_data_json: client_data.try_into().unwrap(),
authenticator_data: authenticator.try_into().unwrap(),
},
passkey_call: call,
};

// act
let v = Passkey::pre_dispatch(&Call::proxy { payload });

// assert
let err: TransactionValidityError = InvalidTransaction::ExhaustsResources.into();
assert_err!(v, err);
});
}
18 changes: 9 additions & 9 deletions pallets/passkey/src/weights.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
//! Autogenerated weights for `pallet_passkey`
//!
//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev
//! DATE: 2024-07-03, STEPS: `20`, REPEAT: `10`, LOW RANGE: `[]`, HIGH RANGE: `[]`
//! DATE: 2024-07-08, STEPS: `20`, REPEAT: `10`, LOW RANGE: `[]`, HIGH RANGE: `[]`
//! WORST CASE MAP SIZE: `1000000`
//! HOSTNAME: `ip-10-99-10-9.us-east-2.compute.internal`, CPU: `<UNKNOWN>`
//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("frequency-bench")`, DB CACHE: `1024`
Expand Down Expand Up @@ -48,8 +48,8 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
// Proof Size summary in bytes:
// Measured: `178`
// Estimated: `5078`
// Minimum execution time: 1_000_000_000 picoseconds.
Weight::from_parts(1_007_000_000, 5078)
// Minimum execution time: 1_002_000_000 picoseconds.
Weight::from_parts(1_011_000_000, 5078)
.saturating_add(T::DbWeight::get().reads(2_u64))
.saturating_add(T::DbWeight::get().writes(1_u64))
}
Expand All @@ -61,8 +61,8 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
// Proof Size summary in bytes:
// Measured: `178`
// Estimated: `5078`
// Minimum execution time: 1_000_000_000 picoseconds.
Weight::from_parts(1_013_000_000, 5078)
// Minimum execution time: 1_001_000_000 picoseconds.
Weight::from_parts(1_015_000_000, 5078)
.saturating_add(T::DbWeight::get().reads(2_u64))
.saturating_add(T::DbWeight::get().writes(1_u64))
}
Expand All @@ -78,8 +78,8 @@ impl WeightInfo for () {
// Proof Size summary in bytes:
// Measured: `178`
// Estimated: `5078`
// Minimum execution time: 1_000_000_000 picoseconds.
Weight::from_parts(1_007_000_000, 5078)
// Minimum execution time: 1_002_000_000 picoseconds.
Weight::from_parts(1_011_000_000, 5078)
.saturating_add(RocksDbWeight::get().reads(2_u64))
.saturating_add(RocksDbWeight::get().writes(1_u64))
}
Expand All @@ -91,8 +91,8 @@ impl WeightInfo for () {
// Proof Size summary in bytes:
// Measured: `178`
// Estimated: `5078`
// Minimum execution time: 1_000_000_000 picoseconds.
Weight::from_parts(1_013_000_000, 5078)
// Minimum execution time: 1_001_000_000 picoseconds.
Weight::from_parts(1_015_000_000, 5078)
.saturating_add(RocksDbWeight::get().reads(2_u64))
.saturating_add(RocksDbWeight::get().writes(1_u64))
}
Expand Down
4 changes: 2 additions & 2 deletions runtime/frequency/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -357,7 +357,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion {
spec_name: create_runtime_str!("frequency"),
impl_name: create_runtime_str!("frequency"),
authoring_version: 1,
spec_version: 91,
spec_version: 92,
impl_version: 0,
apis: RUNTIME_API_VERSIONS,
transaction_version: 1,
Expand All @@ -371,7 +371,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion {
spec_name: create_runtime_str!("frequency-testnet"),
impl_name: create_runtime_str!("frequency"),
authoring_version: 1,
spec_version: 91,
spec_version: 92,
impl_version: 0,
apis: RUNTIME_API_VERSIONS,
transaction_version: 1,
Expand Down

0 comments on commit d2bef1b

Please sign in to comment.