Initial: Offchain Workers (#1942)

* Refactor state-machine stuff.

* Fix tests.

* WiP

* WiP2

* Service support for offchain workers.

* Service support for offchain workers.

* Testing offchain worker.

* Initial version working.

* Pass side effects in call.

* Pass OffchainExt in context.

* Submit extrinsics to the pool.

* Support inherents.

* Insert to inherents pool.

* Inserting to the pool asynchronously.

* Add test to offchain worker.

* Implement convenience syntax for modules.

* Dispatching offchain worker through executive.

* Fix offchain test.

* Remove offchain worker from timestamp.

* Update Cargo.lock.

* Address review comments.

* Use latest patch version for futures.

* Add CLI parameter for offchain worker.

* Fix compilation.

* Fix test.

* Fix extrinsics format for tests.

* Fix RPC test.

* Bump spec version.

* Fix executive.

* Fix support macro.

* Address grumbles.

* Bump runtime
This commit is contained in:
Tomasz Drwięga
2019-03-25 23:22:11 +01:00
committed by Gav Wood
parent 3ee0e69463
commit e2f5e40876
58 changed files with 1158 additions and 178 deletions
@@ -17,15 +17,15 @@
use super::api::BlockBuilder as BlockBuilderApi;
use std::vec::Vec;
use parity_codec::Encode;
use crate::blockchain::HeaderBackend;
use runtime_primitives::ApplyOutcome;
use runtime_primitives::generic::BlockId;
use runtime_primitives::traits::{
Header as HeaderT, Hash, Block as BlockT, One, HashFor, ProvideRuntimeApi, ApiRef
};
use primitives::H256;
use runtime_primitives::generic::BlockId;
use primitives::{H256, ExecutionContext};
use crate::blockchain::HeaderBackend;
use crate::runtime_api::Core;
use crate::error;
use runtime_primitives::{ApplyOutcome, ExecutionContext};
/// Utility for building new (valid) blocks from a stream of extrinsics.
+26 -7
View File
@@ -19,12 +19,12 @@ use parity_codec::{Encode, Decode};
use runtime_primitives::generic::BlockId;
use runtime_primitives::traits::Block as BlockT;
use state_machine::{
self, OverlayedChanges, Ext, CodeExecutor, ExecutionManager, ExecutionStrategy
self, OverlayedChanges, Ext, CodeExecutor, ExecutionManager, ExecutionStrategy, NeverOffchainExt,
};
use executor::{RuntimeVersion, RuntimeInfo, NativeVersion};
use hash_db::Hasher;
use trie::MemoryDB;
use primitives::{H256, Blake2Hasher, NativeOrEncoded, NeverNativeValue};
use primitives::{H256, Blake2Hasher, NativeOrEncoded, NeverNativeValue, OffchainExt};
use crate::backend;
use crate::error;
@@ -42,12 +42,15 @@ where
/// Execute a call to a contract on top of state in a block of given hash.
///
/// No changes are made.
fn call(
fn call<
O: OffchainExt,
>(
&self,
id: &BlockId<B>,
method: &str,
call_data: &[u8],
strategy: ExecutionStrategy,
side_effects_handler: Option<&mut O>,
) -> Result<Vec<u8>, error::Error>;
/// Execute a contextual call on top of state in a block of a given hash.
@@ -56,6 +59,7 @@ where
/// Before executing the method, passed header is installed as the current header
/// of the execution context.
fn contextual_call<
O: OffchainExt,
PB: Fn() -> error::Result<B::Header>,
EM: Fn(
Result<NativeOrEncoded<R>, Self::Error>,
@@ -73,6 +77,7 @@ where
prepare_environment_block: PB,
execution_manager: ExecutionManager<EM>,
native_call: Option<NC>,
side_effects_handler: Option<&mut O>,
) -> error::Result<NativeOrEncoded<R>> where ExecutionManager<EM>: Clone;
/// Extract RuntimeVersion of given block
@@ -84,6 +89,7 @@ where
///
/// No changes are made.
fn call_at_state<
O: OffchainExt,
S: state_machine::Backend<H>,
F: FnOnce(
Result<NativeOrEncoded<R>, Self::Error>,
@@ -98,6 +104,7 @@ where
call_data: &[u8],
manager: ExecutionManager<F>,
native_call: Option<NC>,
side_effects_handler: Option<&mut O>,
) -> Result<(NativeOrEncoded<R>, S::Transaction, Option<MemoryDB<H>>), error::Error>;
/// Execute a call to a contract on top of given state, gathering execution proof.
@@ -140,7 +147,10 @@ pub struct LocalCallExecutor<B, E> {
impl<B, E> LocalCallExecutor<B, E> {
/// Creates new instance of local call executor.
pub fn new(backend: Arc<B>, executor: E) -> Self {
LocalCallExecutor { backend, executor }
LocalCallExecutor {
backend,
executor,
}
}
}
@@ -161,17 +171,19 @@ where
{
type Error = E::Error;
fn call(&self,
fn call<O: OffchainExt>(&self,
id: &BlockId<Block>,
method: &str,
call_data: &[u8],
strategy: ExecutionStrategy
strategy: ExecutionStrategy,
side_effects_handler: Option<&mut O>,
) -> error::Result<Vec<u8>> {
let mut changes = OverlayedChanges::default();
let state = self.backend.state_at(*id)?;
let return_data = state_machine::new(
&state,
self.backend.changes_trie_storage(),
side_effects_handler,
&mut changes,
&self.executor,
method,
@@ -187,6 +199,7 @@ where
}
fn contextual_call<
O: OffchainExt,
PB: Fn() -> error::Result<Block::Header>,
EM: Fn(
Result<NativeOrEncoded<R>, Self::Error>,
@@ -204,6 +217,7 @@ where
prepare_environment_block: PB,
execution_manager: ExecutionManager<EM>,
native_call: Option<NC>,
mut side_effects_handler: Option<&mut O>,
) -> Result<NativeOrEncoded<R>, error::Error> where ExecutionManager<EM>: Clone {
let state = self.backend.state_at(*at)?;
if method != "Core_initialise_block" && initialised_block.map(|id| id != *at).unwrap_or(true) {
@@ -211,6 +225,7 @@ where
state_machine::new(
&state,
self.backend.changes_trie_storage(),
side_effects_handler.as_mut().map(|x| &mut **x),
changes,
&self.executor,
"Core_initialise_block",
@@ -226,6 +241,7 @@ where
let result = state_machine::new(
&state,
self.backend.changes_trie_storage(),
side_effects_handler,
changes,
&self.executor,
method,
@@ -248,12 +264,13 @@ where
fn runtime_version(&self, id: &BlockId<Block>) -> error::Result<RuntimeVersion> {
let mut overlay = OverlayedChanges::default();
let state = self.backend.state_at(*id)?;
let mut ext = Ext::new(&mut overlay, &state, self.backend.changes_trie_storage());
let mut ext = Ext::new(&mut overlay, &state, self.backend.changes_trie_storage(), NeverOffchainExt::new());
self.executor.runtime_version(&mut ext)
.ok_or(error::ErrorKind::VersionInvalid.into())
}
fn call_at_state<
O: OffchainExt,
S: state_machine::Backend<Blake2Hasher>,
F: FnOnce(
Result<NativeOrEncoded<R>, Self::Error>,
@@ -268,10 +285,12 @@ where
call_data: &[u8],
manager: ExecutionManager<F>,
native_call: Option<NC>,
side_effects_handler: Option<&mut O>,
) -> error::Result<(NativeOrEncoded<R>, S::Transaction, Option<MemoryDB<Blake2Hasher>>)> {
state_machine::new(
state,
self.backend.changes_trie_storage(),
side_effects_handler,
changes,
&self.executor,
method,
+21 -8
View File
@@ -33,9 +33,9 @@ use runtime_primitives::traits::{
Block as BlockT, Header as HeaderT, Zero, As, NumberFor, CurrentHeight, BlockNumberToHash,
ApiRef, ProvideRuntimeApi, Digest, DigestItem, AuthorityIdFor
};
use runtime_primitives::{BuildStorage, ExecutionContext};
use runtime_primitives::BuildStorage;
use crate::runtime_api::{CallRuntimeAt, ConstructRuntimeApi};
use primitives::{Blake2Hasher, H256, ChangesTrieConfiguration, convert_hash, NeverNativeValue};
use primitives::{Blake2Hasher, H256, ChangesTrieConfiguration, convert_hash, NeverNativeValue, ExecutionContext};
use primitives::storage::{StorageKey, StorageData};
use primitives::storage::well_known_keys;
use parity_codec::{Encode, Decode};
@@ -43,7 +43,7 @@ use state_machine::{
DBValue, Backend as StateBackend, CodeExecutor, ChangesTrieAnchorBlockId,
ExecutionStrategy, ExecutionManager, prove_read,
ChangesTrieRootsStorage, ChangesTrieStorage,
key_changes, key_changes_proof, OverlayedChanges,
key_changes, key_changes_proof, OverlayedChanges, NeverOffchainExt,
};
use hash_db::Hasher;
@@ -84,6 +84,8 @@ pub struct ExecutionStrategies {
pub importing: ExecutionStrategy,
/// Execution strategy used when constructing blocks.
pub block_construction: ExecutionStrategy,
/// Execution strategy used for offchain workers.
pub offchain_worker: ExecutionStrategy,
/// Execution strategy used in other cases.
pub other: ExecutionStrategy,
}
@@ -94,6 +96,7 @@ impl Default for ExecutionStrategies {
syncing: ExecutionStrategy::NativeElseWasm,
importing: ExecutionStrategy::NativeElseWasm,
block_construction: ExecutionStrategy::AlwaysWasm,
offchain_worker: ExecutionStrategy::NativeWhenPossible,
other: ExecutionStrategy::NativeElseWasm,
}
}
@@ -343,7 +346,7 @@ impl<B, E, Block, RA> Client<B, E, Block, RA> where
pub fn authorities_at(&self, id: &BlockId<Block>) -> error::Result<Vec<AuthorityIdFor<Block>>> {
match self.backend.blockchain().cache().and_then(|cache| cache.authorities_at(*id)) {
Some(cached_value) => Ok(cached_value),
None => self.executor.call(id, "Core_authorities", &[], ExecutionStrategy::NativeElseWasm)
None => self.executor.call(id, "Core_authorities", &[], ExecutionStrategy::NativeElseWasm, NeverOffchainExt::new())
.and_then(|r| Vec::<AuthorityIdFor<Block>>::decode(&mut &r[..])
.ok_or_else(|| error::ErrorKind::InvalidAuthoritiesSet.into()))
}
@@ -871,7 +874,7 @@ impl<B, E, Block, RA> Client<B, E, Block, RA> where
}),
}
};
let (_, storage_update, changes_update) = self.executor.call_at_state::<_, _, NeverNativeValue, fn() -> _>(
let (_, storage_update, changes_update) = self.executor.call_at_state::<_, _, _, NeverNativeValue, fn() -> _>(
transaction_state,
&mut overlay,
"Core_execute_block",
@@ -881,6 +884,7 @@ impl<B, E, Block, RA> Client<B, E, Block, RA> where
_ => get_execution_manager(self.execution_strategies().importing),
},
None,
NeverOffchainExt::new(),
)?;
overlay.commit_prospective();
@@ -1339,7 +1343,8 @@ impl<B, E, Block, RA> CallRuntimeAt<Block> for Client<B, E, Block, RA> where
Block: BlockT<Hash=H256>
{
fn call_api_at<
R: Encode + Decode + PartialEq, NC: FnOnce() -> result::Result<R, &'static str> + UnwindSafe
R: Encode + Decode + PartialEq,
NC: FnOnce() -> result::Result<R, &'static str> + UnwindSafe,
>(
&self,
at: &BlockId<Block>,
@@ -1348,15 +1353,22 @@ impl<B, E, Block, RA> CallRuntimeAt<Block> for Client<B, E, Block, RA> where
changes: &mut OverlayedChanges,
initialised_block: &mut Option<BlockId<Block>>,
native_call: Option<NC>,
context: ExecutionContext
context: ExecutionContext,
) -> error::Result<NativeOrEncoded<R>> {
let manager = match context {
ExecutionContext::BlockConstruction => self.execution_strategies.block_construction.get_manager(),
ExecutionContext::Syncing => self.execution_strategies.syncing.get_manager(),
ExecutionContext::Importing => self.execution_strategies.importing.get_manager(),
ExecutionContext::OffchainWorker(_) => self.execution_strategies.offchain_worker.get_manager(),
ExecutionContext::Other => self.execution_strategies.other.get_manager(),
};
self.executor.contextual_call::<_, fn(_,_) -> _,_,_>(
let mut offchain_extensions = match context {
ExecutionContext::OffchainWorker(ext) => Some(ext),
_ => None,
};
self.executor.contextual_call::<_, _, fn(_,_) -> _,_,_>(
at,
function,
&args,
@@ -1365,6 +1377,7 @@ impl<B, E, Block, RA> CallRuntimeAt<Block> for Client<B, E, Block, RA> where
|| self.prepare_environment_block(at),
manager,
native_call,
offchain_extensions.as_mut(),
)
}
+6
View File
@@ -90,6 +90,7 @@ mod tests {
state_machine::new(
backend,
Some(&InMemoryChangesTrieStorage::new()),
state_machine::NeverOffchainExt::new(),
&mut overlay,
&executor(),
"Core_initialise_block",
@@ -102,6 +103,7 @@ mod tests {
state_machine::new(
backend,
Some(&InMemoryChangesTrieStorage::new()),
state_machine::NeverOffchainExt::new(),
&mut overlay,
&executor(),
"BlockBuilder_apply_extrinsic",
@@ -114,6 +116,7 @@ mod tests {
let (ret_data, _, _) = state_machine::new(
backend,
Some(&InMemoryChangesTrieStorage::new()),
state_machine::NeverOffchainExt::new(),
&mut overlay,
&executor(),
"BlockBuilder_finalise_block",
@@ -160,6 +163,7 @@ mod tests {
let _ = state_machine::new(
&backend,
Some(&InMemoryChangesTrieStorage::new()),
state_machine::NeverOffchainExt::new(),
&mut overlay,
&executor(),
"Core_execute_block",
@@ -188,6 +192,7 @@ mod tests {
let _ = state_machine::new(
&backend,
Some(&InMemoryChangesTrieStorage::new()),
state_machine::NeverOffchainExt::new(),
&mut overlay,
&executor(),
"Core_execute_block",
@@ -216,6 +221,7 @@ mod tests {
let r = state_machine::new(
&backend,
Some(&InMemoryChangesTrieStorage::new()),
state_machine::NeverOffchainExt::new(),
&mut overlay,
&Executor::new(None),
"Core_execute_block",
@@ -21,11 +21,11 @@ use std::{collections::HashSet, sync::Arc, panic::UnwindSafe, result, marker::Ph
use futures::{IntoFuture, Future};
use parity_codec::{Encode, Decode};
use primitives::{H256, Blake2Hasher, convert_hash, NativeOrEncoded};
use primitives::{H256, Blake2Hasher, convert_hash, NativeOrEncoded, OffchainExt};
use runtime_primitives::generic::BlockId;
use runtime_primitives::traits::{As, Block as BlockT, Header as HeaderT};
use state_machine::{self, Backend as StateBackend, CodeExecutor, OverlayedChanges, ExecutionStrategy,
create_proof_check_backend, execution_proof_check_on_trie_backend, ExecutionManager};
create_proof_check_backend, execution_proof_check_on_trie_backend, ExecutionManager, NeverOffchainExt};
use hash_db::Hasher;
use crate::backend::RemoteBackend;
@@ -80,7 +80,16 @@ where
{
type Error = ClientError;
fn call(&self, id: &BlockId<Block>, method: &str, call_data: &[u8], _strategy: ExecutionStrategy)
fn call<
O: OffchainExt,
>(
&self,
id: &BlockId<Block>,
method: &str,
call_data: &[u8],
_strategy: ExecutionStrategy,
_side_effects_handler: Option<&mut O>,
)
-> ClientResult<Vec<u8>> {
let block_hash = self.blockchain.expect_block_hash_from_id(id)?;
let block_header = self.blockchain.expect_header(id.clone())?;
@@ -95,6 +104,7 @@ where
}
fn contextual_call<
O: OffchainExt,
PB: Fn() -> ClientResult<Block::Header>,
EM: Fn(
Result<NativeOrEncoded<R>, Self::Error>,
@@ -112,22 +122,24 @@ where
_prepare_environment_block: PB,
execution_manager: ExecutionManager<EM>,
_native_call: Option<NC>,
side_effects_handler: Option<&mut O>,
) -> ClientResult<NativeOrEncoded<R>> where ExecutionManager<EM>: Clone {
// it is only possible to execute contextual call if changes are empty
if !changes.is_empty() || initialised_block.is_some() {
return Err(ClientErrorKind::NotAvailableOnLightClient.into());
}
self.call(at, method, call_data, (&execution_manager).into()).map(NativeOrEncoded::Encoded)
self.call(at, method, call_data, (&execution_manager).into(), side_effects_handler).map(NativeOrEncoded::Encoded)
}
fn runtime_version(&self, id: &BlockId<Block>) -> ClientResult<RuntimeVersion> {
let call_result = self.call(id, "version", &[], ExecutionStrategy::NativeElseWasm)?;
let call_result = self.call(id, "version", &[], ExecutionStrategy::NativeElseWasm, NeverOffchainExt::new())?;
RuntimeVersion::decode(&mut call_result.as_slice())
.ok_or_else(|| ClientErrorKind::VersionInvalid.into())
}
fn call_at_state<
O: OffchainExt,
S: StateBackend<Blake2Hasher>,
FF: FnOnce(
Result<NativeOrEncoded<R>, Self::Error>,
@@ -142,6 +154,7 @@ where
_call_data: &[u8],
_m: ExecutionManager<FF>,
_native_call: Option<NC>,
_side_effects_handler: Option<&mut O>,
) -> ClientResult<(NativeOrEncoded<R>, S::Transaction, Option<MemoryDB<Blake2Hasher>>)> {
Err(ClientErrorKind::NotAvailableOnLightClient.into())
}
@@ -201,15 +214,24 @@ impl<Block, B, Remote, Local> CallExecutor<Block, Blake2Hasher> for
{
type Error = ClientError;
fn call(&self, id: &BlockId<Block>, method: &str, call_data: &[u8], strategy: ExecutionStrategy)
-> ClientResult<Vec<u8>> {
fn call<
O: OffchainExt,
>(
&self,
id: &BlockId<Block>,
method: &str,
call_data: &[u8],
strategy: ExecutionStrategy,
side_effects_handler: Option<&mut O>,
) -> ClientResult<Vec<u8>> {
match self.backend.is_local_state_available(id) {
true => self.local.call(id, method, call_data, strategy),
false => self.remote.call(id, method, call_data, strategy),
true => self.local.call(id, method, call_data, strategy, side_effects_handler),
false => self.remote.call(id, method, call_data, strategy, side_effects_handler),
}
}
fn contextual_call<
O: OffchainExt,
PB: Fn() -> ClientResult<Block::Header>,
EM: Fn(
Result<NativeOrEncoded<R>, Self::Error>,
@@ -227,12 +249,14 @@ impl<Block, B, Remote, Local> CallExecutor<Block, Blake2Hasher> for
prepare_environment_block: PB,
_manager: ExecutionManager<EM>,
native_call: Option<NC>,
side_effects_handler: Option<&mut O>,
) -> ClientResult<NativeOrEncoded<R>> where ExecutionManager<EM>: Clone {
// there's no actual way/need to specify native/wasm execution strategy on light node
// => we can safely ignore passed values
match self.backend.is_local_state_available(at) {
true => CallExecutor::contextual_call::<
_,
_,
fn(
Result<NativeOrEncoded<R>, Local::Error>,
@@ -250,8 +274,10 @@ impl<Block, B, Remote, Local> CallExecutor<Block, Blake2Hasher> for
prepare_environment_block,
ExecutionManager::NativeWhenPossible,
native_call,
side_effects_handler,
).map_err(|e| ClientErrorKind::Execution(Box::new(e.to_string())).into()),
false => CallExecutor::contextual_call::<
_,
_,
fn(
Result<NativeOrEncoded<R>, Remote::Error>,
@@ -269,6 +295,7 @@ impl<Block, B, Remote, Local> CallExecutor<Block, Blake2Hasher> for
prepare_environment_block,
ExecutionManager::NativeWhenPossible,
native_call,
side_effects_handler,
).map_err(|e| ClientErrorKind::Execution(Box::new(e.to_string())).into()),
}
}
@@ -281,6 +308,7 @@ impl<Block, B, Remote, Local> CallExecutor<Block, Blake2Hasher> for
}
fn call_at_state<
O: OffchainExt,
S: StateBackend<Blake2Hasher>,
FF: FnOnce(
Result<NativeOrEncoded<R>, Self::Error>,
@@ -295,11 +323,13 @@ impl<Block, B, Remote, Local> CallExecutor<Block, Blake2Hasher> for
call_data: &[u8],
_manager: ExecutionManager<FF>,
native_call: Option<NC>,
side_effects_handler: Option<&mut O>,
) -> ClientResult<(NativeOrEncoded<R>, S::Transaction, Option<MemoryDB<Blake2Hasher>>)> {
// there's no actual way/need to specify native/wasm execution strategy on light node
// => we can safely ignore passed values
CallExecutor::call_at_state::<
_,
_,
fn(
Result<NativeOrEncoded<R>, Remote::Error>,
@@ -315,6 +345,7 @@ impl<Block, B, Remote, Local> CallExecutor<Block, Blake2Hasher> for
call_data,
ExecutionManager::NativeWhenPossible,
native_call,
side_effects_handler,
).map_err(|e| ClientErrorKind::Execution(Box::new(e.to_string())).into())
}
@@ -509,7 +540,7 @@ mod tests {
let local_executor = RemoteCallExecutor::new(Arc::new(backend.blockchain().clone()), Arc::new(OkCallFetcher::new(vec![1])));
let remote_executor = RemoteCallExecutor::new(Arc::new(backend.blockchain().clone()), Arc::new(OkCallFetcher::new(vec![2])));
let remote_or_local = RemoteOrLocalCallExecutor::new(backend, remote_executor, local_executor);
assert_eq!(remote_or_local.call(&BlockId::Number(0), "test_method", &[], ExecutionStrategy::NativeElseWasm).unwrap(), vec![1]);
assert_eq!(remote_or_local.call(&BlockId::Number(1), "test_method", &[], ExecutionStrategy::NativeElseWasm).unwrap(), vec![2]);
assert_eq!(remote_or_local.call(&BlockId::Number(0), "test_method", &[], ExecutionStrategy::NativeElseWasm, NeverOffchainExt::new()).unwrap(), vec![1]);
assert_eq!(remote_or_local.call(&BlockId::Number(1), "test_method", &[], ExecutionStrategy::NativeElseWasm, NeverOffchainExt::new()).unwrap(), vec![2]);
}
}
+10 -4
View File
@@ -24,10 +24,12 @@ pub use state_machine::OverlayedChanges;
pub use primitives::NativeOrEncoded;
#[doc(hidden)]
pub use runtime_primitives::{
traits::{AuthorityIdFor, Block as BlockT, GetNodeBlockType, GetRuntimeBlockType, ApiRef, RuntimeApiInfo},
generic::BlockId, transaction_validity::TransactionValidity, ExecutionContext,
traits::{AuthorityIdFor, Block as BlockT, GetNodeBlockType, GetRuntimeBlockType, Header as HeaderT, ApiRef, RuntimeApiInfo},
generic::BlockId, transaction_validity::TransactionValidity,
};
#[doc(hidden)]
pub use primitives::{ExecutionContext, OffchainExt};
#[doc(hidden)]
pub use runtime_version::{ApiId, RuntimeVersion, ApisVec, create_apis_vec};
#[doc(hidden)]
pub use rstd::{slice, mem};
@@ -91,7 +93,10 @@ pub trait ApiExt<Block: BlockT> {
pub trait CallRuntimeAt<Block: BlockT> {
/// Calls the given api function with the given encoded arguments at the given block
/// and returns the encoded result.
fn call_api_at<R: Encode + Decode + PartialEq, NC: FnOnce() -> result::Result<R, &'static str> + UnwindSafe>(
fn call_api_at<
R: Encode + Decode + PartialEq,
NC: FnOnce() -> result::Result<R, &'static str> + UnwindSafe,
>(
&self,
at: &BlockId<Block>,
function: &'static str,
@@ -99,7 +104,7 @@ pub trait CallRuntimeAt<Block: BlockT> {
changes: &mut OverlayedChanges,
initialised_block: &mut Option<BlockId<Block>>,
native_call: Option<NC>,
context: ExecutionContext
context: ExecutionContext,
) -> error::Result<NativeOrEncoded<R>>;
/// Returns the runtime version at the given block.
@@ -132,3 +137,4 @@ decl_runtime_apis! {
fn validate_transaction(tx: <Block as BlockT>::Extrinsic) -> TransactionValidity;
}
}