Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extract the STF execution logic to a separate crate 'stf-executor' #485

Merged
merged 10 commits into from
Nov 4, 2021

Conversation

murerfel
Copy link
Contributor

@murerfel murerfel commented Nov 2, 2021

A further refactoring step towards #301 . This time I extracted code from the enclave-runtime that is related to executing logic on the STF. This includes:

  • Trusted calls
  • Trusted getters
  • General state updates
  • Block updates

The main motivation for separating this code to a separate crate, is that the sidechain components use it, and it's the reason why we still have quite a bit of sidechain implementation in the enclave-runtime instead of the sidechain crate.

The extracted code has not been much beautified or re-designed, it's mostly moved in its original state. I mention this, because there are certainly quite a few improvements that could be done on those parts of the code (that now reside in stf-executor). But in order to move along and get to the end-goal (#301), I leave it as-is for now.

@@ -16,6 +16,7 @@ members = [
"core-primitives/settings",
"core-primitives/sgx/crypto",
"core-primitives/sgx/io",
"core-primitives/stf-executor",
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

new crate in core-primitives: stf-executor

Comment on lines -18 to 28
//! Extrinsic helpers for author RPC module.

use crate::TrustedOperation;
use codec::{Decode, Encode};
use ita_stf::TrustedOperation;
use std::vec::Vec;

/// RPC Trusted call or hash
/// Trusted operation Or hash
///
/// Allows to refer to trusted calls either by its raw representation or its hash.
#[derive(Debug, Encode, Decode)]
#[derive(Clone, Debug, Encode, Decode)]
pub enum TrustedOperationOrHash<Hash> {
/// The hash of the call.
Hash(Hash),
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved this from the top-pool-rpc-author crate to the ita-stf crate, so it can be re-used in our new crate stf-executor.

Comment on lines +50 to +66
pub type StfResult<T> = Result<T, StfError>;

#[derive(Debug, Display, PartialEq, Eq)]
pub enum StfError {
#[display(fmt = "Insufficient privileges {:?}, are you sure you are root?", _0)]
MissingPrivileges(AccountId),
#[display(fmt = "Error dispatching runtime call. {:?}", _0)]
Dispatch(String),
#[display(fmt = "Not enough funds to perform operation")]
MissingFunds,
#[display(fmt = "Account does not exist {:?}", _0)]
InexistentAccount(AccountId),
#[display(fmt = "Invalid Nonce {:?}", _0)]
InvalidNonce(Index),
StorageHashMismatch,
InvalidStorageDiff,
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved the result and error type definition out of the sgx-only module, in order to have more parts of the code available in both std and sgx feature.

Comment on lines -60 to +76
pub fn get_state(ext: &mut State, getter: Getter) -> Option<Vec<u8>> {
pub fn get_state(ext: &mut impl SgxExternalitiesTrait, getter: Getter) -> Option<Vec<u8>> {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generalize this method signature, making it consistent with some of the other methods that already used this type of signature.

Comment on lines 63 to 79
TrustedGetter::free_balance(who) =>
TrustedGetter::free_balance(who) => {
Copy link
Contributor Author

@murerfel murerfel Nov 2, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like there was a change in the formatter after some update that happened on master? Now we have some formatting changes in this PR as a result. Either that, or this crate was never properly formatted to begin with (although shouldn't the formatter have complained?)

Comment on lines +20 to -26
pub extern crate alloc;

use alloc::vec::Vec;
use codec::{Decode, Encode};
use core::fmt::Debug;
use itp_types::{TrustedOperationStatus, WorkerRequest, WorkerResponse};
use its_primitives::traits::SignedBlock;
use sgx_types::*;
use sp_runtime::OpaqueExtrinsic;
use sp_std::prelude::Vec;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here I'm not sure: We have 3 ways to bring some of the alloc or std types into our sgx feature:

  • extern crate alloc + use alloc::vec::Vec
  • use std::vec::Vec
  • use sp_std::prelude::Vec (re-export in sp_std)

What do you guys think? Which option do you prefer and why?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it does not matter. extern crate alloc + use alloc::vec::Vec is the only one that would be available always. But as it is more verbose, I prefer this the least.

For crates that are close to supporting plain no_std, I would choose alloc still because:

  • sp_std: pulls in sp-io, which assumes wasm in no_std, implying that is does not support plain no_std. The library can be used no_std only in wasm (e.g. substrate-node-runtime`), or in sgx (where we patch sp-io, with sgx-sp-io)
  • sgx: can be used no_std only in an sgx-environment.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm against option three, because I don't think we should import substrate deps just to use vec::Vec.

Option 1 & 2.. I don't really care. You said that sgx ports Vec to alloc anyway, so it's only about style. Since we've been using std::vec::Vec quite often, maybe lets stick to that.

Comment on lines 61 to 67
/// STF Executor implementation
///
///
pub struct StfExecutor<OCallApi, StateHandler> {
ocall_api: Arc<OCallApi>,
state_handler: Arc<StateHandler>,
}
Copy link
Contributor Author

@murerfel murerfel Nov 2, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file now contains all the relevant implementation for executing calls, getters and operations in general on the STF. It's all moved from the enclave-runtime. It's probably a bit large and does not have any unit-tests 😞 . There are a couple of indirect tests of these parts, in the enclave-runtime.
Unfortunately this module is only available in sgx feature, because we heavily use Stf and all related structures.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So we should introduce unit tests for this part.. I guess? Maybe create an issue for this if its too much work for this PR?

Copy link
Contributor Author

@murerfel murerfel Nov 4, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having unit tests would certainly be a great improvement. And we're lacking them everywhere. So I'm not sure an issue 'create unit tests for this' will do much good. In my experience those kind of issues never get prioritized in a sprint 😝 Next time we touch this code and we extend or change the behavior, we need to introduce unit tests. So writing the tests should be part of the next feature that happens in this part.

Copy link
Contributor

@haerdib haerdib Nov 4, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You got me there.. can't reason against that :(

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well you could argue to still do them now 😄 I'm a bit on the fence about it myself, because I like to write unit tests. And on the other hand, I should make some progress on the actual feature (#301) I'm trying to achieve

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also agree. Maybe add a //FIXME comment. Not an issue but atleast something that someone will stumble upon when this code part is touched?

Comment on lines +84 to +91
fn execute_trusted_call_on_stf<PB, E>(
&self,
state: &mut E,
stf_call_signed: &TrustedCallSigned,
header: &PB::Header,
shard: &ShardIdentifier,
post_processing: StatePostProcessing,
) -> Result<ExecutedOperation>
Copy link
Contributor Author

@murerfel murerfel Nov 2, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A re-usable method to execute a single trusted call on the STF (not part of a trait)

Comment on lines 146 to 155
fn execute_trusted_call<PB>(
&self,
calls: &mut Vec<OpaqueCall>,
stf_call_signed: &TrustedCallSigned,
header: &PB::Header,
shard: &ShardIdentifier,
post_processing: StatePostProcessing,
) -> Result<Option<(H256, H256)>>
Copy link
Contributor Author

@murerfel murerfel Nov 2, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I quite dislike the fact that the result of this method is both in the returned value, and in the &mut Vec<OpaqueCall> parameter. In some parts, I replaced this out parameter by just a return value. But not here yet.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this has been bugging me for ages too.

Comment on lines 184 to 212
impl<OCallApi, StateHandler> StfExecuteShieldFunds for StfExecutor<OCallApi, StateHandler>
where
OCallApi: EnclaveAttestationOCallApi + EnclaveOnChainOCallApi + GetStorageVerified,
StateHandler: HandleState,
{
fn execute_shield_funds(
&self,
account: AccountId,
amount: Amount,
shard: &ShardIdentifier,
calls: &mut Vec<OpaqueCall>,
) -> Result<H256> {
let (state_lock, mut state) = self.state_handler.load_for_mutation(shard)?;

let root = Stf::get_root(&mut state);
let nonce = Stf::account_nonce(&mut state, &root);

let trusted_call = TrustedCallSigned::new(
TrustedCall::balance_shield(root, account, amount),
nonce,
Default::default(), //don't care about signature here
);

Stf::execute(&mut state, trusted_call, calls).map_err::<Error, _>(|e| e.into())?;

self.state_handler.write(state, state_lock, shard).map_err(|e| e.into())
}
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Execute shield funds logic on the Stf

Comment on lines 282 to 293
fn execute_timed_calls_batch<PB, F>(
&self,
trusted_calls: &[TrustedCallSigned],
header: &PB::Header,
shard: &ShardIdentifier,
max_exec_duration: Duration,
prepare_state_function: F,
) -> Result<ExecutionResult>
where
PB: BlockT<Hash = H256>,
F: FnOnce(Self::Externalities) -> Self::Externalities,
{
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Execute a batch of trusted calls with a given time budget and return an ExecutionResult which includes the previous state hash, the execution status, and more information.

Comment on lines 114 to 121
/// Result of an execution on the STF
///
/// Contains multiple executed calls
#[derive(Clone, Debug)]
pub struct ExecutionResult {
pub previous_state_hash: H256,
pub executed_operations: Vec<ExecutedOperation>,
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Utility structure for returning information about the execution of a collection of operations.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really like the abstractions here. 👍

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very cool. I really like this approach. However, is the ExecutionResult naming correct? Until now, for me an execution was the execution of one extrsinic. Maybe I'm in the wrong here, but how about BatchExecutionResult?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I re-named it to BatchExecutionResult 👍

Comment on lines -699 to +709
fn sync_blocks_on_light_client<PB, V, OCallApi, StateHandler>(
fn sync_blocks_on_light_client<PB, V, OCallApi, StfExecutor>(
blocks_to_sync: Vec<SignedBlockG<PB>>,
validator: &mut V,
on_chain_ocall_api: &OCallApi,
state_handler: &StateHandler,
stf_executor: &StfExecutor,
Copy link
Contributor Author

@murerfel murerfel Nov 2, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the enclave-runtime the StfExecutor now replaces the StateHandler in a lot of places (not everywhere) and thus hides the state handler details.

Comment on lines +908 to +917
|s| {
let mut sidechain_db = SidechainDB::<SB::Block, _>::new(s);
sidechain_db.set_block_number(&sidechain_db.get_block_number().map_or(1, |n| n + 1));
sidechain_db.set_timestamp(&now_as_u64());
sidechain_db.ext
},
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The logic for setting block number, timestamp etc is now packed into a state 'pre-processing' lambda.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think, this looks very nice.

@murerfel murerfel self-assigned this Nov 2, 2021
@murerfel murerfel force-pushed the feature/fm-trusted-call-handler branch from f55dcdd to 174fc87 Compare November 2, 2021 14:53
@murerfel murerfel requested review from clangenb and haerdib November 2, 2021 15:46
@murerfel murerfel marked this pull request as ready for review November 2, 2021 15:46
@murerfel murerfel mentioned this pull request Nov 3, 2021
1 task
Copy link
Contributor

@haerdib haerdib left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Complicated stuff.. left some comments.

Comment on lines +20 to -26
pub extern crate alloc;

use alloc::vec::Vec;
use codec::{Decode, Encode};
use core::fmt::Debug;
use itp_types::{TrustedOperationStatus, WorkerRequest, WorkerResponse};
use its_primitives::traits::SignedBlock;
use sgx_types::*;
use sp_runtime::OpaqueExtrinsic;
use sp_std::prelude::Vec;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm against option three, because I don't think we should import substrate deps just to use vec::Vec.

Option 1 & 2.. I don't really care. You said that sgx ports Vec to alloc anyway, so it's only about style. Since we've been using std::vec::Vec quite often, maybe lets stick to that.

#[error("Trusted operation has invalid signature")]
OperationHasInvalidSignature,
#[error("SGX error, status: {0}")]
SgxError(sgx_status_t),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not for this PR, but as a general Info in case you're introducing a new error type: Clippy will warn about using an Error ending in the future rust version: https://rust-lang.github.io/rust-clippy/master/index.html#enum_variant_names

So don't name them xxxxError

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you have a point here, the error enum element should not contain 'error' itself, I will fix that 👍

Comment on lines 61 to 67
/// STF Executor implementation
///
///
pub struct StfExecutor<OCallApi, StateHandler> {
ocall_api: Arc<OCallApi>,
state_handler: Arc<StateHandler>,
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So we should introduce unit tests for this part.. I guess? Maybe create an issue for this if its too much work for this PR?


use crate::{
error::{Error, Result},
traits::{
Copy link
Contributor

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 so many traits? Is there no other way to abstract the stf?

Copy link
Contributor Author

@murerfel murerfel Nov 3, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the design question of how many methods should a trait have. In other languages, recommendations for interfaces are in the range of 2-5 or 2-7. With traits, where we have trait mix-in (with +) that makes trait aggregation very easy, we can achieve very fine granularity by having one method per trait.
In our case here, most consumers don't ever need more than 2 of these methods. So grouping them together in a trait, will cause unnecessary exposure of methods. That was my reasoning behind this design. However, I think this can be open for debate, I'd be happy to hear your arguments 😄

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I second Felix. However, the real merit of this will not be obvious before we start having generic functions only requiring one of these 'subtraits'.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

However, by better core design, we'd might want to some of these traits, but I think this is a different construction site.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'd might want to do __ what with these traits? I think you lost the verb there in the sentence 😅

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

haha, thought faster than I typed. 😄 I meant 'remove'. However, not in terms of combining traits, but with a better core design. :P

Copy link
Contributor Author

@murerfel murerfel Nov 4, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah okay, now it makes sense 😝. Yes I totally agree, I think these stf executor traits can be reduced in number, because they are somewhat redundant and can be combined into more generalized trait methods. However, I also believe refactoring that requires a bit of effort, it's not that straight forward (and will require writing some tests in advance, to make sure we don't break stuff in the process).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My reasoning is just to keep it simple. The stf is made to be adaptable by clients such that they can easily integrate their stuff into the sgx-runtime. As long as we keep in mind the users who want to remove unshielding & shielding, or whatever, I'm fine with it. And it's not like I have a better solution here myself ;)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can certainly make it more simple. What I have here in this PR is just extracting code as-is into a crate. It is by no means the end-result as we would want to have it. There are still many improvements that can be done on this part, this was just a first step.

Comment on lines 114 to 121
/// Result of an execution on the STF
///
/// Contains multiple executed calls
#[derive(Clone, Debug)]
pub struct ExecutionResult {
pub previous_state_hash: H256,
pub executed_operations: Vec<ExecutedOperation>,
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very cool. I really like this approach. However, is the ExecutionResult naming correct? Until now, for me an execution was the execution of one extrsinic. Maybe I'm in the wrong here, but how about BatchExecutionResult?

.collect()
}

pub fn get_all_execution_hashes(&self) -> Vec<ExecutionHashes> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renaming suggestion: get_executed_operation_hashes

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can do that 👍

@@ -64,7 +65,7 @@ pub type MrEnclave = [u8; 32];
pub type BlockHash = H256;

pub type ConfirmCallFn = ([u8; 2], ShardIdentifier, H256, Vec<u8>);
pub type ShieldFundsFn = ([u8; 2], Vec<u8>, u128, ShardIdentifier);
pub type ShieldFundsFn = ([u8; 2], Vec<u8>, Amount, ShardIdentifier);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nice!


/// Hash results of an operation execution on the STF
#[derive(Clone, Debug)]
pub struct ExecutionHashes {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For me this naming was a little confusing. ExecutionHashes could also be the State hashes after execution. We could simply name it ExtrinsicHash to follow substrates example. Or OperationHash. ( I guess it should be singular, as it only contains one hash each? Also, why are both, call hashes and operation hashes needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes the naming is not ideal here. It's probably a combination of me not understanding exactly what it represents, and the thing having a confusing name before the refactoring.
I don't know why we have both a OperationHash and a CallHash - this is just ported over as it was before. I also don't exactly understand what the difference should be. ExecutionHashes should probably be replaced by a better name. Provided we still need OperationHash AND CallHash, what could the aggregation of those two be called?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think @haerdib introduced these (not sure), so she might correct my statements.

I have taken a look at the code. Deriving from the current usage of handle_trusted_worker_call, which is only at two places. The first time, we only care about the potential error, the second time, we are only interested in the trusted_operation_hash, which is put into a call_hash vector (!!!), I sense some code smell here. :)

I am confident, we can get rid of one. I actually think we should not use the trusted operations hash. Reasoning: this is an internal helper struct of the worker. The client sends either TrustedCalls or TrustedGetters AFAIK. If we want to give a block inclusion proof to a client in the future, it will be more straightforward if we hash the exact object the client has sent to the worker. The hash is not needed for anything else.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would simply use the following structure:

ExecutionResult {
     // hash of the executed trusted call.
    call_hash: H256
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd be happy to get rid of one of those hashes. And yes, we mix the names in the process, that adds to the confusion. We now use the operation_hash and use it in a call_hashes vector. And you suggest to only use the call_hash and get rid of the operation_hash? Won't that break something, because we're now returning a different hash value?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that's fine if everything is still working. But we might want to create an issue to discuss the use case of TrustedOperation itself @clangenb ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I think @mullefel change is fine.

I also agree to discuess the usage of trusted op.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fyi: I had to revert back to using the operation hash (not the call hash), since we have tests that check for that. So to be on the safe side, I'll use the operation hash for now to not alter any behavior in the code.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, even better. 👍

shard,
executed_operation.is_success(),
)
.unwrap();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this was here before, but I think we should not panic in case we can not remove a top. Should we?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We probably shouldn't. I'll change that to properly handle the result 👍

@murerfel
Copy link
Contributor Author

murerfel commented Nov 3, 2021

Unfortunately I cannot answer to your suggestion: #485 (comment) , so I'll do it here.
BatchExecutionResult might already be an improvement, I'll re-name it. Maybe at some point we can come up with better names for these structures (provided they are still around after more refactoring).

@murerfel murerfel force-pushed the feature/fm-trusted-call-handler branch from 174fc87 to 12743a4 Compare November 3, 2021 15:33
Copy link
Contributor

@clangenb clangenb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general, very impressive. I think you made a good compromise between simple modularization and refactoring.

Only some small questions.

Comment on lines +20 to -26
pub extern crate alloc;

use alloc::vec::Vec;
use codec::{Decode, Encode};
use core::fmt::Debug;
use itp_types::{TrustedOperationStatus, WorkerRequest, WorkerResponse};
use its_primitives::traits::SignedBlock;
use sgx_types::*;
use sp_runtime::OpaqueExtrinsic;
use sp_std::prelude::Vec;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it does not matter. extern crate alloc + use alloc::vec::Vec is the only one that would be available always. But as it is more verbose, I prefer this the least.

For crates that are close to supporting plain no_std, I would choose alloc still because:

  • sp_std: pulls in sp-io, which assumes wasm in no_std, implying that is does not support plain no_std. The library can be used no_std only in wasm (e.g. substrate-node-runtime`), or in sgx (where we patch sp-io, with sgx-sp-io)
  • sgx: can be used no_std only in an sgx-environment.

Comment on lines 146 to 155
fn execute_trusted_call<PB>(
&self,
calls: &mut Vec<OpaqueCall>,
stf_call_signed: &TrustedCallSigned,
header: &PB::Header,
shard: &ShardIdentifier,
post_processing: StatePostProcessing,
) -> Result<Option<(H256, H256)>>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this has been bugging me for ages too.

sidechain/top-pool-rpc-author/src/author.rs Show resolved Hide resolved
Comment on lines 114 to 121
/// Result of an execution on the STF
///
/// Contains multiple executed calls
#[derive(Clone, Debug)]
pub struct ExecutionResult {
pub previous_state_hash: H256,
pub executed_operations: Vec<ExecutedOperation>,
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really like the abstractions here. 👍

}
}

impl<OCallApi, StateHandler> StfExecuteGenericUpdate for StfExecutor<OCallApi, StateHandler>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this can be even more useful, if we add Externalities to the generic arguments and require the SgxExternalities trait bound here; what do you think?

This would require the that the externalities are part of the StfExecutor as a PhantomData field, but this is totally fine.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I can do that, it would further decouple us from the concrete SgxExternalities 👍

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made that change now. It required a bit of refactoring in the state handler as well, to make that trait generic over the externalities. And I also had to further generalize some of the method signature in the Stf struct. The explains the rather large change set that I had to commit on top of this PR 😅

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Haha, yes, but I think it was worth the effort. Thanks! Looks good.


use crate::{
error::{Error, Result},
traits::{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

However, by better core design, we'd might want to some of these traits, but I think this is a different construction site.


/// Hash results of an operation execution on the STF
#[derive(Clone, Debug)]
pub struct ExecutionHashes {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think @haerdib introduced these (not sure), so she might correct my statements.

I have taken a look at the code. Deriving from the current usage of handle_trusted_worker_call, which is only at two places. The first time, we only care about the potential error, the second time, we are only interested in the trusted_operation_hash, which is put into a call_hash vector (!!!), I sense some code smell here. :)

I am confident, we can get rid of one. I actually think we should not use the trusted operations hash. Reasoning: this is an internal helper struct of the worker. The client sends either TrustedCalls or TrustedGetters AFAIK. If we want to give a block inclusion proof to a client in the future, it will be more straightforward if we hash the exact object the client has sent to the worker. The hash is not needed for anything else.


/// Hash results of an operation execution on the STF
#[derive(Clone, Debug)]
pub struct ExecutionHashes {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would simply use the following structure:

ExecutionResult {
     // hash of the executed trusted call.
    call_hash: H256
}

Comment on lines +908 to +917
|s| {
let mut sidechain_db = SidechainDB::<SB::Block, _>::new(s);
sidechain_db.set_block_number(&sidechain_db.get_block_number().map_or(1, |n| n + 1));
sidechain_db.set_timestamp(&now_as_u64());
sidechain_db.ext
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think, this looks very nice.

[features]
default = ["std"]
std = [
"ita-stf/std",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This sucks big time, but I think we will have to live with this until we abstract TrustedCalls, TrustedOp, TrustedGetter into core-primitives.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have the ita-stf dependency everywhere because of it, yes. I've looked at it a couple of times, and I'm not sure there is a 'simple' solution, provided we don't break with @brenzi 's intention to keep all the domain specific stuff in that crate (and TrustedCall, TrustedGetter etc. contain exactly the domain specific logic)

Copy link
Contributor

@clangenb clangenb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks very good!

Copy link
Contributor

@haerdib haerdib left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very cool, thanks !

@murerfel murerfel merged commit 4378e63 into master Nov 4, 2021
@murerfel murerfel deleted the feature/fm-trusted-call-handler branch November 4, 2021 10:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants