Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docker: add support for persistent state builds with docker #3402

Merged
merged 1 commit into from
Feb 11, 2025
Merged
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
File renamed without changes.
59 changes: 59 additions & 0 deletions Dockerfile.persistent
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# --- Build stage ---
FROM ubuntu:24.04 AS build-stage

RUN apt-get update && apt-get install -y unzip curl build-essential openssl libssl-dev pkg-config && rm -rf /var/lib/apt/lists/*

RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y

WORKDIR /opt/rusk
ENV RUSK_PROFILE_PATH=/opt/dusk/rusk/
ENV PATH="$PATH:/root/.cargo/bin"

RUN apt-get update && apt-get install -y clang && rm -rf /var/lib/apt/lists/*

# Using this to modify rusk config file before running a node
RUN cargo install toml-cli --version 0.2.3

COPY . .

ARG TARGETPLATFORM
# See also https://github.com/docker/buildx/issues/510
ENV TARGETPLATFORM=${TARGETPLATFORM:-linux/amd64}

# Generate keys and compile genesis contracts
RUN make keys
RUN make wasm

ARG NODE_TYPE="provisioner"

RUN case "$NODE_TYPE" in \
"provisioner") cargo build --release -p dusk-rusk ;; \
"archive") cargo build --release --features archive -p dusk-rusk ;; \
"prover") cargo build --release --no-default-features --features prover -p dusk-rusk ;; \
*) echo "Unrecognized node type: $NODE_TYPE. Expected one of 'provisioner', 'archive' and 'prover'"; exit 1 ;; \
esac

# --- Run stage ---
FROM ubuntu:24.04 AS run-stage

RUN apt-get update && apt-get install -y unzip curl net-tools libssl-dev && rm -rf /var/lib/apt/lists/*

WORKDIR /opt/dusk

ENV RUSK_PROFILE_PATH=/opt/dusk/rusk
ENV RUSK_RECOVERY_INPUT=/opt/dusk/conf/genesis.toml
ENV RUST_BACKTRACE=full
ENV NETWORK=mainnet
Daksh14 marked this conversation as resolved.
Show resolved Hide resolved

EXPOSE 9000/udp
EXPOSE 8080/tcp

# Copy only the necessary files from the build stage
COPY --from=build-stage /opt/rusk/target/release/rusk /opt/dusk/bin/rusk
COPY --from=build-stage /opt/rusk/scripts/persistent-docker-setup/setup.sh /opt/dusk/setup.sh
COPY --from=build-stage /opt/rusk/scripts/persistent-docker-setup/detect_ips.sh /opt/dusk/detect_ips.sh
COPY --from=build-stage /root/.cargo/bin/toml /usr/bin/toml-cli

RUN chmod +x /opt/dusk/setup.sh /opt/dusk/detect_ips.sh

CMD [ "./setup.sh" ]
67 changes: 65 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,18 +160,20 @@ make wasm for=transfer

## 🐳 Docker support

### Local Ephemeral Node

It's also possible to run a local ephemeral node with Docker.

To build the Docker image with archive:

```bash
docker build -t rusk .
docker build -f Dockerfile.ephemeral -t rusk .
Daksh14 marked this conversation as resolved.
Show resolved Hide resolved
```

To build the Docker image **without** archive:

```bash
docker build -t rusk --build-arg CARGO_FEATURES="" .
docker build -t -f Dockerfile.ephemeral rusk --build-arg CARGO_FEATURES="" .
```

To run Rusk inside a Docker container:
Expand All @@ -182,6 +184,67 @@ docker run -p 9000:9000/udp -p 8080:8080/tcp rusk

Port 9000 is used for Kadcast, port 8080 for the HTTP and GraphQL APIs.

### Persistent Node

To build the docker image for a provisioner
```bash
docker build -f Dockerfile.persistent -t rusk --build-arg NODE_TYPE=provisioner .
```

To build for an archiver or prover instead, build with NODE_TYPE=archive or NODE_TYPE=prover,
respectively.

To run:

```bash
docker run -it \
-v /path/to/consensus.keys:/opt/dusk/conf/consensus.keys \
-v /path/to/rusk/profile:/opt/dusk/rusk \
-e NETWORK=<mainnet|testnet|devnet> \
-e DUSK_CONSENSUS_KEYS_PASS=<consensus-keys-password> \
-p 9000:9000/udp \
-p 8080:8080/tcp \
rusk
```

#### Customizing Configuration

The configuration used for rusk is based on the template file at `https://raw.githubusercontent.com/dusk-network/node-installer/9cdf0be1372ca6cb52cb279bd58781a3a27bf8ae/conf/rusk.toml`.
As part of the node setup process when the container is started, the kadcast ID, bootstrapping nodes, and genesis timestamp
will be changed based on the selected network and the resulting configuration will be used for running the node.
The IP addresses used for listening in kadcast and, if configured, http will be detected and automatically configured.

To customize the configuration, the configuration template file can be copied and modified. The custom configuration template
should be mounted on `/opt/dusk/conf/rusk.template.toml`.

```bash
docker run -it \
-v /path/to/consensus.keys:/opt/dusk/conf/consensus.keys
-v /path/to/rusk/profile:/opt/dusk/rusk \
-v /path/to/rusk.modified-template.toml:/opt/dusk/conf/rusk.template.toml \
-e NETWORK=<mainnet|testnet|devnet> \
-e DUSK_CONSENSUS_KEYS_PASS=<consensus-keys-password> \
-p 9000:9000/udp \
-p 8080:8080/tcp \
rusk
```

##### IP Addresses

When using a custom configuration file, properties that use IP addresses should be set to 'N/A'. For example, if
you want HTTP to be configured:

```toml
[http]
listen_address = 'N/A'
```

This entry should be present in the template configuration file. When the node is starting, the address to be used
will be detected and this configuration will be set to listen at port 8080.

Likewise, the `kadcast.public_address` and `kadcast.listen_address` properties in the configuration file should be set
to 'N/A'. During node startup, they will be detected and set to use port 9000.

## License

The Rusk software is licensed under the
Expand Down
56 changes: 56 additions & 0 deletions scripts/persistent-docker-setup/detect_ips.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
#!/bin/bash

# Script for detecting IP addresses in the persistent state Docker container.

# Fetch IPv4 WAN address using ifconfig.me, fallback to ipinfo.io
d-sonuga marked this conversation as resolved.
Show resolved Hide resolved
PUBLIC_IP=$(curl -4 -s https://ifconfig.me)
if [ -z "$PUBLIC_IP" ]; then
PUBLIC_IP=$(curl -4 -s https://ipinfo.io/ip)
fi

# Validate IPv4 address
if [[ -z "$PUBLIC_IP" || ! "$PUBLIC_IP" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "Error: Unable to retrieve a valid WAN IPv4 address"
exit 1
fi

runOnMac=false
int2ip() { printf ${2+-v} $2 "%d.%d.%d.%d" \
$(($1>>24)) $(($1>>16&255)) $(($1>>8&255)) $(($1&255)) ;}
ip2int() { local _a=(${1//./ }) ; printf ${2+-v} $2 "%u" $(( _a<<24 |
${_a[1]} << 16 | ${_a[2]} << 8 | ${_a[3]} )) ;}
while IFS=$' :\t\r\n' read a b c d; do
[ "$a" = "usage" ] && [ "$b" = "route" ] && runOnMac=true
if $runOnMac ;then
case $a in
gateway ) gWay=$b ;;
interface ) iFace=$b ;;
esac
else
[ "$a" = "0.0.0.0" ] && [ "$c" = "$a" ] && iFace=${d##* } gWay=$b
fi
done < <(/sbin/route -n 2>&1 || /sbin/route -n get 0.0.0.0/0)
ip2int $gWay gw
while read lhs rhs; do
[ "$lhs" ] && {
[ -z "${lhs#*:}" ] && iface=${lhs%:}
[ "$lhs" = "inet" ] && [ "$iface" = "$iFace" ] && {
mask=${rhs#*netmask }
mask=${mask%% *}
[ "$mask" ] && [ -z "${mask%0x*}" ] &&
printf -v mask %u $mask ||
ip2int $mask mask
ip2int ${rhs%% *} ip
(( ( ip & mask ) == ( gw & mask ) )) &&
int2ip $ip myIp && int2ip $mask netMask
}
}
done < <(/sbin/ifconfig)

echo "$PUBLIC_IP"
if [ -z "$myIp" ]; then
echo "$PUBLIC_IP"
else
echo "$myIp"
fi

113 changes: 113 additions & 0 deletions scripts/persistent-docker-setup/setup.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
#!/bin/bash
set -e

# Script for setting up a node in the persistent state Docker container.
#
# It detects the IP addresses for the node, generates a configuration file for
# the node based on the default `rusk.toml` used in the dusk-node-installer (or
# a user-supplied configuration file which can be provided by mounting at
# `/opt/dusk/conf/rusk.template.toml`), and runs the node.

echo "Starting node environment"

RUSK_CONFIG_DIR=/opt/dusk/conf
RUSK_TEMPLATE_CONFIG_PATH="$RUSK_CONFIG_DIR/rusk.template.toml"
RUSK_CONFIG_PATH="$RUSK_CONFIG_DIR/rusk.toml"

detect_ips_output=$(./detect_ips.sh)
PUBLIC_IP=$(echo "$detect_ips_output" | sed -n '1p')
LISTEN_IP=$(echo "$detect_ips_output" | sed -n '2p')

toml_set() {
file=$1
property=$2
value=$3

echo -e "$(toml-cli set $file $property $value)" > $file
}

configure_network() {
d-sonuga marked this conversation as resolved.
Show resolved Hide resolved
local network=$1
local kadcast_id
local bootstrapping_nodes
local genesis_timestamp
local base_state
local prover_url

case "$network" in
mainnet)
kadcast_id="0x1"
bootstrapping_nodes="['165.232.91.113:9000', '64.226.105.70:9000', '137.184.232.115:9000']"
genesis_timestamp="'2025-01-07T12:00:00Z'"
base_state="https://nodes.dusk.network/genesis-state"
prover_url="https://provers.dusk.network"
;;
testnet)
kadcast_id="0x2"
bootstrapping_nodes="['134.122.62.88:9000','165.232.64.16:9000','137.184.118.43:9000']"
genesis_timestamp="'2024-12-23T17:00:00Z'"
base_state="https://testnet.nodes.dusk.network/genesis-state"
prover_url="https://testnet.provers.dusk.network"
;;
devnet)
kadcast_id="0x3"
bootstrapping_nodes="['128.199.32.54', '159.223.29.22', '143.198.225.158']"
genesis_timestamp="'2024-12-23T12:00:00Z'"
base_state="https://devnet.nodes.dusk.network/genesis-state"
prover_url="https://devnet.provers.dusk.network"
;;
*)
echo "Unknown network: $network. Defaulting to mainnet."
configure_network "mainnet"
return
;;
esac

echo "Generating configuration"

cat > "$RUSK_CONFIG_DIR/genesis.toml" <<EOF
base_state = "$base_state"
EOF

cp "$RUSK_TEMPLATE_CONFIG_PATH" "$RUSK_CONFIG_PATH"
sed -i "s/^kadcast_id =.*/kadcast_id = $kadcast_id/" "$RUSK_CONFIG_PATH"
sed -i "s/^bootstrapping_nodes =.*/bootstrapping_nodes = $bootstrapping_nodes/" "$RUSK_CONFIG_PATH"
sed -i "s/^genesis_timestamp =.*/genesis_timestamp = $genesis_timestamp/" "$RUSK_CONFIG_PATH"
toml_set "$RUSK_CONFIG_PATH" kadcast.public_address "$PUBLIC_IP:9000"
toml_set "$RUSK_CONFIG_PATH" kadcast.listen_address "$LISTEN_IP:9000"
if toml-cli get "$RUSK_CONFIG_PATH" http &> /dev/null; then
toml_set "$RUSK_CONFIG_PATH" http.listen_address "$LISTEN_IP:8080"
fi
}

download_rusk_config() {
echo "Downloading default template rusk config from the dusk node installer"
REMOTE_LOCATION=https://raw.githubusercontent.com/dusk-network/node-installer/9cdf0be1372ca6cb52cb279bd58781a3a27bf8ae/conf/rusk.toml
mkdir -p "$RUSK_CONFIG_DIR"
curl -o "$RUSK_TEMPLATE_CONFIG_PATH" "$REMOTE_LOCATION"
if [ "$(cat $RUSK_TEMPLATE_CONFIG_PATH)" = "404: Not Found" ]; then
echo "Couldn't find the default rusk template config file. This is a bug, please file an issue."
exit 1
fi
}

if [ ! -f "$RUSK_TEMPLATE_CONFIG_PATH" ]; then
download_rusk_config
fi

configure_network "$NETWORK"

if [ -z "$DUSK_CONSENSUS_KEYS_PASS" ]; then
echo "DUSK_CONSENSUS_KEYS_PASS is not set"
exit 1
fi

echo "Selected network: $NETWORK"

/opt/dusk/bin/rusk recovery keys
/opt/dusk/bin/rusk recovery state

echo "Starting rusk"
echo "Rusk config:"
cat "$RUSK_CONFIG_PATH"
/opt/dusk/bin/rusk --config "$RUSK_CONFIG_PATH"
Loading