diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index bee8a8a465..d007fe1eb4 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -272,7 +272,7 @@ jobs: include: - test: M6 flavor_id: sidechain - demo_name: demo-indirect-invocation + demo_name: demo-shielding-unshielding-multiworker host: test-runner-sgx sgx_mode: HW - test: M8 @@ -287,7 +287,7 @@ jobs: sgx_mode: HW - test: M6 flavor_id: offchain-worker - demo_name: demo-indirect-invocation + demo_name: demo-shielding-unshielding-multiworker host: test-runner-sgx sgx_mode: HW - test: Teeracle diff --git a/cli/demo_indirect_invocation.sh b/cli/demo_shielding_unshielding_multiworker.sh similarity index 77% rename from cli/demo_indirect_invocation.sh rename to cli/demo_shielding_unshielding_multiworker.sh index b1c42dfce6..6d9b687b70 100755 --- a/cli/demo_indirect_invocation.sh +++ b/cli/demo_shielding_unshielding_multiworker.sh @@ -58,4 +58,12 @@ SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) "${SCRIPT_DIR}"/demo_shielding_unshielding.sh -p "${INTEGRITEE_RPC_PORT}" -u "${INTEGRITEE_RPC_URL}" -V "${WORKER_1_URL}" -P "${WORKER_1_PORT}" -C "${CLIENT_BIN}" -t first "${SCRIPT_DIR}"/demo_shielding_unshielding.sh -p "${INTEGRITEE_RPC_PORT}" -u "${INTEGRITEE_RPC_URL}" -V "${WORKER_2_URL}" -P "${WORKER_2_PORT}" -C "${CLIENT_BIN}" -t second +if [ "$FLAVOR_ID" = offchain-worker ]; then + echo "offchain-worker does not support shard vault shielding, therefore we skip those tests" + exit 0 +fi + +"${SCRIPT_DIR}"/demo_shielding_unshielding_using_shard_vault.sh -p "${INTEGRITEE_RPC_PORT}" -u "${INTEGRITEE_RPC_URL}" -V "${WORKER_1_URL}" -P "${WORKER_1_PORT}" -C "${CLIENT_BIN}" -t first +"${SCRIPT_DIR}"/demo_shielding_unshielding_using_shard_vault.sh -p "${INTEGRITEE_RPC_PORT}" -u "${INTEGRITEE_RPC_URL}" -V "${WORKER_2_URL}" -P "${WORKER_2_PORT}" -C "${CLIENT_BIN}" -t second + exit 0 diff --git a/cli/demo_shielding_unshielding_using_shard_vault.sh b/cli/demo_shielding_unshielding_using_shard_vault.sh new file mode 100755 index 0000000000..128a2fa162 --- /dev/null +++ b/cli/demo_shielding_unshielding_using_shard_vault.sh @@ -0,0 +1,265 @@ +#!/bin/bash + +# to make sure the script aborts when (sub-)function exits abnormally +set -e + +# Demonstrates how to shield tokens from the parentchain into the sidechain. +# +# setup: +# run all on localhost: +# integritee-node purge-chain --dev +# integritee-node --dev -lruntime=debug +# rm light_client_db.bin +# export RUST_LOG=integritee_service=info,ita_stf=debug +# integritee-service init_shard +# integritee-service shielding-key +# integritee-service signing-key +# integritee-service run +# +# then run this script + +# usage: +# demo_shielding_unshielding.sh -p -P -t -m file +# +# TEST_BALANCE_RUN is either "first" or "second" +# if -m file is set, the mrenclave will be read from file + +while getopts ":m:p:P:t:u:V:C:" opt; do + case $opt in + t) + TEST=$OPTARG + ;; + m) + READ_MRENCLAVE=$OPTARG + ;; + p) + INTEGRITEE_RPC_PORT=$OPTARG + ;; + P) + WORKER_1_PORT=$OPTARG + ;; + u) + INTEGRITEE_RPC_URL=$OPTARG + ;; + V) + WORKER_1_URL=$OPTARG + ;; + C) + CLIENT_BIN=$OPTARG + ;; + *) + echo "invalid arg ${OPTARG}" + exit 1 + esac +done + +# Using default port if none given as arguments. +INTEGRITEE_RPC_PORT=${INTEGRITEE_RPC_PORT:-9944} +INTEGRITEE_RPC_URL=${INTEGRITEE_RPC_URL:-"ws://127.0.0.1"} + +WORKER_1_PORT=${WORKER_1_PORT:-2000} +WORKER_1_URL=${WORKER_1_URL:-"wss://127.0.0.1"} + +CLIENT_BIN=${CLIENT_BIN:-"./../bin/integritee-cli"} + +echo "Using client binary ${CLIENT_BIN}" +${CLIENT_BIN} --version +echo "Using node uri ${INTEGRITEE_RPC_URL}:${INTEGRITEE_RPC_PORT}" +echo "Using trusted-worker uri ${WORKER_1_URL}:${WORKER_1_PORT}" +echo "" + +# the parentchain token is 12 decimal +UNIT=$(( 10 ** 12 )) + +# make these amounts greater than ED +AMOUNT_SHIELD=$(( 6 * UNIT )) +AMOUNT_TRANSFER=$(( 2 * UNIT )) +AMOUNT_UNSHIELD=$(( 1 * UNIT )) + +CLIENT="${CLIENT_BIN} -p ${INTEGRITEE_RPC_PORT} -P ${WORKER_1_PORT} -u ${INTEGRITEE_RPC_URL} -U ${WORKER_1_URL}" + +# offchain-worker only suppports indirect calls +CALLTYPE= +case "$FLAVOR_ID" in + sidechain) CALLTYPE="--direct" ;; + offchain-worker) : ;; + *) CALLTYPE="--direct" ;; +esac +echo "using call type: ${CALLTYPE} (empty means indirect)" + +# interval and max rounds to wait to check the given account balance in sidechain +WAIT_INTERVAL_SECONDS=10 +WAIT_ROUNDS=20 + +# Poll and assert the given account's state is equal to expected, +# with timeout WAIT_INTERVAL_SECONDS * WAIT_ROUNDS +# usage: +# wait_assert_state +# the `state-name` has to be the supported subcommand, e.g. `balance`, `nonce` +function wait_assert_state() +{ + for i in $(seq 1 $WAIT_ROUNDS); do + sleep $WAIT_INTERVAL_SECONDS + state=$(${CLIENT} trusted --mrenclave "$1" "$3" "$2") + if [ $state -eq "$4" ]; then + return + else + : + fi + done + echo + echo "Assert $2 $3 failed, expected = $4, actual = $state" + exit 1 +} + +# Do a live query and assert the given account's state is equal to expected +# usage: +# assert_state +function assert_state() +{ + state=$(${CLIENT} trusted --mrenclave "$1" "$3" "$2") + if [ -z "$state" ]; then + echo "Query $2 $3 failed" + exit 1 + fi + + if [ $state -eq "$4" ]; then + return + fi + echo + echo "Assert $2 $3 failed, expected = $4, actual = $state" + exit 1 +} + +echo "* Query on-chain enclave registry:" +${CLIENT} list-workers +echo "" + +if [ "$READ_MRENCLAVE" = "file" ] +then + read MRENCLAVE <<< $(cat ~/mrenclave.b58) + echo "Reading MRENCLAVE from file: ${MRENCLAVE}" +else + # this will always take the first MRENCLAVE found in the registry !! + read MRENCLAVE <<< $($CLIENT list-workers | awk '/ MRENCLAVE: / { print $2; exit }') + echo "Reading MRENCLAVE from worker list: ${MRENCLAVE}" +fi +[[ -z $MRENCLAVE ]] && { echo "MRENCLAVE is empty. cannot continue" ; exit 1; } + + +echo "* Create a new incognito account for Bob" +ICGACCOUNTBOB=$(${CLIENT} trusted --mrenclave ${MRENCLAVE} new-account) +echo " Bob's incognito account = ${ICGACCOUNTBOB}" +echo "" + +echo "* Query shard vault account" +VAULT=$(${CLIENT} trusted get-shard-vault) +echo " shard vault account = ${VAULT}" +echo "" + +# Asssert the initial balance of Charlie incognito +# The initial balance of Bob incognito should always be 0, as Bob is newly created +BALANCE_INCOGNITO_CHARLIE=0 +case $TEST in + first) + wait_assert_state ${MRENCLAVE} //Charlie balance 0 ;; + second) + wait_assert_state ${MRENCLAVE} //Charlie balance $(( AMOUNT_SHIELD - AMOUNT_TRANSFER - AMOUNT_UNSHIELD )) + BALANCE_INCOGNITO_CHARLIE=$(( AMOUNT_SHIELD - AMOUNT_TRANSFER - AMOUNT_UNSHIELD )) ;; + *) + echo "assuming first run of test" + wait_assert_state ${MRENCLAVE} //Charlie balance 0 ;; +esac + +echo "* Shield ${AMOUNT_SHIELD} tokens to Charlie's account on L2" +${CLIENT} transfer //Charlie ${VAULT} ${AMOUNT_SHIELD} +echo "" + +echo "* Wait and assert Charlie's L2 account balance... " +wait_assert_state ${MRENCLAVE} //Charlie balance $(( BALANCE_INCOGNITO_CHARLIE + AMOUNT_SHIELD )) +echo "✔ ok" + +echo "* Wait and assert Bob's incognito account balance... " +wait_assert_state ${MRENCLAVE} ${ICGACCOUNTBOB} balance 0 +echo "✔ ok" +echo "" + +echo "* Send ${AMOUNT_TRANSFER} funds from Charlie's L2 account to Bob's incognito account" +$CLIENT trusted --mrenclave ${MRENCLAVE} transfer //Charlie ${ICGACCOUNTBOB} ${AMOUNT_TRANSFER} +echo "" + +echo "* Wait and assert Charlie's L2 account balance... " +wait_assert_state ${MRENCLAVE} //Charlie balance $(( BALANCE_INCOGNITO_CHARLIE + AMOUNT_SHIELD - AMOUNT_TRANSFER )) +echo "✔ ok" + +echo "* Wait and assert Bob's incognito account balance... " +wait_assert_state ${MRENCLAVE} ${ICGACCOUNTBOB} balance ${AMOUNT_TRANSFER} +echo "✔ ok" +echo "" + +echo "* Un-shield ${AMOUNT_UNSHIELD} tokens from Charlie's incognito account to Ferie's L1 account" +${CLIENT} trusted --mrenclave ${MRENCLAVE} unshield-funds //Charlie //Ferdie ${AMOUNT_UNSHIELD} +echo "" + +echo "* Wait and assert Charlie's incognito account balance... " +wait_assert_state ${MRENCLAVE} //Charlie balance $(( BALANCE_INCOGNITO_CHARLIE + AMOUNT_SHIELD - AMOUNT_TRANSFER - AMOUNT_UNSHIELD )) +echo "✔ ok" + +echo "* Wait and assert Bob's incognito account balance... " +wait_assert_state ${MRENCLAVE} ${ICGACCOUNTBOB} balance ${AMOUNT_TRANSFER} +echo "✔ ok" + +# Test the nonce handling, using Bob's incognito account as the sender as Charlie's +# balance needs to be verified in the second round while Bob is newly created each time + +echo "* Create a new incognito account for Charlie" +ICGACCOUNTCHARLIE=$(${CLIENT} trusted --mrenclave ${MRENCLAVE} new-account) +echo " Charlie's incognito account = ${ICGACCOUNTCHARLIE}" +echo "" + +echo "* Assert Bob's incognito initial nonce..." +assert_state ${MRENCLAVE} ${ICGACCOUNTBOB} nonce 0 +echo "✔ ok" +echo "" + +echo "* Send 3 consecutive 0.2 UNIT balance Transfer Bob -> Charlie" +for i in $(seq 1 3); do + # use direct calls so they are submitted to the top pool synchronously + $CLIENT trusted $CALLTYPE --mrenclave ${MRENCLAVE} transfer ${ICGACCOUNTBOB} ${ICGACCOUNTCHARLIE} $(( AMOUNT_TRANSFER / 10 )) +done +echo "" + +echo "* Assert Bob's incognito current nonce..." +wait_assert_state ${MRENCLAVE} ${ICGACCOUNTBOB} nonce 3 +echo "✔ ok" +echo "" + +echo "* Send a 2 UNIT balance Transfer Bob -> Charlie (that will fail)" +$CLIENT trusted $CALLTYPE --mrenclave ${MRENCLAVE} transfer ${ICGACCOUNTBOB} ${ICGACCOUNTCHARLIE} ${AMOUNT_TRANSFER} +echo "" + +echo "* Assert Bob's incognito nonce..." +# the nonce should be increased nontheless, even for the failed tx +wait_assert_state ${MRENCLAVE} ${ICGACCOUNTBOB} nonce 4 +echo "✔ ok" +echo "" + +echo "* Send another 0.2 UNIT balance Transfer Bob -> Charlie" +$CLIENT trusted $CALLTYPE --mrenclave ${MRENCLAVE} transfer ${ICGACCOUNTBOB} ${ICGACCOUNTCHARLIE} $(( AMOUNT_TRANSFER / 10 )) +echo "" + +echo "* Assert Bob's incognito nonce..." +wait_assert_state ${MRENCLAVE} ${ICGACCOUNTBOB} nonce 5 +echo "✔ ok" +echo "" + +echo "* Wait and assert Bob's incognito account balance... " +# in total 4 balance transfer should go through => 1.2 UNIT remaining +wait_assert_state ${MRENCLAVE} ${ICGACCOUNTBOB} balance $(( AMOUNT_TRANSFER * 6 / 10 )) +echo "✔ ok" + +echo "" +echo "-----------------------" +echo "✔ The $TEST test passed!" +echo "-----------------------" +echo "" diff --git a/docker/README.md b/docker/README.md index cac5c2b545..7f9ddb7a86 100644 --- a/docker/README.md +++ b/docker/README.md @@ -28,11 +28,11 @@ Starts all services (node and workers), using the `integritee-worker:dev` images ### Demo indirect invocation (M6) Build ``` -COMPOSE_DOCKER_CLI_BUILD=1 DOCKER_BUILDKIT=1 docker compose -f <(envsubst < docker-compose.yml) -f <(envsubst < demo-indirect-invocation.yml) build --build-arg WORKER_MODE_ARG=offchain-worker +COMPOSE_DOCKER_CLI_BUILD=1 DOCKER_BUILDKIT=1 docker compose -f <(envsubst < docker-compose.yml) -f <(envsubst < demo-shielding-unshielding-multiworker.yml) build --build-arg WORKER_MODE_ARG=offchain-worker ``` Run ``` -FLAVOR_ID=offchain-worker docker compose -f <(envsubst < docker-compose.yml) -f <(envsubst < demo-indirect-invocation.yml) up demo-indirect-invocation --exit-code-from demo-indirect-invocation +FLAVOR_ID=offchain-worker docker compose -f <(envsubst < docker-compose.yml) -f <(envsubst < demo-shielding-unshielding-multiworker.yml) up demo-shielding-unshielding-multiworker --exit-code-from demo-shielding-unshielding-multiworker ``` ### Demo direct call (M8) diff --git a/docker/demo-indirect-invocation.yml b/docker/demo-shielding-unshielding-multiworker.yml similarity index 85% rename from docker/demo-indirect-invocation.yml rename to docker/demo-shielding-unshielding-multiworker.yml index bba1748838..6ca9b3b147 100644 --- a/docker/demo-indirect-invocation.yml +++ b/docker/demo-shielding-unshielding-multiworker.yml @@ -1,5 +1,5 @@ services: - demo-indirect-invocation: + demo-shielding-unshielding-multiworker: image: integritee-cli:${VERSION:-dev} devices: - "${SGX_PROVISION:-/dev/null}:/dev/sgx/provision" @@ -24,9 +24,9 @@ services: networks: - integritee-test-network entrypoint: - "/usr/local/worker-cli/demo_indirect_invocation.sh -p 9912 -u ws://integritee-node + "/usr/local/worker-cli/demo_shielding_unshielding_multiworker.sh -p 9912 -u ws://integritee-node -V wss://integritee-worker-1 -A 2011 -W wss://integritee-worker-2 -B 2012 -C /usr/local/bin/integritee-cli 2>&1" restart: "no" networks: integritee-test-network: - driver: bridge \ No newline at end of file + driver: bridge diff --git a/enclave-runtime/src/rpc/worker_api_direct.rs b/enclave-runtime/src/rpc/worker_api_direct.rs index 019153a9d2..9c99f99b6d 100644 --- a/enclave-runtime/src/rpc/worker_api_direct.rs +++ b/enclave-runtime/src/rpc/worker_api_direct.rs @@ -40,6 +40,7 @@ use itp_utils::{FromHexPrefixed, ToHexPrefixed}; use its_primitives::types::block::SignedBlock; use its_sidechain::rpc_handler::{direct_top_pool_api, import_block_api}; use jsonrpc_core::{serde_json::json, IoHandler, Params, Value}; +use log::debug; use sgx_crypto_helper::rsa3072::Rsa3072PubKey; use sp_runtime::OpaqueExtrinsic; use std::{borrow::ToOwned, format, str, string::String, sync::Arc, vec::Vec}; @@ -74,6 +75,7 @@ where ); io.add_sync_method("author_getShieldingKey", move |_: Params| { + debug!("worker_api_direct rpc was called: author_getShieldingKey"); let rsa_pubkey = match shielding_key.retrieve_pubkey() { Ok(key) => key, Err(status) => { @@ -97,6 +99,7 @@ where let local_top_pool_author = top_pool_author.clone(); io.add_sync_method("author_getShardVault", move |_: Params| { + debug!("worker_api_direct rpc was called: author_getShardVault"); let shard = local_top_pool_author.list_handled_shards().first().copied().unwrap_or_default(); if let Ok(stf_enclave_signer) = get_stf_enclave_signer_from_solo_or_parachain() { @@ -116,12 +119,14 @@ where }); io.add_sync_method("author_getShard", move |_: Params| { + debug!("worker_api_direct rpc was called: author_getShard"); let shard = top_pool_author.list_handled_shards().first().copied().unwrap_or_default(); let json_value = RpcReturnValue::new(shard.encode(), false, DirectRequestStatus::Ok); Ok(json!(json_value.to_hex())) }); io.add_sync_method("author_getMuRaUrl", move |_: Params| { + debug!("worker_api_direct rpc was called: author_getMuRaUrl"); let url = match GLOBAL_PRIMITIVES_CACHE.get_mu_ra_url() { Ok(url) => url, Err(status) => { @@ -135,6 +140,7 @@ where }); io.add_sync_method("author_getUntrustedUrl", move |_: Params| { + debug!("worker_api_direct rpc was called: author_getUntrustedUrl"); let url = match GLOBAL_PRIMITIVES_CACHE.get_untrusted_worker_url() { Ok(url) => url, Err(status) => { @@ -148,22 +154,26 @@ where }); io.add_sync_method("chain_subscribeAllHeads", |_: Params| { + debug!("worker_api_direct rpc was called: chain_subscribeAllHeads"); let parsed = "world"; Ok(Value::String(format!("hello, {}", parsed))) }); io.add_sync_method("state_getMetadata", |_: Params| { + debug!("worker_api_direct rpc was called: tate_getMetadata"); let metadata = Runtime::metadata(); let json_value = RpcReturnValue::new(metadata.into(), false, DirectRequestStatus::Ok); Ok(json!(json_value.to_hex())) }); io.add_sync_method("state_getRuntimeVersion", |_: Params| { + debug!("worker_api_direct rpc was called: state_getRuntimeVersion"); let parsed = "world"; Ok(Value::String(format!("hello, {}", parsed))) }); io.add_sync_method("state_executeGetter", move |params: Params| { + debug!("worker_api_direct rpc was called: state_executeGetter"); let json_value = match execute_getter_inner(getter_executor.as_ref(), params) { Ok(state_getter_value) => RpcReturnValue { do_watch: false, @@ -177,6 +187,7 @@ where }); io.add_sync_method("attesteer_forwardDcapQuote", move |params: Params| { + debug!("worker_api_direct rpc was called: attesteer_forwardDcapQuote"); let json_value = match forward_dcap_quote_inner(params) { Ok(val) => RpcReturnValue { do_watch: false, @@ -191,6 +202,7 @@ where }); io.add_sync_method("attesteer_forwardIasAttestationReport", move |params: Params| { + debug!("worker_api_direct rpc was called: attesteer_forwardIasAttestationReport"); let json_value = match attesteer_forward_ias_attestation_report_inner(params) { Ok(val) => RpcReturnValue { do_watch: false, @@ -205,22 +217,26 @@ where }); io.add_sync_method("system_health", |_: Params| { + debug!("worker_api_direct rpc was called: system_health"); let parsed = "world"; Ok(Value::String(format!("hello, {}", parsed))) }); io.add_sync_method("system_name", |_: Params| { + debug!("worker_api_direct rpc was called: system_name"); let parsed = "world"; Ok(Value::String(format!("hello, {}", parsed))) }); io.add_sync_method("system_version", |_: Params| { + debug!("worker_api_direct rpc was called: system_version"); let parsed = "world"; Ok(Value::String(format!("hello, {}", parsed))) }); let rpc_methods_string = get_all_rpc_methods_string(&io); io.add_sync_method("rpc_methods", move |_: Params| { + debug!("worker_api_direct rpc was called: rpc_methods"); Ok(Value::String(rpc_methods_string.to_owned())) }); diff --git a/sidechain/rpc-handler/src/direct_top_pool_api.rs b/sidechain/rpc-handler/src/direct_top_pool_api.rs index d2318b6e03..235158ac9e 100644 --- a/sidechain/rpc-handler/src/direct_top_pool_api.rs +++ b/sidechain/rpc-handler/src/direct_top_pool_api.rs @@ -46,10 +46,9 @@ where TCS: PartialEq + Encode + Decode + Debug + Send + Sync + 'static, G: PartialEq + Encode + Decode + Debug + Send + Sync + 'static, { - // author_submitAndWatchExtrinsic - let author_submit_and_watch_extrinsic_name: &str = "author_submitAndWatchExtrinsic"; let watch_author = top_pool_author.clone(); - io_handler.add_sync_method(author_submit_and_watch_extrinsic_name, move |params: Params| { + io_handler.add_sync_method("author_submitAndWatchExtrinsic", move |params: Params| { + debug!("worker_api_direct rpc was called: author_submitAndWatchExtrinsic"); let json_value = match author_submit_extrinsic_inner(watch_author.clone(), params) { Ok(hash_value) => RpcReturnValue { do_watch: true, @@ -64,10 +63,9 @@ where Ok(json!(json_value)) }); - // author_submitExtrinsic - let author_submit_extrinsic_name: &str = "author_submitExtrinsic"; let submit_author = top_pool_author.clone(); - io_handler.add_sync_method(author_submit_extrinsic_name, move |params: Params| { + io_handler.add_sync_method("author_submitExtrinsic", move |params: Params| { + debug!("worker_api_direct rpc was called: author_submitExtrinsic"); let json_value = match author_submit_extrinsic_inner(submit_author.clone(), params) { Ok(hash_value) => RpcReturnValue { do_watch: false, @@ -82,10 +80,9 @@ where Ok(json!(json_value)) }); - // author_pendingExtrinsics - let author_pending_extrinsic_name: &str = "author_pendingExtrinsics"; let pending_author = top_pool_author.clone(); - io_handler.add_sync_method(author_pending_extrinsic_name, move |params: Params| { + io_handler.add_sync_method("author_pendingExtrinsics", move |params: Params| { + debug!("worker_api_direct rpc was called: author_pendingExtrinsics"); match params.parse::>() { Ok(shards) => { let mut retrieved_operations = vec![]; @@ -116,10 +113,9 @@ where } }); - // author_pendingTrustedCallsFor - let author_pending_trusted_calls_for_name: &str = "author_pendingTrustedCallsFor"; let pending_author = top_pool_author; - io_handler.add_sync_method(author_pending_trusted_calls_for_name, move |params: Params| { + io_handler.add_sync_method("author_pendingTrustedCallsFor", move |params: Params| { + debug!("worker_api_direct rpc was called: author_pendingTrustedCallsFor"); match params.parse::<(String, String)>() { Ok((shard_base58, account_hex)) => { let shard = match decode_shard_from_base58(shard_base58.as_str()) {