From f10143eca93a4ad35662a3dc19555ee91f2363b2 Mon Sep 17 00:00:00 2001 From: Carlos Alejandro Gutierrez Sandoval Date: Thu, 14 Sep 2023 17:43:10 -0600 Subject: [PATCH 01/30] format --- devenv/bitcoin/docker/Dockerfile | 2 +- devenv/bitcoin/docker/entrypoint.sh | 5 ----- devenv/sbtc/docker/Dockerfile | 2 +- 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/devenv/bitcoin/docker/Dockerfile b/devenv/bitcoin/docker/Dockerfile index c6394204..1bf79882 100644 --- a/devenv/bitcoin/docker/Dockerfile +++ b/devenv/bitcoin/docker/Dockerfile @@ -6,7 +6,7 @@ ARG VERSION=25.0 RUN apt-get update && apt-get install -y \ wget \ file \ - && rm -rf /var/lib/apt/lists/* + && rm -rf /var/lib/apt/lists/* RUN file /bin/bash | grep -q x86-64 && echo x86_64-linux-gnu > /tmp/arch || true RUN file /bin/bash | grep -q aarch64 && echo aarch64-linux-gnu > /tmp/arch || true diff --git a/devenv/bitcoin/docker/entrypoint.sh b/devenv/bitcoin/docker/entrypoint.sh index f71462c4..c1987058 100644 --- a/devenv/bitcoin/docker/entrypoint.sh +++ b/devenv/bitcoin/docker/entrypoint.sh @@ -2,10 +2,5 @@ set -x -#-rpcuser=${BTC_RPCUSER} -rpcpassword=${BTC_RPCPASSWORD} - -# bitcoind needs creds set in the conf file for remote RPC auth -#echo '[regtest]' > ${BITCOIN_CONF} - nginx bitcoind -chain=${BTC_NETWORK} -txindex=${BTC_TXINDEX} -rpcuser=${BTC_RPCUSER} -rpcpassword=${BTC_RPCPASSWORD} -printtoconsole=${BTC_PRINTTOCONSOLE} -disablewallet=${BTC_DISABLEWALLET} -rpcbind=${BTC_RPCBIND} -rpcallowip=${BTC_RPCALLOWIP} diff --git a/devenv/sbtc/docker/Dockerfile b/devenv/sbtc/docker/Dockerfile index 81449b6c..c6f1edce 100644 --- a/devenv/sbtc/docker/Dockerfile +++ b/devenv/sbtc/docker/Dockerfile @@ -28,4 +28,4 @@ ADD devenv/sbtc/docker/entrypoint /usr/local/bin RUN chmod a+x /usr/local/bin/entrypoint ADD romeo/asset-contract /asset-contract -ENTRYPOINT ["entrypoint"] \ No newline at end of file +ENTRYPOINT ["entrypoint"] From 71507e507246211081a1d5380cea7f1ebf2fde74 Mon Sep 17 00:00:00 2001 From: Carlos Alejandro Gutierrez Sandoval Date: Mon, 18 Sep 2023 11:43:52 -0600 Subject: [PATCH 02/30] auto port mapping, auto container name --- devenv/docker-compose.yml | 48 ++++++++++++++------------------------- 1 file changed, 17 insertions(+), 31 deletions(-) diff --git a/devenv/docker-compose.yml b/devenv/docker-compose.yml index 61d96ff1..452b9d14 100644 --- a/devenv/docker-compose.yml +++ b/devenv/docker-compose.yml @@ -3,16 +3,15 @@ version: '3.2' services: bitcoin: image: bitcoin:latest - container_name: bitcoin stop_grace_period: 5s build: context: ./bitcoin/docker args: VERSION: '25.0' ports: - - 18444:18444 - - 18443:18443 - - 18433:18433 + - 18444 + - 18443 + - 18433 environment: - 'BTC_NETWORK=regtest' - 'BTC_DISABLEWALLET=0' @@ -23,29 +22,26 @@ services: - 'BTC_RPCUSER=devnet' postgres: image: postgres:15-alpine - container_name: postgres stop_grace_period: 5s ports: - - 5432:5432 + - 5432 environment: - POSTGRES_USER=postgres - POSTGRES_PASSWORD=postgres mongodb: image: mongo:6.0 - container_name: mongodb stop_grace_period: 5s ports: - - 27017:27017 + - 27017 environment: MONGO_INITDB_ROOT_USERNAME: devnet MONGO_INITDB_ROOT_PASSWORD: devnet MONGO_INITDB_DATABASE: devnet mempool-db: image: mariadb:10.5.21 - container_name: mempool-db stop_grace_period: 5s ports: - - 3306:3306 + - 3306 environment: MYSQL_DATABASE: "mempool" MYSQL_USER: "mempool" @@ -53,7 +49,6 @@ services: MYSQL_ROOT_PASSWORD: "admin" miner: image: miner:latest - container_name: miner stop_grace_period: 5s build: context: ./miner/docker @@ -64,7 +59,6 @@ services: BTC_BLOCK_GEN_TIME: 10 stacks: image: stacks:latest - container_name: stacks stop_grace_period: 5s build: context: ./stacks/docker @@ -73,8 +67,8 @@ services: GIT_URI: https://github.com/stacks-network/stacks-blockchain.git GIT_BRANCH: develop ports: - - 20444:20444 - - 20443:20443 + - 20444 + - 20443 depends_on: - bitcoin - miner @@ -83,7 +77,6 @@ services: - STACKS_LOG_JSON=0 stacks-api: image: stacks-api:latest - container_name: stacks-api stop_grace_period: 5s build: context: ./stacks-api/docker @@ -91,8 +84,8 @@ services: GIT_URI: 'https://github.com/hirosystems/stacks-blockchain-api.git' GIT_BRANCH: 'v7.3.0' ports: - - 3999:3999 - - 3700:3700 + - 3999 + - 3700 depends_on: - postgres - stacks @@ -115,7 +108,6 @@ services: - API_DOCS_URL=http://localhost:3999/doc stacks-explorer: image: stacks-explorer - container_name: stacks-explorer stop_grace_period: 5s build: context: ./stacks-explorer/docker @@ -124,7 +116,7 @@ services: GIT_URI: https://github.com/hirosystems/explorer.git GIT_BRANCH: v1.119.0 ports: - - 3020:3000 + - 3000 depends_on: - bitcoin - stacks @@ -134,13 +126,12 @@ services: - NEXT_PUBLIC_MAINNET_API_SERVER=http://127.0.0.1:3999 electrs: image: electrs:latest - container_name: electrs stop_grace_period: 5s build: context: ./electrs/docker ports: - - 60401:60401 - - 3002:3002 + - 60401 + - 3002 depends_on: - bitcoin - miner @@ -148,7 +139,6 @@ services: RUST_BACKTRACE: 1 sbtc: image: sbtc:latest - container_name: sbtc stop_grace_period: 5s restart: on-failure build: @@ -167,7 +157,6 @@ services: - $PWD/sbtc/docker/config.json:/romeo/config.json sbtc-bridge-api: image: sbtc-bridge-api:latest - container_name: sbtc-bridge-api stop_grace_period: 5s build: context: ./sbtc-bridge-api/docker @@ -185,7 +174,7 @@ services: - mongodb - sbtc ports: - - 3010:3010 + - 3010 environment: NODE_ENV: dev btcNode: bitcoin:18443 @@ -202,7 +191,6 @@ services: mongoPwd: devnet sbtc-bridge-web: image: sbtc-bridge-web:latest - container_name: sbtc-bridge-web stop_grace_period: 5s build: context: ./sbtc-bridge-web/docker @@ -216,10 +204,9 @@ services: - sbtc - sbtc-bridge-api ports: - - 8080:8080 + - 8080 mempool-web: image: mempool/frontend:latest - container_name: mempool-web stop_grace_period: 5s depends_on: - mempool-api @@ -227,14 +214,13 @@ services: user: "1000:1000" restart: on-failure ports: - - 8083:8083 + - 8083 environment: FRONTEND_HTTP_PORT: "8083" BACKEND_MAINNET_HTTP_HOST: "mempool-api" command: "./wait-for mempool-db:3306 --timeout=720 -- nginx -g 'daemon off;'" mempool-api: image: mempool/backend:latest - container_name: mempool-api stop_grace_period: 5s depends_on: - electrs @@ -242,7 +228,7 @@ services: user: "1000:1000" restart: on-failure ports: - - 8999:8999 + - 8999 environment: # Connect to electrs host MEMPOOL_BACKEND: "electrum" From df894496d4bf5fbf3a11104151934b5528c08574 Mon Sep 17 00:00:00 2001 From: Carlos Alejandro Gutierrez Sandoval Date: Mon, 18 Sep 2023 10:54:02 -0600 Subject: [PATCH 03/30] Testing container --- .dockerignore | 1 + devenv/tests/bin/build | 3 ++ devenv/tests/bin/down | 8 +++++ devenv/tests/bin/test | 59 +++++++++++++++++++++++++++++++++ devenv/tests/bin/up | 8 +++++ devenv/tests/docker-compose.yml | 14 ++++++++ devenv/tests/docker/Dockerfile | 19 +++++++++++ devenv/tests/docker/entrypoint | 24 ++++++++++++++ 8 files changed, 136 insertions(+) create mode 100755 devenv/tests/bin/build create mode 100755 devenv/tests/bin/down create mode 100755 devenv/tests/bin/test create mode 100755 devenv/tests/bin/up create mode 100644 devenv/tests/docker-compose.yml create mode 100644 devenv/tests/docker/Dockerfile create mode 100755 devenv/tests/docker/entrypoint diff --git a/.dockerignore b/.dockerignore index 80eae735..feb5045b 100644 --- a/.dockerignore +++ b/.dockerignore @@ -3,6 +3,7 @@ target Cargo.lock devenv/* !devenv/sbtc/docker/entrypoint +!devenv/tests/docker/entrypoint coverage romeo/testing/ romeo/asset-contract/.test diff --git a/devenv/tests/bin/build b/devenv/tests/bin/build new file mode 100755 index 00000000..3de2abf8 --- /dev/null +++ b/devenv/tests/bin/build @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +docker compose -f docker-compose.yml build diff --git a/devenv/tests/bin/down b/devenv/tests/bin/down new file mode 100755 index 00000000..9c921978 --- /dev/null +++ b/devenv/tests/bin/down @@ -0,0 +1,8 @@ +#!/usr/bin/env sh + +if [ -z "$2" ]; then + echo "Use: down " + exit 1 +fi + +docker compose -f $1 -p $2 down diff --git a/devenv/tests/bin/test b/devenv/tests/bin/test new file mode 100755 index 00000000..90554052 --- /dev/null +++ b/devenv/tests/bin/test @@ -0,0 +1,59 @@ +#!/usr/bin/env bash +set -ueo >/dev/null + +project_name() { + local input="$1" + local filter="${input//[![:alnum:]]/_}" + echo "$filter" +} + +filters=("package(romeo)" "test(deposit_parse)" "test(deposit_output)") +filter_union="" +ids=() +projects=() + +for filter in "${filters[@]}"; do + project=$(project_name $filter) + echo Working project: $project + projects+=($project) + + ./tests/bin/down docker-compose.yml $project + ./tests/bin/up docker-compose.yml $project + + network=${project}_default + id=$(docker run -td --network $network -e PROJECT_NAME=$project testbed "$filter") + ids+=($id) + echo with container id: $id + + if [ -z "$filter_union" ]; then + filter_union=$filter + else + filter_union="$filter_union | $filter" + fi + +done + +project="remainder--" +echo Working project: $project +projects+=($project) + +./tests/bin/down docker-compose.yml $project +./tests/bin/up docker-compose.yml $project + +network=${project}_default +id=($(docker run -td --network $network -e PROJECT_NAME=$project testbed "not ($filter_union)")) +ids+=($id) +echo with container id: $id + +for id in ${ids[@]}; do + exit_code=$(docker wait $id) + echo Runner: $id exited with err_no: $exit_code + if [ $exit_code -ne 0 ]; then + docker logs -f $id + exit $exit_code + fi +done + +for project in ${projects[@]}; do + ./tests/bin/down docker-compose.yml $project +done diff --git a/devenv/tests/bin/up b/devenv/tests/bin/up new file mode 100755 index 00000000..d3a37d33 --- /dev/null +++ b/devenv/tests/bin/up @@ -0,0 +1,8 @@ +#!/usr/bin/env sh + +if [ -z "$2" ]; then + echo "Use: up " + exit 1 +fi + +docker compose -f $1 -p $2 up -d --remove-orphans diff --git a/devenv/tests/docker-compose.yml b/devenv/tests/docker-compose.yml new file mode 100644 index 00000000..d60fda8a --- /dev/null +++ b/devenv/tests/docker-compose.yml @@ -0,0 +1,14 @@ +version: '3.2' + +networks: + devenv_default: + external: true + +services: + testbed: + image: testbed:latest + build: + dockerfile: devenv/tests/docker/Dockerfile + context: ./../../ + networks: + - devenv_default diff --git a/devenv/tests/docker/Dockerfile b/devenv/tests/docker/Dockerfile new file mode 100644 index 00000000..0c5cb2c1 --- /dev/null +++ b/devenv/tests/docker/Dockerfile @@ -0,0 +1,19 @@ +FROM rust:alpine + +RUN apk add --no-cache g++ musl-dev git openssl-dev clang-dev libsecp256k1-dev + +RUN rustup component add rustfmt + +ENV RUSTFLAGS "-C target-feature=-crt-static" + +RUN cargo install cargo-nextest --locked + +COPY . . + +RUN cargo nextest list + +RUN apk --no-cache add curl jq + +COPY ./devenv/tests/docker/entrypoint /bin/entrypoint + +ENTRYPOINT [ "entrypoint" ] diff --git a/devenv/tests/docker/entrypoint b/devenv/tests/docker/entrypoint new file mode 100755 index 00000000..425396db --- /dev/null +++ b/devenv/tests/docker/entrypoint @@ -0,0 +1,24 @@ +#!/usr/bin/env sh +set -ueo >/dev/null + +STACKS=$PROJECT_NAME-stacks-1 +API_URL=http://$STACKS:20443/v2/info + +# makes sure the node is ready before proceeding +# stacks node get info +echo "Waiting on Stacks API" +while ! curl -s $API_URL >/dev/null; do + sleep 1 +done + +DEV_READY_HEIGHT=205 + +# bitcoind get info +echo "Waiting on burn block height $DEV_READY_HEIGHT" +while [ "$(curl -s $API_URL | jq '.burn_block_height')" -lt $DEV_READY_HEIGHT ]; do + sleep 2 +done +# any other service checks + +echo with filter: "'$@'" +cargo nextest run -E "$@" From d3639af1bd0c90b3239604123d4aad09c74aabcf Mon Sep 17 00:00:00 2001 From: Carlos Alejandro Gutierrez Sandoval Date: Mon, 2 Oct 2023 17:25:51 -0600 Subject: [PATCH 04/30] move integration container --- .dockerignore | 2 +- devenv/{tests => integration}/bin/build | 0 devenv/{tests => integration}/bin/down | 0 devenv/{tests => integration}/bin/test | 10 +++++----- devenv/{tests => integration}/bin/up | 0 devenv/{tests => integration}/docker-compose.yml | 2 +- devenv/{tests => integration}/docker/Dockerfile | 2 +- devenv/{tests => integration}/docker/entrypoint | 0 8 files changed, 8 insertions(+), 8 deletions(-) rename devenv/{tests => integration}/bin/build (100%) rename devenv/{tests => integration}/bin/down (100%) rename devenv/{tests => integration}/bin/test (81%) rename devenv/{tests => integration}/bin/up (100%) rename devenv/{tests => integration}/docker-compose.yml (76%) rename devenv/{tests => integration}/docker/Dockerfile (84%) rename devenv/{tests => integration}/docker/entrypoint (100%) diff --git a/.dockerignore b/.dockerignore index feb5045b..a80092a0 100644 --- a/.dockerignore +++ b/.dockerignore @@ -3,7 +3,7 @@ target Cargo.lock devenv/* !devenv/sbtc/docker/entrypoint -!devenv/tests/docker/entrypoint +!devenv/integration/docker/entrypoint coverage romeo/testing/ romeo/asset-contract/.test diff --git a/devenv/tests/bin/build b/devenv/integration/bin/build similarity index 100% rename from devenv/tests/bin/build rename to devenv/integration/bin/build diff --git a/devenv/tests/bin/down b/devenv/integration/bin/down similarity index 100% rename from devenv/tests/bin/down rename to devenv/integration/bin/down diff --git a/devenv/tests/bin/test b/devenv/integration/bin/test similarity index 81% rename from devenv/tests/bin/test rename to devenv/integration/bin/test index 90554052..4b48a9f9 100755 --- a/devenv/tests/bin/test +++ b/devenv/integration/bin/test @@ -17,8 +17,8 @@ for filter in "${filters[@]}"; do echo Working project: $project projects+=($project) - ./tests/bin/down docker-compose.yml $project - ./tests/bin/up docker-compose.yml $project + ./integration/bin/down docker-compose.yml $project + ./integration/bin/up docker-compose.yml $project network=${project}_default id=$(docker run -td --network $network -e PROJECT_NAME=$project testbed "$filter") @@ -37,8 +37,8 @@ project="remainder--" echo Working project: $project projects+=($project) -./tests/bin/down docker-compose.yml $project -./tests/bin/up docker-compose.yml $project +./integration/bin/down docker-compose.yml $project +./integration/bin/up docker-compose.yml $project network=${project}_default id=($(docker run -td --network $network -e PROJECT_NAME=$project testbed "not ($filter_union)")) @@ -55,5 +55,5 @@ for id in ${ids[@]}; do done for project in ${projects[@]}; do - ./tests/bin/down docker-compose.yml $project + ./integration/bin/down docker-compose.yml $project done diff --git a/devenv/tests/bin/up b/devenv/integration/bin/up similarity index 100% rename from devenv/tests/bin/up rename to devenv/integration/bin/up diff --git a/devenv/tests/docker-compose.yml b/devenv/integration/docker-compose.yml similarity index 76% rename from devenv/tests/docker-compose.yml rename to devenv/integration/docker-compose.yml index d60fda8a..9bc2f9e0 100644 --- a/devenv/tests/docker-compose.yml +++ b/devenv/integration/docker-compose.yml @@ -8,7 +8,7 @@ services: testbed: image: testbed:latest build: - dockerfile: devenv/tests/docker/Dockerfile + dockerfile: devenv/integration/docker/Dockerfile context: ./../../ networks: - devenv_default diff --git a/devenv/tests/docker/Dockerfile b/devenv/integration/docker/Dockerfile similarity index 84% rename from devenv/tests/docker/Dockerfile rename to devenv/integration/docker/Dockerfile index 0c5cb2c1..54e85edc 100644 --- a/devenv/tests/docker/Dockerfile +++ b/devenv/integration/docker/Dockerfile @@ -14,6 +14,6 @@ RUN cargo nextest list RUN apk --no-cache add curl jq -COPY ./devenv/tests/docker/entrypoint /bin/entrypoint +COPY ./devenv/integration/docker/entrypoint /bin/entrypoint ENTRYPOINT [ "entrypoint" ] diff --git a/devenv/tests/docker/entrypoint b/devenv/integration/docker/entrypoint similarity index 100% rename from devenv/tests/docker/entrypoint rename to devenv/integration/docker/entrypoint From 7c365afa51f9c508a03b4bb7245bc8131632caf2 Mon Sep 17 00:00:00 2001 From: Carlos Alejandro Gutierrez Sandoval Date: Mon, 2 Oct 2023 17:52:46 -0600 Subject: [PATCH 05/30] target integration tests --- devenv/integration/docker/entrypoint | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devenv/integration/docker/entrypoint b/devenv/integration/docker/entrypoint index 425396db..41f4ff7e 100755 --- a/devenv/integration/docker/entrypoint +++ b/devenv/integration/docker/entrypoint @@ -21,4 +21,4 @@ done # any other service checks echo with filter: "'$@'" -cargo nextest run -E "$@" +cargo nextest run -E "$@ and kind(test)" From ba011c81b8d063dea89df471d8ec79efd9a62607 Mon Sep 17 00:00:00 2001 From: Carlos Alejandro Gutierrez Sandoval Date: Mon, 2 Oct 2023 19:11:39 -0600 Subject: [PATCH 06/30] Init readme --- devenv/integration/README.md | 102 +++++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 devenv/integration/README.md diff --git a/devenv/integration/README.md b/devenv/integration/README.md new file mode 100644 index 00000000..7f99e275 --- /dev/null +++ b/devenv/integration/README.md @@ -0,0 +1,102 @@ +## Containerized integration testing + +Hi! Congratulations on making it this far. By now, you should know how to launch +a full node for local development using devnev. To take a step further, these +are the steps you need to follow to run and implement integration tests using +devenv. + +## Quickstart + +1. Build the testbed image with the source and binaries to execute the + integration tests. +2. Run the testing script. + +```bash +> devenv$ cd integration +> devenv/integration$ ./bin/build +> cd - +> devenv$ ./integration/bin/test +``` + +You will see lines like the one below if your tests are completed successfully. + +``` +Runner: 71c1beca7261e2c1b706fa0e9eeb3823ad56977c4846c89b25a315a48a1bbda5 exited with err_no: 0 +Runner: fb2da5362115b5ae37874d39831bbf249b2928c58296dfb23af94b7083d8455b exited with err_no: 0 +``` + +Use the container id to inspect the runner for more details. + +``` +> devenv$ ./logs.sh 71c1beca7261e2c1b706fa0e9eeb3823ad56977c4846c89b25a315a48a1bbda5 +``` + +The script will abort the moment a container fails. The script will print the +logs from the first failed container. You must stop the nodes with +`docker stop $(docker ps -q)`. You can also rerun `test` to 'down' and 'up' any +dangling container and re-execute tests once you have fixed and rebuilt the +testbed image. + +### Running integration tests. + +Start at devenv, `pushd` integration. You will find scripts in the `bin` folder +in this directory. The ones you will be using are `build` and `test`. `Test` is +how you are expected to run the suite. Run `bin/build` and `popd,` back in +devenv, and run `integration/bin/test`. + +### Adding grouping filters + +In /devenv/integration/test, there is a filter array that determines how many +nodes will be spun and what tests will run in parallel inside the node. + +```bash +filters=("package(romeo)" "test(deposit_parse)" "test(deposit_output)") +``` + +Use Nextest's DSL to group up your integration tests. Add new filters as you see +fit. + +### Adding node readiness checks. + +In `devenv/integration/docker/entrypoint`, you can add checks to wait until a +node is ready to take tests. + +In this snip, we wait until the stacks api is responsive and the burchain block +height is 205. + +```bash +STACKS=$PROJECT_NAME-stacks-1 +API_URL=http://$STACKS:20443/v2/info + +# it makes sure the node is ready before proceeding +# stacks node get info +echo "Waiting on Stacks API" +while ! curl -s $API_URL >/dev/null; do + sleep 1 +done + +DEV_READY_HEIGHT=205 + +# bitcoind get info +echo "Waiting on burn block height $DEV_READY_HEIGHT" +while [ "$(curl -s $API_URL | jq '.burn_block_height')" -lt $DEV_READY_HEIGHT ]; do + sleep 2 +done +``` + +### Troubleshooting. + +- If a fresh network fails to be created or you spot a line like the one below, you need + to stop all containers and prune the network. + +``` +! Network test_deposit__default Resource is still in use 0.0s +``` + +``` +> docker stop $(docker ps -q) +> docker network prune +``` + +- The order of magnitude for the tests should be in **minutes**. It took 4mins + in my system last time I checked. From 8d855c2434987d40208e1f6ce61e900238666b09 Mon Sep 17 00:00:00 2001 From: Carlos Alejandro Gutierrez Sandoval Date: Wed, 4 Oct 2023 00:21:02 -0600 Subject: [PATCH 07/30] Add clarinet deployments --- devenv/integration/docker/Dockerfile | 32 ++++++++++++++++++++++++---- devenv/integration/docker/entrypoint | 6 +++++- 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/devenv/integration/docker/Dockerfile b/devenv/integration/docker/Dockerfile index 54e85edc..a36a521e 100644 --- a/devenv/integration/docker/Dockerfile +++ b/devenv/integration/docker/Dockerfile @@ -1,6 +1,16 @@ -FROM rust:alpine +FROM rust:bookworm as clarinet -RUN apk add --no-cache g++ musl-dev git openssl-dev clang-dev libsecp256k1-dev +RUN apt update && apt install -y pkg-config libssl-dev clang && \ + rm -rf /var/lib/apt/lists/* + +RUN rustup update 1.71 && rustup default 1.71 +RUN cargo install clarinet-cli --bin clarinet --branch develop --locked --git https://github.com/hirosystems/clarinet.git + +FROM rust:bookworm as romeo + +RUN apt update && \ + apt install -y libssl-dev libclang-dev libsecp256k1-dev && \ + rm -rf /var/lib/apt/lists/* RUN rustup component add rustfmt @@ -10,9 +20,23 @@ RUN cargo install cargo-nextest --locked COPY . . -RUN cargo nextest list +RUN cargo nextest archive --archive-file integration-tests.tar.zst + +FROM rust:bookworm as runtime + +RUN apt update && \ + apt install -y openssl ca-certificates + +COPY --from=clarinet /usr/local/cargo/bin/clarinet /usr/bin -RUN apk --no-cache add curl jq +RUN apt install -y curl jq && \ + rm -rf /var/lib/apt/lists/* + +RUN cargo install cargo-nextest --locked + +COPY --from=romeo integration-tests.tar.zst . + +COPY . . COPY ./devenv/integration/docker/entrypoint /bin/entrypoint diff --git a/devenv/integration/docker/entrypoint b/devenv/integration/docker/entrypoint index 41f4ff7e..d5f813d9 100755 --- a/devenv/integration/docker/entrypoint +++ b/devenv/integration/docker/entrypoint @@ -19,6 +19,10 @@ while [ "$(curl -s $API_URL | jq '.burn_block_height')" -lt $DEV_READY_HEIGHT ]; sleep 2 done # any other service checks +# push contracts +cd romeo/asset-contract +clarinet deployments apply -d -p deployments/default.devnet-plan.yaml +cd - echo with filter: "'$@'" -cargo nextest run -E "$@ and kind(test)" +cargo nextest run -E "$@ and kind(test)" --archive-file integration-tests.tar.zst From 33b81f591b2615a89879f2045c1c955147aec6e9 Mon Sep 17 00:00:00 2001 From: Carlos Alejandro Gutierrez Sandoval Date: Wed, 4 Oct 2023 01:01:13 -0600 Subject: [PATCH 08/30] Single filter as placeholder --- devenv/integration/bin/test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devenv/integration/bin/test b/devenv/integration/bin/test index 4b48a9f9..d3e6ff19 100755 --- a/devenv/integration/bin/test +++ b/devenv/integration/bin/test @@ -7,7 +7,7 @@ project_name() { echo "$filter" } -filters=("package(romeo)" "test(deposit_parse)" "test(deposit_output)") +filters=("package(romeo)") filter_union="" ids=() projects=() From 0526168da8c3f48785426aeee0f2c7180f6817f9 Mon Sep 17 00:00:00 2001 From: Carlos Alejandro Gutierrez Sandoval Date: Thu, 5 Oct 2023 14:01:31 -0600 Subject: [PATCH 09/30] mount entrypoint --- .dockerignore | 1 - devenv/integration/bin/test | 6 ++++-- devenv/integration/docker/Dockerfile | 2 -- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/.dockerignore b/.dockerignore index a80092a0..80eae735 100644 --- a/.dockerignore +++ b/.dockerignore @@ -3,7 +3,6 @@ target Cargo.lock devenv/* !devenv/sbtc/docker/entrypoint -!devenv/integration/docker/entrypoint coverage romeo/testing/ romeo/asset-contract/.test diff --git a/devenv/integration/bin/test b/devenv/integration/bin/test index d3e6ff19..5bd47c59 100755 --- a/devenv/integration/bin/test +++ b/devenv/integration/bin/test @@ -1,6 +1,8 @@ #!/usr/bin/env bash set -ueo >/dev/null +ENTRYPOINT_PATH="./integration/docker/entrypoint" + project_name() { local input="$1" local filter="${input//[![:alnum:]]/_}" @@ -21,7 +23,7 @@ for filter in "${filters[@]}"; do ./integration/bin/up docker-compose.yml $project network=${project}_default - id=$(docker run -td --network $network -e PROJECT_NAME=$project testbed "$filter") + id=$(docker run -td --network $network -v $ENTRYPOINT_PATH:/usr/local/bin/entrypoint -e PROJECT_NAME=$project testbed "$filter") ids+=($id) echo with container id: $id @@ -41,7 +43,7 @@ projects+=($project) ./integration/bin/up docker-compose.yml $project network=${project}_default -id=($(docker run -td --network $network -e PROJECT_NAME=$project testbed "not ($filter_union)")) +id=($(docker run -td --network $network -v $ENTRYPOINT_PATH:/usr/local/bin/entrypoint -e PROJECT_NAME=$project testbed "not ($filter_union)")) ids+=($id) echo with container id: $id diff --git a/devenv/integration/docker/Dockerfile b/devenv/integration/docker/Dockerfile index a36a521e..8deafe62 100644 --- a/devenv/integration/docker/Dockerfile +++ b/devenv/integration/docker/Dockerfile @@ -38,6 +38,4 @@ COPY --from=romeo integration-tests.tar.zst . COPY . . -COPY ./devenv/integration/docker/entrypoint /bin/entrypoint - ENTRYPOINT [ "entrypoint" ] From b380ed5da6cda37a5decbc6f46c9c307d2743118 Mon Sep 17 00:00:00 2001 From: Carlos Alejandro Gutierrez Sandoval Date: Thu, 5 Oct 2023 14:53:48 -0600 Subject: [PATCH 10/30] Contract deployment --- devenv/integration/docker/entrypoint | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/devenv/integration/docker/entrypoint b/devenv/integration/docker/entrypoint index d5f813d9..e0318f23 100755 --- a/devenv/integration/docker/entrypoint +++ b/devenv/integration/docker/entrypoint @@ -3,10 +3,12 @@ set -ueo >/dev/null STACKS=$PROJECT_NAME-stacks-1 API_URL=http://$STACKS:20443/v2/info +BITCOIN=$PROJECT_NAME-bitcoin-1 + +# --- make sure the node is ready before proceeding -# makes sure the node is ready before proceeding # stacks node get info -echo "Waiting on Stacks API" +echo "Waiting on Stacks API $API_URL" while ! curl -s $API_URL >/dev/null; do sleep 1 done @@ -18,10 +20,21 @@ echo "Waiting on burn block height $DEV_READY_HEIGHT" while [ "$(curl -s $API_URL | jq '.burn_block_height')" -lt $DEV_READY_HEIGHT ]; do sleep 2 done + +#stacks ready to take contracts +STACKS_HEIGHT=1 +echo "Waiting on Stacks height $STACKS_HEIGHT" +while [ "$(curl -s $API_URL | jq '.stacks_tip_height')" -lt $STACKS_HEIGHT ]; do + sleep 2 +done + # any other service checks + # push contracts cd romeo/asset-contract -clarinet deployments apply -d -p deployments/default.devnet-plan.yaml +sed -i "s/localhost:20443/$STACKS:20443/" deployments/default.devnet-plan.yaml +sed -i "s/localhost:18443/$BITCOIN:18443/" deployments/default.devnet-plan.yaml +clarinet deployments apply --no-dashboard -d -p deployments/default.devnet-plan.yaml cd - echo with filter: "'$@'" From 2270cb45ebcb3ed1421ffa24ca41e2b6c3ca64cc Mon Sep 17 00:00:00 2001 From: Carlos Alejandro Gutierrez Sandoval Date: Thu, 12 Oct 2023 21:25:37 -0600 Subject: [PATCH 11/30] Ignore integration tests in coverage --- Makefile.toml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Makefile.toml b/Makefile.toml index 8d8e90ae..643c1cf1 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -17,7 +17,7 @@ TEST_FRAMEWORK = "nextest" [env.github-actions] # TODO: use nexttest when testing becomes too much to do in series. # Installing nexttest takes > 5 minutes on github workflow machines. -TEST_FRAMEWORK = "test" +TEST_FRAMEWORK = "nextest" # Installations # -------------- @@ -86,7 +86,9 @@ args = [ "llvm-cov", "${TEST_FRAMEWORK}", "--all-features", - "--no-report" + "--no-report", + "-E", + "not (kind(test))", ] [tasks._calculated-coverage-clean] From 44fc0b7aa4048196a20c61d20978977a0437d550 Mon Sep 17 00:00:00 2001 From: Carlos Alejandro Gutierrez Sandoval Date: Thu, 5 Oct 2023 16:02:11 -0600 Subject: [PATCH 12/30] Integration: mine empty block --- romeo/Cargo.toml | 3 ++ romeo/tests/bitcoin_client_integration.rs | 50 +++++++++++++++++++++++ 2 files changed, 53 insertions(+) create mode 100644 romeo/tests/bitcoin_client_integration.rs diff --git a/romeo/Cargo.toml b/romeo/Cargo.toml index 58483c26..7b01b468 100644 --- a/romeo/Cargo.toml +++ b/romeo/Cargo.toml @@ -23,3 +23,6 @@ tracing-subscriber.workspace = true tracing.workspace = true url.workspace = true rs_merkle.workspace = true + +[dev-dependencies] +reqwest = { workspace = true, features = ["json", "blocking"] } diff --git a/romeo/tests/bitcoin_client_integration.rs b/romeo/tests/bitcoin_client_integration.rs new file mode 100644 index 00000000..460e3e12 --- /dev/null +++ b/romeo/tests/bitcoin_client_integration.rs @@ -0,0 +1,50 @@ +use std::env; + +use reqwest::blocking::Client; +use serde_json::json; +use url::Url; + +pub fn project_name() -> String { + env::var("PROJECT_NAME").unwrap() +} + +pub fn bitcoin_url() -> Url { + let base = project_name(); + Url::parse(&format!("http://{base}-bitcoin-1:18443")).unwrap() +} + +pub fn electrs_url() -> Url { + let base = project_name(); + Url::parse(&format!("tcp://{base}-electrs-1:60401")).unwrap() +} + +pub fn generate_blocks(blocks: u64, ctx: &Client, address: &str) { + let endpoint = bitcoin_url(); + let user = "devnet"; + let password = "devnet"; + let body = json!({ + "jsonrpc": "1.0", + "id": "1", + "method": "generatetoaddress", + //developer's + "params": [blocks,address] + }); + + let response_json: serde_json::Value = ctx + .post(endpoint) + .basic_auth(user, Some(password)) + .header(reqwest::header::CONTENT_TYPE, "application/json") + .json(&body) + .send() + .unwrap() + .json() + .unwrap(); + + assert_eq!(response_json["error"], serde_json::Value::Null); +} + +#[test] +fn mine_empty_block() { + let client = Client::new(); + generate_blocks(10, &client, "mqVnk6NPRdhntvfm4hh9vvjiRkFDUuSYsH"); +} From 964323185a40381d844894861aff95749647abc3 Mon Sep 17 00:00:00 2001 From: Carlos Alejandro Gutierrez Sandoval Date: Wed, 11 Oct 2023 16:18:06 -0600 Subject: [PATCH 13/30] use romeo as library --- romeo/Cargo.toml | 1 + sbtc-cli/src/lib.rs | 1 + sbtc-cli/src/main.rs | 5 +---- 3 files changed, 3 insertions(+), 4 deletions(-) create mode 100644 sbtc-cli/src/lib.rs diff --git a/romeo/Cargo.toml b/romeo/Cargo.toml index 7b01b468..eba62167 100644 --- a/romeo/Cargo.toml +++ b/romeo/Cargo.toml @@ -26,3 +26,4 @@ rs_merkle.workspace = true [dev-dependencies] reqwest = { workspace = true, features = ["json", "blocking"] } +sbtc-cli = { path = "../sbtc-cli" } diff --git a/sbtc-cli/src/lib.rs b/sbtc-cli/src/lib.rs new file mode 100644 index 00000000..82b6da3c --- /dev/null +++ b/sbtc-cli/src/lib.rs @@ -0,0 +1 @@ +pub mod commands; diff --git a/sbtc-cli/src/main.rs b/sbtc-cli/src/main.rs index 66e18699..2f0a3623 100644 --- a/sbtc-cli/src/main.rs +++ b/sbtc-cli/src/main.rs @@ -7,16 +7,13 @@ //! and interact with the Bitcoin and Stacks networks. use clap::{Parser, Subcommand}; - -use crate::commands::{ +use sbtc_cli::commands::{ broadcast::{broadcast_tx, BroadcastArgs}, deposit::{build_deposit_tx, DepositArgs}, generate::{generate, GenerateArgs}, withdraw::{build_withdrawal_tx, WithdrawalArgs}, }; -mod commands; - #[derive(Parser)] struct Cli { #[command(subcommand)] From 7cf8564ea8f0d98bb4d1705b07ec98632e679bb7 Mon Sep 17 00:00:00 2001 From: Carlos Alejandro Gutierrez Sandoval Date: Sun, 8 Oct 2023 23:26:39 -0600 Subject: [PATCH 14/30] fetch stacks height --- romeo/tests/stacks_client_integration.rs | 30 ++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 romeo/tests/stacks_client_integration.rs diff --git a/romeo/tests/stacks_client_integration.rs b/romeo/tests/stacks_client_integration.rs new file mode 100644 index 00000000..59c49a6f --- /dev/null +++ b/romeo/tests/stacks_client_integration.rs @@ -0,0 +1,30 @@ +use std::env; + +use reqwest::blocking::Client; +use url::Url; + +pub fn stacks_url() -> Url { + let base = env::var("PROJECT_NAME").unwrap(); + Url::parse(&format!("http://{base}-stacks-1:20443")).unwrap() +} + +pub fn fetch_stacks_height(ctx: &Client) -> u64 { + let endpoint = stacks_url().join("v2/info").unwrap(); + + let response_json: serde_json::Value = + ctx.get(endpoint).send().unwrap().json().unwrap(); + + serde_json::from_value(response_json["stacks_tip_height"].clone()).unwrap() +} + +// Test fetch stacks info. +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn fetch_height() { + let ctx = Client::new(); + fetch_stacks_height(&ctx); + } +} From 57039495489b30922a9940c8ea9ef6e70e43af88 Mon Sep 17 00:00:00 2001 From: Carlos Alejandro Gutierrez Sandoval Date: Wed, 11 Oct 2023 15:51:58 -0600 Subject: [PATCH 15/30] init deposit test --- devenv/integration/bin/test | 2 +- romeo/tests/bitcoin_client_integration.rs | 7 +- romeo/tests/deposit_integration.rs | 104 ++++++++++++++++++++++ sbtc-cli/src/commands/broadcast.rs | 4 +- sbtc-cli/src/commands/deposit.rs | 37 +++----- sbtc-cli/src/main.rs | 15 +++- 6 files changed, 140 insertions(+), 29 deletions(-) create mode 100644 romeo/tests/deposit_integration.rs diff --git a/devenv/integration/bin/test b/devenv/integration/bin/test index 5bd47c59..f5ef976c 100755 --- a/devenv/integration/bin/test +++ b/devenv/integration/bin/test @@ -9,7 +9,7 @@ project_name() { echo "$filter" } -filters=("package(romeo)") +filters=("test(deposit)") filter_union="" ids=() projects=() diff --git a/romeo/tests/bitcoin_client_integration.rs b/romeo/tests/bitcoin_client_integration.rs index 460e3e12..090bfe95 100644 --- a/romeo/tests/bitcoin_client_integration.rs +++ b/romeo/tests/bitcoin_client_integration.rs @@ -18,7 +18,11 @@ pub fn electrs_url() -> Url { Url::parse(&format!("tcp://{base}-electrs-1:60401")).unwrap() } -pub fn generate_blocks(blocks: u64, ctx: &Client, address: &str) { +pub fn generate_blocks( + blocks: u64, + ctx: &Client, + address: &str, +) -> Vec { let endpoint = bitcoin_url(); let user = "devnet"; let password = "devnet"; @@ -41,6 +45,7 @@ pub fn generate_blocks(blocks: u64, ctx: &Client, address: &str) { .unwrap(); assert_eq!(response_json["error"], serde_json::Value::Null); + serde_json::from_value(response_json["result"].clone()).expect("block_ids") } #[test] diff --git a/romeo/tests/deposit_integration.rs b/romeo/tests/deposit_integration.rs new file mode 100644 index 00000000..cfb70b78 --- /dev/null +++ b/romeo/tests/deposit_integration.rs @@ -0,0 +1,104 @@ +use std::{thread::sleep, time::Duration}; + +use anyhow::Result; +use bdk::bitcoin::psbt::serialize::Serialize; +use reqwest::blocking::Client; +use sbtc_cli::commands::{ + broadcast::{broadcast_tx, BroadcastArgs}, + deposit::{build_deposit_tx, DepositArgs}, +}; + +mod bitcoin_client_integration; + +use bitcoin_client_integration::{electrs_url, generate_blocks}; + +const WALLET_0_P2TR_ADDRESS: &str = + "bcrt1pte5zmd7qzj4hdu45lh9mmdm0nwq3z35pwnxmzkwld6y0a8g83nnqhj6vc0"; +const WALLET_0_P2WPKH_ADDRESS: &str = + "bcrt1q3tj2fr9scwmcw3rq5m6jslva65f2rqjxfrjz47"; +const WALLET_0_P2WPKH_WIF: &str = + "cTyHitzs3VRnNxrpwxo3fXTTe569wHNUs57tQM7Z1FrzUDNB5mqm"; +const WALLET_1_P2WPKH_WIF: &str = + "cNcXK2r8bNdWJQymtAW8tGS7QHNtFFvG5CdXqhhT752u29WspXRM"; +const WALLET_1_STX_ADDRESS: &str = "ST2ST2H80NP5C9SPR4ENJ1Z9CDM9PKAJVPYWPQZ50"; +const WALLET_1_P2WPKH_ADDRESS: &str = + "bcrt1q3zl64vadtuh3vnsuhdgv6pm93n82ye8q6cr4ch"; + +use bdk::{ + bitcoin::PrivateKey, + blockchain::{ + ConfigurableBlockchain, ElectrumBlockchain, ElectrumBlockchainConfig, + }, + database::MemoryDatabase, + template::P2Wpkh, + SyncOptions, Wallet, +}; + +#[test] +fn broadcast_deposit() -> Result<()> { + let client = Client::new(); + { + generate_blocks(1, &client, WALLET_0_P2WPKH_ADDRESS); + generate_blocks(1, &client, WALLET_1_P2WPKH_ADDRESS); + // pads blocks to get rewards. + generate_blocks(200, &client, WALLET_0_P2WPKH_ADDRESS); + }; + + let electrum_url = electrs_url(); + + // suboptimal, replace once we have better events. + { + let blockchain = + ElectrumBlockchain::from_config(&ElectrumBlockchainConfig { + url: electrum_url.clone().into(), + socks5: None, + retry: 3, + timeout: Some(10), + stop_gap: 10, + validate_domain: false, + }) + .unwrap(); + + let private_key = PrivateKey::from_wif(WALLET_1_P2WPKH_WIF)?; + + let wallet = Wallet::new( + P2Wpkh(private_key), + Some(P2Wpkh(private_key)), + bdk::bitcoin::Network::Regtest, + MemoryDatabase::default(), + ) + .unwrap(); + + loop { + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); + let balance = wallet.get_balance().unwrap(); + if balance.confirmed != 0 { + break; + } + sleep(Duration::from_millis(1_000)); + } + } + + let amount = 10_000; + + let tx = { + let args = DepositArgs { + node_url: electrum_url.clone(), + wif: WALLET_1_P2WPKH_WIF.into(), + network: bdk::bitcoin::Network::Regtest, + recipient: WALLET_1_STX_ADDRESS.into(), + amount, + sbtc_wallet: WALLET_0_P2TR_ADDRESS.into(), + }; + + build_deposit_tx(&args).unwrap() + }; + + broadcast_tx(&BroadcastArgs { + node_url: electrum_url, + tx: hex::encode(tx.serialize()), + }) + .unwrap(); + + Ok(()) +} diff --git a/sbtc-cli/src/commands/broadcast.rs b/sbtc-cli/src/commands/broadcast.rs index a2962484..93318849 100644 --- a/sbtc-cli/src/commands/broadcast.rs +++ b/sbtc-cli/src/commands/broadcast.rs @@ -10,10 +10,10 @@ use url::Url; #[derive(Parser, Debug, Clone)] pub struct BroadcastArgs { /// Where to broadcast the transaction - node_url: Url, + pub node_url: Url, /// The transaction to broadcast - tx: String, + pub tx: String, } pub fn broadcast_tx(broadcast: &BroadcastArgs) -> anyhow::Result<()> { diff --git a/sbtc-cli/src/commands/deposit.rs b/sbtc-cli/src/commands/deposit.rs index 4ad1190f..c78bcbf1 100644 --- a/sbtc-cli/src/commands/deposit.rs +++ b/sbtc-cli/src/commands/deposit.rs @@ -1,9 +1,9 @@ -use std::{io::stdout, str::FromStr}; +use std::str::FromStr; use bdk::{ bitcoin::{ - psbt::serialize::Serialize, Address as BitcoinAddress, - Network as BitcoinNetwork, PrivateKey, + Address as BitcoinAddress, Network as BitcoinNetwork, PrivateKey, + Transaction, }, blockchain::{ ConfigurableBlockchain, ElectrumBlockchain, ElectrumBlockchainConfig, @@ -17,36 +17,34 @@ use sbtc_core::operations::op_return::deposit::build_deposit_transaction; use stacks_core::utils::PrincipalData; use url::Url; -use crate::commands::utils; - #[derive(Parser, Debug, Clone)] pub struct DepositArgs { /// Where to broadcast the transaction #[clap(short('u'), long)] - node_url: Url, + pub node_url: Url, /// Bitcoin WIF of the P2wPKH address #[clap(short, long)] - wif: String, + pub wif: String, /// Bitcoin network where the deposit will be broadcasted to #[clap(short, long)] - network: BitcoinNetwork, + pub network: BitcoinNetwork, /// Stacks address that will receive sBTC #[clap(short, long)] - recipient: String, + pub recipient: String, /// The amount of sats to send #[clap(short, long)] - amount: u64, + pub amount: u64, /// Bitcoin address of the sbtc wallet #[clap(short, long)] - sbtc_wallet: String, + pub sbtc_wallet: String, } -pub fn build_deposit_tx(deposit: &DepositArgs) -> anyhow::Result<()> { +pub fn build_deposit_tx(deposit: &DepositArgs) -> anyhow::Result { let private_key = PrivateKey::from_wif(&deposit.wif)?; let blockchain = @@ -71,21 +69,12 @@ pub fn build_deposit_tx(deposit: &DepositArgs) -> anyhow::Result<()> { let stx_recipient = PrincipalData::try_from(deposit.recipient.to_string())?; let sbtc_wallet_address = BitcoinAddress::from_str(&deposit.sbtc_wallet)?; - let tx = build_deposit_transaction( + build_deposit_transaction( wallet, stx_recipient, sbtc_wallet_address, deposit.amount, deposit.network, - )?; - - serde_json::to_writer_pretty( - stdout(), - &utils::TransactionData { - id: tx.txid().to_string(), - hex: hex::encode(tx.serialize()), - }, - )?; - - Ok(()) + ) + .map_err(|e| e.into()) } diff --git a/sbtc-cli/src/main.rs b/sbtc-cli/src/main.rs index 2f0a3623..5a163375 100644 --- a/sbtc-cli/src/main.rs +++ b/sbtc-cli/src/main.rs @@ -5,12 +5,15 @@ //! //! It also allows you to generate credentials needed to generate transactions //! and interact with the Bitcoin and Stacks networks. +use std::io::stdout; +use bdk::bitcoin::psbt::serialize::Serialize; use clap::{Parser, Subcommand}; use sbtc_cli::commands::{ broadcast::{broadcast_tx, BroadcastArgs}, deposit::{build_deposit_tx, DepositArgs}, generate::{generate, GenerateArgs}, + utils, withdraw::{build_withdrawal_tx, WithdrawalArgs}, }; @@ -32,7 +35,17 @@ fn main() -> Result<(), anyhow::Error> { let args = Cli::parse(); match args.command { - Command::Deposit(deposit_args) => build_deposit_tx(&deposit_args), + Command::Deposit(deposit_args) => build_deposit_tx(&deposit_args) + .and_then(|t| { + serde_json::to_writer_pretty( + stdout(), + &utils::TransactionData { + id: t.txid().to_string(), + hex: hex::encode(t.serialize()), + }, + )?; + Ok(()) + }), Command::Withdraw(withdrawal_args) => { build_withdrawal_tx(&withdrawal_args) } From c57bfaccc547f7410d35f8ccf7b32eedf0e586c1 Mon Sep 17 00:00:00 2001 From: Carlos Alejandro Gutierrez Sandoval Date: Wed, 11 Oct 2023 15:01:27 -0600 Subject: [PATCH 16/30] fix duplicated integration tests --- romeo/tests/integration.rs | 1 + .../bitcoin_client.rs} | 0 romeo/tests/{deposit_integration.rs => tests/deposit.rs} | 4 +--- romeo/tests/tests/mod.rs | 3 +++ .../{stacks_client_integration.rs => tests/stacks_client.rs} | 0 5 files changed, 5 insertions(+), 3 deletions(-) create mode 100644 romeo/tests/integration.rs rename romeo/tests/{bitcoin_client_integration.rs => tests/bitcoin_client.rs} (100%) rename romeo/tests/{deposit_integration.rs => tests/deposit.rs} (96%) create mode 100644 romeo/tests/tests/mod.rs rename romeo/tests/{stacks_client_integration.rs => tests/stacks_client.rs} (100%) diff --git a/romeo/tests/integration.rs b/romeo/tests/integration.rs new file mode 100644 index 00000000..14f00389 --- /dev/null +++ b/romeo/tests/integration.rs @@ -0,0 +1 @@ +mod tests; diff --git a/romeo/tests/bitcoin_client_integration.rs b/romeo/tests/tests/bitcoin_client.rs similarity index 100% rename from romeo/tests/bitcoin_client_integration.rs rename to romeo/tests/tests/bitcoin_client.rs diff --git a/romeo/tests/deposit_integration.rs b/romeo/tests/tests/deposit.rs similarity index 96% rename from romeo/tests/deposit_integration.rs rename to romeo/tests/tests/deposit.rs index cfb70b78..991b4fe2 100644 --- a/romeo/tests/deposit_integration.rs +++ b/romeo/tests/tests/deposit.rs @@ -8,9 +8,7 @@ use sbtc_cli::commands::{ deposit::{build_deposit_tx, DepositArgs}, }; -mod bitcoin_client_integration; - -use bitcoin_client_integration::{electrs_url, generate_blocks}; +use super::bitcoin_client::{electrs_url, generate_blocks}; const WALLET_0_P2TR_ADDRESS: &str = "bcrt1pte5zmd7qzj4hdu45lh9mmdm0nwq3z35pwnxmzkwld6y0a8g83nnqhj6vc0"; diff --git a/romeo/tests/tests/mod.rs b/romeo/tests/tests/mod.rs new file mode 100644 index 00000000..e1c95acd --- /dev/null +++ b/romeo/tests/tests/mod.rs @@ -0,0 +1,3 @@ +pub mod bitcoin_client; +pub mod deposit; +pub mod stacks_client; diff --git a/romeo/tests/stacks_client_integration.rs b/romeo/tests/tests/stacks_client.rs similarity index 100% rename from romeo/tests/stacks_client_integration.rs rename to romeo/tests/tests/stacks_client.rs From c87d5e2b98f0f62df1e51af9f416312ba14bb16e Mon Sep 17 00:00:00 2001 From: Carlos Alejandro Gutierrez Sandoval Date: Wed, 11 Oct 2023 15:12:47 -0600 Subject: [PATCH 17/30] aggresive mining --- devenv/docker-compose.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/devenv/docker-compose.yml b/devenv/docker-compose.yml index 452b9d14..1a028551 100644 --- a/devenv/docker-compose.yml +++ b/devenv/docker-compose.yml @@ -55,8 +55,8 @@ services: depends_on: - bitcoin environment: - INIT_BTC_BLOCKS: 200 - BTC_BLOCK_GEN_TIME: 10 + INIT_BTC_BLOCKS: 101 + BTC_BLOCK_GEN_TIME: 5 stacks: image: stacks:latest stop_grace_period: 5s From 63848e81529bca95fdc78049282d5a08d078cb35 Mon Sep 17 00:00:00 2001 From: Carlos Alejandro Gutierrez Sandoval Date: Wed, 11 Oct 2023 17:18:50 -0600 Subject: [PATCH 18/30] Remove trivial check --- devenv/integration/docker/entrypoint | 8 -------- 1 file changed, 8 deletions(-) diff --git a/devenv/integration/docker/entrypoint b/devenv/integration/docker/entrypoint index e0318f23..88583e24 100755 --- a/devenv/integration/docker/entrypoint +++ b/devenv/integration/docker/entrypoint @@ -13,14 +13,6 @@ while ! curl -s $API_URL >/dev/null; do sleep 1 done -DEV_READY_HEIGHT=205 - -# bitcoind get info -echo "Waiting on burn block height $DEV_READY_HEIGHT" -while [ "$(curl -s $API_URL | jq '.burn_block_height')" -lt $DEV_READY_HEIGHT ]; do - sleep 2 -done - #stacks ready to take contracts STACKS_HEIGHT=1 echo "Waiting on Stacks height $STACKS_HEIGHT" From bfe68e351214ba878fd7283e462dc7fdefffdb6e Mon Sep 17 00:00:00 2001 From: Carlos Alejandro Gutierrez Sandoval Date: Wed, 11 Oct 2023 19:06:01 -0600 Subject: [PATCH 19/30] Reduce initial mining blocks to 100 --- romeo/tests/tests/deposit.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/romeo/tests/tests/deposit.rs b/romeo/tests/tests/deposit.rs index 991b4fe2..b9f3ae9a 100644 --- a/romeo/tests/tests/deposit.rs +++ b/romeo/tests/tests/deposit.rs @@ -39,7 +39,7 @@ fn broadcast_deposit() -> Result<()> { generate_blocks(1, &client, WALLET_0_P2WPKH_ADDRESS); generate_blocks(1, &client, WALLET_1_P2WPKH_ADDRESS); // pads blocks to get rewards. - generate_blocks(200, &client, WALLET_0_P2WPKH_ADDRESS); + generate_blocks(100, &client, WALLET_0_P2WPKH_ADDRESS); }; let electrum_url = electrs_url(); From 6cd5587ac371842bb69156c7612f262b78df034c Mon Sep 17 00:00:00 2001 From: Carlos Alejandro Gutierrez Sandoval Date: Wed, 11 Oct 2023 19:06:45 -0600 Subject: [PATCH 20/30] simplify service addresses --- devenv/integration/docker/entrypoint | 8 +++----- romeo/tests/tests/bitcoin_client.rs | 12 ++---------- romeo/tests/tests/stacks_client.rs | 5 +---- 3 files changed, 6 insertions(+), 19 deletions(-) diff --git a/devenv/integration/docker/entrypoint b/devenv/integration/docker/entrypoint index 88583e24..3b4e7527 100755 --- a/devenv/integration/docker/entrypoint +++ b/devenv/integration/docker/entrypoint @@ -1,9 +1,7 @@ #!/usr/bin/env sh set -ueo >/dev/null -STACKS=$PROJECT_NAME-stacks-1 -API_URL=http://$STACKS:20443/v2/info -BITCOIN=$PROJECT_NAME-bitcoin-1 +API_URL=http://stacks:20443/v2/info # --- make sure the node is ready before proceeding @@ -24,8 +22,8 @@ done # push contracts cd romeo/asset-contract -sed -i "s/localhost:20443/$STACKS:20443/" deployments/default.devnet-plan.yaml -sed -i "s/localhost:18443/$BITCOIN:18443/" deployments/default.devnet-plan.yaml +sed -i "s/localhost:20443/stacks:20443/" deployments/default.devnet-plan.yaml +sed -i "s/localhost:18443/bitcoin:18443/" deployments/default.devnet-plan.yaml clarinet deployments apply --no-dashboard -d -p deployments/default.devnet-plan.yaml cd - diff --git a/romeo/tests/tests/bitcoin_client.rs b/romeo/tests/tests/bitcoin_client.rs index 090bfe95..e2b497c6 100644 --- a/romeo/tests/tests/bitcoin_client.rs +++ b/romeo/tests/tests/bitcoin_client.rs @@ -1,21 +1,13 @@ -use std::env; - use reqwest::blocking::Client; use serde_json::json; use url::Url; -pub fn project_name() -> String { - env::var("PROJECT_NAME").unwrap() -} - pub fn bitcoin_url() -> Url { - let base = project_name(); - Url::parse(&format!("http://{base}-bitcoin-1:18443")).unwrap() + Url::parse("http://bitcoin:18443").unwrap() } pub fn electrs_url() -> Url { - let base = project_name(); - Url::parse(&format!("tcp://{base}-electrs-1:60401")).unwrap() + Url::parse("tcp://electrs:60401").unwrap() } pub fn generate_blocks( diff --git a/romeo/tests/tests/stacks_client.rs b/romeo/tests/tests/stacks_client.rs index 59c49a6f..f4c7d77c 100644 --- a/romeo/tests/tests/stacks_client.rs +++ b/romeo/tests/tests/stacks_client.rs @@ -1,11 +1,8 @@ -use std::env; - use reqwest::blocking::Client; use url::Url; pub fn stacks_url() -> Url { - let base = env::var("PROJECT_NAME").unwrap(); - Url::parse(&format!("http://{base}-stacks-1:20443")).unwrap() + Url::parse("http://stacks:20443").unwrap() } pub fn fetch_stacks_height(ctx: &Client) -> u64 { From 13ea79b97687945a287194c67f94b51ff2951ec1 Mon Sep 17 00:00:00 2001 From: Carlos Alejandro Gutierrez Sandoval Date: Wed, 11 Oct 2023 18:37:15 -0600 Subject: [PATCH 21/30] Add a quickstart section to the readme. --- devenv/integration/README.md | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/devenv/integration/README.md b/devenv/integration/README.md index 7f99e275..4e4743ed 100644 --- a/devenv/integration/README.md +++ b/devenv/integration/README.md @@ -37,6 +37,37 @@ logs from the first failed container. You must stop the nodes with dangling container and re-execute tests once you have fixed and rebuilt the testbed image. +## QuickStart + +1. Build the testbed image that has the source and binaries to execute the integration tests. +2. Run the testing script. + +```bash +> devenv$ cd integration +> devenv/integration$ ./bin/build +> cd - +> devenv$ ./integration/bin/test +``` + +You will see lines like below if your tests completed succesfully. + +``` +Runner: 71c1beca7261e2c1b706fa0e9eeb3823ad56977c4846c89b25a315a48a1bbda5 exited with err_no: 0 +Runner: fb2da5362115b5ae37874d39831bbf249b2928c58296dfb23af94b7083d8455b exited with err_no: 0 +``` + +Use the container id to inspect the runner for more details. + +``` +> devenv$ ./logs.sh 71c1beca7261e2c1b706fa0e9eeb3823ad56977c4846c89b25a315a48a1bbda5 +``` + +the script will abort the moment a container fails. The script will print the +logs from the first failed container. You will need to stop the nodes yourself +with `docker stop $(docker ps -qa)`. You can also run `test` again to down and +up again any dangling container and reexecute tests once you have fixed and +rebuilt the testbed image. + ### Running integration tests. Start at devenv, `pushd` integration. You will find scripts in the `bin` folder From 2043c8333fa45455e2517aa3c4fa4b4ea5a8ac3c Mon Sep 17 00:00:00 2001 From: Carlos Alejandro Gutierrez Sandoval Date: Thu, 2 Nov 2023 23:38:01 -0600 Subject: [PATCH 22/30] static wallets --- romeo/tests/tests/deposit.rs | 48 ++++++++----------- romeo/tests/tests/mod.rs | 92 ++++++++++++++++++++++++++++++++++++ 2 files changed, 111 insertions(+), 29 deletions(-) diff --git a/romeo/tests/tests/deposit.rs b/romeo/tests/tests/deposit.rs index b9f3ae9a..5800f8bc 100644 --- a/romeo/tests/tests/deposit.rs +++ b/romeo/tests/tests/deposit.rs @@ -1,29 +1,8 @@ use std::{thread::sleep, time::Duration}; use anyhow::Result; -use bdk::bitcoin::psbt::serialize::Serialize; -use reqwest::blocking::Client; -use sbtc_cli::commands::{ - broadcast::{broadcast_tx, BroadcastArgs}, - deposit::{build_deposit_tx, DepositArgs}, -}; - -use super::bitcoin_client::{electrs_url, generate_blocks}; - -const WALLET_0_P2TR_ADDRESS: &str = - "bcrt1pte5zmd7qzj4hdu45lh9mmdm0nwq3z35pwnxmzkwld6y0a8g83nnqhj6vc0"; -const WALLET_0_P2WPKH_ADDRESS: &str = - "bcrt1q3tj2fr9scwmcw3rq5m6jslva65f2rqjxfrjz47"; -const WALLET_0_P2WPKH_WIF: &str = - "cTyHitzs3VRnNxrpwxo3fXTTe569wHNUs57tQM7Z1FrzUDNB5mqm"; -const WALLET_1_P2WPKH_WIF: &str = - "cNcXK2r8bNdWJQymtAW8tGS7QHNtFFvG5CdXqhhT752u29WspXRM"; -const WALLET_1_STX_ADDRESS: &str = "ST2ST2H80NP5C9SPR4ENJ1Z9CDM9PKAJVPYWPQZ50"; -const WALLET_1_P2WPKH_ADDRESS: &str = - "bcrt1q3zl64vadtuh3vnsuhdgv6pm93n82ye8q6cr4ch"; - use bdk::{ - bitcoin::PrivateKey, + bitcoin::{psbt::serialize::Serialize, PrivateKey}, blockchain::{ ConfigurableBlockchain, ElectrumBlockchain, ElectrumBlockchainConfig, }, @@ -31,15 +10,26 @@ use bdk::{ template::P2Wpkh, SyncOptions, Wallet, }; +use reqwest::blocking::Client; +use sbtc_cli::commands::{ + broadcast::{broadcast_tx, BroadcastArgs}, + deposit::{build_deposit_tx, DepositArgs}, +}; + +use super::{ + bitcoin_client::{electrs_url, generate_blocks}, + KeyType::*, + WALLETS, +}; #[test] fn broadcast_deposit() -> Result<()> { let client = Client::new(); { - generate_blocks(1, &client, WALLET_0_P2WPKH_ADDRESS); - generate_blocks(1, &client, WALLET_1_P2WPKH_ADDRESS); + generate_blocks(1, &client, WALLETS[0][P2wpkh].address); + generate_blocks(1, &client, WALLETS[1][P2wpkh].address); // pads blocks to get rewards. - generate_blocks(100, &client, WALLET_0_P2WPKH_ADDRESS); + generate_blocks(100, &client, WALLETS[0][P2wpkh].address); }; let electrum_url = electrs_url(); @@ -57,7 +47,7 @@ fn broadcast_deposit() -> Result<()> { }) .unwrap(); - let private_key = PrivateKey::from_wif(WALLET_1_P2WPKH_WIF)?; + let private_key = PrivateKey::from_wif(WALLETS[1][P2wpkh].wif)?; let wallet = Wallet::new( P2Wpkh(private_key), @@ -82,11 +72,11 @@ fn broadcast_deposit() -> Result<()> { let tx = { let args = DepositArgs { node_url: electrum_url.clone(), - wif: WALLET_1_P2WPKH_WIF.into(), + wif: WALLETS[1][P2wpkh].wif.into(), network: bdk::bitcoin::Network::Regtest, - recipient: WALLET_1_STX_ADDRESS.into(), + recipient: WALLETS[1][Stacks].address.into(), amount, - sbtc_wallet: WALLET_0_P2TR_ADDRESS.into(), + sbtc_wallet: WALLETS[0][P2tr].address.into(), }; build_deposit_tx(&args).unwrap() diff --git a/romeo/tests/tests/mod.rs b/romeo/tests/tests/mod.rs index e1c95acd..b992645d 100644 --- a/romeo/tests/tests/mod.rs +++ b/romeo/tests/tests/mod.rs @@ -1,3 +1,95 @@ pub mod bitcoin_client; pub mod deposit; pub mod stacks_client; + +use std::ops::Index; + +struct KeyRing { + address: &'static str, + #[allow(unused)] + private_key: &'static str, + #[allow(unused)] + public_key: &'static str, + wif: &'static str, +} + +#[repr(usize)] +pub(crate) enum KeyType { + #[allow(unused)] + P2pkh, + P2tr, + P2wpkh, + Stacks, +} + +impl Index for [KeyRing] { + type Output = KeyRing; + + fn index(&self, index: KeyType) -> &Self::Output { + &self[index as usize] + } +} + +type FullRing = [KeyRing; 4]; + +const WALLETS: [FullRing; 2] = [[ + KeyRing { + address: "n4dN5bVeriVW9gKZMfNqHn21aJkwTM8QPH", + private_key: + "a38cbb2ca77786b9d37fd0feb34df2e423130ec74d0189736bf52561562c9565", + public_key: + "03bcb048737cc2f239db2b3db6eae00263861bfbe5b2577e573e3c32f61a46ac8c", + wif: "cT4cy7eAKPjhaZ3h72GdpbrtrFzsUQShZEcM5eNCiHrfuzmoXvBt", + }, + KeyRing { + address: + "bcrt1pte5zmd7qzj4hdu45lh9mmdm0nwq3z35pwnxmzkwld6y0a8g83nnqhj6vc0", + private_key: + "6596d84eef5b73430712dde88fbf6a1d96f97f5f241ab1bf247d04bc241dd28d", + public_key: + "034a45bd09cc815da165b8987a7263a2c4111b79951562fc5c0989e9cdf5ceded2", + wif: "cQzBGXC4YACb61oCwxDK9F1a8nxCjUiBZ5rBUaUJAeQvTytUBBFi", + }, + KeyRing { + address: "bcrt1q3tj2fr9scwmcw3rq5m6jslva65f2rqjxfrjz47", + private_key: + "bea4ecfec5cfa1e965ee1b3465ca4deff4f04b36a1fb5286a07660d5158789fb", + public_key: + "03ab37f5b606931d7828855affe75199d952bc6174b4a23861b7ac94132210508c", + wif: "cTyHitzs3VRnNxrpwxo3fXTTe569wHNUs57tQM7Z1FrzUDNB5mqm", + }, + KeyRing { + address: "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM", + private_key: + "753b7cc01a1a2e86221266a154af739463fce51219d97e4f856cd7200c3bd2a6", + public_key: + "0390a5cac7c33fda49f70bc1b0866fa0ba7a9440d9de647fecb8132ceb76a94dfa", + wif: "cRWawjcDj2J28jczAtjJGKs1pzFxM6V6tJHNZp3WrYoLhr2PLMVB", + }, +],[ + KeyRing { + address: "mvTQYAcGa17CTxWcJhRPXn6qecBQLSWuaJ", + private_key: "a1fc751f0bb64c01adb7c60dbe966e2bb9e262aa23bf41158e64c2142fc4fa78", + public_key: "030ede1203e7873388f81a7801df5714152c72273507a0fe0609e3f223fb6f56ae", + wif: "cT1agfN8XrWY1bSW5DGWpXUWYFjrmKWeJHyU79Kk66dPnMC6fw3L" + }, + KeyRing { + address: "bcrt1prm3lfhsgnnxe0def39n7xpa9etqrjfdxeqnar9xegqu4c044td0sfktyxq", + private_key: "73f560df660fb11c9aff8178971acd67e75ecb4e5683f5e9bdc52fb3c967c7a3", + public_key: "02aaed53527e3771645a050568a3cc9820361899c36f689cac15b57cc7885f3ca1", + wif: "cRU7KinnTuwaJ9N6WFDg2YdhtKcU5vxTSKFtgmT2gTtFvNfhnXx9" + }, + KeyRing { + address: "bcrt1q3zl64vadtuh3vnsuhdgv6pm93n82ye8q6cr4ch", + private_key: "1ec64b686cf94a4d8c741ed34db074b86d91c0971a38fe6e161b402489d7a74e", + public_key: "03969ff3e2bf7f2f73dc903cd11442032c8c7811d57d96ce327ee89c9edea63fa8", + wif: "cNcXK2r8bNdWJQymtAW8tGS7QHNtFFvG5CdXqhhT752u29WspXRM" + }, + KeyRing { + address: "ST2ST2H80NP5C9SPR4ENJ1Z9CDM9PKAJVPYWPQZ50", + private_key: "6a7c24ee77649c0cc314864596a6bd1addf3efb93bd63bcdb99be08437420847", + public_key: "038386f533650ff82714eeac9438faaa8a20ada5dd68a7eb8e00cf46cab5325a68", + wif: "cR9hENRFiuHzKpj9B3QCTBrt19c5ZCJKHJwYcqj5dfB6aKyf6ndm" + } + +]]; From c885ed82cbbfb0d37d3b4af4c75cd163ba81a90e Mon Sep 17 00:00:00 2001 From: Carlos Alejandro Gutierrez Sandoval Date: Mon, 6 Nov 2023 12:28:45 -0600 Subject: [PATCH 23/30] add no capture to integration entrypoint --- devenv/integration/docker/entrypoint | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devenv/integration/docker/entrypoint b/devenv/integration/docker/entrypoint index 3b4e7527..c4a58cd6 100755 --- a/devenv/integration/docker/entrypoint +++ b/devenv/integration/docker/entrypoint @@ -28,4 +28,4 @@ clarinet deployments apply --no-dashboard -d -p deployments/default.devnet-plan. cd - echo with filter: "'$@'" -cargo nextest run -E "$@ and kind(test)" --archive-file integration-tests.tar.zst +cargo nextest run -E "$@ and kind(test)" --archive-file integration-tests.tar.zst --nocapture From 94d0d22704bd678eec8fbcf7405f6a38719f35b1 Mon Sep 17 00:00:00 2001 From: Carlos Alejandro Gutierrez Sandoval Date: Mon, 6 Nov 2023 13:14:32 -0600 Subject: [PATCH 24/30] fund example --- romeo/examples/fund.rs | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 romeo/examples/fund.rs diff --git a/romeo/examples/fund.rs b/romeo/examples/fund.rs new file mode 100644 index 00000000..0a9fec55 --- /dev/null +++ b/romeo/examples/fund.rs @@ -0,0 +1,31 @@ +use std::str::FromStr; + +use bdk::{ + bitcoin::{Address, BlockHash}, + bitcoincore_rpc::{Auth, Client, RpcApi}, +}; + +fn mine_blocks(client: &Client, blocks: u64, address: &str) -> Vec { + client + .generate_to_address(blocks, &Address::from_str(address).unwrap()) + .unwrap() +} + +fn main() { + let client = Client::new( + "http://localhost:18443", + Auth::UserPass("devnet".into(), "devnet".into()), + ) + .unwrap(); + + // p2wpkh W0 + let address_0 = "bcrt1q3tj2fr9scwmcw3rq5m6jslva65f2rqjxfrjz47"; + let block_hashes = mine_blocks(&client, 10, address_0); + println!("blocks mined: {block_hashes:#?}"); + let address_1 = "bcrt1q3zl64vadtuh3vnsuhdgv6pm93n82ye8q6cr4ch"; + let block_hashes = mine_blocks(&client, 10, address_1); + println!("blocks mined: {block_hashes:#?}"); + let address_0 = "bcrt1q3tj2fr9scwmcw3rq5m6jslva65f2rqjxfrjz47"; + let block_hashes = mine_blocks(&client, 101, address_0); + println!("padding blocks mined: {block_hashes:#?}"); +} From 4276756d9a8dc33149cc7fe2b7159b39239876c7 Mon Sep 17 00:00:00 2001 From: Carlos Alejandro Gutierrez Sandoval Date: Mon, 6 Nov 2023 13:37:28 -0600 Subject: [PATCH 25/30] wait for confirmation --- romeo/tests/tests/bitcoin_client.rs | 66 +++++++++++++++-------------- romeo/tests/tests/deposit.rs | 33 +++++++++++---- 2 files changed, 60 insertions(+), 39 deletions(-) diff --git a/romeo/tests/tests/bitcoin_client.rs b/romeo/tests/tests/bitcoin_client.rs index e2b497c6..c5937dab 100644 --- a/romeo/tests/tests/bitcoin_client.rs +++ b/romeo/tests/tests/bitcoin_client.rs @@ -1,5 +1,9 @@ -use reqwest::blocking::Client; -use serde_json::json; +use std::{str::FromStr, thread::sleep, time::Duration}; + +use bdk::{ + bitcoin::{hash_types::Txid, Address, BlockHash}, + bitcoincore_rpc::{Auth, Client as BClient, RpcApi}, +}; use url::Url; pub fn bitcoin_url() -> Url { @@ -10,38 +14,38 @@ pub fn electrs_url() -> Url { Url::parse("tcp://electrs:60401").unwrap() } -pub fn generate_blocks( +pub fn client_new(url: &str, user: &str, pass: &str) -> BClient { + BClient::new(url, Auth::UserPass(user.into(), pass.into())).unwrap() +} + +pub fn mine_blocks( + client: &BClient, blocks: u64, - ctx: &Client, address: &str, -) -> Vec { - let endpoint = bitcoin_url(); - let user = "devnet"; - let password = "devnet"; - let body = json!({ - "jsonrpc": "1.0", - "id": "1", - "method": "generatetoaddress", - //developer's - "params": [blocks,address] - }); - - let response_json: serde_json::Value = ctx - .post(endpoint) - .basic_auth(user, Some(password)) - .header(reqwest::header::CONTENT_TYPE, "application/json") - .json(&body) - .send() +) -> Vec { + client + .generate_to_address(blocks, &Address::from_str(address).unwrap()) .unwrap() - .json() - .unwrap(); - - assert_eq!(response_json["error"], serde_json::Value::Null); - serde_json::from_value(response_json["result"].clone()).expect("block_ids") } -#[test] -fn mine_empty_block() { - let client = Client::new(); - generate_blocks(10, &client, "mqVnk6NPRdhntvfm4hh9vvjiRkFDUuSYsH"); +pub fn wait_for_tx_confirmation( + b_client: &BClient, + txid: &Txid, + confirmations: i32, +) { + loop { + match b_client.get_transaction(txid, None) { + Ok(tx) if tx.info.confirmations >= confirmations => { + break; + } + Ok(ok) => { + println!("Waiting confirmation on {txid}:{ok:?}") + } + Err(e) => { + println!("Waiting confirmation on {txid}:{e:?}") + } + } + + sleep(Duration::from_secs(1)); + } } diff --git a/romeo/tests/tests/deposit.rs b/romeo/tests/tests/deposit.rs index 5800f8bc..937ca17c 100644 --- a/romeo/tests/tests/deposit.rs +++ b/romeo/tests/tests/deposit.rs @@ -1,8 +1,9 @@ -use std::{thread::sleep, time::Duration}; +use std::{str::FromStr, thread::sleep, time::Duration}; use anyhow::Result; use bdk::{ - bitcoin::{psbt::serialize::Serialize, PrivateKey}, + bitcoin::{psbt::serialize::Serialize, Address, PrivateKey}, + bitcoincore_rpc::RpcApi, blockchain::{ ConfigurableBlockchain, ElectrumBlockchain, ElectrumBlockchainConfig, }, @@ -10,31 +11,43 @@ use bdk::{ template::P2Wpkh, SyncOptions, Wallet, }; -use reqwest::blocking::Client; use sbtc_cli::commands::{ broadcast::{broadcast_tx, BroadcastArgs}, deposit::{build_deposit_tx, DepositArgs}, }; use super::{ - bitcoin_client::{electrs_url, generate_blocks}, + bitcoin_client::{ + bitcoin_url, client_new, electrs_url, mine_blocks, + wait_for_tx_confirmation, + }, KeyType::*, WALLETS, }; #[test] fn broadcast_deposit() -> Result<()> { - let client = Client::new(); + let b_client = client_new(bitcoin_url().as_str(), "devnet", "devnet"); + + b_client + .import_address( + &Address::from_str(WALLETS[1][P2wpkh].address).unwrap(), + None, + Some(false), + ) + .unwrap(); + { - generate_blocks(1, &client, WALLETS[0][P2wpkh].address); - generate_blocks(1, &client, WALLETS[1][P2wpkh].address); + mine_blocks(&b_client, 1, WALLETS[0][P2wpkh].address); + mine_blocks(&b_client, 1, WALLETS[1][P2wpkh].address); // pads blocks to get rewards. - generate_blocks(100, &client, WALLETS[0][P2wpkh].address); + mine_blocks(&b_client, 100, WALLETS[0][P2wpkh].address); }; let electrum_url = electrs_url(); // suboptimal, replace once we have better events. + // replace with b_client balance? { let blockchain = ElectrumBlockchain::from_config(&ElectrumBlockchainConfig { @@ -88,5 +101,9 @@ fn broadcast_deposit() -> Result<()> { }) .unwrap(); + let txid = tx.txid(); + + wait_for_tx_confirmation(&b_client, &txid, 1); + Ok(()) } From 78adfc375a7dc4b588c99f039dac0d52c3747d35 Mon Sep 17 00:00:00 2001 From: Carlos Alejandro Gutierrez Sandoval Date: Mon, 6 Nov 2023 13:14:32 -0600 Subject: [PATCH 26/30] fund example --- romeo/tests/tests/mod.rs | 94 +++++++++++++++++++++++----------------- 1 file changed, 55 insertions(+), 39 deletions(-) diff --git a/romeo/tests/tests/mod.rs b/romeo/tests/tests/mod.rs index b992645d..5e32d1a9 100644 --- a/romeo/tests/tests/mod.rs +++ b/romeo/tests/tests/mod.rs @@ -32,64 +32,80 @@ impl Index for [KeyRing] { type FullRing = [KeyRing; 4]; -const WALLETS: [FullRing; 2] = [[ +const WALLETS: [FullRing; 3] = [[ KeyRing { address: "n4dN5bVeriVW9gKZMfNqHn21aJkwTM8QPH", - private_key: - "a38cbb2ca77786b9d37fd0feb34df2e423130ec74d0189736bf52561562c9565", - public_key: - "03bcb048737cc2f239db2b3db6eae00263861bfbe5b2577e573e3c32f61a46ac8c", + private_key: "a38cbb2ca77786b9d37fd0feb34df2e423130ec74d0189736bf52561562c9565", + public_key: "03bcb048737cc2f239db2b3db6eae00263861bfbe5b2577e573e3c32f61a46ac8c", wif: "cT4cy7eAKPjhaZ3h72GdpbrtrFzsUQShZEcM5eNCiHrfuzmoXvBt", }, KeyRing { - address: - "bcrt1pte5zmd7qzj4hdu45lh9mmdm0nwq3z35pwnxmzkwld6y0a8g83nnqhj6vc0", - private_key: - "6596d84eef5b73430712dde88fbf6a1d96f97f5f241ab1bf247d04bc241dd28d", - public_key: - "034a45bd09cc815da165b8987a7263a2c4111b79951562fc5c0989e9cdf5ceded2", + address: "bcrt1pte5zmd7qzj4hdu45lh9mmdm0nwq3z35pwnxmzkwld6y0a8g83nnqhj6vc0", + private_key: "6596d84eef5b73430712dde88fbf6a1d96f97f5f241ab1bf247d04bc241dd28d", + public_key: "034a45bd09cc815da165b8987a7263a2c4111b79951562fc5c0989e9cdf5ceded2", wif: "cQzBGXC4YACb61oCwxDK9F1a8nxCjUiBZ5rBUaUJAeQvTytUBBFi", }, KeyRing { address: "bcrt1q3tj2fr9scwmcw3rq5m6jslva65f2rqjxfrjz47", - private_key: - "bea4ecfec5cfa1e965ee1b3465ca4deff4f04b36a1fb5286a07660d5158789fb", - public_key: - "03ab37f5b606931d7828855affe75199d952bc6174b4a23861b7ac94132210508c", + private_key: "bea4ecfec5cfa1e965ee1b3465ca4deff4f04b36a1fb5286a07660d5158789fb", + public_key: "03ab37f5b606931d7828855affe75199d952bc6174b4a23861b7ac94132210508c", wif: "cTyHitzs3VRnNxrpwxo3fXTTe569wHNUs57tQM7Z1FrzUDNB5mqm", }, KeyRing { address: "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM", - private_key: - "753b7cc01a1a2e86221266a154af739463fce51219d97e4f856cd7200c3bd2a6", - public_key: - "0390a5cac7c33fda49f70bc1b0866fa0ba7a9440d9de647fecb8132ceb76a94dfa", + private_key: "753b7cc01a1a2e86221266a154af739463fce51219d97e4f856cd7200c3bd2a6", + public_key: "0390a5cac7c33fda49f70bc1b0866fa0ba7a9440d9de647fecb8132ceb76a94dfa", wif: "cRWawjcDj2J28jczAtjJGKs1pzFxM6V6tJHNZp3WrYoLhr2PLMVB", }, ],[ - KeyRing { - address: "mvTQYAcGa17CTxWcJhRPXn6qecBQLSWuaJ", - private_key: "a1fc751f0bb64c01adb7c60dbe966e2bb9e262aa23bf41158e64c2142fc4fa78", - public_key: "030ede1203e7873388f81a7801df5714152c72273507a0fe0609e3f223fb6f56ae", - wif: "cT1agfN8XrWY1bSW5DGWpXUWYFjrmKWeJHyU79Kk66dPnMC6fw3L" - }, - KeyRing { - address: "bcrt1prm3lfhsgnnxe0def39n7xpa9etqrjfdxeqnar9xegqu4c044td0sfktyxq", - private_key: "73f560df660fb11c9aff8178971acd67e75ecb4e5683f5e9bdc52fb3c967c7a3", - public_key: "02aaed53527e3771645a050568a3cc9820361899c36f689cac15b57cc7885f3ca1", - wif: "cRU7KinnTuwaJ9N6WFDg2YdhtKcU5vxTSKFtgmT2gTtFvNfhnXx9" - }, - KeyRing { - address: "bcrt1q3zl64vadtuh3vnsuhdgv6pm93n82ye8q6cr4ch", - private_key: "1ec64b686cf94a4d8c741ed34db074b86d91c0971a38fe6e161b402489d7a74e", - public_key: "03969ff3e2bf7f2f73dc903cd11442032c8c7811d57d96ce327ee89c9edea63fa8", - wif: "cNcXK2r8bNdWJQymtAW8tGS7QHNtFFvG5CdXqhhT752u29WspXRM" - }, - KeyRing { + KeyRing { + address: "mvTQYAcGa17CTxWcJhRPXn6qecBQLSWuaJ", + private_key: "a1fc751f0bb64c01adb7c60dbe966e2bb9e262aa23bf41158e64c2142fc4fa78", + public_key: "030ede1203e7873388f81a7801df5714152c72273507a0fe0609e3f223fb6f56ae", + wif: "cT1agfN8XrWY1bSW5DGWpXUWYFjrmKWeJHyU79Kk66dPnMC6fw3L" + }, + KeyRing { + address: "bcrt1prm3lfhsgnnxe0def39n7xpa9etqrjfdxeqnar9xegqu4c044td0sfktyxq", + private_key: "73f560df660fb11c9aff8178971acd67e75ecb4e5683f5e9bdc52fb3c967c7a3", + public_key: "02aaed53527e3771645a050568a3cc9820361899c36f689cac15b57cc7885f3ca1", + wif: "cRU7KinnTuwaJ9N6WFDg2YdhtKcU5vxTSKFtgmT2gTtFvNfhnXx9" + }, + KeyRing { + address: "bcrt1q3zl64vadtuh3vnsuhdgv6pm93n82ye8q6cr4ch", + private_key: "1ec64b686cf94a4d8c741ed34db074b86d91c0971a38fe6e161b402489d7a74e", + public_key: "03969ff3e2bf7f2f73dc903cd11442032c8c7811d57d96ce327ee89c9edea63fa8", + wif: "cNcXK2r8bNdWJQymtAW8tGS7QHNtFFvG5CdXqhhT752u29WspXRM" + }, + KeyRing { address: "ST2ST2H80NP5C9SPR4ENJ1Z9CDM9PKAJVPYWPQZ50", private_key: "6a7c24ee77649c0cc314864596a6bd1addf3efb93bd63bcdb99be08437420847", public_key: "038386f533650ff82714eeac9438faaa8a20ada5dd68a7eb8e00cf46cab5325a68", wif: "cR9hENRFiuHzKpj9B3QCTBrt19c5ZCJKHJwYcqj5dfB6aKyf6ndm" - } + } +],[ + KeyRing { + address: "n3cR74zFVWNnEusWTWKvDyuCemjT6zVv2y", + private_key: "9f1e7c24320af2b6c26b977e0eac0d19b69444b5a00b6a4ceca9849dcfa0e1d8", + public_key: "02d1b831b466e71161bfb91c7933483e9414f14435fddfdf37c7e41b78f657a880", + wif: "cSv1SKPZijZm3Kjip78csAiRq5JpQCCJLTumi9CCMqJzEDe2DNji" + }, + KeyRing{ + address: "bcrt1p3cq25gmqaltumf3l9d9e6qz836s3nu7vvjsnjvvkudaucly3h4fqq63tug", + private_key: "39657a54a708a1f2df728c40612aac7605c093daefb4552dceebfebe06aef1c8", + public_key: "02d3f0669085642a8cb94d574fd1cdc74ae0bc01b07c9572d4cd5f32b1e622d378", + wif: "cPWGmk3RD9FWqt3MJN78zGKf3AzpUXmbmvof4x9Gggh54DnFUByL" + }, + KeyRing{ + address: "bcrt1q266gk7s8efpwl0nasamcmc627tm37wnzxmgugt", + private_key: "66f8f1682915abb46f6a669ada600ade92e04739ce6e005fdde34d57ce64d40d", + public_key: "02a6510b8cf31689d9fd51c3237f0e81bf53201072bb9b16e34f86108566465aa6", + wif: "cR2sDUprEWkAQra7hY832dSjPhuc5eTbpJr8KnvBKBNtAJH1pvMJ" + }, + KeyRing { + address: "ST2Y2SFNVZBT8SSZ00XXKH930MCN0RFREB2GQG7CJ", + private_key: "6703304161a59dc3369c650ae97cca299df8bebb5638f12d4ff69778cba6ce3a", + public_key: "0388a0608e9268022ab38bedc8db10e562b6b6672e64dc30191a64f22b9a4a8d4d", + wif: "cR2wjBsDB1HVZghipz8Lxox2XgK47RURJHF694g8WqDzVd9oRv8u" + } ]]; From 1c866f28eb4c6c6c996792450454276faaa45bec Mon Sep 17 00:00:00 2001 From: Carlos Alejandro Gutierrez Sandoval Date: Fri, 10 Nov 2023 17:03:53 -0600 Subject: [PATCH 27/30] assert on balances --- Cargo.toml | 1 + devenv/integration/bin/test | 3 +- romeo/Cargo.toml | 3 +- romeo/src/stacks_client.rs | 43 ++++++++++++++++- romeo/tests/tests/bitcoin_client.rs | 46 +++++++++++++++++- romeo/tests/tests/deposit.rs | 73 ++++++++++++++++++++++------- romeo/tests/tests/stacks_client.rs | 1 + sbtc-cli/src/main.rs | 20 ++++---- stacks-core/Cargo.toml | 4 ++ stacks-core/src/address.rs | 24 ++++++++++ 10 files changed, 190 insertions(+), 28 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 814863ff..5c5db488 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,3 +35,4 @@ tracing = "0.1.37" tracing-subscriber = { version = "0.3.17", features = ["env-filter"] } url = "2.4.1" wsts = "1.2" +blockstack-core = { git = "https://github.com/stacks-network/stacks-blockchain/", branch = "master" } diff --git a/devenv/integration/bin/test b/devenv/integration/bin/test index f5ef976c..aa261849 100755 --- a/devenv/integration/bin/test +++ b/devenv/integration/bin/test @@ -2,6 +2,7 @@ set -ueo >/dev/null ENTRYPOINT_PATH="./integration/docker/entrypoint" +CONFIG_PATH="./sbtc/docker/config.json" project_name() { local input="$1" @@ -23,7 +24,7 @@ for filter in "${filters[@]}"; do ./integration/bin/up docker-compose.yml $project network=${project}_default - id=$(docker run -td --network $network -v $ENTRYPOINT_PATH:/usr/local/bin/entrypoint -e PROJECT_NAME=$project testbed "$filter") + id=$(docker run -td --network $network -v $ENTRYPOINT_PATH:/usr/local/bin/entrypoint -v $CONFIG_PATH:"/romeo/config.json" -e PROJECT_NAME=$project testbed "$filter") ids+=($id) echo with container id: $id diff --git a/romeo/Cargo.toml b/romeo/Cargo.toml index eba62167..1102d57b 100644 --- a/romeo/Cargo.toml +++ b/romeo/Cargo.toml @@ -7,7 +7,7 @@ edition = "2021" anyhow.workspace = true backoff = { workspace = true, features = ["tokio"] } bdk = { workspace = true, features = ["rpc", "esplora", "use-esplora-async"] } -blockstack-core = { git = "https://github.com/stacks-network/stacks-blockchain/", branch = "master" } +blockstack-core.workspace = true clap = { workspace = true, features = ["derive"] } derivative = { workspace = true } futures.workspace = true @@ -27,3 +27,4 @@ rs_merkle.workspace = true [dev-dependencies] reqwest = { workspace = true, features = ["json", "blocking"] } sbtc-cli = { path = "../sbtc-cli" } +stacks-core = { path = "../stacks-core", features = ["test-utils"] } diff --git a/romeo/src/stacks_client.rs b/romeo/src/stacks_client.rs index a0820be0..a20133ba 100644 --- a/romeo/src/stacks_client.rs +++ b/romeo/src/stacks_client.rs @@ -12,6 +12,7 @@ use blockstack_lib::{ codec::StacksMessageCodec, core::CHAIN_ID_TESTNET, types::chainstate::StacksPrivateKey, + util::hash::bytes_to_hex, vm::{ types::{QualifiedContractIdentifier, StandardPrincipalData}, ContractName, @@ -21,7 +22,7 @@ use futures::Future; use rand::{distributions::Alphanumeric, thread_rng, Rng}; use reqwest::{Request, RequestBuilder, Response, StatusCode}; use serde::de::DeserializeOwned; -use serde_json::Value; +use serde_json::{json, Value}; use stacks_core::{codec::Codec, uint::Uint256}; use tokio::{ sync::{Mutex, MutexGuard}, @@ -463,6 +464,46 @@ impl StacksClient { } } +impl StacksClient { + /// Call read-only functions from smart contracts + pub async fn call_read_only_fn( + &self, + contract: QualifiedContractIdentifier, + method: &str, + sender: &str, + args: Vec, + ) -> anyhow::Result + where + R: DeserializeOwned, + { + Ok(self + .http_client + .post( + self.config + .stacks_node_url + .join( + format!( + "/v2/contracts/call-read/{}/{}/{}", + contract.issuer, contract.name, method + ) + .as_str(), + ) + .unwrap(), + ) + .json(&json!({ + "sender": sender, + "arguments": args + .iter() + .map(|a| bytes_to_hex(&a.serialize_to_vec())) + .collect::>() } + )) + .send() + .await? + .json() + .await?) + } +} + #[derive(serde::Deserialize)] struct NonceInfo { possible_next_nonce: u64, diff --git a/romeo/tests/tests/bitcoin_client.rs b/romeo/tests/tests/bitcoin_client.rs index c5937dab..f33e6707 100644 --- a/romeo/tests/tests/bitcoin_client.rs +++ b/romeo/tests/tests/bitcoin_client.rs @@ -1,15 +1,27 @@ -use std::{str::FromStr, thread::sleep, time::Duration}; +use std::{io::Cursor, str::FromStr, thread::sleep, time::Duration}; use bdk::{ bitcoin::{hash_types::Txid, Address, BlockHash}, bitcoincore_rpc::{Auth, Client as BClient, RpcApi}, }; +use blockstack_lib::{ + codec::StacksMessageCodec, + types::chainstate::StacksAddress, + util::hash::hex_bytes, + vm::{ + types::{QualifiedContractIdentifier, StandardPrincipalData}, + ContractName, Value, + }, +}; +use romeo::stacks_client::StacksClient; use url::Url; +/// devenv's service url pub fn bitcoin_url() -> Url { Url::parse("http://bitcoin:18443").unwrap() } +/// devenv's service url pub fn electrs_url() -> Url { Url::parse("tcp://electrs:60401").unwrap() } @@ -49,3 +61,35 @@ pub fn wait_for_tx_confirmation( sleep(Duration::from_secs(1)); } } + +pub async fn sbtc_balance( + stacks_client: &StacksClient, + deployer_address: StacksAddress, + recipient_address: StacksAddress, + contract_name: ContractName, +) -> u128 { + let res: serde_json::Value = stacks_client + .call_read_only_fn( + QualifiedContractIdentifier::new( + StandardPrincipalData::from(deployer_address), + contract_name, + ), + "get-balance", + recipient_address.to_string().as_str(), + vec![StandardPrincipalData::from(recipient_address).into()], + ) + .await + .unwrap(); + + assert!(res["okay"].as_bool().unwrap()); + // request token balance from the asset contract. + let bytes = + hex_bytes(res["result"].as_str().unwrap().trim_start_matches("0x")) + .unwrap(); + + let mut cursor = Cursor::new(&bytes); + Value::consensus_deserialize(&mut cursor) + .unwrap() + .expect_result_ok() + .expect_u128() +} diff --git a/romeo/tests/tests/deposit.rs b/romeo/tests/tests/deposit.rs index 937ca17c..151cf697 100644 --- a/romeo/tests/tests/deposit.rs +++ b/romeo/tests/tests/deposit.rs @@ -1,4 +1,4 @@ -use std::{str::FromStr, thread::sleep, time::Duration}; +use std::{str::FromStr, time::Duration}; use anyhow::Result; use bdk::{ @@ -11,22 +11,25 @@ use bdk::{ template::P2Wpkh, SyncOptions, Wallet, }; +use romeo::{config::Config, stacks_client::StacksClient}; use sbtc_cli::commands::{ broadcast::{broadcast_tx, BroadcastArgs}, deposit::{build_deposit_tx, DepositArgs}, }; +use stacks_core::address::StacksAddress; +use tokio::time::sleep; use super::{ bitcoin_client::{ - bitcoin_url, client_new, electrs_url, mine_blocks, + bitcoin_url, client_new, electrs_url, mine_blocks, sbtc_balance, wait_for_tx_confirmation, }, - KeyType::*, + KeyType::{self, *}, WALLETS, }; -#[test] -fn broadcast_deposit() -> Result<()> { +#[tokio::test] +async fn broadcast_deposit() -> Result<()> { let b_client = client_new(bitcoin_url().as_str(), "devnet", "devnet"); b_client @@ -46,8 +49,6 @@ fn broadcast_deposit() -> Result<()> { let electrum_url = electrs_url(); - // suboptimal, replace once we have better events. - // replace with b_client balance? { let blockchain = ElectrumBlockchain::from_config(&ElectrumBlockchainConfig { @@ -76,18 +77,20 @@ fn broadcast_deposit() -> Result<()> { if balance.confirmed != 0 { break; } - sleep(Duration::from_millis(1_000)); + sleep(Duration::from_millis(1_000)).await; } } let amount = 10_000; + let deployer_stacks_address = WALLETS[0][KeyType::Stacks].address; + let recipient_stacks_address = WALLETS[1][KeyType::Stacks].address; let tx = { let args = DepositArgs { node_url: electrum_url.clone(), wif: WALLETS[1][P2wpkh].wif.into(), network: bdk::bitcoin::Network::Regtest, - recipient: WALLETS[1][Stacks].address.into(), + recipient: recipient_stacks_address.into(), amount, sbtc_wallet: WALLETS[0][P2tr].address.into(), }; @@ -95,15 +98,53 @@ fn broadcast_deposit() -> Result<()> { build_deposit_tx(&args).unwrap() }; - broadcast_tx(&BroadcastArgs { - node_url: electrum_url, - tx: hex::encode(tx.serialize()), - }) - .unwrap(); + let config = Config::from_path("config.json").unwrap(); - let txid = tx.txid(); + // make sure config urls match devenv. + let stacks_client = + StacksClient::new(config.clone(), reqwest::Client::new()); - wait_for_tx_confirmation(&b_client, &txid, 1); + let deployer_address = + StacksAddress::transmute_stacks_address(deployer_stacks_address); + let recipient_address = + StacksAddress::transmute_stacks_address(recipient_stacks_address); + + // prior balance + assert_eq!( + sbtc_balance( + &stacks_client, + deployer_address, + recipient_address, + config.contract_name.clone() + ) + .await, + 0 + ); + + // Sign, send and wait for confirmation. + { + broadcast_tx(&BroadcastArgs { + node_url: electrum_url, + tx: hex::encode(tx.serialize()), + }) + .unwrap(); + + let txid = tx.txid(); + + wait_for_tx_confirmation(&b_client, &txid, 1); + } + + // assert on new sbtc token balance + while sbtc_balance( + &stacks_client, + deployer_address, + recipient_address, + config.contract_name.clone(), + ) + .await != amount as u128 + { + sleep(Duration::from_secs(2)).await + } Ok(()) } diff --git a/romeo/tests/tests/stacks_client.rs b/romeo/tests/tests/stacks_client.rs index f4c7d77c..cccc12a6 100644 --- a/romeo/tests/tests/stacks_client.rs +++ b/romeo/tests/tests/stacks_client.rs @@ -1,6 +1,7 @@ use reqwest::blocking::Client; use url::Url; +/// devenv's service url pub fn stacks_url() -> Url { Url::parse("http://stacks:20443").unwrap() } diff --git a/sbtc-cli/src/main.rs b/sbtc-cli/src/main.rs index 5a163375..2a154ab2 100644 --- a/sbtc-cli/src/main.rs +++ b/sbtc-cli/src/main.rs @@ -7,7 +7,7 @@ //! and interact with the Bitcoin and Stacks networks. use std::io::stdout; -use bdk::bitcoin::psbt::serialize::Serialize; +use bdk::bitcoin::{psbt::serialize::Serialize, Transaction}; use clap::{Parser, Subcommand}; use sbtc_cli::commands::{ broadcast::{broadcast_tx, BroadcastArgs}, @@ -31,19 +31,23 @@ enum Command { GenerateFrom(GenerateArgs), } +fn to_stdout_pretty(txn: Transaction) -> serde_json::Result<()> { + serde_json::to_writer_pretty( + stdout(), + &utils::TransactionData { + id: txn.txid().to_string(), + hex: hex::encode(txn.serialize()), + }, + ) +} + fn main() -> Result<(), anyhow::Error> { let args = Cli::parse(); match args.command { Command::Deposit(deposit_args) => build_deposit_tx(&deposit_args) .and_then(|t| { - serde_json::to_writer_pretty( - stdout(), - &utils::TransactionData { - id: t.txid().to_string(), - hex: hex::encode(t.serialize()), - }, - )?; + to_stdout_pretty(t)?; Ok(()) }), Command::Withdraw(withdrawal_args) => { diff --git a/stacks-core/Cargo.toml b/stacks-core/Cargo.toml index 3ae104ec..f5dc81cb 100644 --- a/stacks-core/Cargo.toml +++ b/stacks-core/Cargo.toml @@ -27,7 +27,11 @@ serde = { workspace = true, features = ["derive"] } sha2.workspace = true strum = { workspace = true, features = ["derive"] } thiserror.workspace = true +blockstack-core.workspace = true [dev-dependencies] hex.workspace = true rand.workspace = true + +[features] +test-utils = [] diff --git a/stacks-core/src/address.rs b/stacks-core/src/address.rs index a002cb9e..6a4ac8b3 100644 --- a/stacks-core/src/address.rs +++ b/stacks-core/src/address.rs @@ -6,6 +6,10 @@ use std::{ use bdk::bitcoin::blockdata::{ opcodes::all::OP_CHECKMULTISIG, script::Builder, }; +use blockstack_lib::{ + codec::StacksMessageCodec, + types::chainstate::StacksAddress as CStacksAddress, +}; use serde::Serialize; use strum::{EnumIter, FromRepr}; @@ -100,6 +104,16 @@ impl StacksAddress { pub fn from_public_key(version: AddressVersion, key: &PublicKey) -> Self { Self::p2pkh(version, key) } + + /// Transmute to stacks-blockchain type + pub fn into_native_stacks_address(self) -> CStacksAddress { + use std::io::Cursor; + + CStacksAddress::consensus_deserialize(&mut Cursor::new( + self.serialize_to_vec(), + )) + .unwrap() + } } impl Codec for StacksAddress { @@ -226,6 +240,16 @@ fn hash_p2wsh<'a>( Hash160Hasher::new(&buff) } +#[cfg(feature = "test-utils")] +impl StacksAddress { + /// copy StacksAddress as a native stacks-blockchain address type + pub fn transmute_stacks_address(address: &str) -> CStacksAddress { + StacksAddress::try_from(address) + .unwrap() + .into_native_stacks_address() + } +} + #[cfg(test)] mod tests { use super::*; From 7e9619516ad25f73526ec4cc9b7fe0bd5a37204a Mon Sep 17 00:00:00 2001 From: Carlos Alejandro Gutierrez Sandoval Date: Fri, 10 Nov 2023 22:18:47 -0600 Subject: [PATCH 28/30] format --- romeo/src/system.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/romeo/src/system.rs b/romeo/src/system.rs index 30d27b25..b20fc17e 100644 --- a/romeo/src/system.rs +++ b/romeo/src/system.rs @@ -227,8 +227,7 @@ async fn update_contract_public_key( .bitcoin_credentials .public_key_p2tr() .serialize() - .try_into() - .unwrap(), + .into(), ) .expect("Cannot convert public key into a Clarity Value")]; From 4171fab0d1242319edddc545d280bea8b6369277 Mon Sep 17 00:00:00 2001 From: Carlos Alejandro Gutierrez Sandoval Date: Thu, 2 Nov 2023 23:21:23 -0600 Subject: [PATCH 29/30] integration: broadcast withdrawal --- devenv/integration/bin/test | 2 +- romeo/tests/tests/mod.rs | 1 + romeo/tests/tests/withdrawal.rs | 105 ++++++++++++++++++++++++++++++ sbtc-cli/src/commands/withdraw.rs | 55 +++++++--------- sbtc-cli/src/main.rs | 5 +- 5 files changed, 134 insertions(+), 34 deletions(-) create mode 100644 romeo/tests/tests/withdrawal.rs diff --git a/devenv/integration/bin/test b/devenv/integration/bin/test index aa261849..6a90ecb9 100755 --- a/devenv/integration/bin/test +++ b/devenv/integration/bin/test @@ -10,7 +10,7 @@ project_name() { echo "$filter" } -filters=("test(deposit)") +filters=("test(deposit)" "test(withdrawal)") filter_union="" ids=() projects=() diff --git a/romeo/tests/tests/mod.rs b/romeo/tests/tests/mod.rs index 5e32d1a9..e267debd 100644 --- a/romeo/tests/tests/mod.rs +++ b/romeo/tests/tests/mod.rs @@ -1,6 +1,7 @@ pub mod bitcoin_client; pub mod deposit; pub mod stacks_client; +pub mod withdrawal; use std::ops::Index; diff --git a/romeo/tests/tests/withdrawal.rs b/romeo/tests/tests/withdrawal.rs new file mode 100644 index 00000000..b7295776 --- /dev/null +++ b/romeo/tests/tests/withdrawal.rs @@ -0,0 +1,105 @@ +use std::{str::FromStr, thread::sleep, time::Duration}; + +use bdk::{ + bitcoin::{psbt::serialize::Serialize, Address, PrivateKey}, + bitcoincore_rpc::RpcApi, + blockchain::{ + ConfigurableBlockchain, ElectrumBlockchain, ElectrumBlockchainConfig, + }, + database::MemoryDatabase, + template::P2Wpkh, + SyncOptions, Wallet, +}; +use sbtc_cli::commands::{ + broadcast::{broadcast_tx, BroadcastArgs}, + withdraw::{build_withdrawal_tx, WithdrawalArgs}, +}; + +use super::{ + bitcoin_client::{ + bitcoin_url, client_new, electrs_url, mine_blocks, + wait_for_tx_confirmation, + }, + KeyType::*, + WALLETS, +}; + +#[test] +fn broadcast_withdrawal() { + let b_client = client_new(bitcoin_url().as_str(), "devnet", "devnet"); + + b_client + .import_address( + &Address::from_str(WALLETS[1][P2wpkh].address).unwrap(), + None, + Some(false), + ) + .unwrap(); + + { + mine_blocks(&b_client, 1, WALLETS[0][P2wpkh].address); + mine_blocks(&b_client, 1, WALLETS[1][P2wpkh].address); + // pads blocks to get rewards. + mine_blocks(&b_client, 100, WALLETS[0][P2wpkh].address); + }; + + let electrum_url = electrs_url(); + + // suboptimal, replace once we have better events. + { + let blockchain = + ElectrumBlockchain::from_config(&ElectrumBlockchainConfig { + url: electrum_url.clone().into(), + socks5: None, + retry: 3, + timeout: Some(10), + stop_gap: 10, + validate_domain: false, + }) + .unwrap(); + + let private_key = PrivateKey::from_wif(WALLETS[1][P2wpkh].wif).unwrap(); + + let wallet = Wallet::new( + P2Wpkh(private_key), + Some(P2Wpkh(private_key)), + bdk::bitcoin::Network::Regtest, + MemoryDatabase::default(), + ) + .unwrap(); + + loop { + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); + let balance = wallet.get_balance().unwrap(); + if balance.confirmed != 0 { + break; + } + sleep(Duration::from_millis(1_000)); + } + } + + // amount random [1000,2000) + // fee random [1000,2000) + let args = WithdrawalArgs { + node_url: electrs_url(), + network: bdk::bitcoin::Network::Regtest, + wif: WALLETS[1][P2wpkh].wif.into(), + drawee_wif: WALLETS[1][Stacks].wif.into(), + payee_address: WALLETS[1][P2wpkh].address.into(), + amount: 2000, + fulfillment_fee: 2000, + sbtc_wallet: WALLETS[0][P2tr].address.into(), + }; + + let tx = build_withdrawal_tx(&args).unwrap(); + + broadcast_tx(&BroadcastArgs { + node_url: electrum_url, + tx: hex::encode(tx.serialize()), + }) + .unwrap(); + + let txid = tx.txid(); + + wait_for_tx_confirmation(&b_client, &txid, 1); +} diff --git a/sbtc-cli/src/commands/withdraw.rs b/sbtc-cli/src/commands/withdraw.rs index 774dce2f..0f431b4a 100644 --- a/sbtc-cli/src/commands/withdraw.rs +++ b/sbtc-cli/src/commands/withdraw.rs @@ -1,8 +1,8 @@ -use std::{io::stdout, str::FromStr}; +use std::str::FromStr; use bdk::{ bitcoin::{ - psbt::serialize::Serialize, Address as BitcoinAddress, + blockdata::transaction::Transaction, Address as BitcoinAddress, Network as BitcoinNetwork, PrivateKey, }, blockchain::{ @@ -15,45 +15,45 @@ use bdk::{ use clap::Parser; use url::Url; -use crate::commands::utils::TransactionData; - #[derive(Parser, Debug, Clone)] pub struct WithdrawalArgs { /// Where to broadcast the transaction #[clap(short('u'), long)] - node_url: Url, + pub node_url: Url, /// Bitcoin network where the deposit will be broadcasted to #[clap(short, long)] - network: BitcoinNetwork, + pub network: BitcoinNetwork, /// WIF of the Bitcoin P2WPKH address that will broadcast and pay for the /// withdrawal request #[clap(short, long)] - wif: String, + pub wif: String, /// WIF of the Stacks address that owns sBTC to be withdrawn #[clap(short, long)] - drawee_wif: String, + pub drawee_wif: String, /// Bitcoin address that will receive BTC #[clap(short('b'), long)] - payee_address: String, + pub payee_address: String, /// The amount of sats to withdraw #[clap(short, long)] - amount: u64, + pub amount: u64, /// The amount of sats to send for the fulfillment fee #[clap(short, long)] - fulfillment_fee: u64, + pub fulfillment_fee: u64, /// Bitcoin address of the sbtc wallet #[clap(short, long)] - sbtc_wallet: String, + pub sbtc_wallet: String, } -pub fn build_withdrawal_tx(withdrawal: &WithdrawalArgs) -> anyhow::Result<()> { +pub fn build_withdrawal_tx( + withdrawal: &WithdrawalArgs, +) -> anyhow::Result { let private_key = PrivateKey::from_wif(&withdrawal.wif)?; let blockchain = @@ -82,23 +82,14 @@ pub fn build_withdrawal_tx(withdrawal: &WithdrawalArgs) -> anyhow::Result<()> { let sbtc_wallet_bitcoin_address = BitcoinAddress::from_str(&withdrawal.sbtc_wallet)?; - let tx = sbtc_core::operations::op_return::withdrawal_request::build_withdrawal_tx( - &wallet, - withdrawal.network, - drawee_stacks_private_key, - payee_bitcoin_address, - sbtc_wallet_bitcoin_address, - withdrawal.amount, - withdrawal.fulfillment_fee, - )?; - - serde_json::to_writer_pretty( - stdout(), - &TransactionData { - id: tx.txid().to_string(), - hex: hex::encode(tx.serialize()), - }, - )?; - - Ok(()) + sbtc_core::operations::op_return::withdrawal_request::build_withdrawal_tx( + &wallet, + withdrawal.network, + drawee_stacks_private_key, + payee_bitcoin_address, + sbtc_wallet_bitcoin_address, + withdrawal.amount, + withdrawal.fulfillment_fee, + ) + .map_err(|e| e.into()) } diff --git a/sbtc-cli/src/main.rs b/sbtc-cli/src/main.rs index 2a154ab2..51d0660f 100644 --- a/sbtc-cli/src/main.rs +++ b/sbtc-cli/src/main.rs @@ -51,7 +51,10 @@ fn main() -> Result<(), anyhow::Error> { Ok(()) }), Command::Withdraw(withdrawal_args) => { - build_withdrawal_tx(&withdrawal_args) + build_withdrawal_tx(&withdrawal_args).and_then(|t| { + to_stdout_pretty(t)?; + Ok(()) + }) } Command::Broadcast(broadcast_args) => broadcast_tx(&broadcast_args), Command::GenerateFrom(generate_args) => generate(&generate_args), From 805cba58bc0986204b946b6f5a79aee55bd07622 Mon Sep 17 00:00:00 2001 From: Carlos Alejandro Gutierrez Sandoval Date: Thu, 16 Nov 2023 00:19:14 -0600 Subject: [PATCH 30/30] withdrawal test + example --- devenv/integration/bin/test | 4 +- romeo/examples/deposit_withdrawal.rs | 190 +++++++++++++++++++++++++++ romeo/tests/tests/bitcoin_client.rs | 7 +- romeo/tests/tests/deposit.rs | 9 +- romeo/tests/tests/withdrawal.rs | 123 ++++++++++------- 5 files changed, 280 insertions(+), 53 deletions(-) create mode 100644 romeo/examples/deposit_withdrawal.rs diff --git a/devenv/integration/bin/test b/devenv/integration/bin/test index 6a90ecb9..819b7214 100755 --- a/devenv/integration/bin/test +++ b/devenv/integration/bin/test @@ -5,12 +5,12 @@ ENTRYPOINT_PATH="./integration/docker/entrypoint" CONFIG_PATH="./sbtc/docker/config.json" project_name() { - local input="$1" + local input="$@" local filter="${input//[![:alnum:]]/_}" echo "$filter" } -filters=("test(deposit)" "test(withdrawal)") +filters=("test(deposit) or test(withdrawal)") filter_union="" ids=() projects=() diff --git a/romeo/examples/deposit_withdrawal.rs b/romeo/examples/deposit_withdrawal.rs new file mode 100644 index 00000000..5a1d3076 --- /dev/null +++ b/romeo/examples/deposit_withdrawal.rs @@ -0,0 +1,190 @@ +use std::{io::Cursor, time::Duration}; + +use bdk::{ + bitcoin::{psbt::serialize::Serialize, PrivateKey}, + blockchain::{ + ConfigurableBlockchain, ElectrumBlockchain, ElectrumBlockchainConfig, + }, + database::MemoryDatabase, + template::P2Wpkh, + SyncOptions, Wallet, +}; +use blockstack_lib::{ + codec::StacksMessageCodec, + util::hash::hex_bytes, + vm::{ + types::{QualifiedContractIdentifier, StandardPrincipalData}, + Value, + }, +}; +use romeo::{config::Config, stacks_client::StacksClient}; +use sbtc_cli::commands::{ + broadcast::{broadcast_tx, BroadcastArgs}, + deposit::{build_deposit_tx, DepositArgs}, + withdraw::{build_withdrawal_tx, WithdrawalArgs}, +}; +use stacks_core::address::StacksAddress; +use tokio::time::sleep; +use url::Url; + +/// Wait until all your services are ready before running. +/// Don't forget to fund W0 (deployer) and W1 (recipient). +#[tokio::main] +async fn main() { + let mut config = + Config::from_path("./devenv/sbtc/docker/config.json").unwrap(); + config.stacks_node_url = "http://localhost:3999".parse().unwrap(); + config.bitcoin_node_url = "http://localhost:18443".parse().unwrap(); + config.electrum_node_url = "tcp://localhost:60401".parse().unwrap(); + + let blockchain = + ElectrumBlockchain::from_config(&ElectrumBlockchainConfig { + url: config.electrum_node_url.clone().into(), + socks5: None, + retry: 3, + timeout: Some(10), + stop_gap: 10, + validate_domain: false, + }) + .unwrap(); + + let recipient_p2wpkh_wif = + "cNcXK2r8bNdWJQymtAW8tGS7QHNtFFvG5CdXqhhT752u29WspXRM"; + + // W1 + let wallet = { + let private_key = PrivateKey::from_wif(recipient_p2wpkh_wif).unwrap(); + + Wallet::new( + P2Wpkh(private_key), + Some(P2Wpkh(private_key)), + bdk::bitcoin::Network::Regtest, + MemoryDatabase::default(), + ) + .unwrap() + }; + + loop { + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); + let balance = wallet.get_balance().unwrap().confirmed; + println!("recipient's btc: {balance}"); + if balance != 0 { + break; + } + sleep(Duration::from_secs(1)).await; + } + + let recipient = "ST2ST2H80NP5C9SPR4ENJ1Z9CDM9PKAJVPYWPQZ50"; + let amount = 1000; + + // deposit + { + let electrum_url = + Url::parse(config.electrum_node_url.as_str()).unwrap(); + let tx = { + let args = DepositArgs { + node_url: electrum_url.clone(), + wif: recipient_p2wpkh_wif.into(), + network: bdk::bitcoin::Network::Regtest, + recipient:recipient.into(), + amount, + sbtc_wallet: "bcrt1pte5zmd7qzj4hdu45lh9mmdm0nwq3z35pwnxmzkwld6y0a8g83nnqhj6vc0".into(), + }; + + build_deposit_tx(&args).unwrap() + }; + + broadcast_tx(&BroadcastArgs { + node_url: electrum_url, + tx: hex::encode(tx.serialize()), + }) + .unwrap(); + } + + let deployer = "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM"; + let deployer_address = StacksAddress::transmute_stacks_address(deployer); + let recipient_address = StacksAddress::transmute_stacks_address(recipient); + + let stacks_client = + StacksClient::new(config.clone(), reqwest::Client::new()); + + println!("Waiting on sBTC mint"); + // request token balance from the asset contract. + while { + let res: serde_json::Value = stacks_client + .call_read_only_fn( + QualifiedContractIdentifier::new( + StandardPrincipalData::from(deployer_address), + config.contract_name.clone(), + ), + "get-balance", + recipient_address.to_string().as_str(), + vec![StandardPrincipalData::from(recipient_address).into()], + ) + .await + .unwrap(); + + assert!(res["okay"].as_bool().unwrap()); + let bytes = + hex_bytes(res["result"].as_str().unwrap().trim_start_matches("0x")) + .unwrap(); + + let mut cursor = Cursor::new(&bytes); + Value::consensus_deserialize(&mut cursor) + .unwrap() + .expect_result_ok() + .expect_u128() + } < amount as u128 + { + sleep(Duration::from_secs(2)).await; + } + + let fee = 331; + // withdraw + let args = WithdrawalArgs { + node_url: config.electrum_node_url.clone(), + network: bdk::bitcoin::Network::Regtest, + // p2wpkh + wif: "cNcXK2r8bNdWJQymtAW8tGS7QHNtFFvG5CdXqhhT752u29WspXRM".into(), + // Stacks + drawee_wif: "cR9hENRFiuHzKpj9B3QCTBrt19c5ZCJKHJwYcqj5dfB6aKyf6ndm" + .into(), + payee_address: "bcrt1q3zl64vadtuh3vnsuhdgv6pm93n82ye8q6cr4ch".into(), + amount, + fulfillment_fee: fee, + sbtc_wallet: + "bcrt1pte5zmd7qzj4hdu45lh9mmdm0nwq3z35pwnxmzkwld6y0a8g83nnqhj6vc0" + .into(), + }; + + let tx = build_withdrawal_tx(&args).unwrap(); + + let balance = loop { + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); + let balance = wallet.get_balance().unwrap().confirmed; + if 0 < balance { + println!("recipient's btc: {balance}"); + break balance; + } + sleep(Duration::from_secs(1)).await + }; + + broadcast_tx(&BroadcastArgs { + node_url: config.electrum_node_url.clone(), + tx: hex::encode(tx.serialize()), + }) + .unwrap(); + + println!("Waiting on fulfillment"); + loop { + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); + let current = wallet.get_balance().unwrap().confirmed; + // will fail if tx_fees is not an upper bound for real fees. + let tx_fees = 400; + println!("recipient's btc: {balance}"); + if balance.saturating_sub(fee + tx_fees) < current { + break; + } + sleep(Duration::from_secs(2)).await; + } +} diff --git a/romeo/tests/tests/bitcoin_client.rs b/romeo/tests/tests/bitcoin_client.rs index f33e6707..8614aacd 100644 --- a/romeo/tests/tests/bitcoin_client.rs +++ b/romeo/tests/tests/bitcoin_client.rs @@ -1,4 +1,4 @@ -use std::{io::Cursor, str::FromStr, thread::sleep, time::Duration}; +use std::{io::Cursor, str::FromStr, time::Duration}; use bdk::{ bitcoin::{hash_types::Txid, Address, BlockHash}, @@ -14,6 +14,7 @@ use blockstack_lib::{ }, }; use romeo::stacks_client::StacksClient; +use tokio::time::sleep; use url::Url; /// devenv's service url @@ -40,7 +41,7 @@ pub fn mine_blocks( .unwrap() } -pub fn wait_for_tx_confirmation( +pub async fn wait_for_tx_confirmation( b_client: &BClient, txid: &Txid, confirmations: i32, @@ -58,7 +59,7 @@ pub fn wait_for_tx_confirmation( } } - sleep(Duration::from_secs(1)); + sleep(Duration::from_secs(1)).await; } } diff --git a/romeo/tests/tests/deposit.rs b/romeo/tests/tests/deposit.rs index 151cf697..242cf056 100644 --- a/romeo/tests/tests/deposit.rs +++ b/romeo/tests/tests/deposit.rs @@ -24,11 +24,12 @@ use super::{ bitcoin_url, client_new, electrs_url, mine_blocks, sbtc_balance, wait_for_tx_confirmation, }, - KeyType::{self, *}, + KeyType::*, WALLETS, }; #[tokio::test] +/// preceeds withdrawal async fn broadcast_deposit() -> Result<()> { let b_client = client_new(bitcoin_url().as_str(), "devnet", "devnet"); @@ -82,8 +83,8 @@ async fn broadcast_deposit() -> Result<()> { } let amount = 10_000; - let deployer_stacks_address = WALLETS[0][KeyType::Stacks].address; - let recipient_stacks_address = WALLETS[1][KeyType::Stacks].address; + let deployer_stacks_address = WALLETS[0][Stacks].address; + let recipient_stacks_address = WALLETS[1][Stacks].address; let tx = { let args = DepositArgs { @@ -131,7 +132,7 @@ async fn broadcast_deposit() -> Result<()> { let txid = tx.txid(); - wait_for_tx_confirmation(&b_client, &txid, 1); + wait_for_tx_confirmation(&b_client, &txid, 1).await; } // assert on new sbtc token balance diff --git a/romeo/tests/tests/withdrawal.rs b/romeo/tests/tests/withdrawal.rs index b7295776..8ece0e84 100644 --- a/romeo/tests/tests/withdrawal.rs +++ b/romeo/tests/tests/withdrawal.rs @@ -1,4 +1,4 @@ -use std::{str::FromStr, thread::sleep, time::Duration}; +use std::{str::FromStr, time::Duration}; use bdk::{ bitcoin::{psbt::serialize::Serialize, Address, PrivateKey}, @@ -10,22 +10,53 @@ use bdk::{ template::P2Wpkh, SyncOptions, Wallet, }; +use romeo::{config::Config, stacks_client::StacksClient}; use sbtc_cli::commands::{ broadcast::{broadcast_tx, BroadcastArgs}, withdraw::{build_withdrawal_tx, WithdrawalArgs}, }; +use stacks_core::address::StacksAddress; +use tokio::time::sleep; use super::{ bitcoin_client::{ - bitcoin_url, client_new, electrs_url, mine_blocks, + bitcoin_url, client_new, electrs_url, sbtc_balance, wait_for_tx_confirmation, }, KeyType::*, WALLETS, }; -#[test] -fn broadcast_withdrawal() { +#[tokio::test] +/// Depends on deposit test. +async fn broadcast_withdrawal() { + // wait until stacks addr has some balance + + let deployer_address = + StacksAddress::transmute_stacks_address(WALLETS[0][Stacks].address); + let recipient_address = + StacksAddress::transmute_stacks_address(WALLETS[1][Stacks].address); + + let config = Config::from_path("config.json").unwrap(); + + let stacks_client = + StacksClient::new(config.clone(), reqwest::Client::new()); + + // sbtc credited + let amount = loop { + let amount = sbtc_balance( + &stacks_client, + deployer_address, + recipient_address, + config.contract_name.clone(), + ) + .await; + if amount != 0 { + break amount as u64; + } + sleep(Duration::from_secs(2)).await; + }; + let b_client = client_new(bitcoin_url().as_str(), "devnet", "devnet"); b_client @@ -36,20 +67,50 @@ fn broadcast_withdrawal() { ) .unwrap(); - { - mine_blocks(&b_client, 1, WALLETS[0][P2wpkh].address); - mine_blocks(&b_client, 1, WALLETS[1][P2wpkh].address); - // pads blocks to get rewards. - mine_blocks(&b_client, 100, WALLETS[0][P2wpkh].address); + let args = WithdrawalArgs { + node_url: electrs_url(), + network: bdk::bitcoin::Network::Regtest, + wif: WALLETS[1][P2wpkh].wif.into(), + drawee_wif: WALLETS[1][Stacks].wif.into(), + payee_address: WALLETS[2][P2wpkh].address.into(), + amount, + fulfillment_fee: 2000, + sbtc_wallet: WALLETS[0][P2tr].address.into(), }; - let electrum_url = electrs_url(); + let tx = build_withdrawal_tx(&args).unwrap(); + + broadcast_tx(&BroadcastArgs { + node_url: electrs_url(), + tx: hex::encode(tx.serialize()), + }) + .unwrap(); + + let txid = tx.txid(); + + wait_for_tx_confirmation(&b_client, &txid, 1).await; - // suboptimal, replace once we have better events. + // sbtc debited + { + while { + sbtc_balance( + &stacks_client, + deployer_address, + recipient_address, + config.contract_name.clone(), + ) + } + .await != 0 + { + sleep(Duration::from_secs(2)).await; + } + } + + // btc credited { let blockchain = ElectrumBlockchain::from_config(&ElectrumBlockchainConfig { - url: electrum_url.clone().into(), + url: electrs_url().into(), socks5: None, retry: 3, timeout: Some(10), @@ -58,7 +119,7 @@ fn broadcast_withdrawal() { }) .unwrap(); - let private_key = PrivateKey::from_wif(WALLETS[1][P2wpkh].wif).unwrap(); + let private_key = PrivateKey::from_wif(WALLETS[2][P2wpkh].wif).unwrap(); let wallet = Wallet::new( P2Wpkh(private_key), @@ -68,38 +129,12 @@ fn broadcast_withdrawal() { ) .unwrap(); - loop { + while { wallet.sync(&blockchain, SyncOptions::default()).unwrap(); - let balance = wallet.get_balance().unwrap(); - if balance.confirmed != 0 { - break; - } - sleep(Duration::from_millis(1_000)); + wallet.get_balance().unwrap().confirmed + } == 0 + { + sleep(Duration::from_secs(2)).await; } } - - // amount random [1000,2000) - // fee random [1000,2000) - let args = WithdrawalArgs { - node_url: electrs_url(), - network: bdk::bitcoin::Network::Regtest, - wif: WALLETS[1][P2wpkh].wif.into(), - drawee_wif: WALLETS[1][Stacks].wif.into(), - payee_address: WALLETS[1][P2wpkh].address.into(), - amount: 2000, - fulfillment_fee: 2000, - sbtc_wallet: WALLETS[0][P2tr].address.into(), - }; - - let tx = build_withdrawal_tx(&args).unwrap(); - - broadcast_tx(&BroadcastArgs { - node_url: electrum_url, - tx: hex::encode(tx.serialize()), - }) - .unwrap(); - - let txid = tx.txid(); - - wait_for_tx_confirmation(&b_client, &txid, 1); }