diff --git a/.github/workflows/build.yml b/.github/workflows/ci.yml similarity index 58% rename from .github/workflows/build.yml rename to .github/workflows/ci.yml index 2f3e2fd..875b77a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/ci.yml @@ -65,4 +65,41 @@ jobs: with: subject-name: ${{ env.IMAGE_BASE }} subject-digest: ${{ steps.build.outputs.digest }} - push-to-registry: true \ No newline at end of file + push-to-registry: true + test: + runs-on: ubuntu-latest + needs: build + timeout-minutes: 45 + env: + CONTAINER_NAME: testnet4 + steps: + - run: | + docker pull ${{ needs.build.outputs.IMAGE }} + docker tag ${{ needs.build.outputs.IMAGE }} bitcoin-core-docker + - uses: actions/checkout@v4 + - run: ./examples/${CONTAINER_NAME}.sh + - name: Wait for healthy + run: | + while ! docker exec -i ${CONTAINER_NAME} /opt/wallet-health.sh; do + if ! docker ps | grep ${CONTAINER_NAME}; then + echo "Container stopped?" + exit 1 + fi + echo "waiting for ${CONTAINER_NAME} health" + echo "Last log: $(docker logs -n1 ${CONTAINER_NAME})" + sleep 15 + done + - name: Restart container + run: | + docker restart ${CONTAINER_NAME} + - name: Wait for healthy after restart + run: | + while ! docker exec -i ${CONTAINER_NAME} /opt/wallet-health.sh; do + if ! docker ps | grep ${CONTAINER_NAME}; then + echo "Container stopped?" + exit 1 + fi + echo "waiting for ${CONTAINER_NAME} health" + echo "Last log: $(docker logs -n1 ${CONTAINER_NAME})" + sleep 15 + done \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 326b966..f29287b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,18 +1,12 @@ FROM debian:bullseye-slim -ARG UID=101 -ARG GID=101 - -RUN groupadd --gid ${GID} bitcoin \ - && useradd --create-home --no-log-init -u ${UID} -g ${GID} bitcoin \ - && apt-get update -y \ - && apt-get install -y curl gosu \ +RUN apt-get update -y \ + && apt-get install -y curl procps procps jq \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* ARG TARGETPLATFORM ARG BITCOIN_VERSION=28.0 -ENV BITCOIN_DATA=/home/bitcoin/.bitcoin ENV PATH=/opt/bitcoin-${BITCOIN_VERSION}/bin:$PATH RUN set -ex \ @@ -24,14 +18,10 @@ RUN set -ex \ && rm *.tar.gz \ && rm -rf /opt/bitcoin-${BITCOIN_VERSION}/bin/bitcoin-qt -COPY docker-entrypoint.sh /entrypoint.sh - -VOLUME ["/home/bitcoin/.bitcoin"] - -EXPOSE 8332 8333 18332 18333 18443 18444 38333 38332 - -ENTRYPOINT ["/entrypoint.sh"] +VOLUME ["/root/.bitcoin"] RUN bitcoind -version | grep "Bitcoin Core version v${BITCOIN_VERSION}" +COPY wallet.sh wallet-health.sh /opt/ + CMD ["bitcoind"] \ No newline at end of file diff --git a/README.md b/README.md index 6c20625..5e4f58e 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,30 @@ # bitcoin-core-docker -This is a hard fork of [ruimarinho/docker-bitcoin-core](https://github.com/ruimarinho/docker-bitcoin-core) with support for new bitcoin core versions. \ No newline at end of file +This is a hard fork of [ruimarinho/docker-bitcoin-core](https://github.com/ruimarinho/docker-bitcoin-core) with support for new bitcoin core versions. + +## `wallet.sh` + +We also include a script which can start a bitcoin rpc node with a wallet in watch only mode. We require this functionality for our [observer/signer nodes](https://github.com/zeta-chain/node). + +This script is stored at `/opt/wallet.sh`. + +You should set several environment variables when running this container: + +| variable | description | +| -------- | ------- | +| `CHAIN` | `chain` config setting. Allowed values: main, test, testnet4, signet, regtest. | +| `RPC_USER` | `rpcuser` config setting. | +| `RPC_PASSWORD` | `rpcpassword` config setting. | +| `WALLET_NAME` | name of the wallet for the `createwallet` and `loadwallet` commands | +| `WALLET_ADDRESS` | address of the wallet | +| `NETWORK_HEIGHT_URL` | url which will return the current height of the network. Will use mempool.space if unset. | + +ID mapping: + +| chain | zetachain chain ID | port | +| -------- | ------- | --- | +| `main` | 8332 | 8332 | +| `testnet3` | 18332 | 18332 | +| `regtest` | 18444 | 18443 | +| `signet` | 18333 | 38332 | +| `testnet4` | 18334 | 48332 | diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh deleted file mode 100755 index c8b852a..0000000 --- a/docker-entrypoint.sh +++ /dev/null @@ -1,39 +0,0 @@ -#!/bin/bash -set -e - -if [ -n "${UID+x}" ] && [ "${UID}" != "0" ]; then - usermod -u "$UID" bitcoin -fi - -if [ -n "${GID+x}" ] && [ "${GID}" != "0" ]; then - groupmod -g "$GID" bitcoin -fi - -echo "$0: assuming uid:gid for bitcoin:bitcoin of $(id -u bitcoin):$(id -g bitcoin)" - -if [ $(echo "$1" | cut -c1) = "-" ]; then - echo "$0: assuming arguments for bitcoind" - - set -- bitcoind "$@" -fi - -if [ $(echo "$1" | cut -c1) = "-" ] || [ "$1" = "bitcoind" ]; then - mkdir -p "$BITCOIN_DATA" - chmod 700 "$BITCOIN_DATA" - # Fix permissions for home dir. - chown -R bitcoin:bitcoin "$(getent passwd bitcoin | cut -d: -f6)" - # Fix permissions for bitcoin data dir. - chown -R bitcoin:bitcoin "$BITCOIN_DATA" - - echo "$0: setting data directory to $BITCOIN_DATA" - - set -- "$@" -datadir="$BITCOIN_DATA" -fi - -if [ "$1" = "bitcoind" ] || [ "$1" = "bitcoin-cli" ] || [ "$1" = "bitcoin-tx" ]; then - echo - exec gosu bitcoin "$@" -fi - -echo -exec "$@" \ No newline at end of file diff --git a/examples/testnet4.sh b/examples/testnet4.sh new file mode 100755 index 0000000..e73733b --- /dev/null +++ b/examples/testnet4.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +docker run -d \ + --name testnet4 \ + -e CHAIN=testnet4 \ + -e RPC_USER=default \ + -e RPC_PASSWORD=default \ + -e WALLET_NAME=default \ + -e WALLET_ADDRESS=tb1qfm8a8pxer0kmfa4xlk34e44xpr8g46ae0v04dw \ + bitcoin-core-docker /opt/wallet.sh \ No newline at end of file diff --git a/wallet-health.sh b/wallet-health.sh new file mode 100755 index 0000000..c9e4a10 --- /dev/null +++ b/wallet-health.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +set -eo pipefail + +wallet_name=default +if [[ -n $1 ]]; then + wallet_name=$1 +fi + +# this script checks if a wallet exists and is not scanning + +echo "Getting wallet info" +wallet_info=$(bitcoin-cli -rpcwallet=${wallet_name} getwalletinfo) + +if [[ $(echo "$wallet_info" | jq -r '.scanning') == "true" ]]; then + echo "Error: Wallet is currently scanning" >&2 + exit 1 +fi diff --git a/wallet.sh b/wallet.sh new file mode 100755 index 0000000..efef279 --- /dev/null +++ b/wallet.sh @@ -0,0 +1,189 @@ +#!/bin/bash + +# set default config +# this makes running bitcoin-cli interactively much easier + +echo " +chain=${CHAIN} +rpcuser=${RPC_USER} +rpcpassword=${RPC_PASSWORD} +rpcallowip=0.0.0.0/0 + +[${CHAIN}] +rpcbind=0.0.0.0 +">~/.bitcoin/bitcoin.conf + +ensure_bitcoin_is_running() { + process_check=`ps aux | grep -v grep | grep bitcoind` + if [[ -z "${process_check}" ]]; then + echo "Bitcoind seems to have crashed, we are going to ensure no lock exists on the process and restart the daemon." + remove_lock_if_exists + start_bitcoind_daemon + fi +} + +check_bitcoin_is_running() { + process_check=`ps aux | grep -v grep | grep bitcoind` + echo "${process_check}" +} + +remove_lock_if_exists() { + if [ -f "~/.bitcoin/bitcoind.pid" ]; then + rm -rf ~/.bitcoin/bitcoind.pid || echo "Failed to delete PID" + else + echo "PID Doesn't Exist" + fi + + if [ -f "~/.bitcoin/${CHAIN}/.lock" ]; then + rm -rf ~/.bitcoin/${CHAIN}/.lock || echo "Failed to delete data lock" + else + echo "Failed to delete data lock" + fi +} + +start_bitcoind_daemon() { + start_bitcoind -daemon +} + +start_bitcoind() { + bitcoind \ + -pid=${HOME}/.bitcoin/bitcoind.pid \ + -listen=1 \ + -server=1 \ + -txindex=1 \ + -deprecatedrpc=create_bdb \ + -deprecatedrpc=warnings \ + $@ +} + +stop_bitcoind_daemon() { + bitcoin_pid=$(pgrep bitcoind) + echo "Kill bitcoind with kill -SIGTERM" + kill -SIGTERM "$bitcoin_pid" + echo "bitcoind PID: ${bitcoin_pid}" + while kill -0 "$bitcoin_pid" 2> /dev/null; do + echo "Waiting for bitcoind process to stop." + check_bitcoin_is_running + sleep 1 + done +} + +wait_for_daemon_active() { + while true; do + check_bitcoin_is_running + if bitcoin-cli getblockchaininfo ; then + return + fi + echo "Waiting for bitcoind to start..." + sleep 5 + done +} + +get_current_height() { + if [[ -z $NETWORK_HEIGHT_URL ]]; then + case $CHAIN in + "main") + NETWORK_HEIGHT_URL=https://mempool.space/api/blocks/tip/height + ;; + "testnet3") + NETWORK_HEIGHT_URL=https://mempool.space/testnet/api/blocks/tip/height + ;; + "testnet4") + NETWORK_HEIGHT_URL=https://mempool.space/testnet4/api/blocks/tip/height + ;; + "signet") + NETWORK_HEIGHT_URL=https://mempool.space/signet/api/blocks/tip/height + ;; + "regtest") + echo 0 + return + ;; + *) + echo "Unsupported chain: $CHAIN" >&2 + return 1 + ;; + esac + fi + curl -s ${NETWORK_HEIGHT_URL} +} + +wait_for_network_sync() { + echo "Wait until network is completely synced." + while true + do + network_current_block=`get_current_height || echo "No height was observed. Waiting for external network to return check height."` + node_current_block=`bitcoin-cli getblockchaininfo | jq -r '.blocks' || echo "No height was observed. Waiting for local network to return height."` + if [[ "${node_current_block}" -ge "${network_current_block}" ]]; then + echo "Bitcoin node is now synced, the local height is greater than or equal to the external height." + break + else + echo "Node height: ${node_current_block} Network Height: ${network_current_block} - Network Still Syncing" + fi + sleep 30 + done +} + +load_wallet() { + bitcoin-cli -named createwallet wallet_name=${WALLET_NAME} disable_private_keys=true load_on_startup=true descriptors=false || echo "wallet exists" + sleep 5 + bitcoin-cli loadwallet ${WALLET_NAME} || echo "wallet already loaded" + sleep 5 + bitcoin-cli -rpcwallet="${WALLET_NAME}" importaddress "${WALLET_ADDRESS}" "${WALLET_NAME}" true || echo "importaddress failed" +} + +snapshot_restore() { + if [ "$SNAPSHOT_RESTORE" != "true" ]; then + return + fi + + if [ -f ~/.bitcoin/extracted ]; then + echo "Snapshot already extracted. Skipping download and extraction." + return + fi + + echo "Use restore from snapshot: $SNAPSHOT_RESTORE" + + mkdir -p ~/.bitcoin/ || echo "already exists." + cd ~/.bitcoin/ + rm -rf ~/.bitcoin/{$CHAIN} + + curl -L "${SNAPSHOT_URL}" | tar -xzf - + touch ~/.bitcoin/extracted + + echo "Snapshot Restored. Verify the data and folder structure." + echo "Bitcoin network snapshot restart process is complete." +} + + +echo "Remove Lock if Exists" +remove_lock_if_exists + +echo "Check snapshot restore." +snapshot_restore + +echo "Start Bitcoind Daemon" +start_bitcoind_daemon + +echo "Wait for bitcoind to be active." +wait_for_daemon_active + +echo "Wait for network sync." +wait_for_network_sync + +echo "Ensure bitcoind is running." +ensure_bitcoin_is_running + +echo "Wait for daemon active." +wait_for_daemon_active + +echo "Load Wallet" +load_wallet + +echo "Stop the daemon to start the non daemon forground process." +stop_bitcoind_daemon + +echo "Check bitcoind is running." +check_bitcoin_is_running + +echo "Start bitcoind foreground process" +start_bitcoind \ No newline at end of file