When building your own solution, based on the sidechain SDK or the offchain-worker SDK, a key task is to integrate your own business logic (also called STF, state transition function). Here we will show you how that is done in the Integritee worker.
Introducing your own business logic is mostly done in the STF crate ita-stf
. The TrustedCall
and TrustedGetter
structs are the declarations of the business logic and should be extended to match your use-case.
The template declaration contains some basic logic for transferring, shielding and unshielding funds.
pub enum TrustedCall {
balance_set_balance(...),
balance_transfer(...),
balance_unshield(...),
balance_shield(...)
}
pub enum TrustedGetter {
free_balance(...),
reserved_balance(...),
nonce(...),
}
Implementing the business logic is done in stf_sgx.rs
, for each trusted call (fn execute(..)
) and trusted getter (fn get_state(..)
).
For developers who are used to writing substrate pallets, they can do so for the Integritee worker as well: Pallets can be imported and used inside the worker STF. This is possible thanks to our own adaptation of the substrate runtime to SGX (see sgx-runtime repo).
An example is the existing implementation of balance_transfer
, using the standard substrate Balance
pallet (imported here):
TrustedCall::balance_transfer(from, to, value) => {
let origin = sgx_runtime::Origin::signed(from.clone());
// ...
sgx_runtime::BalancesCall::<Runtime>::transfer {
dest: MultiAddress::Id(to),
value,
}
.dispatch_bypass_filter(origin)
.map_err(|e| {
StfError::Dispatch(format!("Balance Transfer error: {:?}", e.error))
})?;
Ok(())
},
Code snippet taken from here.
The CLI client in the worker repository is used as a driver for worker request (both direct and indirect invocation). So after you have implemented your business logic in the stf
crate of the worker, you will want to extend the CLI client to support the new operations.
- Extend the command-line interface of the CLI client, using the clap v3.0 CLI parser, e.g. here.
#[derive(Subcommand)] pub enum TrustedCommands { /// generates a new incognito account for the given shard NewAccount, /// lists all incognito accounts in a given shard ListAccounts, /// send funds from one incognito account to another Transfer { /// sender's AccountId in ss58check format from: String, /// recipient's AccountId in ss58check format to: String, /// amount to be transferred amount: Balance, }, ... }
- Translate the new command-line parameters into a corresponding
TrustedCall
, as is done for example forTrustedCall::balance_transfer
:fn transfer(cli: &Cli, trusted_args: &TrustedArgs, arg_from: &str, arg_to: &str, amount: &Balance) { let from = get_pair_from_str(trusted_args, arg_from); let to = get_accountid_from_str(arg_to); let (mrenclave, shard) = get_identifiers(trusted_args); let nonce = get_layer_two_nonce!(from, cli, trusted_args); let top: TrustedOperation = TrustedCall::balance_transfer(from.public().into(), to, *amount) .sign(&KeyPair::Sr25519(from), nonce, &mrenclave, &shard) .into_trusted_operation(trusted_args.direct); let _ = perform_operation(cli, trusted_args, &top); }