A TEE off-chain worker (TOCW) is used to execute arbitrary computing in a trusted manner. A client submits these operations to a substrate 'parentchain', which can be either a parachain or a solochain. Usually, this will be the Integritee Parachain. The operations are opaque to the parent chain because they are encrypted. Only the client and the TOCW TEE can see the operation's content.
Sending an encrypted operation to the parent chain, which is then picked up and executed by a TEE off-chain worker, is called indirect invocation. An off-chain-worker only supports indirect invocation and 'getters'. In order to encrypt the operation, a client can query the off-chain worker directly (with a 'getter') for the 'shielding key', which is an RSA 3072 public key.
Multiple off-chain workers running in parallel share only the shielding key, but nothing else. They arrive at the same state independently by executing all the operations found on the parent chain, which guarantees ordering of operations.
An off-chain worker supports only 'indirect invocation', which means it executes only operations that are submitted to the parent chain (encrypted, in an extrinsic). As a result, the operation throughput is limited by the block production cycle on a parent chain. 'Direct invocation', available in a sidechain validateer, allows a client to send operations directly to a worker, allowing for much higher operation throughput. This is possible because the sidechain allows sharing and synchronizing states between workers, without having to rely on the parent chain.
- Experiment with the template, running the offchain-worker demo
- Fork the worker repository
https://github.com/integritee-network/worker.git
from the SDK release branch (sdk-v0.13.0-polkadot-v0.9.42
) - Build the worker in offchain-worker mode
- Write and integrate your own business logic, e.g. in a substrate pallet
- Deploy on the Integritee parachain or your own testnet
In order to build the worker in off-chain worker mode, the corresponding cargo feature offchain-worker
needs to be set. In the Makefiles, the environment variable WORKER_MODE
is used to set the cargo features for the worker mode.
In case you build with make
directly, do so with:
WORKER_MODE=offchain-worker make
In case you use docker with our build.Dockerfile
, use --build-arg WORKER_MODE_ARG=offchain-worker
to set the corresponding docker ARG
.
An example of a docker build command (as currently used for GitHub CI):
{% code lineNumbers="true" %}
docker build -t integritee-worker --target deployed-worker --build-arg WORKER_MODE_ARG=offchain-worker -f build.Dockerfile .
{% endcode %}
In case there are multiple off-chain workers, any of them will work, since they all share the same shielding key
- Business logic / STF
****This is the core part of the code changes necessary to turn the generic worker into your specific use-case. Read more in this dedicated section. - RPC Interface
****In case you want to extend the existing RPC interface, this section tells you how. - CLI Client
****A simple CLI client implementation is available in the worker repository, to show case communication with the worker, either by 'direct invocation' or 'indirect invocation'. For a sidechain validateer, 'direct invocation' will likely be the predominant way of communicating with a worker.
The CLI client uses JSON RPC 2.0 over web-socket to communicate with a worker (direct invocation and trusted getters), as can be seen here for example. The web-socket connection is secured by TLS with an enclave self-signed certificate.
Default workflow for executing operations on an off-chain worker from a client's perspective:
- Compose your trusted call, e.g.
TrustedCall::balance_transfer
- Get the shielding key from an off-chain worker
- In case there are multiple off-chain workers, any of them will work, since they all share the same shielding key
- Encode and encrypt the operation using the shielding key
- Wrap the encrypted operation into a parentchain extrinsic
- Send the extrinsic to the parent chain
- Wait for the
ProcessedParentchainBlock
event, with the hash of the parent chain block including your extrinsic.
Examples of this workflow can be found in our CLI client implementation, here and here.