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

Add nonce validation to trusted calls #225

Merged
merged 33 commits into from
Apr 6, 2021
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
dd84146
add sgx nonce state update functions
haerdib Mar 10, 2021
227e90a
first steps towards rpc get nonce function
haerdib Mar 12, 2021
17b7cb8
change Index type to u64
haerdib Mar 12, 2021
079b54b
fix cli function and nonce type to Index
haerdib Mar 12, 2021
61117ab
simplify shard check
haerdib Mar 15, 2021
e0cddda
first compiling version with get_nonce()
haerdib Mar 15, 2021
7e967b2
rename direct call status to direct request status
haerdib Mar 15, 2021
e3dc456
replace hard coded nonce with get_nonce function
haerdib Mar 15, 2021
43f77fc
bug fix
haerdib Mar 15, 2021
deae382
add unittest
haerdib Mar 15, 2021
adb3a9b
add requires to ValidTransaction and enclave unit tests
haerdib Mar 19, 2021
5c89667
add ready tests
haerdib Mar 19, 2021
0a2d31f
add sensible validation_transaction in pool function
haerdib Mar 19, 2021
09eb3fc
add sensible transaction validation test
haerdib Mar 24, 2021
675d29e
fix pool unit tests
haerdib Mar 24, 2021
88f8284
unit test working
haerdib Mar 24, 2021
76103c9
fix unit tests
haerdib Mar 24, 2021
809adec
fix Index.. should be changed in sgx runtime to u64
haerdib Mar 24, 2021
c8a6bbb
working nonce validation and incrementation
haerdib Mar 25, 2021
2b0d79e
add invalid nonce unit test
haerdib Mar 25, 2021
5631f5c
decrease block building
haerdib Mar 25, 2021
87537c4
readd block.rs
haerdib Mar 29, 2021
bfe1974
rempve unnecessary encoding of account_data and account_nonce function
haerdib Mar 30, 2021
a87fb5e
change read_shard matching to simple unwrap()
haerdib Mar 30, 2021
4de488b
change harde coded max future tops in pool to constant
haerdib Mar 30, 2021
26f04cd
make validate_nonce better readable
haerdib Mar 30, 2021
2343398
add error handling to increment nonce
haerdib Mar 30, 2021
fa1d6f8
add FIXME to increment nonce and remove possible panic
haerdib Mar 31, 2021
f81e058
add FIXME: test feature for account_data function
haerdib Mar 31, 2021
4e6f2bc
move increment nonce outside of match statement
haerdib Mar 31, 2021
ce67544
Trusted getter for nonce (#232)
haerdib Mar 31, 2021
3bb6953
Merge branch 'sidechain' into add-nonce
haerdib Mar 31, 2021
7f33b2a
move validate nonce outside of match call statement
haerdib Apr 6, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 46 additions & 5 deletions client/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ use substrate_api_client::{
use substrate_client_keystore::LocalKeystore;
use substratee_stf::{ShardIdentifier, TrustedCallSigned, TrustedOperation};
use substratee_worker_api::direct_client::DirectApi as DirectWorkerApi;
use substratee_worker_primitives::{DirectCallStatus, RpcRequest, RpcResponse, RpcReturnValue};
use substratee_worker_primitives::{DirectRequestStatus, RpcRequest, RpcResponse, RpcReturnValue};

type AccountPublic = <Signature as Verify>::Signer;
const KEYSTORE_PATH: &str = "my_keystore";
Expand Down Expand Up @@ -418,7 +418,7 @@ fn main() {
Ok(())
}),
)
.add_cmd(substratee_stf::cli::cmd(&perform_trusted_operation))
.add_cmd(substratee_stf::cli::cmd(&perform_trusted_operation, &get_nonce_direct))
.no_cmd(|_args, _matches| {
println!("No subcommand matched");
Ok(())
Expand Down Expand Up @@ -449,6 +449,47 @@ fn perform_trusted_operation(matches: &ArgMatches<'_>, top: &TrustedOperation) -
}
}

/// Returns the next valid index (aka nonce) for given account.
fn get_nonce_direct(matches: &ArgMatches<'_>, account: &AccountId) -> Vec<u8> {
let (_encoded, account_encrypted) = match encode_encrypt(matches, account)
{
Ok((encoded, encrypted)) => (encoded, encrypted),
Err(msg) => {
panic!("[Error] {}", msg);
}
};
let shard = match read_shard(matches) {
Ok(shard) => shard,
Err(e) => panic!(e),
};

// compose jsonrpc call
let data = Request {
shard,
cyphertext: account_encrypted,
};
let rpc_method = "system_accountNextIndex".to_owned();
let jsonrpc_call: String = RpcRequest::compose_jsonrpc_call(rpc_method, data.encode());

let direct_api = get_worker_api_direct(matches);
let response_string = match direct_api.get(jsonrpc_call) {
Ok(string) => string,
Err(_) => panic!("Error sending direct invocation call"),
};
let response: RpcResponse = serde_json::from_str(&response_string).unwrap();
if let Ok(return_value) = RpcReturnValue::decode(&mut response.result.as_slice()) {
if return_value.status == DirectRequestStatus::Error {
panic!(
"[Error] {}",
String::decode(&mut return_value.value.as_slice()).unwrap()
);
}
return return_value.value
};
panic!("[Error] Could not decode server response");
}


fn get_state(matches: &ArgMatches<'_>, getter: TrustedOperation) -> Option<Vec<u8>> {
// TODO: ensure getter is signed?
let (_operation_call_encoded, operation_call_encrypted) = match encode_encrypt(matches, getter)
Expand Down Expand Up @@ -484,7 +525,7 @@ fn get_state(matches: &ArgMatches<'_>, getter: TrustedOperation) -> Option<Vec<u
Ok(response) => {
let response: RpcResponse = serde_json::from_str(&response).unwrap();
if let Ok(return_value) = RpcReturnValue::decode(&mut response.result.as_slice()) {
if return_value.status == DirectCallStatus::Error {
if return_value.status == DirectRequestStatus::Error {
println!(
"[Error] {}",
String::decode(&mut return_value.value.as_slice()).unwrap()
Expand Down Expand Up @@ -650,13 +691,13 @@ fn send_direct_request(
let response: RpcResponse = serde_json::from_str(&response).unwrap();
if let Ok(return_value) = RpcReturnValue::decode(&mut response.result.as_slice()) {
match return_value.status {
DirectCallStatus::Error => {
DirectRequestStatus::Error => {
if let Ok(value) = String::decode(&mut return_value.value.as_slice()) {
println!("[Error] {}", value);
}
return None;
}
DirectCallStatus::TrustedOperationStatus(status) => {
DirectRequestStatus::TrustedOperationStatus(status) => {
if let Ok(value) = Hash::decode(&mut return_value.value.as_slice()) {
println!("Trusted call {:?} is {:?}", value, status);
}
Expand Down
5 changes: 3 additions & 2 deletions enclave/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ use substratee_stf::{

use rpc::author::{hash::TrustedOperationOrHash, Author, AuthorApi};
use rpc::worker_api_direct;
use rpc::{api::FillerChainApi, basic_pool::BasicPool};
use rpc::{api::SideChainApi, basic_pool::BasicPool};

mod aes;
mod attestation;
Expand Down Expand Up @@ -108,7 +108,7 @@ pub enum Timeout {
}

pub type Hash = sp_core::H256;
type BPool = BasicPool<FillerChainApi<Block>, Block>;
type BPool = BasicPool<SideChainApi<Block>, Block>;

#[no_mangle]
pub unsafe extern "C" fn init() -> sgx_status_t {
Expand Down Expand Up @@ -588,6 +588,7 @@ fn execute_top_pool_calls(
Ok((calls, _)) => calls,
Err(_) => return Err(sgx_status_t::SGX_ERROR_UNEXPECTED),
};
debug!("Got following trusted calls from pool: {:?}", trusted_calls);
// call execution
for trusted_call_signed in trusted_calls.into_iter() {
match handle_trusted_worker_call(
Expand Down
1 change: 1 addition & 0 deletions enclave/src/rpc.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
pub mod author;
pub mod error;
pub mod system;

pub mod api;
pub mod basic_pool;
Expand Down
162 changes: 147 additions & 15 deletions enclave/src/rpc/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,41 +21,78 @@ extern crate alloc;
use alloc::{boxed::Box, vec::Vec};
use log::*;

use codec::Encode;
use codec::{Encode, Decode};
use jsonrpc_core::futures::future::{ready, Future, Ready};
use std::{marker::PhantomData, pin::Pin};

use sp_runtime::{
generic::BlockId,
traits::{Block as BlockT, Hash as HashT, Header as HeaderT},
transaction_validity::{
TransactionValidity, TransactionValidityError, UnknownTransaction, ValidTransaction,
TransactionValidity, TransactionValidityError, UnknownTransaction,
InvalidTransaction, ValidTransaction,
},
};

use crate::top_pool::pool::{ChainApi, ExtrinsicHash, NumberFor};
use crate::top_pool::primitives::TrustedOperationSource;
use crate::state;

use substratee_stf::{Getter, TrustedOperation as StfTrustedOperation};
use substratee_stf::{Getter, TrustedOperation as StfTrustedOperation, AccountId,
Index, ShardIdentifier, Stf};
use substratee_worker_primitives::BlockHash as SidechainBlockHash;

use crate::rpc::error;

/// Future that resolves to account nonce.
pub type Result<T> = core::result::Result<T, ()>;

/// The operation pool logic for full client.
pub struct FillerChainApi<Block> {
pub struct SideChainApi<Block> {
_marker: PhantomData<Block>,
}

impl<Block> FillerChainApi<Block> {
impl<Block> SideChainApi<Block> {
/// Create new operation pool logic.
pub fn new() -> Self {
FillerChainApi {
SideChainApi {
_marker: Default::default(),
}
}
}

impl<Block> ChainApi for FillerChainApi<Block>
fn expected_nonce(shard: ShardIdentifier, account: &AccountId) -> Result<Index> {
if !state::exists(&shard) {
//FIXME: Should this be an error? -> Issue error handling
error!("Shard does not exists");
return Ok(0 as Index)
}

let mut state = match state::load(&shard) {
Ok(s) => s,
Err(e) => {
//FIXME: Should this be an error? -> Issue error handling
error!("State could not be loaded");
return Err(())
}
};

let nonce: Index = if let Some(nonce_encoded) = Stf::account_nonce(&mut state, &account) {
match Decode::decode(&mut nonce_encoded.as_slice()) {
Ok(index) => index,
Err(e) => {
error!("Could not decode index");
return Err(())
},
}
} else {
0 as Index
};

Ok(nonce)
}

impl<Block> ChainApi for SideChainApi<Block>
where
Block: BlockT,
{
Expand All @@ -71,17 +108,47 @@ where

fn validate_transaction(
&self,
_at: &BlockId<Self::Block>,
_source: TrustedOperationSource,
uxt: StfTrustedOperation,
shard: ShardIdentifier,
) -> Self::ValidationFuture {
let operation = match uxt {
StfTrustedOperation::direct_call(call) => ValidTransaction {
priority: 1 << 20,
requires: vec![],
provides: vec![vec![call.nonce as u8], call.signature.encode()],
longevity: 3,
propagate: true,
StfTrustedOperation::direct_call(signed_call) => {
let nonce = signed_call.nonce;
let from = signed_call.call.account();

let expected_nonce = match expected_nonce(shard, &from) {
Ok(nonce) => nonce,
Err(_) => return Box::pin(ready(Ok(Err(TransactionValidityError::Unknown(
UnknownTransaction::CannotLookup,
))))),
};
if nonce < expected_nonce {
return Box::pin(ready(Ok(Err(TransactionValidityError::Invalid(
InvalidTransaction::Stale
)))))
}
if nonce > expected_nonce + 64 {
return Box::pin(ready(Ok(Err(TransactionValidityError::Invalid(
InvalidTransaction::Future
)))))
}
let encode = |from: AccountId, nonce: Index| (from, nonce).encode();
let requires = if nonce != expected_nonce && nonce > 0 {
vec![encode(from.clone(), nonce - 1)]
} else {
vec![]
};

let provides = vec![encode(from.clone(), nonce)];

ValidTransaction {
priority: 1 << 20,
requires: requires,
provides: provides,
longevity: 64,
propagate: true,
}
},
StfTrustedOperation::get(getter) => match getter {
Getter::public(_) => {
Expand All @@ -93,7 +160,7 @@ where
priority: 1 << 20,
requires: vec![],
provides: vec![trusted_getter.signature.encode()],
longevity: 3,
longevity: 64,
propagate: true,
},
},
Expand Down Expand Up @@ -138,3 +205,68 @@ where
})
}
}


pub mod tests {
use super::*;
use substratee_stf::TrustedCall;
use sp_core::{ed25519 as spEd25519, Pair};
use jsonrpc_core::futures::executor;
use chain_relay::Block;

pub fn test_validate_transaction_works() {
// given
let api = SideChainApi::<Block>::new();
let shard = ShardIdentifier::default();
// ensure state starts empty
state::init_shard(&shard).unwrap();
Stf::init_state();

// create account
let account_pair = spEd25519::Pair::from_seed(b"12345678901234567890123456789012");
let account = account_pair.public();

let source = TrustedOperationSource::External;

// create top call function
let new_top_call = |nonce: Index| {
let mrenclave = [0u8; 32];
let call = TrustedCall::balance_set_balance(
account.into(),
account.into(),
42,
42,
);
let signed_call = call.sign(&account_pair.clone().into(), nonce, &mrenclave, &shard);
signed_call.into_trusted_operation(true)
};
let top0 = new_top_call(0);
let top1 = new_top_call(1);

// when
let validation_result = async { api
.validate_transaction(source, top0, shard)
.await };
let valid_transaction: ValidTransaction = executor::block_on(validation_result).unwrap().unwrap();

let validation_result = async { api
.validate_transaction(source, top1, shard)
.await };
let valid_transaction_two: ValidTransaction = executor::block_on(validation_result).unwrap().unwrap();

// then
assert_eq!(valid_transaction.priority, 1<<20);
assert_eq!(valid_transaction.provides, vec![(&account,0 as Index).encode()]);
assert_eq!(valid_transaction.longevity, 64);
assert!(valid_transaction.propagate);

assert_eq!(valid_transaction_two.priority, 1<<20);
assert_eq!(valid_transaction_two.requires, vec![(&account, 0 as Index).encode()]);
assert_eq!(valid_transaction_two.provides, vec![(&account, 1 as Index).encode()]);
assert_eq!(valid_transaction_two.longevity, 64);
assert!(valid_transaction_two.propagate);

// clean up
state::remove_shard_dir(&shard);
}
}
22 changes: 8 additions & 14 deletions enclave/src/rpc/author/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,13 +134,10 @@ where
shard: ShardIdentifier,
) -> FutureResult<TxHash<P>, RpcError> {
// check if shard already exists
let shards = match state::list_shards() {
Ok(shards) => shards,
Err(_) => return Box::pin(ready(Err(ClientError::InvalidShard.into()))),
};
if !shards.contains(&shard) {
return Box::pin(ready(Err(ClientError::InvalidShard.into())));
}
if !state::exists(&shard) {
//FIXME: Should this be an error? -> Issue error handling
return Box::pin(ready(Err(ClientError::InvalidShard.into())))
}
// decrypt call
let rsa_keypair = rsa3072::unseal_pair().unwrap();
let request_vec: Vec<u8> = match rsa3072::decrypt(&ext.as_slice(), &rsa_keypair) {
Expand Down Expand Up @@ -237,13 +234,10 @@ where

fn watch_top(&self, ext: Vec<u8>, shard: ShardIdentifier) -> FutureResult<TxHash<P>, RpcError> {
// check if shard already exists
let shards = match state::list_shards() {
Ok(shards) => shards,
Err(_) => return Box::pin(ready(Err(ClientError::InvalidShard.into()))),
};
if !shards.contains(&shard) {
return Box::pin(ready(Err(ClientError::InvalidShard.into())));
}
if !state::exists(&shard) {
//FIXME: Should this be an error? -> Issue error handling
return Box::pin(ready(Err(ClientError::InvalidShard.into())))
}
// decrypt call
let rsa_keypair = rsa3072::unseal_pair().unwrap();
let request_vec: Vec<u8> = match rsa3072::decrypt(&ext.as_slice(), &rsa_keypair) {
Expand Down
Loading