Skip to content
This repository was archived by the owner on Nov 15, 2023. It is now read-only.

Commit 01ff4ce

Browse files
authored
Result<Option<>> rather than Option<Option<>> (#9119)
* Clearer API to code against.
1 parent 7dc2534 commit 01ff4ce

File tree

7 files changed

+121
-86
lines changed

7 files changed

+121
-86
lines changed

frame/election-provider-multi-phase/src/unsigned.rs

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,10 @@ use sp_npos_elections::{
2929
CompactSolution, ElectionResult, assignment_ratio_to_staked_normalized,
3030
assignment_staked_to_ratio_normalized, is_score_better, seq_phragmen,
3131
};
32-
use sp_runtime::{offchain::storage::StorageValueRef, traits::TrailingZeroInput, SaturatedConversion};
32+
use sp_runtime::{
33+
offchain::storage::{MutateStorageError, StorageValueRef},
34+
traits::TrailingZeroInput, SaturatedConversion
35+
};
3336
use sp_std::{cmp::Ordering, convert::TryFrom, vec::Vec};
3437

3538
/// Storage key used to store the last block number at which offchain worker ran.
@@ -98,9 +101,9 @@ fn save_solution<T: Config>(call: &Call<T>) -> Result<(), MinerError> {
98101
log!(debug, "saving a call to the offchain storage.");
99102
let storage = StorageValueRef::persistent(&OFFCHAIN_CACHED_CALL);
100103
match storage.mutate::<_, (), _>(|_| Ok(call.clone())) {
101-
Ok(Ok(_)) => Ok(()),
102-
Ok(Err(_)) => Err(MinerError::FailedToStoreSolution),
103-
Err(_) => {
104+
Ok(_) => Ok(()),
105+
Err(MutateStorageError::ConcurrentModification(_)) => Err(MinerError::FailedToStoreSolution),
106+
Err(MutateStorageError::ValueFunctionFailed(_)) => {
104107
// this branch should be unreachable according to the definition of
105108
// `StorageValueRef::mutate`: that function should only ever `Err` if the closure we
106109
// pass it returns an error. however, for safety in case the definition changes, we do
@@ -114,6 +117,7 @@ fn save_solution<T: Config>(call: &Call<T>) -> Result<(), MinerError> {
114117
fn restore_solution<T: Config>() -> Result<Call<T>, MinerError> {
115118
StorageValueRef::persistent(&OFFCHAIN_CACHED_CALL)
116119
.get()
120+
.ok()
117121
.flatten()
118122
.ok_or(MinerError::NoStoredSolution)
119123
}
@@ -135,12 +139,9 @@ fn clear_offchain_repeat_frequency() {
135139
}
136140

137141
/// `true` when OCW storage contains a solution
138-
///
139-
/// More precise than `restore_solution::<T>().is_ok()`; that invocation will return `false`
140-
/// if a solution exists but cannot be decoded, whereas this just checks whether an item is present.
141142
#[cfg(test)]
142143
fn ocw_solution_exists<T: Config>() -> bool {
143-
StorageValueRef::persistent(&OFFCHAIN_CACHED_CALL).get::<Call<T>>().is_some()
144+
matches!(StorageValueRef::persistent(&OFFCHAIN_CACHED_CALL).get::<Call<T>>(), Ok(Some(_)))
144145
}
145146

146147
impl<T: Config> Pallet<T> {
@@ -584,13 +585,13 @@ impl<T: Config> Pallet<T> {
584585
let last_block = StorageValueRef::persistent(&OFFCHAIN_LAST_BLOCK);
585586

586587
let mutate_stat = last_block.mutate::<_, &'static str, _>(
587-
|maybe_head: Option<Option<T::BlockNumber>>| {
588+
|maybe_head: Result<Option<T::BlockNumber>, _>| {
588589
match maybe_head {
589-
Some(Some(head)) if now < head => Err("fork."),
590-
Some(Some(head)) if now >= head && now <= head + threshold => {
590+
Ok(Some(head)) if now < head => Err("fork."),
591+
Ok(Some(head)) if now >= head && now <= head + threshold => {
591592
Err("recently executed.")
592593
}
593-
Some(Some(head)) if now > head + threshold => {
594+
Ok(Some(head)) if now > head + threshold => {
594595
// we can run again now. Write the new head.
595596
Ok(now)
596597
}
@@ -604,11 +605,12 @@ impl<T: Config> Pallet<T> {
604605

605606
match mutate_stat {
606607
// all good
607-
Ok(Ok(_)) => Ok(()),
608+
Ok(_) => Ok(()),
608609
// failed to write.
609-
Ok(Err(_)) => Err(MinerError::Lock("failed to write to offchain db.")),
610+
Err(MutateStorageError::ConcurrentModification(_)) =>
611+
Err(MinerError::Lock("failed to write to offchain db (concurrent modification).")),
610612
// fork etc.
611-
Err(why) => Err(MinerError::Lock(why)),
613+
Err(MutateStorageError::ValueFunctionFailed(why)) => Err(MinerError::Lock(why)),
612614
}
613615
}
614616

@@ -1117,15 +1119,15 @@ mod tests {
11171119
assert!(MultiPhase::current_phase().is_unsigned());
11181120

11191121
// initially, the lock is not set.
1120-
assert!(guard.get::<bool>().is_none());
1122+
assert!(guard.get::<bool>().unwrap().is_none());
11211123

11221124
// a successful a-z execution.
11231125
MultiPhase::offchain_worker(25);
11241126
assert_eq!(pool.read().transactions.len(), 1);
11251127

11261128
// afterwards, the lock is not set either..
1127-
assert!(guard.get::<bool>().is_none());
1128-
assert_eq!(last_block.get::<BlockNumber>().unwrap().unwrap(), 25);
1129+
assert!(guard.get::<bool>().unwrap().is_none());
1130+
assert_eq!(last_block.get::<BlockNumber>().unwrap(), Some(25));
11291131
});
11301132
}
11311133

@@ -1280,7 +1282,7 @@ mod tests {
12801282
// this ensures that when the resubmit window rolls around, we're ready to regenerate
12811283
// from scratch if necessary
12821284
let mut call_cache = StorageValueRef::persistent(&OFFCHAIN_CACHED_CALL);
1283-
assert!(matches!(call_cache.get::<Call<Runtime>>(), Some(Some(_call))));
1285+
assert!(matches!(call_cache.get::<Call<Runtime>>(), Ok(Some(_call))));
12841286
call_cache.clear();
12851287

12861288
// attempts to resubmit the tx after the threshold has expired

frame/example-offchain-worker/src/lib.rs

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ use frame_support::traits::Get;
5353
use sp_core::crypto::KeyTypeId;
5454
use sp_runtime::{
5555
RuntimeDebug,
56-
offchain::{http, Duration, storage::StorageValueRef},
56+
offchain::{http, Duration, storage::{MutateStorageError, StorageRetrievalError, StorageValueRef}},
5757
traits::Zero,
5858
transaction_validity::{InvalidTransaction, ValidTransaction, TransactionValidity},
5959
};
@@ -366,15 +366,11 @@ impl<T: Config> Pallet<T> {
366366
// low-level method of local storage API, which means that only one worker
367367
// will be able to "acquire a lock" and send a transaction if multiple workers
368368
// happen to be executed concurrently.
369-
let res = val.mutate(|last_send: Option<Option<T::BlockNumber>>| {
370-
// We match on the value decoded from the storage. The first `Option`
371-
// indicates if the value was present in the storage at all,
372-
// the second (inner) `Option` indicates if the value was succesfuly
373-
// decoded to expected type (`T::BlockNumber` in our case).
369+
let res = val.mutate(|last_send: Result<Option<T::BlockNumber>, StorageRetrievalError>| {
374370
match last_send {
375371
// If we already have a value in storage and the block number is recent enough
376372
// we avoid sending another transaction at this time.
377-
Some(Some(block)) if block_number < block + T::GracePeriod::get() => {
373+
Ok(Some(block)) if block_number < block + T::GracePeriod::get() => {
378374
Err(RECENTLY_SENT)
379375
},
380376
// In every other case we attempt to acquire the lock and send a transaction.
@@ -390,7 +386,7 @@ impl<T: Config> Pallet<T> {
390386
// written to in the meantime.
391387
match res {
392388
// The value has been set correctly, which means we can safely send a transaction now.
393-
Ok(Ok(block_number)) => {
389+
Ok(block_number) => {
394390
// Depending if the block is even or odd we will send a `Signed` or `Unsigned`
395391
// transaction.
396392
// Note that this logic doesn't really guarantee that the transactions will be sent
@@ -406,13 +402,13 @@ impl<T: Config> Pallet<T> {
406402
else { TransactionType::Raw }
407403
},
408404
// We are in the grace period, we should not send a transaction this time.
409-
Err(RECENTLY_SENT) => TransactionType::None,
405+
Err(MutateStorageError::ValueFunctionFailed(RECENTLY_SENT)) => TransactionType::None,
410406
// We wanted to send a transaction, but failed to write the block number (acquire a
411407
// lock). This indicates that another offchain worker that was running concurrently
412408
// most likely executed the same logic and succeeded at writing to storage.
413409
// Thus we don't really want to send the transaction, knowing that the other run
414410
// already did.
415-
Ok(Err(_)) => TransactionType::None,
411+
Err(MutateStorageError::ConcurrentModification(_)) => TransactionType::None,
416412
}
417413
}
418414

frame/im-online/src/lib.rs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ use sp_core::offchain::OpaqueNetworkState;
8080
use sp_std::prelude::*;
8181
use sp_std::convert::TryInto;
8282
use sp_runtime::{
83-
offchain::storage::StorageValueRef,
83+
offchain::storage::{MutateStorageError, StorageRetrievalError, StorageValueRef},
8484
traits::{AtLeast32BitUnsigned, Convert, Saturating, TrailingZeroInput},
8585
Perbill, Permill, PerThing, RuntimeDebug, SaturatedConversion,
8686
};
@@ -719,14 +719,15 @@ impl<T: Config> Pallet<T> {
719719
key
720720
};
721721
let storage = StorageValueRef::persistent(&key);
722-
let res = storage.mutate(|status: Option<Option<HeartbeatStatus<T::BlockNumber>>>| {
722+
let res = storage.mutate(
723+
|status: Result<Option<HeartbeatStatus<T::BlockNumber>>, StorageRetrievalError>| {
723724
// Check if there is already a lock for that particular block.
724725
// This means that the heartbeat has already been sent, and we are just waiting
725726
// for it to be included. However if it doesn't get included for INCLUDE_THRESHOLD
726727
// we will re-send it.
727728
match status {
728729
// we are still waiting for inclusion.
729-
Some(Some(status)) if status.is_recent(session_index, now) => {
730+
Ok(Some(status)) if status.is_recent(session_index, now) => {
730731
Err(OffchainErr::WaitingForInclusion(status.sent_at))
731732
},
732733
// attempt to set new status
@@ -735,7 +736,10 @@ impl<T: Config> Pallet<T> {
735736
sent_at: now,
736737
}),
737738
}
738-
})?;
739+
});
740+
if let Err(MutateStorageError::ValueFunctionFailed(err)) = res {
741+
return Err(err);
742+
}
739743

740744
let mut new_status = res.map_err(|_| OffchainErr::FailedToAcquireLock)?;
741745

frame/session/src/historical/offchain.rs

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,10 @@
2525
//! This is used in conjunction with [`ProvingTrie`](super::ProvingTrie) and
2626
//! the off-chain indexing API.
2727
28-
use sp_runtime::{offchain::storage::StorageValueRef, KeyTypeId};
28+
use sp_runtime::{
29+
offchain::storage::{MutateStorageError, StorageRetrievalError, StorageValueRef},
30+
KeyTypeId
31+
};
2932
use sp_session::MembershipProof;
3033

3134
use super::super::{Pallet as SessionModule, SessionIndex};
@@ -49,6 +52,7 @@ impl<T: Config> ValidatorSet<T> {
4952
let derived_key = shared::derive_key(shared::PREFIX, session_index);
5053
StorageValueRef::persistent(derived_key.as_ref())
5154
.get::<Vec<(T::ValidatorId, T::FullIdentification)>>()
55+
.ok()
5256
.flatten()
5357
.map(|validator_set| Self { validator_set })
5458
}
@@ -100,19 +104,19 @@ pub fn prove_session_membership<T: Config, D: AsRef<[u8]>>(
100104
pub fn prune_older_than<T: Config>(first_to_keep: SessionIndex) {
101105
let derived_key = shared::LAST_PRUNE.to_vec();
102106
let entry = StorageValueRef::persistent(derived_key.as_ref());
103-
match entry.mutate(|current: Option<Option<SessionIndex>>| -> Result<_, ()> {
107+
match entry.mutate(|current: Result<Option<SessionIndex>, StorageRetrievalError>| -> Result<_, ()> {
104108
match current {
105-
Some(Some(current)) if current < first_to_keep => Ok(first_to_keep),
109+
Ok(Some(current)) if current < first_to_keep => Ok(first_to_keep),
106110
// do not move the cursor, if the new one would be behind ours
107-
Some(Some(current)) => Ok(current),
108-
None => Ok(first_to_keep),
111+
Ok(Some(current)) => Ok(current),
112+
Ok(None) => Ok(first_to_keep),
109113
// if the storage contains undecodable data, overwrite with current anyways
110114
// which might leak some entries being never purged, but that is acceptable
111115
// in this context
112-
Some(None) => Ok(first_to_keep),
116+
Err(_) => Ok(first_to_keep),
113117
}
114118
}) {
115-
Ok(Ok(new_value)) => {
119+
Ok(new_value) => {
116120
// on a re-org this is not necessarily true, with the above they might be equal
117121
if new_value < first_to_keep {
118122
for session_index in new_value..first_to_keep {
@@ -121,8 +125,8 @@ pub fn prune_older_than<T: Config>(first_to_keep: SessionIndex) {
121125
}
122126
}
123127
}
124-
Ok(Err(_)) => {} // failed to store the value calculated with the given closure
125-
Err(_) => {} // failed to calculate the value to store with the given closure
128+
Err(MutateStorageError::ConcurrentModification(_)) => {}
129+
Err(MutateStorageError::ValueFunctionFailed(_)) => {}
126130
}
127131
}
128132

primitives/runtime/src/offchain/storage.rs

Lines changed: 54 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,25 @@ pub struct StorageValueRef<'a> {
2828
kind: StorageKind,
2929
}
3030

31+
/// Reason for not being able to provide the stored value
32+
#[derive(Debug, PartialEq, Eq)]
33+
pub enum StorageRetrievalError {
34+
/// Value found but undecodable
35+
Undecodable,
36+
}
37+
38+
/// Possible errors when mutating a storage value.
39+
#[derive(Debug, PartialEq, Eq)]
40+
pub enum MutateStorageError<T, E> {
41+
/// The underlying db failed to update due to a concurrent modification.
42+
/// Contains the new value that was not stored.
43+
ConcurrentModification(T),
44+
/// The function given to us to create the value to be stored failed.
45+
/// May be used to signal that having looked at the existing value,
46+
/// they don't want to mutate it.
47+
ValueFunctionFailed(E)
48+
}
49+
3150
impl<'a> StorageValueRef<'a> {
3251
/// Create a new reference to a value in the persistent local storage.
3352
pub fn persistent(key: &'a [u8]) -> Self {
@@ -58,30 +77,40 @@ impl<'a> StorageValueRef<'a> {
5877
/// Retrieve & decode the value from storage.
5978
///
6079
/// Note that if you want to do some checks based on the value
61-
/// and write changes after that you should rather be using `mutate`.
80+
/// and write changes after that, you should rather be using `mutate`.
6281
///
63-
/// The function returns `None` if the value was not found in storage,
64-
/// otherwise a decoding of the value to requested type.
65-
pub fn get<T: codec::Decode>(&self) -> Option<Option<T>> {
82+
/// Returns the value if stored.
83+
/// Returns an error if the value could not be decoded.
84+
pub fn get<T: codec::Decode>(&self) -> Result<Option<T>, StorageRetrievalError> {
6685
sp_io::offchain::local_storage_get(self.kind, self.key)
67-
.map(|val| T::decode(&mut &*val).ok())
86+
.map(|val| T::decode(&mut &*val)
87+
.map_err(|_| StorageRetrievalError::Undecodable))
88+
.transpose()
6889
}
6990

70-
/// Retrieve & decode the value and set it to a new one atomically.
91+
/// Retrieve & decode the current value and set it to a new value atomically.
92+
///
93+
/// Function `mutate_val` takes as input the current value and should
94+
/// return a new value that is attempted to be written to storage.
7195
///
72-
/// Function `f` should return a new value that we should attempt to write to storage.
7396
/// This function returns:
74-
/// 1. `Ok(Ok(T))` in case the value has been successfully set.
75-
/// 2. `Ok(Err(T))` in case the value was calculated by the passed closure `f`,
76-
/// but it could not be stored.
77-
/// 3. `Err(_)` in case `f` returns an error.
78-
pub fn mutate<T, E, F>(&self, f: F) -> Result<Result<T, T>, E> where
97+
/// 1. `Ok(T)` in case the value has been successfully set.
98+
/// 2. `Err(MutateStorageError::ConcurrentModification(T))` in case the value was calculated
99+
/// by the passed closure `mutate_val`, but it could not be stored.
100+
/// 3. `Err(MutateStorageError::ValueFunctionFailed(_))` in case `mutate_val` returns an error.
101+
pub fn mutate<T, E, F>(&self, mutate_val: F) -> Result<T, MutateStorageError<T,E>> where
79102
T: codec::Codec,
80-
F: FnOnce(Option<Option<T>>) -> Result<T, E>
103+
F: FnOnce(Result<Option<T>, StorageRetrievalError>) -> Result<T, E>
81104
{
82105
let value = sp_io::offchain::local_storage_get(self.kind, self.key);
83-
let decoded = value.as_deref().map(|mut v| T::decode(&mut v).ok());
84-
let val = f(decoded)?;
106+
let decoded = value.as_deref()
107+
.map(|mut bytes| {
108+
T::decode(&mut bytes)
109+
.map_err(|_| StorageRetrievalError::Undecodable)
110+
}).transpose();
111+
112+
let val = mutate_val(decoded).map_err(|err| MutateStorageError::ValueFunctionFailed(err))?;
113+
85114
let set = val.using_encoded(|new_val| {
86115
sp_io::offchain::local_storage_compare_and_set(
87116
self.kind,
@@ -90,11 +119,10 @@ impl<'a> StorageValueRef<'a> {
90119
new_val,
91120
)
92121
});
93-
94122
if set {
95-
Ok(Ok(val))
123+
Ok(val)
96124
} else {
97-
Ok(Err(val))
125+
Err(MutateStorageError::ConcurrentModification(val))
98126
}
99127
}
100128
}
@@ -117,12 +145,12 @@ mod tests {
117145
t.execute_with(|| {
118146
let val = StorageValue::persistent(b"testval");
119147

120-
assert_eq!(val.get::<u32>(), None);
148+
assert_eq!(val.get::<u32>(), Ok(None));
121149

122150
val.set(&15_u32);
123151

124-
assert_eq!(val.get::<u32>(), Some(Some(15_u32)));
125-
assert_eq!(val.get::<Vec<u8>>(), Some(None));
152+
assert_eq!(val.get::<u32>(), Ok(Some(15_u32)));
153+
assert_eq!(val.get::<Vec<u8>>(), Err(StorageRetrievalError::Undecodable));
126154
assert_eq!(
127155
state.read().persistent_storage.get(b"testval"),
128156
Some(vec![15_u8, 0, 0, 0])
@@ -140,23 +168,23 @@ mod tests {
140168
let val = StorageValue::persistent(b"testval");
141169

142170
let result = val.mutate::<u32, (), _>(|val| {
143-
assert_eq!(val, None);
171+
assert_eq!(val, Ok(None));
144172

145173
Ok(16_u32)
146174
});
147-
assert_eq!(result, Ok(Ok(16_u32)));
148-
assert_eq!(val.get::<u32>(), Some(Some(16_u32)));
175+
assert_eq!(result, Ok(16_u32));
176+
assert_eq!(val.get::<u32>(), Ok(Some(16_u32)));
149177
assert_eq!(
150178
state.read().persistent_storage.get(b"testval"),
151179
Some(vec![16_u8, 0, 0, 0])
152180
);
153181

154182
// mutate again, but this time early-exit.
155183
let res = val.mutate::<u32, (), _>(|val| {
156-
assert_eq!(val, Some(Some(16_u32)));
184+
assert_eq!(val, Ok(Some(16_u32)));
157185
Err(())
158186
});
159-
assert_eq!(res, Err(()));
187+
assert_eq!(res, Err(MutateStorageError::ValueFunctionFailed(())));
160188
})
161189
}
162190
}

0 commit comments

Comments
 (0)