transfers: add scripts and readme (#63)

Co-authored-by: Daniel Gushchyan <d.gushchyan@gmail.com>
Co-authored-by: hu55a1n1 <sufialhussaini@gmail.com>
Co-authored-by: David Kajpust <kajpustd@gmail.com>
Co-authored-by: Daniel Gushchyan <39884512+dangush@users.noreply.github.com>
This commit is contained in:
Ethan Buchman 2024-06-28 06:56:50 -04:00 committed by GitHub
parent 47544c66ae
commit b9d0ef2213
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 543 additions and 12 deletions

5
.gitignore vendored
View file

@ -1,6 +1,11 @@
*~ *~
*.manifest *.manifest
*.manifest.sgx *.manifest.sgx
*.sig
*.hash
*.height
*light-client-proof.json
*output
.idea/ .idea/
target/ target/
artifacts/ artifacts/

View file

@ -38,12 +38,6 @@ pub struct RunClearingMessage {
liquidity_sources: Vec<HexBinary>, liquidity_sources: Vec<HexBinary>,
} }
#[derive(Clone, Debug, Serialize, Deserialize)]
struct AttestedMsg<M> {
msg: M,
quote: Vec<u8>,
}
impl<A> MtcsService<A> impl<A> MtcsService<A>
where where
A: Attestor, A: Attestor,
@ -119,6 +113,7 @@ where
let attested_msg = RawAttested { msg, attestation }; let attested_msg = RawAttested { msg, attestation };
let message = serde_json::to_string(&attested_msg).unwrap(); let message = serde_json::to_string(&attested_msg).unwrap();
Ok(Response::new(RunClearingResponse { message })) Ok(Response::new(RunClearingResponse { message }))
} }
} }

96
apps/transfers/README.md Normal file
View file

@ -0,0 +1,96 @@
# Transfers
This is a simple Quartz demo app. It allows users to deposit funds to a contract, transfer them around privately within the contract's encrypted state,
and withdraw whatever funds they have.
## Run
First set the `NODE_URL` variable to the address of the blockchain node. If it's a local node, set it to `localhost:26657`. If it's a remote node, set it to that node's address.
The `scripts` dir contains some bash scripts to help run the app.
These scripts should be replaced by a new `quartz` tool. See [issue](https://github.com/informalsystems/cycles-quartz/issues/61).
### Build the Binaries
Build the enclave binary and the smart contract binary:
```
bash scripts/build.sh
```
### Configure and Run Gramine
Setup and sign the Gramine config, and then start the gramine process, which will run the
grpc server that hosts the transfer application.
```
bash scripts/start.sh
```
The enclave binary is now running, waiting for commands.
### Contract Setup
With the enclave running in one window, open another window to deploy the contract and start the listener.
In the new window, set the NODE_URL env variable again (eg. `export NODE_URL=143.244.186.205:26657`)
Now we can deploy the contract:
```
bash scripts/deploy.sh
```
Note the deployed contract address and save it into the `CONTRACT` env variable.
Now run the quartz handshake between contract and enclave:
```
bash scripts/handshake.sh $CONTRACT
```
This should output the pubkey and nonce.
### Run the Listener
Finally, we're ready to listen to events from the contract and trigger execution on the enclave:
```
bash scripts/listen.sh $CONTRACT
```
Now we can interact with the contract, and we'll see the events and contract data come through.
### Run the Frontend
Now on your own machine, checkout the https://github.com/informalsystems/cycles-hackathon-app.
Make sure to create a `.env.local` file and set the contract address and TEE pubkey (see the output from `init.sh` and `handshake.sh`). For example:
```
#.env.local
NEXT_PUBLIC_TRANSFERS_CONTRACT_ADDRESS=wasm1ch9ed27cdu3a4fkx37gnagm7jcthj0rggnmmjwwwe4xhwmk0d65q8fn9pz
NEXT_PUBLIC_ENCLAVE_PUBLIC_KEY=030c25e39743fd4c7553d87873919281d567b5c328fb903cbfbe9541518736a2d2
```
Install and run the app:
```
npm install -f
npm run dev
```
Note the frontend app is currently hardcoded to talk to our remote digital ocean node.
Make sure you have Keplr installed in your browser and you should now be able to use the app!
You may have to go to "Manage Chain Visibility" in Keplr settings to add the `My Testing Chain`.
Then you should be able to deposit, transfer, and withdraw using different Keplr accounts. And everything will get processed automatically by the transfer.sh script we have running on the enclave host!

View file

@ -0,0 +1 @@
RUSTFLAGS='-C link-arg=-s' cargo wasm

View file

@ -0,0 +1,75 @@
#!/bin/bash
# Deploy the specified contract's `WASM_BIN` to the chain specified by `CHAIN_ID` using the `USER_ADDR` account.
set -eo pipefail
usage() {
echo "Usage: $0 WASM_BIN [COUNT]"
echo "Example: $0 artifacts/cofi_karma_game.wasm"
exit 1
}
if [ -z "$1" ]; then
echo "❌ Error: Missing WASM_BIN parameter. Please check if all parameters were specified."
usage
fi
if [ "$#" -gt 9 ]; then
echo "❌ Error: Incorrect number of parameters."
usage
fi
USER_ADDR=${USER_ADDR:-$(wasmd keys show -a admin)}
WASM_BIN="$1"
CHAIN_ID=${CHAIN_ID:-testing}
NODE_URL=${NODE_URL:-127.0.0.1:26657}
LABEL=${LABEL:-bisenzone-mvp}
COUNT=${COUNT:-0}
INSTANTIATE_MSG=${INSTANTIATE_MSG:-"null"}
TXFLAG="--chain-id ${CHAIN_ID} --gas-prices 0.0025ucosm --gas auto --gas-adjustment 1.3"
CMD="wasmd --node http://$NODE_URL"
echo "🚀 Deploying WASM contract '${WASM_BIN}' on chain '${CHAIN_ID}' using account '${USER_ADDR}'..."
echo " with cmd : $CMD"
echo "===================================================================="
RES=$($CMD tx wasm store "$WASM_BIN" --from "$USER_ADDR" $TXFLAG -y --output json)
echo $RES
TX_HASH=$(echo $RES | jq -r '.["txhash"]')
while ! $CMD query tx $TX_HASH &> /dev/null; do
echo "... 🕐 waiting for contract to deploy from tx hash $TX_HASH"
sleep 1
done
RES=$($CMD query tx "$TX_HASH" --output json)
CODE_ID=$(echo $RES | jq -r '.logs[0].events[1].attributes[1].value')
echo ""
echo "🚀 Instantiating contract with the following parameters:"
echo "--------------------------------------------------------"
echo "Label: ${LABEL}"
echo "--------------------------------------------------------"
RES=$($CMD tx wasm instantiate "$CODE_ID" "$INSTANTIATE_MSG" --from "$USER_ADDR" --label $LABEL $TXFLAG -y --no-admin --output json)
TX_HASH=$(echo $RES | jq -r '.["txhash"]')
echo ""
while ! $CMD query tx $TX_HASH &> /dev/null; do
echo "... 🕐 waiting for contract to be queryable"
sleep 1
done
RES=$($CMD query wasm list-contract-by-code "$CODE_ID" --output json)
CONTRACT=$(echo $RES | jq -r '.contracts[0]')
echo "🚀 Successfully deployed and instantiated contract!"
echo "🔗 Chain ID: ${CHAIN_ID}"
echo "🆔 Code ID: ${CODE_ID}"
echo "📌 Contract Address: ${CONTRACT}"
echo "🔑 Contract Key: ${KEY}"
echo "🔖 Contract Label: ${LABEL}"

View file

@ -60,7 +60,7 @@ pub fn execute(
} }
pub mod execute { pub mod execute {
use cosmwasm_std::{coins, BankMsg, DepsMut, Env, MessageInfo, Response}; use cosmwasm_std::{coins, BankMsg, DepsMut, Env, Event, MessageInfo, Response};
use cw_utils::must_pay; use cw_utils::must_pay;
use crate::{ use crate::{
@ -81,7 +81,10 @@ pub mod execute {
REQUESTS.save(deps.storage, &requests)?; REQUESTS.save(deps.storage, &requests)?;
Ok(Response::new()) let event = Event::new("transfer").add_attribute("action", "user");
let resp = Response::new().add_event(event);
Ok(resp)
} }
pub fn update( pub fn update(
@ -127,7 +130,10 @@ pub mod execute {
REQUESTS.save(deps.storage, &requests)?; REQUESTS.save(deps.storage, &requests)?;
Ok(Response::new()) let event = Event::new("transfer").add_attribute("action", "user");
let resp = Response::new().add_event(event);
Ok(resp)
} }
pub fn withdraw( pub fn withdraw(
@ -143,6 +149,9 @@ pub mod execute {
REQUESTS.save(deps.storage, &requests)?; REQUESTS.save(deps.storage, &requests)?;
Ok(Response::new()) let event = Event::new("transfer").add_attribute("action", "user");
let resp = Response::new().add_event(event);
Ok(resp)
} }
} }

View file

@ -0,0 +1,57 @@
# Quartz manifest file
loader.entrypoint = "file:{{ gramine.libos }}"
libos.entrypoint = "{{ quartz_dir }}/target/release/enclave"
loader.log_level = "{{ log_level }}"
loader.env.LD_LIBRARY_PATH = "/lib:{{ arch_libdir }}:/usr/{{ arch_libdir }}"
loader.env.HOME = "{{ home }}"
loader.env.INSIDE_SGX = "1"
loader.env.TLS = { passthrough = true }
loader.env.RA_TYPE = { passthrough = true }
loader.env.RA_TLS_ALLOW_DEBUG_ENCLAVE_INSECURE = { passthrough = true }
loader.env.RA_TLS_ALLOW_OUTDATED_TCB_INSECURE = { passthrough = true }
loader.env.RA_TLS_MRENCLAVE = { passthrough = true }
loader.env.RA_TLS_MRSIGNER = { passthrough = true }
loader.env.RA_TLS_ISV_SVN = { passthrough = true }
loader.env.RA_TLS_ISV_PROD_ID = { passthrough = true }
loader.env.RA_TLS_EPID_API_KEY = { passthrough = true }
loader.env.MYAPP_DATA = { passthrough = true }
loader.argv = ["enclave",
"--chain-id", "testing",
"--trusted-height", "1",
"--trusted-hash", "9A5627F601CA82FF4C3967BAE6521B67B6CECB8FF6FCBAE11A1DF563192E7EB9"]
fs.mounts = [
{ uri = "file:{{ gramine.runtimedir() }}", path = "/lib" },
{ uri = "file:{{ arch_libdir }}", path = "{{ arch_libdir }}" },
{ uri = "file:/usr/{{ arch_libdir }}", path = "/usr{{ arch_libdir }}" },
{ uri = "file:{{ quartz_dir }}", path = "{{ quartz_dir }}" },
]
# sgx.debug = true
sgx.enclave_size = "512M"
sgx.max_threads = 4
sgx.edmm_enable = {{ 'true' if env.get('EDMM', '0') == '1' else 'false' }}
sgx.remote_attestation = "{{ ra_type }}"
sgx.ra_client_spid = "{{ ra_client_spid }}"
sgx.ra_client_linkable = {{ 'true' if ra_client_linkable == '1' else 'false' }}
sgx.trusted_files = [
"file:{{ gramine.libos }}",
"file:{{ quartz_dir }}/target/release/enclave",
"file:{{ gramine.runtimedir() }}/",
"file:{{ arch_libdir }}/",
"file:/usr/{{ arch_libdir }}/",
]
sgx.allowed_files = [
"file:{{ quartz_dir }}/exchange.sk",
"file:{{ quartz_dir }}/request.json",
]
sys.insecure__allow_eventfd = true
sys.enable_sigterm_injection = true

View file

@ -0,0 +1,18 @@
#!/bin/bash
set -eo pipefail
ROOT=${ROOT:-$HOME}
echo "--------------------------------------------------------"
echo "building enclave binary"
cd $ROOT/cycles-quartz/apps/transfers/enclave
CARGO_TARGET_DIR=./target cargo build --release
echo "--------------------------------------------------------"
echo "building cosmwasm contract binary"
cd $ROOT/cycles-quartz/apps/transfers/contracts
bash build.sh

View file

@ -0,0 +1,23 @@
#!/bin/bash
set -eo pipefail
ROOT=${ROOT:-$HOME}
echo "--------------------------------------------------------"
echo "instantiate"
cd $ROOT/cycles-quartz/relayer/
export INSTANTIATE_MSG=$(./scripts/relay.sh Instantiate | jq '{quartz: .} + {denom: "ucosm"}' )
echo "--------------------------------------------------------"
echo "deploy contract"
cd $ROOT/cycles-quartz/apps/transfers/contracts/
bash deploy-contract.sh target/wasm32-unknown-unknown/release/transfers_contracts.wasm |& tee output
export CONTRACT=$(cat output | grep Address | awk '{print $NF}' | sed 's/\x1b\[[0-9;]*m//g')
echo $CONTRACT

View file

@ -0,0 +1,116 @@
#!/bin/bash
#
# Perform the SessionCreate and SessionSetPubKey handshake between the contract and the sgx node
# Expects:
# - enclave is already initialized
# - contract is already deployed
# - apps/transfers/trusted.hash exists
#
set -eo pipefail
ROOT=${ROOT:-$HOME}
NODE_URL=${NODE_URL:-127.0.0.1:26657}
if [ "$#" -eq 0 ]; then
echo "Usage: $0 <contract_address>"
exit 1 # Exit with a non-zero status to indicate an error
fi
CONTRACT="$1"
CMD="wasmd --node http://$NODE_URL"
cd "$ROOT/cycles-quartz/apps/transfers"
export TRUSTED_HASH=$(cat trusted.hash)
echo "using CMD: $CMD"
echo "--------------------------------------------------------"
echo "create session"
# change to relay dir
cd $ROOT/cycles-quartz/relayer
# execute SessionCreate on enclave
export EXECUTE_CREATE=$(./scripts/relay.sh SessionCreate)
# submit SessionCreate to contract
RES=$($CMD tx wasm execute "$CONTRACT" "$EXECUTE_CREATE" --from admin --chain-id testing -y --output json)
TX_HASH=$(echo $RES | jq -r '.["txhash"]')
# wait for tx to commit
while ! $CMD query tx $TX_HASH &> /dev/null; do
echo "... 🕐 waiting for tx"
sleep 1
done
# need to wait another block for light client proof
BLOCK_HEIGHT=$($CMD query block | jq .block.header.height)
echo "at heigh $BLOCK_HEIGHT. need to wait for a block"
while [[ $BLOCK_HEIGHT == $($CMD query block | jq .block.header.height) ]]; do
echo "... 🕐 waiting for another block"
sleep 1
done
# need to wait another block for light client proof
BLOCK_HEIGHT=$($CMD query block | jq .block.header.height)
echo "at heigh $BLOCK_HEIGHT. need to wait for a block"
while [[ $BLOCK_HEIGHT == $($CMD query block | jq .block.header.height) ]]; do
echo "... 🕐 waiting for another block"
sleep 1
done
echo "--------------------------------------------------------"
echo "set session pk"
# change to prover dir
cd $ROOT/cycles-quartz/utils/tm-prover
export PROOF_FILE="light-client-proof.json"
if [ -f "$PROOF_FILE" ]; then
rm "$PROOF_FILE"
echo "removed old $PROOF_FILE"
fi
# TODO: pass this in?
echo "trusted hash $TRUSTED_HASH"
echo "contract $CONTRACT"
# run prover to get light client proof
# TODO: assume this binary is pre-built?
# TODO: pass in addresses and chain id
cargo run -vvv -- --chain-id testing \
--primary "http://$NODE_URL" \
--witnesses "http://$NODE_URL" \
--trusted-height 1 \
--trusted-hash $TRUSTED_HASH \
--contract-address $CONTRACT \
--storage-key "quartz_session" \
--trace-file $PROOF_FILE
export POP=$(cat $PROOF_FILE)
export POP_MSG=$(jq -nc --arg message "$POP" '$ARGS.named')
# execute SessionSetPubKey on enclave
cd $ROOT/cycles-quartz/relayer
export EXECUTE_SETPUB=$(./scripts/relay.sh SessionSetPubKey "$POP_MSG")
RES=$($CMD tx wasm execute "$CONTRACT" "$EXECUTE_SETPUB" --from admin --chain-id testing -y --output json)
TX_HASH=$(echo $RES | jq -r '.["txhash"]')
# wait for tx to commit
while ! $CMD query tx $TX_HASH &> /dev/null; do
echo "... 🕐 waiting for tx"
sleep 1
done
echo "--------------------------------------------------------"
echo "check session success"
export NONCE_AND_KEY=$($CMD query wasm contract-state raw "$CONTRACT" $(printf '%s' "quartz_session" | hexdump -ve '/1 "%02X"') -o json | jq -r .data | base64 -d)
echo $NONCE_AND_KEY
export PUBKEY=$(echo $NONCE_AND_KEY | jq -r .pub_key)

View file

@ -0,0 +1,73 @@
ROOT=${ROOT:-$HOME}
DEFAULT_NODE="127.0.0.1:26657"
NODE_URL=${NODE_URL:-$DEFAULT_NODE}
if [ "$#" -eq 0 ]; then
echo "Usage: $0 <contract_address>"
exit 1 # Exit with a non-zero status to indicate an error
fi
CONTRACT=$1
CMD="wasmd --node http://$NODE_URL"
WSURL="ws://$NODE_URL/websocket"
SUBSCRIBE="{\"jsonrpc\":\"2.0\",\"method\":\"subscribe\",\"params\":[\"execute._contract_address = '$CONTRACT' AND wasm-transfer.action = 'user'\"],\"id\":1}"
echo $SUBSCRIBE
echo "--------------------------------------------------------"
echo "subscribe to events"
# cat keeps the stdin open so websocat doesnt close
(echo "$SUBSCRIBE"; cat) | websocat $WSURL | while read msg; do
if [[ "$msg" == '{"jsonrpc":"2.0","id":1,"result":{}}' ]]; then
echo "... subscribed"
echo "---------------------------------------------------------"
echo "... waiting for event"
continue
fi
if echo "$msg" | jq 'has("error")' > /dev/null; then
echo "... error msg $msg"
echo "---------------------------------------------------------"
echo "... waiting for event"
continue
fi
echo "... received event! "
echo $msg
echo "... fetching requests"
REQUESTS=$($CMD query wasm contract-state raw $CONTRACT $(printf '%s' "requests" | hexdump -ve '/1 "%02X"') -o json | jq -r .data | base64 -d)
STATE=$($CMD query wasm contract-state raw $CONTRACT $(printf '%s' "state" | hexdump -ve '/1 "%02X"') -o json | jq -r .data | base64 -d)
export ENCLAVE_REQUEST=$(jq -nc --argjson requests "$REQUESTS" --argjson state $STATE '$ARGS.named')
echo $ENCLAVE_REQUEST | jq .
export REQUEST_MSG=$(jq -nc --arg message "$ENCLAVE_REQUEST" '$ARGS.named')
cd $ROOT/cycles-quartz/apps/transfers/enclave
echo "... executing transfer"
export UPDATE=$(grpcurl -plaintext -import-path ./proto/ -proto transfers.proto -d "$REQUEST_MSG" '127.0.0.1:11090' transfers.Settlement/Run | jq .message | jq -R 'fromjson | fromjson' | jq -c )
echo "... submitting update"
$CMD tx wasm execute $CONTRACT "{\"update\": "$UPDATE" }" --chain-id testing --from admin --node http://$NODE_URL -y
echo " ... done"
echo "---------------------------------------------------------"
echo "... waiting for event"
done

64
apps/transfers/scripts/start.sh Executable file
View file

@ -0,0 +1,64 @@
#!/bin/bash
#set -eo pipefail
ROOT=${ROOT:-$HOME}
DIR_QUARTZ="$ROOT/cycles-quartz"
DIR_QUARTZ_APP="$DIR_QUARTZ/apps/transfers"
DIR_QUARTZ_ENCLAVE="$DIR_QUARTZ_APP/enclave"
DIR_QUARTZ_TM_PROVER="$DIR_QUARTZ/utils/tm-prover"
NODE_URL=${NODE_URL:-127.0.0.1:26657}
CMD="wasmd --node http://$NODE_URL"
echo "--------------------------------------------------------"
echo "set trusted hash"
cd "$DIR_QUARTZ_TM_PROVER"
cargo run -- --chain-id testing \
--primary "http://$NODE_URL" \
--witnesses "http://$NODE_URL" \
--trusted-height 1 \
--trusted-hash "5237772462A41C0296ED688A0327B8A60DF310F08997AD760EB74A70D0176C27" \
--contract-address "wasm14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9s0phg4d" \
--storage-key "quartz_session" \
--trace-file light-client-proof.json &> $DIR_QUARTZ_APP/output
cd $DIR_QUARTZ_APP
cat output | grep found | head -1 | awk '{print $NF}' | sed 's/\x1b\[[0-9;]*m//g' > trusted.hash
export TRUSTED_HASH=$(cat trusted.hash)
echo "... $TRUSTED_HASH"
rm output
echo "--------------------------------------------------------"
echo "configure gramine"
cd "$DIR_QUARTZ_ENCLAVE"
echo "... gen priv key if it doesnt exist"
gramine-sgx-gen-private-key > /dev/null 2>&1 || : # may fail
echo "... update manifest template with trusted hash $TRUSTED_HASH"
sed -i -r "s/(\"--trusted-hash\", \")[A-Z0-9]+(\"])/\1$TRUSTED_HASH\2/" quartz.manifest.template
echo "... create manifest"
gramine-manifest \
-Dlog_level="error" \
-Dhome="$HOME" \
-Darch_libdir="/lib/$(gcc -dumpmachine)" \
-Dra_type="epid" \
-Dra_client_spid="51CAF5A48B450D624AEFE3286D314894" \
-Dra_client_linkable=1 \
-Dquartz_dir="$(pwd)" \
-Dtrusted_height="$TRUSTED_HEIGHT" \
-Dtrusted_hash="$TRUSTED_HASH" \
quartz.manifest.template quartz.manifest
echo "... sign manifest"
gramine-sgx-sign --manifest quartz.manifest --output quartz.manifest.sgx
echo "--------------------------------------------------------"
echo "... start gramine"
gramine-sgx ./quartz

View file

@ -8,7 +8,7 @@ usage() {
exit 1 exit 1
} }
ROOT=${ROOT:-$(pwd)} ROOT=${ROOT:-$HOME}
DIR_QUARTZ="$ROOT/cycles-quartz" DIR_QUARTZ="$ROOT/cycles-quartz"
DIR_PROTO="$DIR_QUARTZ/core/quartz-proto/proto" DIR_PROTO="$DIR_QUARTZ/core/quartz-proto/proto"
IAS_API_KEY="669244b3e6364b5888289a11d2a1726d" IAS_API_KEY="669244b3e6364b5888289a11d2a1726d"

File diff suppressed because one or more lines are too long