Upgrading your node

Upgrades are coordinated at a specific block height. Nodes running old binaries halt at that height and must be upgraded before they can continue.

For validators, the primary risk during upgrades is double-signing. This happens when more than one active validator signer uses the same validator key at the same time (or with inconsistent signer state), which can lead to slashing/jailing.

This guide is a security-first runbook for all run stacks:

  • systemctl
  • cosmovisor
  • docker
  • raw binary upgrades
  • source-built upgrades
List of upgrades

Upgrade heights and target versions for Mainnet/Testnet/Devnet are listed in Networks.

Critical validator safety rules

Follow these rules for every validator upgrade:

  1. One signer only: keep a single active validator signer for priv_validator_key.json.
  2. Stop before replace: fully stop old process/container/service before changing binaries.
  3. Preserve signer state: never delete or roll back priv_validator_state.json.
  4. Never clone signing state to a second active host.
  5. Verify single active instance before restart.
Double-sign risk

Do not start a second node (VM, container, backup host, or old binary) with the same validator key while your primary validator is still running. This is the most common path to double-signing and can tombstone your validator, meaning that validator key can never become an active validator again.

Secure upgrade flow (mandatory order)

Use this exact sequence for validators, regardless of stack.

Use your actual node home path in commands below:

  • If you run as root defaults: ~/.exrpd
  • If you followed hardened systemd/cosmovisor setup: /var/lib/exrpd/.exrpd

1) Confirm target upgrade

  • Read target height/version from Networks.
  • Download the target release artifact from XRPL EVM node releases.
  • Use the on-chain upgrade name from upgrade-info.json for Cosmovisor folder naming. Do not assume upgrade proposal name equals release tag (for example, proposal name v10.0.0 may require binary v10.0.1 or v10.0.2).

2) Pre-upgrade checks

exrpd status

Record current height and ensure your node is healthy before starting.

3) Stop signer process completely

Stop your runtime stack and confirm the process is gone.

pgrep -fa exrpd

Expected: no active validator signer exrpd process.

4) Backup validator-critical files

NODE_HOME=${NODE_HOME:-~/.exrpd}
mkdir -p ~/exrpd-upgrade-backup
cp "$NODE_HOME"/config/priv_validator_key.json ~/exrpd-upgrade-backup/
cp "$NODE_HOME"/data/priv_validator_state.json ~/exrpd-upgrade-backup/
cp "$NODE_HOME"/config/node_key.json ~/exrpd-upgrade-backup/

Do not edit these files manually.

5) Upgrade binary (raw or source)

Raw binary upgrade

cd /tmp
TARGET_TAG=<target-tag>
wget "https://github.com/xrplevm/node/releases/download/${TARGET_TAG}/node_${TARGET_TAG#v}_Linux_amd64.tar.gz"
tar -xzf "node_${TARGET_TAG#v}_Linux_amd64.tar.gz"
sudo mv bin/exrpd /usr/local/bin/exrpd
sudo chmod +x /usr/local/bin/exrpd
exrpd version

Upgrade from source

cd /tmp
rm -rf node
git clone https://github.com/xrplevm/node.git
cd node
git checkout <target-tag>
make build
sudo mv build/exrpd /usr/local/bin/exrpd
sudo chmod +x /usr/local/bin/exrpd
exrpd version

If you build from source, check required Go version first:

REQUIRED_GO=$(curl -fsSL https://raw.githubusercontent.com/xrplevm/node/main/go.mod | awk '/^go /{print $2; exit}')
echo "Required Go: ${REQUIRED_GO}"
go version

5.1) Apply mandatory EVM chain-id config (v10+)

After installing the target binary and before starting the node, ensure evm-chain-id is present under [evm] in app.toml:

  • Mainnet (xrplevm_1440000-1): evm-chain-id = "1440000"
  • Testnet (xrplevm_1449000-1): evm-chain-id = "1449000"

File location depends on your home path:

  • ~/.exrpd/config/app.toml
  • or /var/lib/exrpd/.exrpd/config/app.toml

Example check:

NODE_HOME=${NODE_HOME:-~/.exrpd}
awk '/^\[evm\]/{f=1;next} /^\[/{f=0} f && /^evm-chain-id/{print;found=1} END{if(!found) print "MISSING"}' "$NODE_HOME"/config/app.toml
Consensus safety

If evm-chain-id is missing or wrong for your network, your node can fail to participate correctly in consensus after upgrade height.

6) Start exactly one signer

Start your runtime stack (only one instance) and follow logs.

7) Post-upgrade validation

exrpd status
curl -s localhost:26657/status | jq .result.sync_info

Confirm blocks are advancing and no upgrade halt error remains.

Stack-specific upgrade commands

systemctl

sudo systemctl stop exrpd
pgrep -fa exrpd

# replace /usr/local/bin/exrpd with target binary

sudo systemctl start exrpd
sudo journalctl -u exrpd -f

cosmovisor

Place the new binary in the upgrade folder matching the on-chain upgrade name:

DAEMON_HOME=${DAEMON_HOME:-~/.exrpd}
mkdir -p "$DAEMON_HOME"/cosmovisor/upgrades/<upgrade-name>/bin
cp /usr/local/bin/exrpd "$DAEMON_HOME"/cosmovisor/upgrades/<upgrade-name>/bin/exrpd
chmod +x "$DAEMON_HOME"/cosmovisor/upgrades/<upgrade-name>/bin/exrpd

If upgrade-info.json is present, you can derive the folder name directly:

DAEMON_HOME=${DAEMON_HOME:-~/.exrpd}
UPGRADE_NAME=$(jq -r '.name // empty' "$DAEMON_HOME"/data/upgrade-info.json 2>/dev/null || true)
echo "$UPGRADE_NAME"

UPGRADE_NAME is the folder to use under cosmovisor/upgrades/, even when the required release binary version differs from that name.

Then run:

sudo systemctl restart cosmovisor-exrpd
sudo journalctl -u cosmovisor-exrpd -f
Cosmovisor path

Use ~/.exrpd/... (not ~/.exprd/...).

docker

docker pull peersyst/exrp:<target-tag>
docker stop xrplevm-node
docker rm xrplevm-node

docker run -d \
  --name xrplevm-node \
  --restart unless-stopped \
  -v /root/.exrpd:/root/.exrpd \
  --entrypoint exrpd \
  peersyst/exrp:<target-tag> \
  start

docker logs -f xrplevm-node

Do not keep an old validator container running in parallel.

Hotfix recovery runbook (only for impacted existing nodes)

Use this section only when governance/core team announces an incident hotfix and your node/validator was already running and impacted.

If you are a fresh node setup, skip this section and follow the normal install flow for your network's current version.

  1. Stop all signer instances that could use the same validator key, then verify no signer is running:
sudo systemctl stop exrpd 2>/dev/null || true
sudo systemctl stop cosmovisor-exrpd 2>/dev/null || true
docker stop xrplevm-node 2>/dev/null || true
pgrep -fa exrpd

Expected: no active validator signer process.

  1. Install the announced hotfix binary (do not start yet).
  2. Backup signer state:
cp ~/.exrpd/data/priv_validator_state.json ~/.exrpd/priv_validator_state.json
  1. Restore state:
  • Recommended: restore from a compatible snapshot provider (for example PolkaChu, Cumulo, or XRPL EVM snapshot S3).
  • Alternative (resource-heavy): exrpd rollback
  1. Apply required config changes announced with the hotfix.

If governance requires an EVM chain-id update, set it in app.toml under [evm]:

[evm]
evm-chain-id = "<network-evm-chain-id>"
  1. Restore signer state file and permissions:
cp ~/.exrpd/priv_validator_state.json ~/.exrpd/data/priv_validator_state.json
chmod 600 ~/.exrpd/data/priv_validator_state.json
  1. Before start, confirm only one host/container will be allowed to sign with this validator key.

  2. Start exactly one runtime stack:

exrpd start
Critical double-sign protection

Never run two active instances with the same priv_validator_key.json at the same time (primary + backup, old + new binary, systemd + docker, etc.). During hotfixes, this is the highest-risk mistake and can lead to slashing/jailing and tombstoning (permanent validator removal for that key).

Current known example

During the Testnet v10 incident, affected nodes moved from v10.0.0 to v10.0.1 and required evm-chain-id = "1449000".

Snapshot restore during incident

If restoring from a pre-upgrade snapshot with Cosmovisor, stage the required binary in the upgrade folder from upgrade-info.json before service start.

Automated upgrades with Cosmovisor

Cosmovisor can reduce manual intervention during upgrades. It can also increase operational risk if misconfigured, so validator operators should test in non-production first.

Install:

sudo apt-get update
sudo apt-get install -y golang-go
sudo env GOBIN=/usr/local/bin go install cosmossdk.io/tools/cosmovisor/cmd/cosmovisor@latest
/usr/local/bin/cosmovisor --help >/dev/null

Recommended environment variables:

export DAEMON_NAME=exrpd
export DAEMON_HOME=$HOME/.exrpd
export DAEMON_RESTART_AFTER_UPGRADE=true
export UNSAFE_SKIP_BACKUP=false
export DAEMON_ALLOW_DOWNLOAD_BINARIES=false
Auto-download caution

For validators, prefer DAEMON_ALLOW_DOWNLOAD_BINARIES=false and stage binaries manually. Auto-download introduces an external dependency at upgrade time.

Incident prevention checklist

Before starting after upgrade, confirm:

  • old process is fully stopped
  • no second host/container is signing with same key
  • priv_validator_state.json is intact and not rolled back
  • target binary version is installed
  • logs show normal block progression

Additional Resources