Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions .github/workflows/basic_bitcoin.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ on:
pull_request:
paths:
- motoko/basic_bitcoin/**
- rust/basic_bitcoin/**
- .github/workflows/basic_bitcoin.yml

concurrency:
Expand Down Expand Up @@ -41,3 +42,36 @@ jobs:
icp network start -d
icp deploy --cycles 30t
make test

rust-basic_bitcoin:
# Run directly on the host (no container:) so that icp-cli can bind-mount
# the status directory into our custom Docker image. When icp-cli runs inside
# a container, the tmpdir it creates is invisible to the host Docker daemon.
runs-on: ubuntu-24.04
env:
ICP_CLI_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
- name: Install icp-cli and ic-wasm
run: npm install -g @icp-sdk/icp-cli @icp-sdk/ic-wasm
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
targets: wasm32-unknown-unknown
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
- name: Build network launcher image
uses: docker/build-push-action@14487ce63c7a62a4a324b0bfb37086795e31c6c1 # v6.16.0
with:
context: rust/basic_bitcoin
push: false
load: true
tags: icp-cli-network-launcher-bitcoin:latest
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Deploy and test
working-directory: rust/basic_bitcoin
run: |
icp network start -d
icp deploy --cycles 30t
make test
2 changes: 1 addition & 1 deletion rust/basic_bitcoin/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

23 changes: 3 additions & 20 deletions rust/basic_bitcoin/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,20 +1,3 @@
[package]
name = "basic_bitcoin"
version = "0.1.0"
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[lib]
crate-type = ["cdylib"]

[dependencies]
hex = "0.4.3"
bitcoin = "0.32.7"
candid = "0.10.19"
ic-cdk = "0.20.0"
ic-cdk-bitcoin-canister = "0.2"
ic-cdk-management-canister = "0.1"
serde = "1.0.132"
serde_bytes = "0.11.15"
leb128 = "0.2.5"
[workspace]
members = ["backend"]
resolver = "2"
32 changes: 32 additions & 0 deletions rust/basic_bitcoin/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Always use the latest network launcher image
# Before building we must pull to pick up new releases
# For real world usage, consider pinning the version instead of using :latest
FROM ghcr.io/dfinity/icp-cli-network-launcher:latest

ARG TARGETARCH
ARG BITCOIN_VERSION=27.2

RUN apt-get update && apt-get install -y --no-install-recommends curl && \
case "${TARGETARCH}" in \
"amd64") \
BITCOIN_TARBALL="bitcoin-${BITCOIN_VERSION}-x86_64-linux-gnu.tar.gz" ; \
BITCOIN_SHA256="acc223af46c178064c132b235392476f66d486453ddbd6bca6f1f8411547da78" ;; \
"arm64") \
BITCOIN_TARBALL="bitcoin-${BITCOIN_VERSION}-aarch64-linux-gnu.tar.gz" ; \
BITCOIN_SHA256="154c9b9e6e17136edc8f20fda5d252fb339e727e4a85ef49e7d8facb9085f2d3" ;; \
*) echo "Unsupported architecture: ${TARGETARCH}" && exit 1 ;; \
esac && \
curl -fsSL "https://bitcoincore.org/bin/bitcoin-core-${BITCOIN_VERSION}/${BITCOIN_TARBALL}" \
-o /tmp/bitcoin.tar.gz && \
echo "${BITCOIN_SHA256} /tmp/bitcoin.tar.gz" | sha256sum -c && \
tar xzf /tmp/bitcoin.tar.gz --strip-components=2 \
-C /usr/local/bin \
"bitcoin-${BITCOIN_VERSION}/bin/bitcoind" \
"bitcoin-${BITCOIN_VERSION}/bin/bitcoin-cli" && \
rm /tmp/bitcoin.tar.gz && \
apt-get purge -y curl && apt-get autoremove -y && rm -rf /var/lib/apt/lists/*

COPY docker/start.sh /app/start.sh
RUN chmod +x /app/start.sh

ENTRYPOINT ["/app/start.sh"]
118 changes: 78 additions & 40 deletions rust/basic_bitcoin/Makefile
Original file line number Diff line number Diff line change
@@ -1,42 +1,80 @@
.PHONY: all
all: deploy

.PHONY: deploy
.SILENT: deploy
deploy:
dfx deploy basic_bitcoin --argument '(variant { regtest })'

.PHONY: regtest_topup
.SILENT: regtest_topup
regtest_topup:
P2PKH_ADDR=$(shell dfx canister call basic_bitcoin get_p2pkh_address | tr -d '()') && \
P2TR_ADDR=$(shell dfx canister call basic_bitcoin get_p2tr_address | tr -d '()') && \
P2TR_KEY_ONLY_ADDR=$(shell dfx canister call basic_bitcoin get_p2tr_key_only_address | tr -d '()') && \
TOPUP_CMD_P2PKH_ADDR="bitcoin-cli -regtest -rpcport=8333 sendtoaddress $${P2PKH_ADDR} 1" && \
TOPUP_CMD_P2TR_ADDR="bitcoin-cli -regtest -rpcport=8333 sendtoaddress $${P2TR_ADDR} 1" && \
TOPUP_CMD_P2TR_KEY_ONLY_ADDR="bitcoin-cli -regtest -rpcport=8333 sendtoaddress $${P2TR_KEY_ONLY_ADDR} 1" && \
eval "$${TOPUP_CMD_P2PKH_ADDR}" && \
eval "$${TOPUP_CMD_P2PKH_ADDR}" && \
eval "$${TOPUP_CMD_P2PKH_ADDR}" && \
eval "$${TOPUP_CMD_P2TR_ADDR}" && \
eval "$${TOPUP_CMD_P2TR_ADDR}" && \
eval "$${TOPUP_CMD_P2TR_ADDR}" && \
eval "$${TOPUP_CMD_P2TR_KEY_ONLY_ADDR}" && \
eval "$${TOPUP_CMD_P2TR_KEY_ONLY_ADDR}" && \
eval "$${TOPUP_CMD_P2TR_KEY_ONLY_ADDR}" && \
bitcoin-cli -regtest -rpcport=8333 -generate 6

.PHONY: test
.SILENT: test
# No tests yet. This target exists so CI doesn't fail when it runs `make test`.
IMAGE_NAME = icp-cli-network-launcher-bitcoin
# Find the running container built from our custom image
BITCOIN_CONTAINER = $(shell docker ps --filter "ancestor=$(IMAGE_NAME)" --format "{{.ID}}" | head -1)

.PHONY: build-image test topup

build-image:
# Because we're building off of :latest, use --pull to fetch the latest image.
# Remove `--pull` if the Dockerfile is updated to pin the base image version.
docker build --pull -t $(IMAGE_NAME) .

topup:
icp canister top-up --amount 30t backend

test:
@echo "=== Test 1: get_p2pkh_address returns a valid Bitcoin address ==="
@result=$$(icp canister call backend get_p2pkh_address '()') && \
echo "$$result" && \
echo "$$result" | grep -q '"' && \
echo "PASS" || (echo "FAIL" && exit 1)

@echo "=== Test 2: get_p2wpkh_address returns a valid Bitcoin address ==="
@result=$$(icp canister call backend get_p2wpkh_address '()') && \
echo "$$result" && \
echo "$$result" | grep -q '"' && \
echo "PASS" || (echo "FAIL" && exit 1)

@echo "=== Test 3: get_p2tr_key_path_only_address returns a valid Bitcoin address ==="
@result=$$(icp canister call backend get_p2tr_key_path_only_address '()') && \
echo "$$result" && \
echo "$$result" | grep -q '"' && \
echo "PASS" || (echo "FAIL" && exit 1)

@echo "=== Test 4: get_p2tr_script_path_enabled_address returns a valid Bitcoin address ==="
@result=$$(icp canister call backend get_p2tr_script_path_enabled_address '()') && \
echo "$$result" && \
echo "$$result" | grep -q '"' && \
echo "PASS" || (echo "FAIL" && exit 1)

@echo "=== Test 5: get_current_fee_percentiles returns a vec ==="
@result=$$(icp canister call backend get_current_fee_percentiles '()') && \
echo "$$result" && \
echo "PASS" || (echo "FAIL" && exit 1)

@echo "=== Mining 101 blocks to fund test address ==="
@[ -n "$(BITCOIN_CONTAINER)" ] || (echo "ERROR: network launcher container not running — run 'icp network start -d' first" && exit 1)
@addr=$$(icp canister call backend get_p2pkh_address '()' | grep -o '"[^"]*"' | tr -d '"') && \
docker exec $(BITCOIN_CONTAINER) bitcoin-cli -regtest \
-rpcuser=ic-btc-integration -rpcpassword=ic-btc-integration \
generatetoaddress 101 "$$addr" > /dev/null && \
echo "mined 101 blocks to $$addr"

@echo "=== Waiting for IC to sync Bitcoin blocks ==="
@sleep 5

@echo "=== Test 6: get_balance returns non-zero after mining ==="
@addr=$$(icp canister call backend get_p2pkh_address '()' | grep -o '"[^"]*"' | tr -d '"') && \
result=$$(icp canister call backend get_balance "(\"$$addr\")") && \
echo "$$result" && \
echo "$$result" | grep -qE '[1-9]' && \
echo "PASS" || (echo "FAIL" && exit 1)

@echo "=== Test 7: get_utxos returns synced chain state after mining ==="
@addr=$$(icp canister call backend get_p2pkh_address '()' | grep -o '"[^"]*"' | tr -d '"') && \
result=$$(icp canister call backend get_utxos "(\"$$addr\")") && \
echo "$$result" && \
echo "$$result" | grep -q 'tip_height = 101' && \
echo "PASS" || (echo "FAIL" && exit 1)

@echo "=== Test 8: get_blockchain_info returns tip_height ==="
@result=$$(icp canister call backend get_blockchain_info '()') && \
echo "$$result" && \
echo "$$result" | grep -q 'height' && \
echo "PASS" || (echo "FAIL" && exit 1)

.PHONY: clean
.SILENT: clean
clean:
rm -rf .dfx
rm -rf dist
rm -rf node_modules
rm -rf src/declarations
rm -f .env
cargo clean
@echo "=== Test 9: get_block_headers returns headers ==="
@result=$$(icp canister call backend get_block_headers '(0: nat32, null)') && \
echo "$$result" && \
echo "$$result" | grep -q 'tip_height' && \
echo "PASS" || (echo "FAIL" && exit 1)
Loading
Loading