Bootstrap a node using Import Sync
Import sync bootstraps a new node from published bootstrap data — a pre-synchronized database, signed by its publishers — instead of syncing the whole chain from peers. RSKj downloads the bootstrap data, checks it against signatures from keys it already trusts, loads it into a fresh database, and then continues syncing normally from that point.
It is a one-time operation you run once, on a new node, before putting it into service.
Enabling import does not mean "import if needed". Every time a node starts with import enabled, it deletes its database directory and downloads the bootstrap data again — even if the node was fully synced a minute earlier.
Run it as a one-off from the command line, and never leave database.import.enabled = true in a configuration file. A node whose config has it enabled will wipe itself on every restart, including restarts you did not ask for, such as a reboot or a crash recovery.
At a glance
- Check your prerequisites and free space — the database is sized for a full node, and the download needs temporary space of its own.
- Optionally, check which height the import will land you on, and how large the download is, before committing to it.
- Run the node once with
--import. RSKj downloads the published bootstrap data, checks that enough trusted signers agree on it, and loads it into a fresh database. There is nothing for you to download, and the flag takes no file path. - Restart the node without the flag and confirm it is syncing on from the imported height.
- Delete the leftover files from the temporary directory.
If anything fails along the way, look the message up under Common failures.
Who this page is for
These instructions cover running the node from the JAR file, on Mainnet or Testnet.
| Import sync | |
|---|---|
JAR (java -cp … co.rsk.Start) | Supported — this page |
| Docker | Supported, with a different procedure — see Docker |
Ubuntu package / systemd service | Supported, with a different procedure — see Ubuntu package |
| Regtest | Not supported. No bootstrap data is published for Regtest, and because the database is erased before the import is attempted, running --import there deletes the Regtest database and then fails |
Before you start
- Java 17 JDK and RSKj
VETIVER-9.0.4or later — see Setup node using Java. Earlier versions cannot read the bootstrap data published today, and will fail the import. - Disk for the database. Size the data directory for a full node, per the minimum requirements — not for the size of the download.
- Disk for temporary files. RSKj downloads the bootstrap archive and extracts it into the JVM's temporary directory:
/tmpon Linux, a per-user directory under/var/folderson macOS. To see the exact path yours will use, runjava -XshowSettings:properties -version 2>&1 | grep java.io.tmpdir. Both files exist there at the same time, so size the temporary space for the archive plus the larger file extracted from it, on top of the database itself. To check that size before committing to the download, see Check where import sync will land you.
How bootstrap data is trusted
Bootstrap data is published by several independent signers. RSKj ships, per network, the source URL and the public keys it will accept, so a default installation needs no configuration to use import sync.
| Network | database.import.url |
|---|---|
| Mainnet | https://import.mainnet.rskcomputing.net/dbs/mainnet/ |
| Testnet | https://import.testnet.rskcomputing.net/dbs/testnet/ |
Each signer publishes its own index under that URL, at a path named for its public key:
- Mainnet
- Testnet
https://import.mainnet.rskcomputing.net/dbs/mainnet/<TRUSTED-PUBLIC-KEY>/index.json
https://import.testnet.rskcomputing.net/dbs/testnet/<TRUSTED-PUBLIC-KEY>/index.json
Every entry in an index carries a block height, the path to the archive, its hash, and the signer's signature over that entry.
RSKj will only import bootstrap data that enough trusted signers agree on: the same height, with the same hash, correctly signed by each. The threshold is a majority of the keys you have configured, and never fewer than two — with the three keys shipped for each network, that is two. A height offered by too few signers is ignored, however recent it is. Among the heights that meet that bar, RSKj takes the highest.
Every configured signer's index must also be reachable. RSKj fetches all of them before it compares anything, so one index that cannot be fetched or parsed stops the import, even when the signers that did respond would have met the threshold. The threshold decides how many signers must agree; it does not make a missing publisher optional. If a publisher is down, wait for it to come back and run the import again.
The trusted keys are long, and they are the one thing worth taking from the JAR you are about to run rather than from this page, since that is the authoritative answer for your version:
- Mainnet
- Testnet
unzip -p <PATH-TO-THE-RSKJ-JAR> config/main.conf | sed -n '/import {/,/}/p'
unzip -p <PATH-TO-THE-RSKJ-JAR> config/testnet.conf | sed -n '/import {/,/}/p'
Check where import sync will land you
Import sync does not leave you at the chain tip. It leaves you at the newest height that enough signers agree on, and the node then syncs the remaining blocks from peers in the normal way. Depending on how recent the published data is, that remainder can still be substantial: import sync shortens the initial sync, it does not remove it.
You can see exactly which height you would land on before downloading anything. Substitute the three trusted keys for your network, then:
- Mainnet
- Testnet
mkdir -p ~/rskj-index/mainnet && cd ~/rskj-index/mainnet
URL=https://import.mainnet.rskcomputing.net/dbs/mainnet/
for KEY in \
<TRUSTED-KEY-1> \
<TRUSTED-KEY-2> \
<TRUSTED-KEY-3>
do
curl -sS -o "index-$KEY.json" "$URL$KEY/index.json"
done
jq -s '(((length / 2) | floor) + 1 | if . < 2 then 2 else . end) as $required
| [.[].dbs[] | {height, hash}] | group_by(.height)
| map({height: .[0].height, agreeing: ([group_by(.hash)[] | length] | max)})
| map(select(.agreeing >= $required)) | max_by(.height)' index-*.json
mkdir -p ~/rskj-index/testnet && cd ~/rskj-index/testnet
URL=https://import.testnet.rskcomputing.net/dbs/testnet/
for KEY in \
<TRUSTED-KEY-1> \
<TRUSTED-KEY-2> \
<TRUSTED-KEY-3>
do
curl -sS -o "index-$KEY.json" "$URL$KEY/index.json"
done
jq -s '(((length / 2) | floor) + 1 | if . < 2 then 2 else . end) as $required
| [.[].dbs[] | {height, hash}] | group_by(.height)
| map({height: .[0].height, agreeing: ([group_by(.hash)[] | length] | max)})
| map(select(.agreeing >= $required)) | max_by(.height)' index-*.json
This reproduces the node's own selection rule and prints the height it would choose, along with how many signers agree on it. The threshold is derived from the number of index files you fetched, so the query stays correct if you configure a different set of keys.
Each network gets its own directory because the final jq reads every index-*.json it finds. Index files are named after the signer's key, and the two networks use different keys, so running both checks in one directory leaves six files there rather than overwriting three. That breaks the query rather than giving a wrong answer: the threshold is derived from the file count, so six files require four agreeing signers, more than either network has, and the query prints null. With a partial mix of the two networks in one directory, it can instead return a height from the wrong network.
Do not read the last entry of a single index and assume that is your landing height. Signers publish independently, so the most recent entries may be offered by only one of them — and those can never be selected. The query above applies the same threshold RSKj does.
Once you know the height, you can check how large the download will be. Run this in the same directory, so $URL and the index files still refer to the network you just checked. Any of the agreeing signers' indexes will do, since they publish the same archive:
DB=$(jq -r --argjson h <HEIGHT> '.dbs[] | select(.height == $h) | .db' index-<TRUSTED-KEY>.json)
curl -fsSI "$URL$DB" | grep -i content-length
Use that figure to size your temporary space, as described in Before you start.
Run the import
Run this on a node that is not already running, with no valuable database in place — the first thing it does is erase the database directory for the selected network.
- Mainnet
- Testnet
java -Xmx4G -cp <PATH-TO-THE-RSKJ-JAR> co.rsk.Start --import
java -Xmx4G -cp <PATH-TO-THE-RSKJ-JAR> co.rsk.Start --testnet --import
The --import flag sets database.import.enabled for that run only. It supplies no URL and no keys of its own — those come from the network configuration shipped in the JAR. -Xmx4G is the heap recommended for this command in Setup node using Java; left out, the JVM sizes the heap from physical RAM instead.
The import prints nothing to the console. It writes to logs/rsk.log, relative to the directory you ran the command from. A successful import logs there, in order:
Bootstrap data downloaded
Bootstrap data hash checked
Bootstrap data extracted
Detected bootstrap-data v2 (chunked) format
Bootstrap-data v2 imported <blocks> blocks, <values> long values and <nodes> state nodes in <n> ms
Bootstrap data has successfully been imported in <n> mills
Other subsystems log in between; what matters is that these six appear in this order. Older bootstrap data logs Detected bootstrap-data v1 (legacy) format in place of the two v2 lines.
The node then continues into normal operation and starts importing blocks from peers, beginning just above the imported height.
Both stages take time: the download depends on your connection, and the load that follows depends on the machine. Neither is instant, and the whole import is expected to take minutes rather than the hours a full sync would.
Confirm it worked, then restart without the flag
Once you see the import succeed and blocks being processed, stop the node and start it again without --import. This is the step that turns a one-off import into a normally operating node.
- Mainnet
- Testnet
java -cp <PATH-TO-THE-RSKJ-JAR> co.rsk.Start
java -cp <PATH-TO-THE-RSKJ-JAR> co.rsk.Start --testnet
Check that it is serving and that it kept the imported data:
curl -sS http://localhost:4444 -X POST -H "Content-Type: application/json" \
--data '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}'
{"jsonrpc":"2.0","id":1,"result":"0x…"}
The result is the latest synced block in hexadecimal. If it is at or above the height you identified earlier and climbing, the import held and the node is syncing the remainder from peers. If it comes back at zero, the node did not keep the imported database — check that you started it without --import and against the same network.
Clean up temporary files
RSKj leaves the downloaded archive and its extracted contents behind in the temporary directory — together, the archive plus the larger file extracted from it. They are not needed once the import has completed.
TMP=$(java -XshowSettings:properties -version 2>&1 | sed -n 's/.*java.io.tmpdir = //p')
ls -d "$TMP"/import*
Remove anything left there.
Common failures
Expand the message you are seeing
| Message | What it means |
|---|---|
Failed to download and parse index from <url> | A signer's index could not be fetched or parsed. This stops the import even if the other signers already agree, so it is often an outage at the publisher rather than a problem on your side. Your database has already been erased at this point. Check network access to the import URL, and that you have not overridden database.import.url with something unreachable. If the URL is right and reachable for the other keys, wait for that publisher to recover and retry. |
Downloaded files doesn't contain enough entries for a common height | No single height is offered, with a matching hash, by the required majority of trusted signers. This is what you see if database.import.trusted-keys has been narrowed below the threshold, or if the indexes have no height in common. An unreachable signer does not produce this message; it fails earlier, with Failed to download and parse index from <url>. |
Not enough valid signatures: selected height <n> doesn't have enough trustworthy sources: <x> of <y> | A height looked agreed-upon, but too few signatures actually verified against the trusted keys. The bootstrap data is not trustworthy. Do not work around this by lowering the requirement. |
Failed to create a temporary directory. Please start again the import process | RSKj could not create its working directory under the JVM's temporary directory. This happens before anything is downloaded. Check that java.io.tmpdir exists and is writable by the user running the node — a read-only or otherwise restricted temporary directory is the usual cause. |
File: <path> does not match with expected hash: <hash> | The downloaded archive does not match the hash the signers committed to. Usually an incomplete or corrupted download; remove the temporary files and retry. |
Error downloading bootstrap data from <url>. Please start again the import process | The archive could not be retrieved, or could not be written to disk. Check network access, and check free space in the temporary directory — a download that fills the filesystem fails here. If it persists, the published archive may be missing from the location its index advertises. |
The file is corrupted or incomplete. Please start again the import process | The archive downloaded and matched its hash, but could not be unpacked. Check free space in the temporary directory first: running out during extraction produces this message even though the archive itself is fine. If space is adequate and it persists, the published archive is faulty — report it rather than working around it. |
Error trying to read bootstrap data contents. Please start again the import process | The downloaded archive could not be read back while verifying its hash — this happens before extraction, not after. Usually a truncated or removed temporary file, or an I/O error on the filesystem holding the temporary directory. Retry the import. |
Error reading bootstrap data from <path> or Error reading bootstrap-data v2 from <path> | The extracted bootstrap-data.bin could not be read. Check that the temporary directory still holds it and that nothing is cleaning temporary files while the node runs. |
Any message starting Bootstrap-data v2 (bad magic, unsupported version, chunk length, section order, truncated or trailing bytes) | The extracted file is not intact. Check first that you are on VETIVER-9.0.4 or later, then delete the temporary files and run the import again. If it repeats, the published archive is faulty — report it rather than working around it. |
Configuration has less trusted sources than the minimum required <n> of 2 | Fewer than two trusted keys are configured, and two is the floor however few you configure. Restore the shipped keys for the network. This is a warning at startup, not the failure itself — the run continues and then fails on one of the messages above. |
java.lang.OutOfMemoryError | Note this one appears on the terminal, not in logs/rsk.log — if the log stops after Bootstrap data extracted, check the terminal before assuming the node is still working. Java heap space means the load stage ran out of heap: raise -Xmx above the 4G used above and run the import again. Required array size too large means the node is older than VETIVER-9.0.4 and cannot read the bootstrap data published today; raising -Xmx will not help, so upgrade instead. Either way, a retry downloads the bootstrap data again. |
Migrating from LevelDB to RocksDB
If your node still runs on LevelDB, migrate it to RocksDB. LevelDB is deprecated and will be removed in a future release; a node that opens a LevelDB database logs a warning saying so on every start. RocksDB is the default for any node set up from scratch.
Changing keyvalue.datasource requires starting with an empty database directory, because an existing database cannot be reopened under a different engine. Running with --import gets you there, which is why import sync is described as a way to switch:
java -Xmx4G -Dkeyvalue.datasource=rocksdb -cp <PATH-TO-THE-RSKJ-JAR> co.rsk.Start --import
Import sync is not a database conversion, though. It discards your current database and replaces it with the published bootstrap data, so a node that had synced to the tip comes back at the imported height and has to sync the difference again. If you would rather keep the history you already have, convert the database in place with DbMigrate instead — it downloads nothing.
The one-shot rule still applies. Use --import on the command line for the switchover, and do not leave import enabled in configuration afterwards.
Docker and the Ubuntu package
Import sync works under both, but neither runs the node the way the procedure above assumes. That procedure ends with stop the node and start it again without the flag — and these two restart the node for you.
So run the import as a one-off invocation, and keep --import out of anything that restarts: a docker run in a compose file, a systemd unit, or database.import.enabled = true in a configuration file will erase and re-download the database on every restart and every reboot, indefinitely.
Docker
The image passes its arguments through to the node, so --import can be given to docker run. Two things differ from the JAR:
- The volume has to be writable by the node. The node runs as the unprivileged
rskuser, and/var/lib/rsk/.rskdoes not exist in the image — so Docker creates the mount point owned byroot, and the import fails on its first write withdbKind.properties (No such file or directory). Create the volume and hand it torskonce, before importing. - Set a maximum heap. The image sets
DEFAULT_JVM_OPTS="-Xms4G", which is the initial heap, not a ceiling. Without-Xmxthe JVM still sizes the maximum from the memory the container is given, so set both.
docker volume create rsk-data
docker run --rm -u 0 -v rsk-data:/var/lib/rsk/.rsk \
--entrypoint chown rsksmart/rskj:latest -R rsk:rsk /var/lib/rsk/.rsk
Then import, mounting that volume at the rsk user's home so the database lands at /var/lib/rsk/.rsk/<network>/database:
- Mainnet
- Testnet
docker run --rm -v rsk-data:/var/lib/rsk/.rsk \
-e DEFAULT_JVM_OPTS="-Xms4G -Xmx4G" \
rsksmart/rskj:latest --import
docker run --rm -v rsk-data:/var/lib/rsk/.rsk \
-e DEFAULT_JVM_OPTS="-Xms4G -Xmx4G" \
rsksmart/rskj:latest --testnet --import
The container does not exit when the import finishes — the node carries on into normal operation. Watch for Bootstrap data has successfully been imported in the logs, stop the container, then start your usual container against the same volume without --import.
The archive is downloaded and unpacked inside the container, under /tmp, which is the container's own writable layer rather than the volume. Allow room for the archive and the larger file extracted from it at the same time, on top of the space the database needs in the volume.
Ubuntu package
The package installs the JAR at /usr/share/rsk/rsk.jar and its configuration in /etc/rsk, and runs the node as the rsk user. Stop the service, run the same JAR once with --import, then start the service again:
sudo service rsk stop
sudo -u rsk java -Xmx4G -Dlogback.configurationFile=/etc/rsk/logback.xml -cp /usr/share/rsk/rsk.jar co.rsk.Start --import
sudo service rsk start
Three details matter here:
- Run it as the
rskuser, as above. The service runs asrsk, so an import run asrootor as your own account leaves behind a database the service cannot read. - There is no configuration flag to pass, and no network flag either. RSKj reads
/etc/rsk/node.confon its own when that file exists, exactly as the service does. That file is a symlink to the network you chose at installation, and it sets the database location —/var/lib/rsk/database/<network>. See switching networks if you need to change it. - Stop the import once it reports success. As with Docker, the node continues into normal operation rather than exiting; interrupt it after
Bootstrap data has successfully been imported, then hand the node back to the service. The-Dlogback.configurationFileoption above is what the service uses, so the import logs to/var/log/rsk/rsk.log. Without it the log goes to./logs/rsk.login whatever directory you ran the command from.