Skip to main content
Time to read: 1 min
For the complete documentation index, see llms.txt

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.

Import sync erases the database every time it runs

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

  1. Check your prerequisites and free space — the database is sized for a full node, and the download needs temporary space of its own.
  2. Optionally, check which height the import will land you on, and how large the download is, before committing to it.
  3. 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.
  4. Restart the node without the flag and confirm it is syncing on from the imported height.
  5. 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
DockerSupported, with a different procedure — see Docker
Ubuntu package / systemd serviceSupported, with a different procedure — see Ubuntu package
RegtestNot 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.4 or 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: /tmp on Linux, a per-user directory under /var/folders on macOS. To see the exact path yours will use, run java -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.

Networkdatabase.import.url
Mainnethttps://import.mainnet.rskcomputing.net/dbs/mainnet/
Testnethttps://import.testnet.rskcomputing.net/dbs/testnet/

Each signer publishes its own index under that URL, at a path named for its public key:

https://import.mainnet.rskcomputing.net/dbs/mainnet/<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:

unzip -p <PATH-TO-THE-RSKJ-JAR> config/main.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:

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

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.

The newest entry in an index is not necessarily the one you get

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.

java -Xmx4G -cp <PATH-TO-THE-RSKJ-JAR> co.rsk.Start --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.

java -cp <PATH-TO-THE-RSKJ-JAR> co.rsk.Start

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
MessageWhat 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 heightNo 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 processRSKj 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 processThe 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 processThe 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 processThe 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 2Fewer 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.OutOfMemoryErrorNote 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 rsk user, and /var/lib/rsk/.rsk does not exist in the image — so Docker creates the mount point owned by root, and the import fails on its first write with dbKind.properties (No such file or directory). Create the volume and hand it to rsk once, before importing.
  • Set a maximum heap. The image sets DEFAULT_JVM_OPTS="-Xms4G", which is the initial heap, not a ceiling. Without -Xmx the 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:

docker run --rm -v rsk-data:/var/lib/rsk/.rsk \
-e DEFAULT_JVM_OPTS="-Xms4G -Xmx4G" \
rsksmart/rskj:latest --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 rsk user, as above. The service runs as rsk, so an import run as root or 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.conf on 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.configurationFile option above is what the service uses, so the import logs to /var/log/rsk/rsk.log. Without it the log goes to ./logs/rsk.log in whatever directory you ran the command from.
Last updated on by rsk-devx-app[bot]