Core-GethThe pages of this site follow this link

Migrate Core-Geth

Move a Core-Geth node from v1.12 to v1.13

Every node still on a v1.12 release should move to the v1.13 line. From v1.12.17 on, your chain data carries over. Budget about twenty minutes for the steps; the node is down only between its stop and its start, and each of those takes seconds.

This guide walks through the move on each platform: what to check first, every step in order from the download to deleting the old binary, with recordings of the clean stop and the start on Linux, and what to do if a step goes wrong.

The Core-Geth move at a glance

What the move keeps, and what it changes

  • The chain data stays. From v1.12.17 on, the node starts again near the block it stopped at, with no resync. A node on an older release resyncs instead, as Check what your node runs explains.
  • The old binary stays for a while. Each tab keeps the v1.12 binary or container, so you can roll back while you find what went wrong, and ends by deleting it once the node is stable on the v1.13 line: every v1.12 release carries the flaws the audits found.
  • The P2P node key changes, which the move requires. One of the flaws in the v1.12 line leaks bits of that key across repeated handshakes, and no fix can take back what already leaked. The new key gives the node a new enode ID.
  • You set MESS. An upgrade can change whether a node runs MESS, so the steps have you set it: --mess turns it on and --mess=false turns it off.

Checking a Core-Geth node

Check what your node runs

Ask the running node for its name with geth attach --exec 'admin.nodeInfo.name' and the node's IPC path, the command the move's last step uses. A name that starts CoreGeth/v1.12 is a v1.12 release, and this guide is for that node from v1.12.17 on.

A release before v1.12.17 does not schedule the Spiral upgrade, so a node on one has followed an obsolete chain since Spiral activated and cannot move in place. Remove its chain database as the documentation's Resync from scratch shows, which keeps the node's P2P node key, rename that key as the move steps do, and then install and start the v1.13 release with Install Core-Geth, which syncs the chain again.

geth version reports the binary on your PATH, which need not be the one running. What the network is running counts the nodes on each release.

Before moving a Core-Geth node

Decide before you stop it

  • Whether to sync a trial node first. Best practice before you move a v1.12 node is one new v1.13 node on a machine of its own, with no RPC clients or miners pointed at it, until it has caught up and holds peers. The tabs below open with it, as an optional step.
  • The node's MESS setting. Give every node you run the same one. Which setting fits which operator sets out the choice.
  • Who lists this node. Nodes that list it as a static or trusted peer, and any monitoring entry or allowlist that names it, need the new enode ID after the move. The last step gives the new ID to the nodes that list it; monitoring and allowlists are yours to update.

Moving a Core-Geth node

The move, step by step

Run one block at a time, in order, and read the note under each before the next. In the Linux tab, the clean stop and the first start on the new key carry recordings, made moving v1.12.20 to v1.13.0.

Choose your platform for moving a node

Linux

Optional: if you have not run v1.13 yet, sync one new node first

Best practice before you move a v1.12 node: set up one new node on the v1.13 line, on a machine of its own beside the nodes you run, by following Install Core-Geth, and point no RPC clients or miners at it. It can sync from the network, or start from a copy of a stopped v1.12 node's data directory without its geth/nodekey. Once eth.syncing returns false and net.peerCount is above zero (How to tell it is done), move each v1.12 node with the steps below.

Install curl and unzip

The node can keep running for this step. On Debian or Ubuntu:

sudo apt-get update && sudo apt-get install -y curl unzip

On Fedora or RHEL, run sudo dnf install -y --refresh curl unzip && sudo dnf upgrade -y curl unzip instead, and on Arch, sudo pacman -Syu --needed curl unzip. Each line also updates curl and unzip if they are already installed.

Download the release and check it

curl -LO https://github.com/ethereumclassic/core-geth/releases/download/v1.13.0/core-geth-linux-v1.13.0.zip
curl -LO https://github.com/ethereumclassic/core-geth/releases/download/v1.13.0/core-geth-linux-v1.13.0.zip.sha256
sha256sum -c core-geth-linux-v1.13.0.zip.sha256

The node can keep running for this step. sha256sum -c must print OK: if it prints FAILED, delete the file and download it again rather than running it. Verify what you downloaded also checks where the file came from. On an Arm machine, put the name for your processor where the file name says linux, here and in "Install the release": arm64 if uname -m prints aarch64 or arm64, arm7 for armv7l, arm6 for armv6l, and arm5 for armv5tel.

Record the node's block number and ID

geth attach --exec 'eth.blockNumber' ~/.ethereum/classic/geth.ipc
geth attach --exec 'admin.nodeInfo.enode' ~/.ethereum/classic/geth.ipc

Keep what this prints: "Confirm the move" compares the new ID and block number with it. These commands use the default data directory. For Mordor, use ~/.ethereum/mordor; if you pass --datadir, use yours. On the unit in the Linux run guide, the geth user owns /var/lib/core-geth, so attach as sudo -u geth -H geth --classic attach --exec 'admin.nodeInfo.enode' /var/lib/core-geth/geth.ipc instead, here and in every later attach line.

Stop the node cleanly

Press Ctrl-C once in the node's terminal and wait, or stop the systemd unit you wrote, for example sudo systemctl stop core-geth. Wait until the log prints Blockchain stopped and the process exits. If it exits without that line, the stop was not clean: the node recovers at its next start, and its block number can start lower than the one you recorded. Under systemd, journalctl -u core-geth shows that log.

Recorded on Linux, moving v1.12.20 to v1.13.0One Ctrl-C in the node's terminal: the node writes its state to disk and logs Blockchain stopped, and the prompt returns once the process exits.

Install the release

unzip -o core-geth-linux-v1.13.0.zip &&
  { test -e /usr/local/bin/geth-v1.12 || sudo mv /usr/local/bin/geth /usr/local/bin/geth-v1.12; } &&
  sudo install -m 0755 geth /usr/local/bin/geth &&
  geth version

Go on only if geth version reports a v1.13 release. geth-v1.12 is the old binary, kept so you can roll back. These commands install over /usr/local/bin/geth. If your unit or start command runs a geth at another path, use that path wherever they say /usr/local/bin/geth.

Rename the old P2P node key

mv -n ~/.ethereum/classic/geth/nodekey ~/.ethereum/classic/geth/nodekey.old-rotated-$(date +%F)

The next start writes a fresh key. If you start the node with --nodekey or --nodekeyhex, or set GETH_NODEKEY or GETH_NODEKEYHEX, take that out too: it overrides the file. If the rename prints an error, check the data directory: geth/nodekey must not be there when the node starts. On the unit in the Linux run guide, run sudo -u geth mv -n /var/lib/core-geth/geth/nodekey /var/lib/core-geth/geth/nodekey.old-rotated-$(date +%F) instead.

If this node lists static peers, update the list

They are under [Node.P2P] in the file you pass with --config. Edit it while the node is down:

[Node.P2P]
StaticNodes = ["enode://<node ID>@<IP>:30303"]

A peer that has already moved has a new ID, so use that. Separate entries with commas. TrustedNodes takes the same entries. With no file yet, save these two lines and add --config and the file's path to the start command. The client ignores static-nodes.json, so move its entries here.

Check the flags and settings you start the node with

While the node is down, decide MESS, which makes a node resist deep chain reorganizations. An upgrade can change whether a node runs MESS, so set it yourself: --mess turns it on and --mess=false turns it off. Which setting fits which operator sets out the choice. An exchange setting its confirmation policy can use the MESS confirmation calculator to see what that policy asks of an attacker. Give every node you run the same setting. Take out any --discovery.dns or --bootnodes flag added on v1.12, the two Keep a v1.12 node finding peers adds among them: the v1.13 line reads its own lists and bootnodes, --bootnodes would hold the node to the bootnodes it names, and with --discovery.dns it reads no snap lists. If the node starts with --config, delete the file's BootstrapNodes, EthDiscoveryURLs and SnapDiscoveryURLs lines, unless you put them there to name bootnodes or lists other than the release's own: a file dumpconfig wrote on v1.12 has all three, and the node keeps what they name in place of what the v1.13 line ships. The lists that guide puts in a file are the release's own, so delete those too. BootstrapNodesV5 can stay: the v1.13 line puts its own discovery v5 bootnodes in place of the ones a v1.12 file holds. Then go through the RPC exposure checklist for the flags, any GETH_ environment variables, and the file's [Node] settings: HTTPHost, HTTPModules, HTTPCors, WSHost, WSModules and WSOrigins.

Start the node

sudo systemctl daemon-reload && sudo systemctl start core-geth

core-geth stands for the systemd unit you wrote, and the reload picks up any flag you changed in that unit. From a terminal, run geth --classic instead, with the flags you just checked, --mess or --mess=false among them, and any other flag you start the node with, then run the next steps in a second terminal.

Recorded on Linux, moving v1.12.20 to v1.13.0Started from a terminal with geth --classic --syncmode full --mess=false, which sets MESS explicitly, as the flags step asks. The node starts on its new key, opens its IPC endpoint and goes on importing blocks.

Confirm the move

geth attach --exec 'admin.nodeInfo.name' ~/.ethereum/classic/geth.ipc
geth attach --exec 'admin.nodeInfo.enode' ~/.ethereum/classic/geth.ipc
geth attach --exec 'net.peerCount' ~/.ethereum/classic/geth.ipc
geth attach --exec 'eth.blockNumber' ~/.ethereum/classic/geth.ipc

If an attach line cannot connect, wait a minute and run it again. The first line must name a v1.13 release. The enode ID must differ from the one you recorded. If it is the same, the node is still on the old key: stop it and redo the rename step. The peer count climbs within minutes. If it is still zero after five minutes, see Why does the node have no peers?, and if the lists and bootnodes have gone dark, start it once from the node records committed in ethereumclassic/discv4-dns-lists. The block number starts near the one you recorded, or lower after an unclean stop, and climbs to the network's head: compare it with a block explorer or another node you run. If it stops climbing short of the head, investigate rather than resync.

If other nodes list this node, give them the new ID

They can no longer connect to this node, and neither side logs it at the default verbosity. On each one, on v1.12 or v1.13, run these lines with the old ID you recorded, the new ID from the step above, and the address and port the entry already has.

geth attach --exec "admin.removePeer('enode://<old ID>@<IP>:30303')" ~/.ethereum/classic/geth.ipc
geth attach --exec "admin.addPeer('enode://<new ID>@<IP>:30303')" ~/.ethereum/classic/geth.ipc
geth attach --exec "admin.peers.map(function(p){return p.enode})" ~/.ethereum/classic/geth.ipc

The first two lines each print true and take effect with no restart. The third lists the new ID once the nodes connect. The change lasts only until that node restarts, so put the new ID in that node's own config file too. Send the new ID to anyone else who lists this node. For an entry in TrustedNodes, use admin.removeTrustedPeer and admin.addTrustedPeer in place of the first two lines, or as well as them if the entry is in StaticNodes too. Neither opens a connection. Static and trusted peers explains the two lists.

If you need to roll back

The data directory works with both lines, so rolling back swaps the binary. Stop the node, and take any --mess flag out of its start command, because a v1.12 binary refuses it. Under systemd, run sudo systemctl daemon-reload. Then put the old binary back, and start the node again:

sudo mv /usr/local/bin/geth-v1.12 /usr/local/bin/geth

Roll back only for as long as it takes to find what went wrong: every v1.12 release carries the flaws the audits found. Keep the new key: rolling back does not make the old key safe, and a release before v1.12.21 leaks bits of the key it runs with, so rolling back to one exposes the new key too. Once the cause is fixed, move again with these steps, renaming the key again. A second rename on the same day finds its target name taken, so add -2 after $(date +%F) in it. If the node has to stay back longer, Keep a v1.12 node finding peers keeps it finding peers.

Once the node is stable, delete the v1.12 binary

Once the node has caught up, holds peers, and has run on the v1.13 line long enough that you will not roll back:

sudo rm /usr/local/bin/geth-v1.12

Every v1.12 release carries the flaws the audits found, so a copy left on the machine is one a later mistake can start. If you installed over a geth at another path, delete the geth-v1.12 beside it instead.

Linux users guide

macOS

Optional: if you have not run v1.13 yet, sync one new node first

Best practice before you move a v1.12 node: set up one new node on the v1.13 line, on a machine of its own beside the nodes you run, by following Install Core-Geth, and point no RPC clients or miners at it. It can sync from the network, or start from a copy of a stopped v1.12 node's data directory without its geth/nodekey. Once eth.syncing returns false and net.peerCount is above zero (How to tell it is done), move each v1.12 node with the steps below.

Download the release and check it

curl -LO https://github.com/ethereumclassic/core-geth/releases/download/v1.13.0/core-geth-osx-arm64-v1.13.0.zip
curl -LO https://github.com/ethereumclassic/core-geth/releases/download/v1.13.0/core-geth-osx-arm64-v1.13.0.zip.sha256
shasum -a 256 -c core-geth-osx-arm64-v1.13.0.zip.sha256

The node can keep running for this step. shasum -a 256 -c must print OK: if it prints FAILED, delete the file and download it again rather than running it. Verify what you downloaded also checks where the file came from. On an Intel Mac, put osx where the file name says osx-arm64, here and in "Install the release".

Record the node's block number and ID

geth attach --exec 'eth.blockNumber' ~/Library/Ethereum/classic/geth.ipc
geth attach --exec 'admin.nodeInfo.enode' ~/Library/Ethereum/classic/geth.ipc

Keep what this prints: "Confirm the move" compares the new ID and block number with it. These commands use the default data directory. For Mordor, use ~/Library/Ethereum/mordor; if you pass --datadir, use yours.

Stop the node cleanly

Press Ctrl-C once in the node's terminal and wait, or unload the launchd job. Stop it cleanly has the command for the run guide's agent. Wait until the log prints Blockchain stopped and the process exits. If it exits without that line, the stop was not clean: the node recovers at its next start, and its block number can start lower than the one you recorded.

Install the release

unzip -o core-geth-osx-arm64-v1.13.0.zip &&
  { test -e /usr/local/bin/geth-v1.12 || sudo mv /usr/local/bin/geth /usr/local/bin/geth-v1.12; } &&
  sudo install -m 0755 geth /usr/local/bin/geth &&
  geth version

Go on only if geth version reports a v1.13 release. geth-v1.12 is the old binary, kept so you can roll back. These commands install over /usr/local/bin/geth. If your unit or start command runs a geth at another path, use that path wherever they say /usr/local/bin/geth.

Rename the old P2P node key

mv -n ~/Library/Ethereum/classic/geth/nodekey ~/Library/Ethereum/classic/geth/nodekey.old-rotated-$(date +%F)

The next start writes a fresh key. If you start the node with --nodekey or --nodekeyhex, or set GETH_NODEKEY or GETH_NODEKEYHEX, take that out too: it overrides the file. If the rename prints an error, check the data directory: geth/nodekey must not be there when the node starts.

If this node lists static peers, update the list

They are under [Node.P2P] in the file you pass with --config. Edit it while the node is down:

[Node.P2P]
StaticNodes = ["enode://<node ID>@<IP>:30303"]

A peer that has already moved has a new ID, so use that. Separate entries with commas. TrustedNodes takes the same entries. With no file yet, save these two lines and add --config and the file's path to the start command. The client ignores static-nodes.json, so move its entries here.

Check the flags and settings you start the node with

While the node is down, decide MESS, which makes a node resist deep chain reorganizations. An upgrade can change whether a node runs MESS, so set it yourself: --mess turns it on and --mess=false turns it off. Which setting fits which operator sets out the choice. An exchange setting its confirmation policy can use the MESS confirmation calculator to see what that policy asks of an attacker. Give every node you run the same setting. Take out any --discovery.dns or --bootnodes flag added on v1.12, the two Keep a v1.12 node finding peers adds among them: the v1.13 line reads its own lists and bootnodes, --bootnodes would hold the node to the bootnodes it names, and with --discovery.dns it reads no snap lists. If the node starts with --config, delete the file's BootstrapNodes, EthDiscoveryURLs and SnapDiscoveryURLs lines, unless you put them there to name bootnodes or lists other than the release's own: a file dumpconfig wrote on v1.12 has all three, and the node keeps what they name in place of what the v1.13 line ships. The lists that guide puts in a file are the release's own, so delete those too. BootstrapNodesV5 can stay: the v1.13 line puts its own discovery v5 bootnodes in place of the ones a v1.12 file holds. Then go through the RPC exposure checklist for the flags, any GETH_ environment variables, and the file's [Node] settings: HTTPHost, HTTPModules, HTTPCors, WSHost, WSModules and WSOrigins.

Start the node

In a terminal. Add the flags you just checked to this line, --mess or --mess=false among them, and any other flag you start the node with: a node on --datadir started without it starts an empty chain in the default directory. If the node runs under the run guide's launchd agent, start the agent instead, as Keep it running shows:

geth --classic

The node runs in this terminal until you stop it, so run the next steps in a second terminal. For Mordor, use --mordor in place of --classic.

Confirm the move

geth attach --exec 'admin.nodeInfo.name' ~/Library/Ethereum/classic/geth.ipc
geth attach --exec 'admin.nodeInfo.enode' ~/Library/Ethereum/classic/geth.ipc
geth attach --exec 'net.peerCount' ~/Library/Ethereum/classic/geth.ipc
geth attach --exec 'eth.blockNumber' ~/Library/Ethereum/classic/geth.ipc

If an attach line cannot connect, wait a minute and run it again. The first line must name a v1.13 release. The enode ID must differ from the one you recorded. If it is the same, the node is still on the old key: stop it and redo the rename step. The peer count climbs within minutes. If it is still zero after five minutes, see Why does the node have no peers?, and if the lists and bootnodes have gone dark, start it once from the node records committed in ethereumclassic/discv4-dns-lists. The block number starts near the one you recorded, or lower after an unclean stop, and climbs to the network's head: compare it with a block explorer or another node you run. If it stops climbing short of the head, investigate rather than resync.

If other nodes list this node, give them the new ID

They can no longer connect to this node, and neither side logs it at the default verbosity. On each one, on v1.12 or v1.13, run these lines with the old ID you recorded, the new ID from the step above, and the address and port the entry already has.

geth attach --exec "admin.removePeer('enode://<old ID>@<IP>:30303')" ~/Library/Ethereum/classic/geth.ipc
geth attach --exec "admin.addPeer('enode://<new ID>@<IP>:30303')" ~/Library/Ethereum/classic/geth.ipc
geth attach --exec "admin.peers.map(function(p){return p.enode})" ~/Library/Ethereum/classic/geth.ipc

The first two lines each print true and take effect with no restart. The third lists the new ID once the nodes connect. The change lasts only until that node restarts, so put the new ID in that node's own config file too. Send the new ID to anyone else who lists this node. For an entry in TrustedNodes, use admin.removeTrustedPeer and admin.addTrustedPeer in place of the first two lines, or as well as them if the entry is in StaticNodes too. Neither opens a connection. Static and trusted peers explains the two lists.

If you need to roll back

The data directory works with both lines, so rolling back swaps the binary. Stop the node, and take any --mess flag out of its start command, because a v1.12 binary refuses it. Under systemd, run sudo systemctl daemon-reload. Then put the old binary back, and start the node again:

sudo mv /usr/local/bin/geth-v1.12 /usr/local/bin/geth

Roll back only for as long as it takes to find what went wrong: every v1.12 release carries the flaws the audits found. Keep the new key: rolling back does not make the old key safe, and a release before v1.12.21 leaks bits of the key it runs with, so rolling back to one exposes the new key too. Once the cause is fixed, move again with these steps, renaming the key again. A second rename on the same day finds its target name taken, so add -2 after $(date +%F) in it. If the node has to stay back longer, Keep a v1.12 node finding peers keeps it finding peers.

Once the node is stable, delete the v1.12 binary

Once the node has caught up, holds peers, and has run on the v1.13 line long enough that you will not roll back:

sudo rm /usr/local/bin/geth-v1.12

Every v1.12 release carries the flaws the audits found, so a copy left on the machine is one a later mistake can start. If you installed over a geth at another path, delete the geth-v1.12 beside it instead.

macOS users guide

Windows

Optional: if you have not run v1.13 yet, sync one new node first

Best practice before you move a v1.12 node: set up one new node on the v1.13 line, on a machine of its own beside the nodes you run, by following Install Core-Geth, and point no RPC clients or miners at it. It can sync from the network, or start from a copy of a stopped v1.12 node's data directory without its geth/nodekey. Once eth.syncing returns false and net.peerCount is above zero (How to tell it is done), move each v1.12 node with the steps below.

Download the release and check it

# In PowerShell, in the folder that holds your current geth.exe
Invoke-WebRequest "https://github.com/ethereumclassic/core-geth/releases/download/v1.13.0/core-geth-win64-v1.13.0.zip" -OutFile "core-geth-win64-v1.13.0.zip"
Invoke-WebRequest "https://github.com/ethereumclassic/core-geth/releases/download/v1.13.0/core-geth-win64-v1.13.0.zip.sha256" -OutFile "core-geth-win64-v1.13.0.zip.sha256"

# Compare these two lines
(Get-FileHash "core-geth-win64-v1.13.0.zip" -Algorithm SHA256).Hash
Get-Content "core-geth-win64-v1.13.0.zip.sha256"

The node can keep running for this step. Compare those two lines yourself: the letters must match and the case need not, since Get-FileHash prints uppercase and the .sha256 file holds lowercase followed by the file name. If they differ, delete the file and download it again rather than running it. Verify what you downloaded also checks where the file came from.

Record the node's block number and ID

.\geth.exe attach --exec 'eth.blockNumber' \\.\pipe\geth.ipc
.\geth.exe attach --exec 'admin.nodeInfo.enode' \\.\pipe\geth.ipc

Keep what this prints: "Confirm the move" compares the new ID and block number with it.

Stop the node cleanly

Press Ctrl+C once in the node's console window and wait. A window opened from the Startup folder then asks Terminate batch job (Y/N)?; type Y. Under a service wrapper, stop it with that tool. Wait until the log prints Blockchain stopped and the process exits. If it exits without that line, the stop was not clean: the node recovers at its next start, and its block number can start lower than the one you recorded.

Install the release

Rename-Item .\geth.exe geth-v1.12.exe
Expand-Archive "core-geth-win64-v1.13.0.zip" -DestinationPath . -Force
.\geth.exe version

Go on only if .\geth.exe version reports a v1.13 release. geth-v1.12.exe is the old binary, kept so you can roll back.

Rename the old P2P node key

$key = "$env:LOCALAPPDATA\Ethereum\classic\geth\nodekey"
Rename-Item $key "nodekey.old-rotated-$(Get-Date -Format yyyy-MM-dd)"

The next start writes a fresh key. If you start the node with --nodekey or --nodekeyhex, or set GETH_NODEKEY or GETH_NODEKEYHEX, take that out too: it overrides the file. If the rename prints an error, check the data directory: geth/nodekey must not be there when the node starts. The commands use the default data directory, but the node uses %USERPROFILE%\AppData\Roaming\Ethereum\classic instead if an older AppData\Roaming\Ethereum folder exists and is not empty. For Mordor, use mordor in place of classic; if you pass --datadir, use yours.

If this node lists static peers, update the list

They are under [Node.P2P] in the file you pass with --config. Edit it while the node is down:

[Node.P2P]
StaticNodes = ["enode://<node ID>@<IP>:30303"]

A peer that has already moved has a new ID, so use that. Separate entries with commas. TrustedNodes takes the same entries. With no file yet, save these two lines and add --config and the file's path to the start command. The client ignores static-nodes.json, so move its entries here. Save the file as UTF-8 with no byte order mark, after this edit and any other. A byte order mark, or UTF-16, stops the start with invalid TOML syntax.

Check the flags and settings you start the node with

While the node is down, decide MESS, which makes a node resist deep chain reorganizations. An upgrade can change whether a node runs MESS, so set it yourself: --mess turns it on and --mess=false turns it off. Which setting fits which operator sets out the choice. An exchange setting its confirmation policy can use the MESS confirmation calculator to see what that policy asks of an attacker. Give every node you run the same setting. Take out any --discovery.dns or --bootnodes flag added on v1.12, the two Keep a v1.12 node finding peers adds among them: the v1.13 line reads its own lists and bootnodes, --bootnodes would hold the node to the bootnodes it names, and with --discovery.dns it reads no snap lists. If the node starts with --config, delete the file's BootstrapNodes, EthDiscoveryURLs and SnapDiscoveryURLs lines, unless you put them there to name bootnodes or lists other than the release's own: a file dumpconfig wrote on v1.12 has all three, and the node keeps what they name in place of what the v1.13 line ships. The lists that guide puts in a file are the release's own, so delete those too. BootstrapNodesV5 can stay: the v1.13 line puts its own discovery v5 bootnodes in place of the ones a v1.12 file holds. Then go through the RPC exposure checklist for the flags, any GETH_ environment variables, and the file's [Node] settings: HTTPHost, HTTPModules, HTTPCors, WSHost, WSModules and WSOrigins.

Start the node

In PowerShell, in the folder that holds the node's geth.exe. Add the flags you just checked to this line, --mess or --mess=false among them, and any other flag you start the node with: a node on --datadir started without it starts an empty chain in the default directory. If the node starts from the Startup folder, as the run guide's Keep it running sets up, run that command file instead:

.\geth.exe --classic

The node runs in this window until you stop it, so run the next steps in a second PowerShell window, in the same folder. For Mordor, use --mordor in place of --classic.

Confirm the move

.\geth.exe attach --exec 'admin.nodeInfo.name' \\.\pipe\geth.ipc
.\geth.exe attach --exec 'admin.nodeInfo.enode' \\.\pipe\geth.ipc
.\geth.exe attach --exec 'net.peerCount' \\.\pipe\geth.ipc
.\geth.exe attach --exec 'eth.blockNumber' \\.\pipe\geth.ipc

If an attach line cannot connect, wait a minute and run it again. The first line must name a v1.13 release. The enode ID must differ from the one you recorded. If it is the same, the node is still on the old key: stop it and redo the rename step. The peer count climbs within minutes. If it is still zero after five minutes, see Why does the node have no peers?, and if the lists and bootnodes have gone dark, start it once from the node records committed in ethereumclassic/discv4-dns-lists. The block number starts near the one you recorded, or lower after an unclean stop, and climbs to the network's head: compare it with a block explorer or another node you run. If it stops climbing short of the head, investigate rather than resync.

If other nodes list this node, give them the new ID

They can no longer connect to this node, and neither side logs it at the default verbosity. On each one, on v1.12 or v1.13, run these lines with the old ID you recorded, the new ID from the step above, and the address and port the entry already has.

.\geth.exe attach --exec "admin.removePeer('enode://<old ID>@<IP>:30303')" \\.\pipe\geth.ipc
.\geth.exe attach --exec "admin.addPeer('enode://<new ID>@<IP>:30303')" \\.\pipe\geth.ipc
.\geth.exe attach --exec "admin.peers.map(function(p){return p.enode})" \\.\pipe\geth.ipc

The first two lines each print true and take effect with no restart. The third lists the new ID once the nodes connect. The change lasts only until that node restarts, so put the new ID in that node's own config file too. Send the new ID to anyone else who lists this node. For an entry in TrustedNodes, use admin.removeTrustedPeer and admin.addTrustedPeer in place of the first two lines, or as well as them if the entry is in StaticNodes too. Neither opens a connection. Static and trusted peers explains the two lists.

If you need to roll back

The data directory works with both lines, so rolling back swaps the binary. Stop the node, and take any --mess flag out of its start command, because a v1.12 binary refuses it. Then, in PowerShell, in the folder that holds the node's geth.exe, put the old binary back, and start the node again:

Remove-Item .\geth.exe
Rename-Item .\geth-v1.12.exe geth.exe

Roll back only for as long as it takes to find what went wrong: every v1.12 release carries the flaws the audits found. Keep the new key: rolling back does not make the old key safe, and a release before v1.12.21 leaks bits of the key it runs with, so rolling back to one exposes the new key too. Once the cause is fixed, move again with these steps, renaming the key again. A second rename on the same day finds its target name taken, so add -2 after $(Get-Date -Format yyyy-MM-dd) in it. If the node has to stay back longer, Keep a v1.12 node finding peers keeps it finding peers.

Once the node is stable, delete the v1.12 binary

Once the node has caught up, holds peers, and has run on the v1.13 line long enough that you will not roll back, in PowerShell, in the folder that holds the node's geth.exe:

Remove-Item .\geth-v1.12.exe

Every v1.12 release carries the flaws the audits found, so a copy left on the machine is one a later mistake can start.

Windows users guide

Containers

Optional: if you have not run v1.13 yet, sync one new node first

Best practice before you move a v1.12 node: set up one new node on the v1.13 line, on a machine of its own beside the nodes you run, by following Install Core-Geth, and point no RPC clients or miners at it. It can sync from the network, or start from a copy of a stopped v1.12 node's data directory without its geth/nodekey. Once eth.syncing returns false and net.peerCount is above zero (How to tell it is done), move each v1.12 node with the steps below.

Check Docker and the GitHub CLI

These steps need Docker to pull and run the image, and the GitHub CLI to check where the image came from:

docker version --format '{{.Server.Version}}'
gh --version
gh auth status

The node can keep running for this step. The first line must print the Docker engine's version. An error in its place means Docker is not installed or not running: Get Docker says how to install it. gh --version must print 2.49.0 or later, the release that added gh attestation. gh auth status must say you are logged in to github.com. If it does not, run gh auth login. Install or update the GitHub CLI from cli.github.com. On Debian and Ubuntu, take GitHub's own package: theirs are older than 2.49.0.

Pull the release

docker pull ghcr.io/ethereumclassic/core-geth:latest
gh attestation verify oci://ghcr.io/ethereumclassic/core-geth:latest --repo ethereumclassic/core-geth

The node can keep running for this step. gh attestation verify must print Verification succeeded and name .github/workflows/docker-publish.yml. If it does not, do not run the image. Verify what you downloaded checks an image file from the release. A loaded image file carries its release tag and no latest tag, so put that tag in place of latest in the steps below, or Docker pulls from the registry instead.

Record the node's block number, ID and restart policy

docker exec core-geth geth attach --exec 'eth.blockNumber' /root/.ethereum/classic/geth.ipc
docker exec core-geth geth attach --exec 'admin.nodeInfo.enode' /root/.ethereum/classic/geth.ipc
docker inspect core-geth --format '{{.HostConfig.RestartPolicy.Name}}'

Keep what this prints: "Confirm the move" compares the new ID and block number with it. The last line prints the container's restart policy, which the rollback puts back. These commands use a container named core-geth with its data at /root, so put your own name in its place. For Mordor, use .ethereum/mordor in the paths.

Check where the container keeps its data

docker inspect core-geth --format '{{range .Mounts}}{{.Destination}} {{end}}'

This prints /root, or /root/.ethereum, when the data is in a host directory or a named volume, which the steps below carry to the new container. If it prints neither, the data is inside the container itself: after the stop, take the copy step below in place of the rename, or the new container starts with no chain and resyncs.

Stop the node cleanly

docker stop -t 300 core-geth
docker logs --tail 50 core-geth 2>&1 | grep 'Blockchain stopped'

The second command prints Blockchain stopped after a clean stop. If it prints nothing, the node did not stop cleanly: it recovers at its next start, moving its head back and importing those blocks again (After an unclean stop), so its block number can start lower than the one you recorded. Use -t 300: a plain docker stop kills the node if it has not stopped within 10 seconds.

Rename the old P2P node key

If the check printed /root or /root/.ethereum:

docker run --rm --volumes-from core-geth --entrypoint sh ghcr.io/ethereumclassic/core-geth:latest \
  -c "mv -n /root/.ethereum/classic/geth/nodekey /root/.ethereum/classic/geth/nodekey.old-rotated-$(date +%F)"

The next start writes a fresh key. If you start the node with --nodekey or --nodekeyhex, or set GETH_NODEKEY or GETH_NODEKEYHEX, take that out too: it overrides the file. If the rename prints an error, check the data directory: geth/nodekey must not be there when the node starts.

If the data is inside the container, copy it out and rename the key there

If the check printed neither, run this in place of the rename above. It copies the data out of the stopped container and renames the key in the copy, which needs as much free space as the chain takes:

mkdir -p $HOME/core-geth-data &&
  docker cp core-geth:/root/. $HOME/core-geth-data/ &&
  docker run --rm -v $HOME/core-geth-data:/root --entrypoint sh ghcr.io/ethereumclassic/core-geth:latest \
    -c "mv -n /root/.ethereum/classic/geth/nodekey /root/.ethereum/classic/geth/nodekey.old-rotated-$(date +%F)"

The next start writes a fresh key. If you start the node with --nodekey or --nodekeyhex, or set GETH_NODEKEY or GETH_NODEKEYHEX, take that out too: it overrides the file. If the rename prints an error, check the data directory: geth/nodekey must not be there when the node starts. The chain comes with the copy, so the node does not resync.

If this node lists static peers, update the list

They are under [Node.P2P] in the file you pass with --config. Edit it while the node is down:

[Node.P2P]
StaticNodes = ["enode://<node ID>@<IP>:30303"]

A peer that has already moved has a new ID, so use that. Separate entries with commas. TrustedNodes takes the same entries. With no file yet, save these two lines and add --config and the file's path to the start command. The client ignores static-nodes.json, so move its entries here. To give the new container the file, add -v <path>/config.toml:/config.toml:ro to the docker run under "Replace the container", and --config /config.toml after the image name.

Check the flags and settings you start the node with

While the node is down, decide MESS, which makes a node resist deep chain reorganizations. An upgrade can change whether a node runs MESS, so set it yourself: --mess turns it on and --mess=false turns it off. Which setting fits which operator sets out the choice. An exchange setting its confirmation policy can use the MESS confirmation calculator to see what that policy asks of an attacker. Give every node you run the same setting. Take out any --discovery.dns or --bootnodes flag added on v1.12, the two Keep a v1.12 node finding peers adds among them: the v1.13 line reads its own lists and bootnodes, --bootnodes would hold the node to the bootnodes it names, and with --discovery.dns it reads no snap lists. If the node starts with --config, delete the file's BootstrapNodes, EthDiscoveryURLs and SnapDiscoveryURLs lines, unless you put them there to name bootnodes or lists other than the release's own: a file dumpconfig wrote on v1.12 has all three, and the node keeps what they name in place of what the v1.13 line ships. The lists that guide puts in a file are the release's own, so delete those too. BootstrapNodesV5 can stay: the v1.13 line puts its own discovery v5 bootnodes in place of the ones a v1.12 file holds. Then go through the RPC exposure checklist for the flags, any GETH_ environment variables, and the file's [Node] settings: HTTPHost, HTTPModules, HTTPCors, WSHost, WSModules and WSOrigins.

Replace the container

Before you run this block, put the old container's flags in its last line and its GETH_ variables in as -e options: docker inspect core-geth-v1.12 --format '{{json .Args}} {{json .Config.Env}}' prints both. Keep its two P2P port mappings. The RPC check applies to the published RPC port, which stays on 127.0.0.1 even if the old container published it everywhere; --http.addr 0.0.0.0 inside the container reaches only that port. If the old container ran with --network host, run this one that way too, without the -p options and with --http.addr 127.0.0.1. If you copied the data out, put -v $HOME/core-geth-data:/root in place of --volumes-from core-geth-v1.12.

docker rename core-geth core-geth-v1.12
docker update --restart no core-geth-v1.12
docker run -d --name core-geth \
  --restart unless-stopped --stop-timeout 300 \
  --volumes-from core-geth-v1.12 \
  -p 30303:30303 -p 30303:30303/udp -p 127.0.0.1:8545:8545 \
  ghcr.io/ethereumclassic/core-geth:latest \
  --classic --http --http.addr 0.0.0.0 --http.port 8545

For Mordor, use --mordor in place of --classic. The old container stays, stopped, and is what you roll back to.

Confirm the move

docker exec core-geth geth version
docker exec core-geth geth attach --exec 'admin.nodeInfo.enode' /root/.ethereum/classic/geth.ipc
docker exec core-geth geth attach --exec 'net.peerCount' /root/.ethereum/classic/geth.ipc
docker exec core-geth geth attach --exec 'eth.blockNumber' /root/.ethereum/classic/geth.ipc

If an attach line cannot connect, wait a minute and run it again. The first line must name a v1.13 release. The enode ID must differ from the one you recorded. If it is the same, the node is still on the old key: stop it and redo the rename step. The peer count climbs within minutes. If it is still zero after five minutes, see Why does the node have no peers?, and if the lists and bootnodes have gone dark, start it once from the node records committed in ethereumclassic/discv4-dns-lists. The block number starts near the one you recorded, or lower after an unclean stop, and climbs to the network's head: compare it with a block explorer or another node you run. If it stops climbing short of the head, investigate rather than resync.

If other nodes list this node, give them the new ID

They can no longer connect to this node, and neither side logs it at the default verbosity. On each one, on v1.12 or v1.13, run these lines with the old ID you recorded, the new ID from the step above, and the address and port the entry already has. Put that node's container name in place of core-geth, which here is the node you just moved. On a node outside a container, run geth attach with that node's IPC path, as this section's other tabs show.

docker exec core-geth geth attach --exec "admin.removePeer('enode://<old ID>@<IP>:30303')" /root/.ethereum/classic/geth.ipc
docker exec core-geth geth attach --exec "admin.addPeer('enode://<new ID>@<IP>:30303')" /root/.ethereum/classic/geth.ipc
docker exec core-geth geth attach --exec "admin.peers.map(function(p){return p.enode})" /root/.ethereum/classic/geth.ipc

The first two lines each print true and take effect with no restart. The third lists the new ID once the nodes connect. The change lasts only until that node restarts, so put the new ID in that node's own config file too. Send the new ID to anyone else who lists this node. For an entry in TrustedNodes, use admin.removeTrustedPeer and admin.addTrustedPeer in place of the first two lines, or as well as them if the entry is in StaticNodes too. Neither opens a connection. Static and trusted peers explains the two lists.

If you need to roll back

If you copied the data out, do not run this block, because the old container's own copy of the data still holds the old key. Stop and remove the new container as its first two lines do, then run the image the old one used, docker inspect core-geth-v1.12 --format '{{.Config.Image}}', on the copy, with -v $HOME/core-geth-data:/root, the old container's ports and flags, and no --mess.

docker stop -t 300 core-geth &&
  docker rm core-geth &&
  docker rename core-geth-v1.12 core-geth &&
  docker update --restart unless-stopped core-geth &&
  docker start core-geth

Put the restart policy you recorded in place of unless-stopped. Roll back only for as long as it takes to find what went wrong: every v1.12 release carries the flaws the audits found. Keep the new key: rolling back does not make the old key safe, and a release before v1.12.21 leaks bits of the key it runs with, so rolling back to one exposes the new key too. Once the cause is fixed, move again with these steps, renaming the key again. A second rename on the same day finds its target name taken, so add -2 after $(date +%F) in it. If the node has to stay back longer, Keep a v1.12 node finding peers keeps it finding peers.

Once the node is stable, remove the v1.12 container and image

Once the node has caught up, holds peers, and has run on the v1.13 line long enough that you will not roll back:

OLD=$(docker inspect core-geth-v1.12 --format '{{.Config.Image}}') &&
  docker rm core-geth-v1.12 &&
  docker rmi "$OLD"

Every v1.12 release carries the flaws the audits found, so a copy left on the machine is one a later mistake can start. The first line reads which image the old container ran, then the block removes the stopped container and that image. docker rm leaves the volumes the new container uses. If you copied the data out, the old container's own copy of the data goes with it, and the node runs on the copy. docker rmi refuses while another container still uses the image.

Docker users guide

Source

Each tab starts from a machine with none of the build tools: it installs them, builds the main branch, and moves the node on that platform.

Choose your platform for building from source and moving a node

Build from source and move the node on Linux

Optional: if you have not run v1.13 yet, sync one new node first

Best practice before you move a v1.12 node: set up one new node on the v1.13 line, on a machine of its own beside the nodes you run, by following Install Core-Geth, and point no RPC clients or miners at it. It can sync from the network, or start from a copy of a stopped v1.12 node's data directory without its geth/nodekey. Once eth.syncing returns false and net.peerCount is above zero (How to tell it is done), move each v1.12 node with the steps below.

Install Git, a C compiler and make

The node can keep running while you install the tools and build. On Debian or Ubuntu:

sudo apt-get update && sudo apt-get install -y build-essential git curl

On Fedora or RHEL, run sudo dnf install -y --refresh gcc make git && sudo dnf upgrade -y gcc make git instead, and on Arch, sudo pacman -Syu --needed base-devel git, which brings the rest of the system current with them, as Arch requires. Each line also updates a tool that is already installed. build-essential and base-devel carry the C compiler and make, and the next step downloads with curl.

Install Go 1.26.8

main pins Go 1.26.8 in its build/checksums.txt, the Go its release builds use. This installs Go 1.26.8 from the Go project's downloads at go.dev, and checks the download against the SHA-256 the same file pins:

curl -LO https://go.dev/dl/go1.26.8.linux-amd64.tar.gz
echo "d0f743b33e8d8945e6b1f432edd15785c70507121d6e2a723b21285eddf8b57b  go1.26.8.linux-amd64.tar.gz" | sha256sum -c &&
  sudo rm -rf /usr/local/go &&
  sudo tar -C /usr/local -xzf go1.26.8.linux-amd64.tar.gz &&
  sudo ln -sf /usr/local/go/bin/go /usr/local/bin/go &&
  hash -r &&
  go version

sha256sum -c must print OK: if it prints FAILED, delete the file and download it again rather than installing it. The block replaces any Go in /usr/local/go, and go version must then print go1.26.8. On a 64-bit Arm machine, where uname -m prints aarch64, put go1.26.8.linux-arm64.tar.gz in place of the file name and 211ffced9dcb9633a55eac6364816ec0ddd951389a740e88fa8b3337971bdda0 in place of the SHA-256. On 32-bit Arm, use go1.26.8.linux-armv6l.tar.gz and eab440beabf395870752021fa74cedf04f97b61e43ebbe005ecbb98b94e55697.

Check the tools
git --version
go version
gcc --version
make --version

Each line must print a version, and go version must print go1.26.8. A line that prints an error names a tool the steps above did not install. If go version prints another release, a Go earlier on PATH than /usr/local/bin runs in its place: uninstall that Go.

Build the main branch
git clone --branch main --single-branch https://github.com/ethereumclassic/core-geth.git &&
  cd core-geth &&
  make geth &&
  ./build/bin/geth version

Go on only if the first line geth version prints names version 1.13 or later; a build of main ends it with -unstable, and two builds a day apart can be different code. Keep the Git Commit line: "Confirm the move" checks the running node against it. If a core-geth folder is already there, run git pull in it and build there, or remove it first. The Source tab under Install says why these steps build main.

Record the node's block number and ID
geth attach --exec 'eth.blockNumber' ~/.ethereum/classic/geth.ipc
geth attach --exec 'admin.nodeInfo.enode' ~/.ethereum/classic/geth.ipc

Keep what this prints: "Confirm the move" compares the new ID and block number with it. These commands use the default data directory. For Mordor, use ~/.ethereum/mordor; if you pass --datadir, use yours. On the unit in the Linux run guide, the geth user owns /var/lib/core-geth, so attach as sudo -u geth -H geth --classic attach --exec 'admin.nodeInfo.enode' /var/lib/core-geth/geth.ipc instead, here and in every later attach line.

Stop the node cleanly

Press Ctrl-C once in the node's terminal and wait, or stop the systemd unit you wrote, for example sudo systemctl stop core-geth. Wait until the log prints Blockchain stopped and the process exits. If it exits without that line, the stop was not clean: the node recovers at its next start, and its block number can start lower than the one you recorded. Under systemd, journalctl -u core-geth shows that log.

Install the build
ls build/bin/geth &&
  { test -e /usr/local/bin/geth-v1.12 || sudo mv /usr/local/bin/geth /usr/local/bin/geth-v1.12; } &&
  sudo install -m 0755 build/bin/geth /usr/local/bin/geth &&
  geth version

Run this in the folder the build step left you in: the first line stops the block anywhere else. Go on only if geth version prints the same first line and Git Commit line it printed after the build. geth-v1.12 is the old binary, kept so you can roll back. These commands install over /usr/local/bin/geth. If your unit or start command runs a geth at another path, use that path wherever they say /usr/local/bin/geth.

Rename the old P2P node key
mv -n ~/.ethereum/classic/geth/nodekey ~/.ethereum/classic/geth/nodekey.old-rotated-$(date +%F)

The next start writes a fresh key. If you start the node with --nodekey or --nodekeyhex, or set GETH_NODEKEY or GETH_NODEKEYHEX, take that out too: it overrides the file. If the rename prints an error, check the data directory: geth/nodekey must not be there when the node starts. On the unit in the Linux run guide, run sudo -u geth mv -n /var/lib/core-geth/geth/nodekey /var/lib/core-geth/geth/nodekey.old-rotated-$(date +%F) instead.

If this node lists static peers, update the list

They are under [Node.P2P] in the file you pass with --config. Edit it while the node is down:

[Node.P2P]
StaticNodes = ["enode://<node ID>@<IP>:30303"]

A peer that has already moved has a new ID, so use that. Separate entries with commas. TrustedNodes takes the same entries. With no file yet, save these two lines and add --config and the file's path to the start command. The client ignores static-nodes.json, so move its entries here.

Check the flags and settings you start the node with

While the node is down, decide MESS, which makes a node resist deep chain reorganizations. An upgrade can change whether a node runs MESS, so set it yourself: --mess turns it on and --mess=false turns it off. Which setting fits which operator sets out the choice. An exchange setting its confirmation policy can use the MESS confirmation calculator to see what that policy asks of an attacker. Give every node you run the same setting. Take out any --discovery.dns or --bootnodes flag added on v1.12, the two Keep a v1.12 node finding peers adds among them: the v1.13 line reads its own lists and bootnodes, --bootnodes would hold the node to the bootnodes it names, and with --discovery.dns it reads no snap lists. If the node starts with --config, delete the file's BootstrapNodes, EthDiscoveryURLs and SnapDiscoveryURLs lines, unless you put them there to name bootnodes or lists other than the release's own: a file dumpconfig wrote on v1.12 has all three, and the node keeps what they name in place of what the v1.13 line ships. The lists that guide puts in a file are the release's own, so delete those too. BootstrapNodesV5 can stay: the v1.13 line puts its own discovery v5 bootnodes in place of the ones a v1.12 file holds. Then go through the RPC exposure checklist for the flags, any GETH_ environment variables, and the file's [Node] settings: HTTPHost, HTTPModules, HTTPCors, WSHost, WSModules and WSOrigins.

Start the node
sudo systemctl daemon-reload && sudo systemctl start core-geth

core-geth stands for the systemd unit you wrote, and the reload picks up any flag you changed in that unit. From a terminal, run geth --classic instead, with the flags you just checked, --mess or --mess=false among them, and any other flag you start the node with, then run the next steps in a second terminal.

Confirm the move
geth attach --exec 'admin.nodeInfo.name' ~/.ethereum/classic/geth.ipc
geth attach --exec 'admin.nodeInfo.enode' ~/.ethereum/classic/geth.ipc
geth attach --exec 'net.peerCount' ~/.ethereum/classic/geth.ipc
geth attach --exec 'eth.blockNumber' ~/.ethereum/classic/geth.ipc

If an attach line cannot connect, wait a minute and run it again. The first line must name the version the build printed, followed by the first eight characters of its Git Commit. The enode ID must differ from the one you recorded. If it is the same, the node is still on the old key: stop it and redo the rename step. The peer count climbs within minutes. If it is still zero after five minutes, see Why does the node have no peers?, and if the lists and bootnodes have gone dark, start it once from the node records committed in ethereumclassic/discv4-dns-lists. The block number starts near the one you recorded, or lower after an unclean stop, and climbs to the network's head: compare it with a block explorer or another node you run. If it stops climbing short of the head, investigate rather than resync.

If other nodes list this node, give them the new ID

They can no longer connect to this node, and neither side logs it at the default verbosity. On each one, on v1.12 or v1.13, run these lines with the old ID you recorded, the new ID from the step above, and the address and port the entry already has.

geth attach --exec "admin.removePeer('enode://<old ID>@<IP>:30303')" ~/.ethereum/classic/geth.ipc
geth attach --exec "admin.addPeer('enode://<new ID>@<IP>:30303')" ~/.ethereum/classic/geth.ipc
geth attach --exec "admin.peers.map(function(p){return p.enode})" ~/.ethereum/classic/geth.ipc

The first two lines each print true and take effect with no restart. The third lists the new ID once the nodes connect. The change lasts only until that node restarts, so put the new ID in that node's own config file too. Send the new ID to anyone else who lists this node. For an entry in TrustedNodes, use admin.removeTrustedPeer and admin.addTrustedPeer in place of the first two lines, or as well as them if the entry is in StaticNodes too. Neither opens a connection. Static and trusted peers explains the two lists.

If you need to roll back

The data directory works with both lines, so rolling back swaps the binary. Stop the node, and take any --mess flag out of its start command, because a v1.12 binary refuses it. Under systemd, run sudo systemctl daemon-reload. Then put the old binary back, and start the node again:

sudo mv /usr/local/bin/geth-v1.12 /usr/local/bin/geth

Roll back only for as long as it takes to find what went wrong: every v1.12 release carries the flaws the audits found. Keep the new key: rolling back does not make the old key safe, and a release before v1.12.21 leaks bits of the key it runs with, so rolling back to one exposes the new key too. Once the cause is fixed, move again with these steps, renaming the key again. A second rename on the same day finds its target name taken, so add -2 after $(date +%F) in it. If the node has to stay back longer, Keep a v1.12 node finding peers keeps it finding peers.

Once the node is stable, delete the v1.12 binary

Once the node has caught up, holds peers, and has run on the v1.13 line long enough that you will not roll back:

sudo rm /usr/local/bin/geth-v1.12

Every v1.12 release carries the flaws the audits found, so a copy left on the machine is one a later mistake can start. If you installed over a geth at another path, delete the geth-v1.12 beside it instead.

Linux users guide

Build from source and move the node on macOS

Optional: if you have not run v1.13 yet, sync one new node first

Best practice before you move a v1.12 node: set up one new node on the v1.13 line, on a machine of its own beside the nodes you run, by following Install Core-Geth, and point no RPC clients or miners at it. It can sync from the network, or start from a copy of a stopped v1.12 node's data directory without its geth/nodekey. Once eth.syncing returns false and net.peerCount is above zero (How to tell it is done), move each v1.12 node with the steps below.

Install the Xcode Command Line Tools

The node can keep running while you install the tools and build.

xcode-select --install

A window asks whether to install the tools: choose Install, and wait until it says the software was installed. The tools carry Git, the C compiler and make. If they are already installed, the command says so: install any update for them that Software Update lists in System Settings, then go on to the next step.

On an Apple Silicon Mac, install Go 1.26.8

main pins Go 1.26.8 in its build/checksums.txt, the Go its release builds use. This installs Go 1.26.8 from the Go project's downloads at go.dev, and checks the download against the SHA-256 the same file pins. uname -m prints arm64 on Apple Silicon and x86_64 on Intel:

curl -LO https://go.dev/dl/go1.26.8.darwin-arm64.tar.gz
echo "a012b25b571bd0138a03dcd25375ceba866fe5ca822f426d2c66a4de56fd3f4b  go1.26.8.darwin-arm64.tar.gz" | shasum -a 256 -c &&
  sudo rm -rf /usr/local/go &&
  sudo tar -C /usr/local -xzf go1.26.8.darwin-arm64.tar.gz &&
  sudo mkdir -p /usr/local/bin &&
  sudo ln -sf /usr/local/go/bin/go /usr/local/bin/go &&
  hash -r &&
  go version

shasum -a 256 -c must print OK: if it prints FAILED, delete the file and download it again rather than installing it. The block replaces any Go in /usr/local/go, and go version must then print go1.26.8.

On an Intel Mac, install Go 1.26.8

The same release, built for Intel:

curl -LO https://go.dev/dl/go1.26.8.darwin-amd64.tar.gz
echo "186be014105aa6542b767d2c6ed5cca10a0214bdff809ef1724022a8c7894150  go1.26.8.darwin-amd64.tar.gz" | shasum -a 256 -c &&
  sudo rm -rf /usr/local/go &&
  sudo tar -C /usr/local -xzf go1.26.8.darwin-amd64.tar.gz &&
  sudo mkdir -p /usr/local/bin &&
  sudo ln -sf /usr/local/go/bin/go /usr/local/bin/go &&
  hash -r &&
  go version

shasum -a 256 -c must print OK: if it prints FAILED, delete the file and download it again rather than installing it. The block replaces any Go in /usr/local/go, and go version must then print go1.26.8.

Check the tools
git --version
go version
gcc --version
make --version

Each line must print a version, and go version must print go1.26.8. gcc --version prints Apple clang, the C compiler on macOS. A line that prints an error names a tool the steps above did not install. If go version prints another release, a Go earlier on PATH, such as one from Homebrew, runs in its place: uninstall that Go.

Build the main branch
git clone --branch main --single-branch https://github.com/ethereumclassic/core-geth.git &&
  cd core-geth &&
  make geth &&
  ./build/bin/geth version

Go on only if the first line geth version prints names version 1.13 or later; a build of main ends it with -unstable, and two builds a day apart can be different code. Keep the Git Commit line: "Confirm the move" checks the running node against it. If a core-geth folder is already there, run git pull in it and build there, or remove it first. The Source tab under Install says why these steps build main.

Record the node's block number and ID
geth attach --exec 'eth.blockNumber' ~/Library/Ethereum/classic/geth.ipc
geth attach --exec 'admin.nodeInfo.enode' ~/Library/Ethereum/classic/geth.ipc

Keep what this prints: "Confirm the move" compares the new ID and block number with it. These commands use the default data directory. For Mordor, use ~/Library/Ethereum/mordor; if you pass --datadir, use yours.

Stop the node cleanly

Press Ctrl-C once in the node's terminal and wait, or unload the launchd job. Stop it cleanly has the command for the run guide's agent. Wait until the log prints Blockchain stopped and the process exits. If it exits without that line, the stop was not clean: the node recovers at its next start, and its block number can start lower than the one you recorded.

Install the build
ls build/bin/geth &&
  { test -e /usr/local/bin/geth-v1.12 || sudo mv /usr/local/bin/geth /usr/local/bin/geth-v1.12; } &&
  sudo install -m 0755 build/bin/geth /usr/local/bin/geth &&
  geth version

Run this in the folder the build step left you in: the first line stops the block anywhere else. Go on only if geth version prints the same first line and Git Commit line it printed after the build. geth-v1.12 is the old binary, kept so you can roll back. These commands install over /usr/local/bin/geth. If your unit or start command runs a geth at another path, use that path wherever they say /usr/local/bin/geth.

Rename the old P2P node key
mv -n ~/Library/Ethereum/classic/geth/nodekey ~/Library/Ethereum/classic/geth/nodekey.old-rotated-$(date +%F)

The next start writes a fresh key. If you start the node with --nodekey or --nodekeyhex, or set GETH_NODEKEY or GETH_NODEKEYHEX, take that out too: it overrides the file. If the rename prints an error, check the data directory: geth/nodekey must not be there when the node starts.

If this node lists static peers, update the list

They are under [Node.P2P] in the file you pass with --config. Edit it while the node is down:

[Node.P2P]
StaticNodes = ["enode://<node ID>@<IP>:30303"]

A peer that has already moved has a new ID, so use that. Separate entries with commas. TrustedNodes takes the same entries. With no file yet, save these two lines and add --config and the file's path to the start command. The client ignores static-nodes.json, so move its entries here.

Check the flags and settings you start the node with

While the node is down, decide MESS, which makes a node resist deep chain reorganizations. An upgrade can change whether a node runs MESS, so set it yourself: --mess turns it on and --mess=false turns it off. Which setting fits which operator sets out the choice. An exchange setting its confirmation policy can use the MESS confirmation calculator to see what that policy asks of an attacker. Give every node you run the same setting. Take out any --discovery.dns or --bootnodes flag added on v1.12, the two Keep a v1.12 node finding peers adds among them: the v1.13 line reads its own lists and bootnodes, --bootnodes would hold the node to the bootnodes it names, and with --discovery.dns it reads no snap lists. If the node starts with --config, delete the file's BootstrapNodes, EthDiscoveryURLs and SnapDiscoveryURLs lines, unless you put them there to name bootnodes or lists other than the release's own: a file dumpconfig wrote on v1.12 has all three, and the node keeps what they name in place of what the v1.13 line ships. The lists that guide puts in a file are the release's own, so delete those too. BootstrapNodesV5 can stay: the v1.13 line puts its own discovery v5 bootnodes in place of the ones a v1.12 file holds. Then go through the RPC exposure checklist for the flags, any GETH_ environment variables, and the file's [Node] settings: HTTPHost, HTTPModules, HTTPCors, WSHost, WSModules and WSOrigins.

Start the node

In a terminal. Add the flags you just checked to this line, --mess or --mess=false among them, and any other flag you start the node with: a node on --datadir started without it starts an empty chain in the default directory. If the node runs under the run guide's launchd agent, start the agent instead, as Keep it running shows:

geth --classic

The node runs in this terminal until you stop it, so run the next steps in a second terminal. For Mordor, use --mordor in place of --classic.

Confirm the move
geth attach --exec 'admin.nodeInfo.name' ~/Library/Ethereum/classic/geth.ipc
geth attach --exec 'admin.nodeInfo.enode' ~/Library/Ethereum/classic/geth.ipc
geth attach --exec 'net.peerCount' ~/Library/Ethereum/classic/geth.ipc
geth attach --exec 'eth.blockNumber' ~/Library/Ethereum/classic/geth.ipc

If an attach line cannot connect, wait a minute and run it again. The first line must name the version the build printed, followed by the first eight characters of its Git Commit. The enode ID must differ from the one you recorded. If it is the same, the node is still on the old key: stop it and redo the rename step. The peer count climbs within minutes. If it is still zero after five minutes, see Why does the node have no peers?, and if the lists and bootnodes have gone dark, start it once from the node records committed in ethereumclassic/discv4-dns-lists. The block number starts near the one you recorded, or lower after an unclean stop, and climbs to the network's head: compare it with a block explorer or another node you run. If it stops climbing short of the head, investigate rather than resync.

If other nodes list this node, give them the new ID

They can no longer connect to this node, and neither side logs it at the default verbosity. On each one, on v1.12 or v1.13, run these lines with the old ID you recorded, the new ID from the step above, and the address and port the entry already has.

geth attach --exec "admin.removePeer('enode://<old ID>@<IP>:30303')" ~/Library/Ethereum/classic/geth.ipc
geth attach --exec "admin.addPeer('enode://<new ID>@<IP>:30303')" ~/Library/Ethereum/classic/geth.ipc
geth attach --exec "admin.peers.map(function(p){return p.enode})" ~/Library/Ethereum/classic/geth.ipc

The first two lines each print true and take effect with no restart. The third lists the new ID once the nodes connect. The change lasts only until that node restarts, so put the new ID in that node's own config file too. Send the new ID to anyone else who lists this node. For an entry in TrustedNodes, use admin.removeTrustedPeer and admin.addTrustedPeer in place of the first two lines, or as well as them if the entry is in StaticNodes too. Neither opens a connection. Static and trusted peers explains the two lists.

If you need to roll back

The data directory works with both lines, so rolling back swaps the binary. Stop the node, and take any --mess flag out of its start command, because a v1.12 binary refuses it. Under systemd, run sudo systemctl daemon-reload. Then put the old binary back, and start the node again:

sudo mv /usr/local/bin/geth-v1.12 /usr/local/bin/geth

Roll back only for as long as it takes to find what went wrong: every v1.12 release carries the flaws the audits found. Keep the new key: rolling back does not make the old key safe, and a release before v1.12.21 leaks bits of the key it runs with, so rolling back to one exposes the new key too. Once the cause is fixed, move again with these steps, renaming the key again. A second rename on the same day finds its target name taken, so add -2 after $(date +%F) in it. If the node has to stay back longer, Keep a v1.12 node finding peers keeps it finding peers.

Once the node is stable, delete the v1.12 binary

Once the node has caught up, holds peers, and has run on the v1.13 line long enough that you will not roll back:

sudo rm /usr/local/bin/geth-v1.12

Every v1.12 release carries the flaws the audits found, so a copy left on the machine is one a later mistake can start. If you installed over a geth at another path, delete the geth-v1.12 beside it instead.

macOS users guide

Build from source and move the node on Windows

Optional: if you have not run v1.13 yet, sync one new node first

Best practice before you move a v1.12 node: set up one new node on the v1.13 line, on a machine of its own beside the nodes you run, by following Install Core-Geth, and point no RPC clients or miners at it. It can sync from the network, or start from a copy of a stopped v1.12 node's data directory without its geth/nodekey. Once eth.syncing returns false and net.peerCount is above zero (How to tell it is done), move each v1.12 node with the steps below.

Check for Chocolatey

The node can keep running while you install the tools and build. In a PowerShell window opened with Run as administrator, which the Chocolatey steps need:

choco --version

If PowerShell says the term is not recognized, Chocolatey is not installed: take the next step. If the version starts with 1, skip the next step and take the one after it. If it starts with 2, go to "Update Chocolatey, then install Git and a C compiler".

If Chocolatey is not installed, install it

WinGet, which comes with Windows 11, installs Chocolatey from its publisher:

winget install --id Chocolatey.Chocolatey -e --source winget --accept-source-agreements --accept-package-agreements

Approve the prompt the installer shows. Then open a new PowerShell window with Run as administrator for the next steps: a window opened before the install does not find Chocolatey.

If Chocolatey is version 1, move it to 1.4.0 first
choco upgrade chocolatey --version 1.4.0 --allow-downgrade -y

Chocolatey's guide to version 2 moves a version 1 to 1.4.0 before the upgrade to version 2, which the next step makes. Skip this step on version 2: it would downgrade Chocolatey.

Update Chocolatey, then install Git and a C compiler
choco upgrade chocolatey -y
choco upgrade mingw git -y
choco --version

choco upgrade installs what is missing and moves what is installed to its current release, so an older Chocolatey, Git or MinGW-w64 is updated rather than kept. The last line must print a version starting with 2. If the first line asks for a restart to install .NET Framework 4.8, which Chocolatey 2 needs, restart and run this block again. mingw is MinGW-w64, the C compiler cgo needs on Windows. The next two steps can run in this window.

Download Go 1.26.8 and check it

main pins Go 1.26.8 in its build/checksums.txt, the Go its release builds use. This downloads the Go 1.26.8 installer from the Go project's downloads at go.dev, and checks the download against the SHA-256 go.dev publishes for the installer. An administrator window opens in C:\Windows\System32, so the first line moves to your home folder:

cd $HOME
Invoke-WebRequest "https://go.dev/dl/go1.26.8.windows-amd64.msi" -OutFile "go1.26.8.windows-amd64.msi"
(Get-FileHash "go1.26.8.windows-amd64.msi" -Algorithm SHA256).Hash -eq "0581e37d54c8f2876c447204737d4aaa3f65f983005c65a1d5186b5f18ae3eca"

The last line must print True. If it prints False, delete the file and download it again rather than running it.

If the check printed True, run the Go installer
msiexec /i "$HOME\go1.26.8.windows-amd64.msi" /passive | Out-Null

The installer shows a progress bar and takes its defaults, which put Go in C:\Program Files\Go and on PATH. The line returns once it has finished.

Check the tools

In the same window:

$env:Path = [Environment]::GetEnvironmentVariable("Path", "Machine") + ";" + [Environment]::GetEnvironmentVariable("Path", "User")
git --version
go version
gcc --version

The first line reloads PATH from Windows' settings, so this window finds what the steps above installed. Each line after it must print a version, and go version must print go1.26.8. A line saying the term is not recognized names a tool the steps above did not install.

Build the main branch
cd $HOME
git clone --branch main --single-branch https://github.com/ethereumclassic/core-geth.git
cd core-geth
go run build/ci.go install ./cmd/geth
.\build\bin\geth.exe version

Go on only if the first line geth version prints names version 1.13 or later; a build of main ends it with -unstable, and two builds a day apart can be different code. Keep the Git Commit line: "Confirm the move" checks the running node against it. Run these one line at a time. If git clone reports that core-geth already exists, run git pull in that folder before the build line. The Source tab under Install says why these steps build main.

Record the node's block number and ID

In PowerShell, in the folder that holds the node's geth.exe, such as C:\core-geth:

.\geth.exe attach --exec 'eth.blockNumber' \\.\pipe\geth.ipc
.\geth.exe attach --exec 'admin.nodeInfo.enode' \\.\pipe\geth.ipc

Keep what this prints: "Confirm the move" compares the new ID and block number with it.

Stop the node cleanly

Press Ctrl+C once in the node's console window and wait. A window opened from the Startup folder then asks Terminate batch job (Y/N)?; type Y. Under a service wrapper, stop it with that tool. Wait until the log prints Blockchain stopped and the process exits. If it exits without that line, the stop was not clean: the node recovers at its next start, and its block number can start lower than the one you recorded.

Install the build

In the same folder:

Rename-Item .\geth.exe geth-v1.12.exe
Copy-Item "$HOME\core-geth\build\bin\geth.exe" .\geth.exe
.\geth.exe version

Go on only if .\geth.exe version prints the same first line and Git Commit line the build printed. geth-v1.12.exe is the old binary, kept so you can roll back. The second line copies the build from core-geth in your home folder, where the build step cloned it.

Rename the old P2P node key
$key = "$env:LOCALAPPDATA\Ethereum\classic\geth\nodekey"
Rename-Item $key "nodekey.old-rotated-$(Get-Date -Format yyyy-MM-dd)"

The next start writes a fresh key. If you start the node with --nodekey or --nodekeyhex, or set GETH_NODEKEY or GETH_NODEKEYHEX, take that out too: it overrides the file. If the rename prints an error, check the data directory: geth/nodekey must not be there when the node starts. The commands use the default data directory, but the node uses %USERPROFILE%\AppData\Roaming\Ethereum\classic instead if an older AppData\Roaming\Ethereum folder exists and is not empty. For Mordor, use mordor in place of classic; if you pass --datadir, use yours.

If this node lists static peers, update the list

They are under [Node.P2P] in the file you pass with --config. Edit it while the node is down:

[Node.P2P]
StaticNodes = ["enode://<node ID>@<IP>:30303"]

A peer that has already moved has a new ID, so use that. Separate entries with commas. TrustedNodes takes the same entries. With no file yet, save these two lines and add --config and the file's path to the start command. The client ignores static-nodes.json, so move its entries here. Save the file as UTF-8 with no byte order mark, after this edit and any other. A byte order mark, or UTF-16, stops the start with invalid TOML syntax.

Check the flags and settings you start the node with

While the node is down, decide MESS, which makes a node resist deep chain reorganizations. An upgrade can change whether a node runs MESS, so set it yourself: --mess turns it on and --mess=false turns it off. Which setting fits which operator sets out the choice. An exchange setting its confirmation policy can use the MESS confirmation calculator to see what that policy asks of an attacker. Give every node you run the same setting. Take out any --discovery.dns or --bootnodes flag added on v1.12, the two Keep a v1.12 node finding peers adds among them: the v1.13 line reads its own lists and bootnodes, --bootnodes would hold the node to the bootnodes it names, and with --discovery.dns it reads no snap lists. If the node starts with --config, delete the file's BootstrapNodes, EthDiscoveryURLs and SnapDiscoveryURLs lines, unless you put them there to name bootnodes or lists other than the release's own: a file dumpconfig wrote on v1.12 has all three, and the node keeps what they name in place of what the v1.13 line ships. The lists that guide puts in a file are the release's own, so delete those too. BootstrapNodesV5 can stay: the v1.13 line puts its own discovery v5 bootnodes in place of the ones a v1.12 file holds. Then go through the RPC exposure checklist for the flags, any GETH_ environment variables, and the file's [Node] settings: HTTPHost, HTTPModules, HTTPCors, WSHost, WSModules and WSOrigins.

Start the node

In PowerShell, in the folder that holds the node's geth.exe. Add the flags you just checked to this line, --mess or --mess=false among them, and any other flag you start the node with: a node on --datadir started without it starts an empty chain in the default directory. If the node starts from the Startup folder, as the run guide's Keep it running sets up, run that command file instead:

.\geth.exe --classic

The node runs in this window until you stop it, so run the next steps in a second PowerShell window, in the same folder. For Mordor, use --mordor in place of --classic.

Confirm the move
.\geth.exe attach --exec 'admin.nodeInfo.name' \\.\pipe\geth.ipc
.\geth.exe attach --exec 'admin.nodeInfo.enode' \\.\pipe\geth.ipc
.\geth.exe attach --exec 'net.peerCount' \\.\pipe\geth.ipc
.\geth.exe attach --exec 'eth.blockNumber' \\.\pipe\geth.ipc

If an attach line cannot connect, wait a minute and run it again. The first line must name the version the build printed, followed by the first eight characters of its Git Commit. The enode ID must differ from the one you recorded. If it is the same, the node is still on the old key: stop it and redo the rename step. The peer count climbs within minutes. If it is still zero after five minutes, see Why does the node have no peers?, and if the lists and bootnodes have gone dark, start it once from the node records committed in ethereumclassic/discv4-dns-lists. The block number starts near the one you recorded, or lower after an unclean stop, and climbs to the network's head: compare it with a block explorer or another node you run. If it stops climbing short of the head, investigate rather than resync.

If other nodes list this node, give them the new ID

They can no longer connect to this node, and neither side logs it at the default verbosity. On each one, on v1.12 or v1.13, run these lines with the old ID you recorded, the new ID from the step above, and the address and port the entry already has.

.\geth.exe attach --exec "admin.removePeer('enode://<old ID>@<IP>:30303')" \\.\pipe\geth.ipc
.\geth.exe attach --exec "admin.addPeer('enode://<new ID>@<IP>:30303')" \\.\pipe\geth.ipc
.\geth.exe attach --exec "admin.peers.map(function(p){return p.enode})" \\.\pipe\geth.ipc

The first two lines each print true and take effect with no restart. The third lists the new ID once the nodes connect. The change lasts only until that node restarts, so put the new ID in that node's own config file too. Send the new ID to anyone else who lists this node. For an entry in TrustedNodes, use admin.removeTrustedPeer and admin.addTrustedPeer in place of the first two lines, or as well as them if the entry is in StaticNodes too. Neither opens a connection. Static and trusted peers explains the two lists.

If you need to roll back

The data directory works with both lines, so rolling back swaps the binary. Stop the node, and take any --mess flag out of its start command, because a v1.12 binary refuses it. Then, in PowerShell, in the folder that holds the node's geth.exe, put the old binary back, and start the node again:

Remove-Item .\geth.exe
Rename-Item .\geth-v1.12.exe geth.exe

Roll back only for as long as it takes to find what went wrong: every v1.12 release carries the flaws the audits found. Keep the new key: rolling back does not make the old key safe, and a release before v1.12.21 leaks bits of the key it runs with, so rolling back to one exposes the new key too. Once the cause is fixed, move again with these steps, renaming the key again. A second rename on the same day finds its target name taken, so add -2 after $(Get-Date -Format yyyy-MM-dd) in it. If the node has to stay back longer, Keep a v1.12 node finding peers keeps it finding peers.

Once the node is stable, delete the v1.12 binary

Once the node has caught up, holds peers, and has run on the v1.13 line long enough that you will not roll back, in PowerShell, in the folder that holds the node's geth.exe:

Remove-Item .\geth-v1.12.exe

Every v1.12 release carries the flaws the audits found, so a copy left on the machine is one a later mistake can start.

Windows users guide

What the v1.13 line changes

What the move changes for the node

  • Where it finds peers. The v1.13 line reads peer discovery lists published from the ethereumclassic organization and ships bootnodes run by the Core-Geth maintainers. Take out any --discovery.dns or --bootnodes flag added on v1.12: the flags step says how.
  • Its place in the discovery lists. The nightly crawl that builds the lists stops publishing the old enode ID once it stops answering, and lists the new one once it finds it, if the node is on its network's current fork and makes the list's cap. A node that answers at the same IP address and UDP port takes over its old key's standing (How nodes are chosen).
  • What runs with no network flag. No network flag runs Ethereum Classic says what that changes for a node started without one.
  • The MESS default. A release ships MESS on or off by default, and which one is a per-release decision, which is why the steps have you set it. The release report's MESS section explains v1.13.0's default.
  • A chain run from its own genesis. For a node started from its own genesis or chainspec file, see Custom genesis and chainspec files.
  • Everything else. The v1.13.0 release report has every change the release makes.

Troubleshooting a Core-Geth move

If a step goes wrong

  • The enode ID did not change. The node started on its old key. Stop it, redo the rename step, and start it again. A --nodekey or --nodekeyhex flag, or GETH_NODEKEY or GETH_NODEKEYHEX in its environment, overrides the key file, so take that out too.
  • No peers after five minutes. Why does the node have no peers? lists the causes. A new key on its own does not keep a node at zero peers.
  • The block number went back. A node that was killed rather than stopped moves its head back at its next start and imports those blocks again, with no resync. After an unclean stop has the details.
  • Rolling back. Each tab's rollback step puts the v1.12 binary or container back, for as long as it takes to find what went wrong: every v1.12 release carries the flaws the audits found. Then move again, renaming the key again, and delete the old binary once the node is stable. A v1.12 binary refuses the --mess flag, so take it out of the start command before you roll back. The documentation's migration guide also says what else names the node.

A Core-Geth v1.12 node that has to wait

If a v1.12 node cannot move yet

A v1.12 node that has to wait before it moves can keep finding peers. Keep a v1.12 node finding peers points it at the peer discovery lists and bootnodes the v1.13 line ships, with the steps for each platform and a config file, and shows how to start a node from the records in ethereumclassic/discv4-dns-lists if the lists and bootnodes ever go dark. It closes none of the flaws the audits found in the v1.12 line, so move the node as soon as you can.

Why move to the v1.13 line

Why every v1.12 node should move

Security makes the case: what the audits found in every v1.12 release, what the v1.13 line fixes, and which releases the network still runs.