Skip to content

Commit 66dd4cc

Browse files
committed
Inc provider ref count of authorized accounts
And decrement when their authorization is removed, so the account in frame_system gets cleaned up.
1 parent 1149763 commit 66dd4cc

File tree

4 files changed

+78
-27
lines changed

4 files changed

+78
-27
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pallets/transaction-storage/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ targets = ["x86_64-unknown-linux-gnu"]
1515
[dependencies]
1616
array-bytes = { version = "6.1", optional = true }
1717
codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false }
18+
log = "0.4.17"
1819
scale-info = { version = "2.5.0", default-features = false, features = ["derive"] }
1920
frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v1.0.0" }
2021
frame-support = { version = "4.0.0-dev", default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v1.0.0" }

pallets/transaction-storage/src/lib.rs

Lines changed: 67 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ use sp_transaction_storage_proof::{
4848
pub use pallet::*;
4949
pub use weights::WeightInfo;
5050

51+
const LOG_TARGET: &str = "runtime::transaction-storage";
52+
5153
/// Maximum bytes that can be stored in one transaction.
5254
// Setting higher limit also requires raising the allocator limit.
5355
pub const DEFAULT_MAX_TRANSACTION_SIZE: u32 = 8 * 1024 * 1024;
@@ -621,6 +623,35 @@ pub mod pallet {
621623
now >= expiration
622624
}
623625

626+
fn authorization_added(scope: &AuthorizationScopeFor<T>) {
627+
match scope {
628+
AuthorizationScope::Account(who) => {
629+
// Allow nonce storage for transaction replay protection
630+
frame_system::Pallet::<T>::inc_providers(who);
631+
},
632+
AuthorizationScope::Preimage(_) => (),
633+
}
634+
}
635+
636+
fn authorization_removed(scope: &AuthorizationScopeFor<T>) {
637+
match scope {
638+
AuthorizationScope::Account(who) => {
639+
// Cleanup nonce storage. Authorized accounts should be careful to use a short
640+
// enough lifetime for their store/renew transactions that they aren't at risk
641+
// of replay when the account is next authorized.
642+
if let Err(err) = frame_system::Pallet::<T>::dec_providers(who) {
643+
log::warn!(
644+
target: LOG_TARGET,
645+
"Failed to decrement provider reference count for authorized account {:?}, \
646+
leaking reference: {:?}",
647+
who, err
648+
);
649+
}
650+
},
651+
AuthorizationScope::Preimage(_) => (),
652+
}
653+
}
654+
624655
/// Authorize data storage.
625656
fn authorize(scope: AuthorizationScopeFor<T>, transactions: u32, bytes: u64) {
626657
let expiration = frame_system::Pallet::<T>::block_number()
@@ -629,34 +660,37 @@ pub mod pallet {
629660
Authorizations::<T>::mutate(&scope, |maybe_authorization| {
630661
if let Some(authorization) = maybe_authorization {
631662
if Self::expired(authorization.expiration) {
632-
*maybe_authorization = None;
633-
}
634-
}
635-
636-
if let Some(authorization) = maybe_authorization {
637-
// An unexpired authorization already exists. Extend it.
638-
match scope {
639-
AuthorizationScope::Account(_) => {
640-
// Add
641-
authorization.extent.transactions =
642-
authorization.extent.transactions.saturating_add(transactions);
643-
authorization.extent.bytes =
644-
authorization.extent.bytes.saturating_add(bytes);
645-
},
646-
AuthorizationScope::Preimage(_) => {
647-
// Max
648-
authorization.extent.transactions =
649-
authorization.extent.transactions.max(transactions);
650-
authorization.extent.bytes = authorization.extent.bytes.max(bytes);
651-
},
663+
// Previous authorization expired. Overwrite it.
664+
*authorization = Authorization {
665+
extent: AuthorizationExtent { transactions, bytes },
666+
expiration,
667+
};
668+
} else {
669+
// An unexpired authorization already exists. Extend it.
670+
match scope {
671+
AuthorizationScope::Account(_) => {
672+
// Add
673+
authorization.extent.transactions =
674+
authorization.extent.transactions.saturating_add(transactions);
675+
authorization.extent.bytes =
676+
authorization.extent.bytes.saturating_add(bytes);
677+
},
678+
AuthorizationScope::Preimage(_) => {
679+
// Max
680+
authorization.extent.transactions =
681+
authorization.extent.transactions.max(transactions);
682+
authorization.extent.bytes = authorization.extent.bytes.max(bytes);
683+
},
684+
}
685+
authorization.expiration = expiration;
652686
}
653-
authorization.expiration = expiration;
654687
} else {
655-
// No previous authorization, or it expired. Create a fresh one.
688+
// No previous authorization. Create a fresh one.
656689
*maybe_authorization = Some(Authorization {
657690
extent: AuthorizationExtent { transactions, bytes },
658691
expiration,
659692
});
693+
Self::authorization_added(&scope);
660694
}
661695
});
662696
}
@@ -669,6 +703,7 @@ pub mod pallet {
669703
return Err(Error::<T>::AuthorizationNotFound.into())
670704
};
671705
ensure!(Self::expired(authorization.expiration), Error::<T>::AuthorizationNotExpired);
706+
Self::authorization_removed(&scope);
672707
Ok(())
673708
}
674709

@@ -741,7 +776,8 @@ pub mod pallet {
741776
size: u32,
742777
consume: bool,
743778
) -> Result<(), TransactionValidityError> {
744-
let consume_authorization = |maybe_authorization: &mut Option<Authorization<_>>| -> Result<(), TransactionValidityError> {
779+
// Returns true if authorization was removed
780+
let consume_authorization = |maybe_authorization: &mut Option<Authorization<_>>| -> Result<bool, TransactionValidityError> {
745781
let Some(authorization) = maybe_authorization else {
746782
return Err(InvalidTransaction::Payment.into())
747783
};
@@ -764,21 +800,26 @@ pub mod pallet {
764800
// left.
765801
if transactions == 0 || bytes == 0 {
766802
*maybe_authorization = None;
803+
Ok(true)
767804
} else {
768805
authorization.extent.transactions = transactions;
769806
authorization.extent.bytes = bytes;
807+
Ok(false)
770808
}
771-
Ok(())
772809
};
773810

774811
if consume {
775-
Authorizations::<T>::mutate(&scope, consume_authorization)
812+
if Authorizations::<T>::mutate(&scope, consume_authorization)? {
813+
Self::authorization_removed(&scope);
814+
}
776815
} else {
777816
// Note we call consume_authorization on a temporary; the authorization in storage
778817
// is untouched and doesn't actually get consumed
779818
let mut authorization = Authorizations::<T>::get(&scope);
780-
consume_authorization(&mut authorization)
819+
consume_authorization(&mut authorization)?;
781820
}
821+
822+
Ok(())
782823
}
783824

784825
/// Check that authorization with the given scope exists in storage but has expired.

pallets/transaction-storage/src/tests.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ use super::{
2828
use frame_support::{assert_noop, assert_ok};
2929
use sp_core::blake2_256;
3030
use sp_runtime::{
31-
traits::{Dispatchable, ValidateUnsigned},
31+
traits::{Dispatchable, ValidateUnsigned, Zero},
3232
transaction_validity::InvalidTransaction,
3333
};
3434
use sp_transaction_storage_proof::registration::build_proof;
@@ -224,11 +224,13 @@ fn expired_authorization_clears() {
224224
new_test_ext().execute_with(|| {
225225
run_to_block(1, || None);
226226
let who = 1;
227+
assert!(System::providers(&who).is_zero());
227228
assert_ok!(TransactionStorage::authorize_account(RuntimeOrigin::root(), who, 2, 2000));
228229
assert_eq!(
229230
TransactionStorage::account_authorization_extent(who),
230231
AuthorizationExtent { transactions: 2, bytes: 2000 },
231232
);
233+
assert!(!System::providers(&who).is_zero());
232234

233235
// User uses some of the authorization, and the remaining amount gets updated appropriately
234236
run_to_block(2, || None);
@@ -251,6 +253,7 @@ fn expired_authorization_clears() {
251253
// User has sufficient storage authorization, but it has expired
252254
run_to_block(11, || None);
253255
assert!(Authorizations::contains_key(AuthorizationScope::Account(who)));
256+
assert!(!System::providers(&who).is_zero());
254257
// User cannot use authorization
255258
assert_noop!(
256259
TransactionStorage::pre_dispatch_signed(&who, &store_call),
@@ -264,6 +267,7 @@ fn expired_authorization_clears() {
264267
));
265268
// No longer in storage
266269
assert!(!Authorizations::contains_key(AuthorizationScope::Account(who)));
270+
assert!(System::providers(&who).is_zero());
267271
});
268272
}
269273

@@ -272,11 +276,13 @@ fn consumed_authorization_clears() {
272276
new_test_ext().execute_with(|| {
273277
run_to_block(1, || None);
274278
let who = 1;
279+
assert!(System::providers(&who).is_zero());
275280
assert_ok!(TransactionStorage::authorize_account(RuntimeOrigin::root(), who, 2, 2000));
276281
assert_eq!(
277282
TransactionStorage::account_authorization_extent(who),
278283
AuthorizationExtent { transactions: 2, bytes: 2000 },
279284
);
285+
assert!(!System::providers(&who).is_zero());
280286

281287
// User uses some of the authorization, and the remaining amount gets updated appropriately
282288
let call = Call::store { data: vec![0; 1000] };
@@ -286,9 +292,11 @@ fn consumed_authorization_clears() {
286292
TransactionStorage::account_authorization_extent(who),
287293
AuthorizationExtent { transactions: 1, bytes: 1000 },
288294
);
295+
assert!(!System::providers(&who).is_zero());
289296
// Consume the remaining amount
290297
assert_ok!(TransactionStorage::pre_dispatch_signed(&who, &call));
291298
// Key should be cleared from Authorizations
292299
assert!(!Authorizations::contains_key(AuthorizationScope::Account(who)));
300+
assert!(System::providers(&who).is_zero());
293301
});
294302
}

0 commit comments

Comments
 (0)