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:
systemctlcosmovisordocker- raw binary upgrades
- source-built 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:
- One signer only: keep a single active validator signer for
priv_validator_key.json. - Stop before replace: fully stop old process/container/service before changing binaries.
- Preserve signer state: never delete or roll back
priv_validator_state.json. - Never clone signing state to a second active host.
- Verify single active instance before restart.
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/cosmovisorsetup:/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.jsonfor Cosmovisor folder naming. Do not assume upgrade proposal name equals release tag (for example, proposal namev10.0.0may require binaryv10.0.1orv10.0.2).
2) Pre-upgrade checks
exrpd statusRecord 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 exrpdExpected: 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 versionUpgrade 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 versionIf 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 version5.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.tomlIf 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_infoConfirm 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 -fcosmovisor
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/exrpdIf 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 -fUse ~/.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-nodeDo 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.
- 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 exrpdExpected: no active validator signer process.
- Install the announced hotfix binary (do not start yet).
- Backup signer state:
cp ~/.exrpd/data/priv_validator_state.json ~/.exrpd/priv_validator_state.json- Restore state:
- Recommended: restore from a compatible snapshot provider (for example PolkaChu, Cumulo, or XRPL EVM snapshot S3).
- Alternative (resource-heavy):
exrpd rollback
- 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>"- 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.jsonBefore start, confirm only one host/container will be allowed to sign with this validator key.
Start exactly one runtime stack:
exrpd startNever 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).
During the Testnet v10 incident, affected nodes moved from v10.0.0 to v10.0.1 and required evm-chain-id = "1449000".
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/nullRecommended 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=falseFor 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.jsonis intact and not rolled back- target binary version is installed
- logs show normal block progression