-
Notifications
You must be signed in to change notification settings - Fork 2.6k
Introduces account existence providers reference counting #7363
Changes from 1 commit
ebcc2fe
16724ae
8a15927
866e78b
51aec12
3609f9e
934fea6
cbe14af
82bb7f1
be9eb12
5823e35
4b49fd4
4134218
dc5a96e
24e41a3
44fb86e
4f214a6
a20c16e
0f02f26
84d94f4
e818dc7
567e1b5
dadb7f9
93d07b4
928381f
514bc5d
b6a7378
237bdb0
3149164
6bb2813
968ca3f
160838c
f15cfeb
702ca55
d34d7f2
1e23f19
58643c5
47c1cbe
c856136
66e9be1
269f36c
a075a5e
e5abf6b
dfc9bfe
fbcde4a
a3596de
2df412e
f0e1c6b
d76a08b
7d9d63a
9c03134
fd925e9
95a2255
f55da2f
16238a3
054516b
4aa1bb4
2f4a6db
17438be
282a391
d5248d1
9f1df71
4491e73
859abab
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -297,44 +297,72 @@ mod test_impl_filter_stack { | |
} | ||
} | ||
|
||
/// Error that can be returned by our impl of `StoredMap`. | ||
pub enum StoredMapError { | ||
/// Attempt to create map value when it is a consumer and there are no providers in place. | ||
NoProviders, | ||
/// Attempt to anull/remove value when it is the last provider and there is still at | ||
/// least one consumer left. | ||
ConsumerRemaining, | ||
} | ||
|
||
/// An abstraction of a value stored within storage, but possibly as part of a larger composite | ||
/// item. | ||
pub trait StoredMap<K, T> { | ||
pub trait StoredMap<K, T: Default> { | ||
/// Get the item, or its default if it doesn't yet exist; we make no distinction between the | ||
/// two. | ||
fn get(k: &K) -> T; | ||
/// Get whether the item takes up any storage. If this is `false`, then `get` will certainly | ||
/// return the `T::default()`. If `true`, then there is no implication for `get` (i.e. it | ||
/// may return any value, including the default). | ||
/// | ||
/// NOTE: This may still be `true`, even after `remove` is called. This is the case where | ||
/// a single storage entry is shared between multiple `StoredMap` items single, without | ||
/// additional logic to enforce it, deletion of any one them doesn't automatically imply | ||
/// deletion of them all. | ||
fn is_explicit(k: &K) -> bool; | ||
/// Mutate the item. | ||
fn mutate<R>(k: &K, f: impl FnOnce(&mut T) -> R) -> R; | ||
/// Mutate the item, removing or resetting to default value if it has been mutated to `None`. | ||
fn mutate_exists<R>(k: &K, f: impl FnOnce(&mut Option<T>) -> R) -> R; | ||
|
||
/// Maybe mutate the item only if an `Ok` value is returned from `f`. Do nothing if an `Err` is | ||
/// returned. It is removed or reset to default value if it has been mutated to `None` | ||
fn try_mutate_exists<R, E>(k: &K, f: impl FnOnce(&mut Option<T>) -> Result<R, E>) -> Result<R, E>; | ||
fn try_mutate_exists<R, E: From<StoredMapError>>( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe should add associated type There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No - need to be like this otherwise it cannot support this error assimilation API. |
||
k: &K, | ||
f: impl FnOnce(&mut Option<T>) -> Result<R, E>, | ||
) -> Result<R, E>; | ||
|
||
// Everything past here has a default implementation. | ||
|
||
/// Mutate the item. | ||
fn mutate<R>(k: &K, f: impl FnOnce(&mut T) -> R) -> Result<R, StoredMapError> { | ||
Self::mutate_exists(k, |maybe_account| match maybe_account { | ||
Some(ref mut account) => f(account), | ||
x @ None => { | ||
let mut account = Default::default(); | ||
let r = f(&mut account); | ||
*x = Some(account); | ||
r | ||
} | ||
}) | ||
} | ||
|
||
/// Mutate the item, removing or resetting to default value if it has been mutated to `None`. | ||
/// | ||
/// This is infallible as long as the value does not get destroyed. | ||
fn mutate_exists<R>( | ||
k: &K, | ||
f: impl FnOnce(&mut Option<T>) -> R, | ||
) -> Result<R, StoredMapError> { | ||
Self::try_mutate_exists(k, |x| -> Result<R, StoredMapError> { Ok(f(x)) }) | ||
} | ||
|
||
/// Set the item to something new. | ||
fn insert(k: &K, t: T) { Self::mutate(k, |i| *i = t); } | ||
fn insert(k: &K, t: T) -> Result<(), StoredMapError> { Self::mutate(k, |i| *i = t) } | ||
gavofyork marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
/// Remove the item or otherwise replace it with its default value; we don't care which. | ||
fn remove(k: &K); | ||
fn remove(k: &K) -> Result<(), StoredMapError> { Self::mutate_exists(k, |x| *x = None) } | ||
} | ||
|
||
/// A simple, generic one-parameter event notifier/handler. | ||
pub trait Happened<T> { | ||
gavofyork marked this conversation as resolved.
Show resolved
Hide resolved
|
||
/// The thing happened. | ||
fn happened(t: &T); | ||
} | ||
pub trait HandleLifetime<T> { | ||
/// An account was created. | ||
fn created(_t: &T) -> Result<(), StoredMapError> { Ok(()) } | ||
gavofyork marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
impl<T> Happened<T> for () { | ||
fn happened(_: &T) {} | ||
/// An account was killed. | ||
fn killed(_t: &T) -> Result<(), StoredMapError> { Ok(()) } | ||
} | ||
|
||
impl<T> HandleLifetime<T> for () {} | ||
|
||
/// A shim for placing around a storage item in order to use it as a `StoredValue`. Ideally this | ||
/// wouldn't be needed as `StorageValue`s should blanket implement `StoredValue`s, however this | ||
/// would break the ability to have custom impls of `StoredValue`. The other workaround is to | ||
|
@@ -346,68 +374,63 @@ impl<T> Happened<T> for () { | |
/// be the default value), or where the account is being removed or reset back to the default value | ||
/// where previously it did exist (though may have been in a default state). This works well with | ||
/// system module's `CallOnCreatedAccount` and `CallKillAccount`. | ||
pub struct StorageMapShim< | ||
S, | ||
Created, | ||
Removed, | ||
K, | ||
T | ||
>(sp_std::marker::PhantomData<(S, Created, Removed, K, T)>); | ||
pub struct StorageMapShim<S, L, K, T>(sp_std::marker::PhantomData<(S, L, K, T)>); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. sidenote: it is a bit confusing to me, like the Thus if someone want to use For instance pallet balances encourages to use A newcomer to the codebase could modify pallet balances thinking they do not have to care about the difference between inserting default and removing. And they would break At some point I would prefer to reduce the complexity and only keep the Small additional note: But anyway this is not an issue of this PR at all. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Switching There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Indeed, better to have it in another PR, this PR makes complete sense by itself |
||
impl< | ||
S: StorageMap<K, T, Query=T>, | ||
Created: Happened<K>, | ||
Removed: Happened<K>, | ||
L: HandleLifetime<K>, | ||
K: FullCodec, | ||
T: FullCodec, | ||
> StoredMap<K, T> for StorageMapShim<S, Created, Removed, K, T> { | ||
T: FullCodec + Default, | ||
> StoredMap<K, T> for StorageMapShim<S, L, K, T> { | ||
fn get(k: &K) -> T { S::get(k) } | ||
fn is_explicit(k: &K) -> bool { S::contains_key(k) } | ||
fn insert(k: &K, t: T) { | ||
let existed = S::contains_key(&k); | ||
S::insert(k, t); | ||
if !existed { | ||
Created::happened(k); | ||
fn insert(k: &K, t: T) -> Result<(), StoredMapError> { | ||
if !S::contains_key(&k) { | ||
gavofyork marked this conversation as resolved.
Show resolved
Hide resolved
|
||
L::created(k)?; | ||
} | ||
S::insert(k, t); | ||
Ok(()) | ||
} | ||
fn remove(k: &K) { | ||
let existed = S::contains_key(&k); | ||
S::remove(k); | ||
if existed { | ||
Removed::happened(&k); | ||
fn remove(k: &K) -> Result<(), StoredMapError> { | ||
if S::contains_key(&k) { | ||
L::killed(&k)?; | ||
S::remove(k); | ||
} | ||
Ok(()) | ||
} | ||
fn mutate<R>(k: &K, f: impl FnOnce(&mut T) -> R) -> R { | ||
let existed = S::contains_key(&k); | ||
let r = S::mutate(k, f); | ||
if !existed { | ||
Created::happened(k); | ||
fn mutate<R>(k: &K, f: impl FnOnce(&mut T) -> R) -> Result<R, StoredMapError> { | ||
if !S::contains_key(&k) { | ||
L::created(k)?; | ||
} | ||
r | ||
Ok(S::mutate(k, f)) | ||
} | ||
fn mutate_exists<R>(k: &K, f: impl FnOnce(&mut Option<T>) -> R) -> R { | ||
let (existed, exists, r) = S::mutate_exists(k, |maybe_value| { | ||
fn mutate_exists<R>(k: &K, f: impl FnOnce(&mut Option<T>) -> R) -> Result<R, StoredMapError> { | ||
S::try_mutate_exists(k, |maybe_value| { | ||
let existed = maybe_value.is_some(); | ||
let r = f(maybe_value); | ||
(existed, maybe_value.is_some(), r) | ||
}); | ||
if !existed && exists { | ||
Created::happened(k); | ||
} else if existed && !exists { | ||
Removed::happened(k); | ||
} | ||
r | ||
let exists = maybe_value.is_some(); | ||
|
||
if !existed && exists { | ||
L::created(k)?; | ||
} else if existed && !exists { | ||
L::killed(k)?; | ||
} | ||
Ok(r) | ||
}) | ||
} | ||
fn try_mutate_exists<R, E>(k: &K, f: impl FnOnce(&mut Option<T>) -> Result<R, E>) -> Result<R, E> { | ||
fn try_mutate_exists<R, E: From<StoredMapError>>( | ||
k: &K, | ||
f: impl FnOnce(&mut Option<T>) -> Result<R, E>, | ||
) -> Result<R, E> { | ||
S::try_mutate_exists(k, |maybe_value| { | ||
let existed = maybe_value.is_some(); | ||
f(maybe_value).map(|v| (existed, maybe_value.is_some(), v)) | ||
}).map(|(existed, exists, v)| { | ||
let r = f(maybe_value)?; | ||
let exists = maybe_value.is_some(); | ||
|
||
if !existed && exists { | ||
Created::happened(k); | ||
L::created(k).map_err(E::from)?; | ||
} else if existed && !exists { | ||
Removed::happened(k); | ||
L::killed(k).map_err(E::from)?; | ||
} | ||
v | ||
Ok(r) | ||
}) | ||
} | ||
} | ||
|
@@ -506,18 +529,6 @@ pub trait ContainsLengthBound { | |
fn max_len() -> usize; | ||
} | ||
|
||
/// Determiner to say whether a given account is unused. | ||
pub trait IsDeadAccount<AccountId> { | ||
/// Is the given account dead? | ||
fn is_dead_account(who: &AccountId) -> bool; | ||
} | ||
|
||
impl<AccountId> IsDeadAccount<AccountId> for () { | ||
fn is_dead_account(_who: &AccountId) -> bool { | ||
true | ||
} | ||
} | ||
|
||
/// Handler for when a new account has been created. | ||
#[impl_for_tuples(30)] | ||
pub trait OnNewAccount<AccountId> { | ||
|
@@ -1557,7 +1568,7 @@ pub mod schedule { | |
/// Reschedule a task. For one-off tasks, this dispatch is guaranteed to succeed | ||
/// only if it is executed *before* the currently scheduled block. For periodic tasks, | ||
/// this dispatch is guaranteed to succeed only before the *initial* execution; for | ||
/// others, use `reschedule_named`. | ||
/// others, use `reschedule_named`. | ||
/// | ||
/// Will return an error if the `address` is invalid. | ||
fn reschedule( | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it really necessary to have
T: Default
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Basically there must be a way to determine whether the value is set or not, since the account item can now exist without the data being set (via some external existential provider, unlike before). Two other options are to make it require an
IsProvided
trait or to store the data under anOption
. The latter would require an additional migration (😕). And I generally try to avoid introducing extra traits.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
afaik, change storage from non option type to option type does not require migration. Option wrapping is handled by the decl_storage macro.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1 that option should not change the underlying storage
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The option would not be for a storage item itself, but rather an inner component of a storage item's structure. Concretely, it would be the
data
field of theframe_system::AccountInfo
(i.e.T::AccountData
) which would need to become anOption
, which would require a migration.