Skip to content

Commit

Permalink
docker: add support for persistent state builds with docker
Browse files Browse the repository at this point in the history
  • Loading branch information
d-sonuga committed Jan 31, 2025
1 parent 2db27ec commit 7db8d75
Show file tree
Hide file tree
Showing 6 changed files with 332 additions and 2 deletions.
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

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 .
```

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/conf/rusk.template.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# log_type = 'coloured' (default)
# log_type = 'json'

# log_level = 'info' (default)
# log_filter = 'dusk_consensus=debug,node=debug,kadcast=debug'

[chain]
genesis_timestamp = ''
generation_timeout = '3s'
db_path = '/opt/dusk/rusk'
consensus_keys_path = '/opt/dusk/conf/consensus.keys'
min_gas_limit = 150_000
block_gas_limit = 3000000000

[databroker]
max_inv_entries = 100
max_ongoing_requests = 1000

[mempool]
max_queue_size = 5000
max_mempool_txn_count = 10000
idle_interval = '5m'
mempool_expiry = '30m'
mempool_download_redundancy = 5

[kadcast]
kadcast_id = 0x0
public_address = 'N/A'
listen_address = 'N/A'
bootstrapping_nodes = []
auto_propagate = false
channel_size = 10000
recursive_discovery = true

[kadcast.bucket]
node_ttl = '120s'
node_evict_after = '15s'
bucket_ttl = '10m'
min_peers = 20

[kadcast.network]
udp_recv_buffer_size = 5000000
# udp_send_backoff_timeout = '50us'
udp_send_retry_interval = '5ms'
udp_send_retry_count = 3
blocklist_refresh_interval = '10s'

[kadcast.fec.encoder]
min_repair_packets_per_block = 5
mtu = 1300
fec_redundancy = 0.15

[kadcast.fec.decoder]
cache_ttl = '1m'
cache_prune_every = '30s'
max_udp_len = 2097152
54 changes: 54 additions & 0 deletions scripts/persistent-docker-setup/detect_ips.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
#!/bin/bash

# Fetch IPv4 WAN address using ifconfig.me, fallback to ipinfo.io
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

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

echo "Starting node environment"

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() {
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

# Create genesis.toml
cat > /opt/dusk/conf/genesis.toml <<EOF
base_state = "$base_state"
EOF

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

download_rusk_config() {
LOCATION=https://raw.githubusercontent.com/dusk-network/node-installer/9cdf0be1372ca6cb52cb279bd58781a3a27bf8ae/conf/rusk.toml
mkdir -p /opt/dusk/conf
curl -o /opt/dusk/conf/rusk.template.toml "$LOCATION"
if [ "$(cat /opt/dusk/conf/rusk.template.toml)" = "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
}

download_rusk_config
configure_network "$NETWORK"

# Get consensus keys password
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 /opt/dusk/conf/rusk.toml
/opt/dusk/bin/rusk --config /opt/dusk/conf/rusk.toml

0 comments on commit 7db8d75

Please sign in to comment.