Skip to content

Latest commit

 

History

39 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

mina-provision

Fetches, verifies and places the published artifacts a Mina node needs before it can start: archive database dumps, precomputed blocks and runtime configuration files.

mina-provision archive --network mainnet --pg-uri postgres://...   # a database, from a published dump
mina-provision blocks  --network mainnet --range 50000-51000       # precomputed blocks, onto disk
mina-provision daemon-config   --network mainnet --out /var/lib/coda  # the config a daemon auto-loads
mina-provision genesis-config  --network mainnet                      # the fork config a chain starts from

Installing

# Debian and Ubuntu, from the signed apt repository
sudo install -d -m 0755 /etc/apt/keyrings
sudo wget -qO /etc/apt/keyrings/minaprotocol.gpg \
  https://stable.apt.packages.minaprotocol.com/repo-signing-key.gpg
gpg --show-keys /etc/apt/keyrings/minaprotocol.gpg   # compare the fingerprint, see below
echo "deb [signed-by=/etc/apt/keyrings/minaprotocol.gpg] https://stable.apt.packages.minaprotocol.com $(. /etc/os-release && echo "$VERSION_CODENAME") stable" \
  | sudo tee /etc/apt/sources.list.d/mina.list
sudo apt-get update && sudo apt-get install mina-provision

# or a single .deb from a release, see "Checking a release download" below
sudo dpkg -i mina-provision_<version>_amd64.deb

# or a container
mkdir -p out
docker run --rm --user "$(id -u):$(id -g)" -v "$PWD/out:/out" \
  ghcr.io/minaprotocol/mina-provision daemon-config --network mainnet --out /out

The repository key must have this fingerprint. Do not continue if gpg --show-keys shows a different one:

386E 9DAC 3787 26A4 8ED5  CE56 ADB3 0D9A CE02 F414

signed-by makes apt trust the key for this one source only. A key in /etc/apt/trusted.gpg.d/ is trusted for every configured source: an index signed with it would be accepted from any of them, also the Debian or Ubuntu ones.

arm64 packages exist only for bookworm and noble. The bullseye, focal and jammy distributions of the repository do not declare arm64, so on an arm64 host with one of them apt-get install fails with "Unable to locate package". Use the arm64 binary from a release, or the container, there.

The container runs as the unprivileged user provision, uid and gid 10001, in the working directory /work. Without --user, files written to a mounted directory belong to uid 10001 on the host, and the directory must be writable by that uid. With --user "$(id -u):$(id -g)" they belong to you.

Checking a release download

Each GitHub Release has a SHA256SUMS file and a build provenance attestation for every file it has. The attestation is a signed statement that the file was built by this repository's Release workflow, from the tagged commit.

sha256sum -c --ignore-missing SHA256SUMS
gh attestation verify mina-provision_<version>_amd64.deb -R MinaProtocol/mina-provision

postgresql-client is a recommendation, not a dependency. Only archive shells out to psql. The published dumps use \restrict, which needs psql 17.6 or later, or 16.10, 15.14, 14.19 or 13.22 on the older branches. archive checks this before it loads a dump. The container image has a psql that qualifies.

Commands

archive

Fetches an archive dump, extracts it, applies the recommended PostgreSQL tuning, and loads the SQL.

Flag Meaning
--pg-uri the server to restore into; required unless --skip-pg. See Where the dump goes
--date dump date, YYYY-MM-DD; defaults to today, UTC. A date after today (UTC) is refused
--hour dump hour, HHMM, from 0000 to 2359; default 0000. Dumps are produced hourly
--work-dir where the download and the extracted SQL are written; created if missing. Default: the current directory
--skip-pg download and extract only
--if-present what to do when the database already holds an archive: import, skip, fail
--max-extract-bytes the most bytes the extracted dump may hold; extraction stops with an error above it. Default: 200 GiB

Only the .sql files in the dump are extracted. Other entries are skipped.

ALTER SYSTEM writes to postgresql.auto.conf, so PostgreSQL must be restarted for the tuning to take effect.

The password

--pg-uri must be a postgres:// or postgresql:// URI. A key=value connection string (host=… password=…) is refused. archive removes the password from the URI, from the user info (user:pw@) or from a password query parameter, and gives it to psql in PGPASSWORD. The password is therefore not in the arguments of a psql process, which any local user can read, and not in the log, also with -v.

The password is still in the arguments of mina-provision itself. To keep it out of them too, leave it out of --pg-uri and set PGPASSWORD or use ~/.pgpass: psql reads both when the URI has no password.

Where the dump goes

The published dumps are made with pg_dump --create. They start with CREATE DATABASE archive and \connect archive, so they restore into the database archive whatever database --pg-uri names. --pg-uri is then only the way to reach the server: postgres://user:pw@host:5432/postgres and …/archive both work, also on a new server where archive does not exist yet. A --pg-uri that names another database gets a warning.

The provider's database setting says which database a dump creates. It is archive for the built-in providers. Before a dump is loaded, its header is checked against this setting, and a dump that creates a different database is refused. For a mirror of dumps made without --create, leave database out: those dumps restore into the database --pg-uri names.

Errors stop the load

The load stops at the first SQL error, and archive fails with psql's message. The load is not one transaction, because CREATE DATABASE cannot run inside one.

A dump import does not replace an existing archive. Loaded onto a server that already has the archive database, it stops at CREATE DATABASE and changes nothing. To replace an archive, drop its database first, deliberately, outside this tool.

Re-running against a database that already has an archive

A one-shot bootstrap container that is restarted -- docker compose down && up -- runs archive again against an archive that has since advanced. Use --if-present to decide what happens, before anything is downloaded:

--if-present Behaviour
import download and load regardless. The default. The load then fails on a server that already has the database
skip leave the database alone and exit successfully, downloading nothing
fail leave the database alone and exit with an error
$ mina-provision archive --network mainnet --pg-uri postgres://… --if-present=skip
Database "archive" already holds an archive: 548146 blocks, highest at 548146.
Nothing was downloaded or changed (--if-present=skip).

The check looks at the database the dump restores into, not at the one --pg-uri names. It treats that database as empty only on positive evidence: the database does not exist, it has no blocks table, or the table has no rows. A first run on a new server therefore proceeds.

When the check cannot be made -- the server cannot be reached, is still starting, or refuses to show the blocks table -- archive fails and downloads nothing. It keeps trying for up to a minute while the server cannot be reached, so a compose stack that restarts PostgreSQL and the bootstrap together sorts itself out.

blocks

Fetches precomputed block files for a height range.

Range Meaning
--range 50000 the blocks at height 50000
--range 50000-51000 inclusive on both ends
--range 50000- open-ended, up to the chain tip

A block name embeds the network, the height and the state hash, so a height on its own narrows the name only to a prefix, which the provider is then asked to list. One height can yield several blocks when the chain had competing blocks at that height.

An open-ended range stops after 1000 consecutive heights with no block. A single run fetches at most 50000 blocks, and a closed range can cover at most 50000 heights. Heights must be from 0 to 9223372036854775806.

daemon-config

Fetches the runtime configuration a daemon auto-loads.

Flag Meaning
--out directory to write into
--version exact package version; default is the highest
--repository, --component, --codename, --package override the provider's repository settings
--ref git branch, tag or commit, for a provider that serves a source tree. Letters, digits and . _ / + @ - only; no .. segment, no leading / or -

The default provider serves this as a Debian package rather than as a plain file, because the daemon auto-loads /var/lib/coda/config_<hash>.json, and that hash is derived from the commit at build time. It is not the hash in the package version:

package version   3.4.0-bd0fe9e         (7 characters)
file inside it    config_bd0fe9e9.json  (9 characters)

The same JSON exists in the mina source tree as genesis_ledgers/<network>.json. --provider github --ref <tag> fetches it from there, but it arrives under the source-tree name, which the daemon does not auto-load.

With no --version, the highest version is chosen by Debian ordering — the package apt itself would install. Debian ordering is not upload order, so a build with a longer version string can outrank a newer one. Pin with --version when a specific build is meant.

genesis-config

Fetches the fork configuration a chain starts from: the fork point, the ledger and epoch ledger hashes, and the genesis timestamp. An archive node and the replayer both need it to start.

It names no accounts, so it stays about a kilobyte however large the ledger is. Whichever program consumes it resolves the ledger from the hashes it carries, downloading and verifying the ledger itself.

It is regenerated as the chain advances, so the command reports which fork point arrived:

Wrote ./mainnet-ledger.json
  fork state hash: 3NKHyxzgM9z4C1wUz5ZhJBmNhtDzDQGr1PGvWRwiCUcFvsdTco2T
  blockchain length: 548146
  global slot since genesis: 958440

This is a different artifact from daemon-config. That one is the runtime configuration a daemon auto-loads from its own installation.

replayer-input

Rewrites a fork configuration into the input file the replayer reads.

mina-provision genesis-config --network mainnet
mina-provision replayer-input --from mainnet-ledger.json --out replayer-input.json
Flag Meaning
--from the fork configuration to convert; required
--out where to write it; - for standard output
--start-slot slot to start replaying from; default 0, a full replay
--target-state-hash state hash to stop at; omitted means run to the end

The replayer takes three fields, and the ledger it takes is the same type as the ledger object in a fork configuration, so that object is copied across unchanged — by its hashes, not expanded into accounts. The replayer resolves a ledger given only its hash, so this command never reads ledger data and the result stays small. A configuration that states its accounts converts equally well: whatever the ledger object holds is what the replayer receives.

Two values are not inferred, because the fork point and the replay range are different things. --start-slot defaults to 0 rather than to the fork's slot, and no stop point is set unless --target-state-hash is given.

The epoch ledger hashes and seeds are not carried over. The replayer input has no field for them and the replayer rebuilds epoch ledgers from the archive database; the command says so when it drops them.

Providers

Every endpoint, bucket and naming rule is configuration, not code. The Mina Foundation is the default publisher of these artifacts, not the only possible one: a mirror, an internal artifact store or a directory on disk is selected with --provider, with no change to the program.

mina-provision blocks --provider acme-mirror --range 50000-50100

The built-in defaults are written in the same schema an operator writes, are embedded in the binary, and are read by the same parser. A custom provider can therefore express everything the defaults express.

docs/providers.md is the full guide: worked examples per backend, what merging does, and how to add a provider to the built-in defaults.

Where the configuration is read from

In order; the first file found is used.

  1. --provider-config <path>
  2. MINA_PROVISION_CONFIG
  3. $XDG_CONFIG_HOME/mina-provision/config.yaml
  4. /etc/mina-provision/config.yaml

Behaviour change: ./mina-provision.yaml in the current directory is no longer read automatically. The file can redirect every endpoint, and the tool is often run from a directory that others can write to, such as a --work-dir a dump was extracted into. To use a file in the current directory, name it: --provider-config ./mina-provision.yaml.

The file is merged over the built-in defaults, per provider, per network, per artifact. Adding a mirror therefore does not mean restating the default entries, and those entries keep receiving updates. An artifact that is mentioned is replaced whole, so a partial entry cannot inherit half of the endpoint it replaces.

The configuration is read from disk only, and is never fetched. It states which hosts may supply artifacts, so downloading it would remove the property that makes it worth having.

Writing a provider

version: 1
default_provider: acme

providers:
  acme:
    description: internal mirror
    networks:
      mainnet:
        archive_dump:
          backend: http
          base_url: https://artifacts.acme.internal/mina
          name: "dumps/mainnet-{date}.sql.tar.gz"
          checksum: sidecar          # expects <file>.sha256 beside it
          database: archive          # the database the dumps create
        precomputed_blocks:
          backend: file
          path: /srv/mina/blocks
          name: "mainnet-{height}-{state_hash}.json"
          index: https://artifacts.acme.internal/mina/blocks.txt

Backends:

Backend Reads from Can list names
gcs a public Google Cloud Storage bucket yes
http any web server only with an index:
file a local or mounted directory yes
apt a Debian repository; daemon_config only not applicable

A web server cannot be enumerated, so blocks needs an index: URL — one object name per line — from an http provider. Without it the command reports that discovery is impossible rather than reporting no blocks, which would read as "the block is not published".

Name templates carry these fields, and a template using any other field is rejected when the configuration is read:

Artifact Fields
archive_dump {date}, {hour}
precomputed_blocks {height}, {state_hash}
daemon_config {ref}
genesis_config none — one fixed name per network

Verification

checksum: states how a download is proved to be what the publisher intended.

Mode Meaning
index the digest comes from the Debian repository index; apt only
sidecar the digest comes from <file>.sha256 beside the object
none only the transfer itself is checked

A configured check that cannot be performed is a failure, not a silent pass: a missing sidecar fails the download.

checksum: index proves that the package matches the index. The index itself is not checked against a signed InRelease file, so it is as trustworthy as the https connection and the repository host, and no more. It does not protect against a compromised mirror.

Transport

base_url, index and repository must be https:// URLs. Plain http:// is accepted only for a loopback host (localhost, 127.0.0.0/8, ::1), or when the artifact sets insecure: true, which makes that choice visible in the configuration. A redirect from https to plain http is always refused.

A request fails when the response headers do not arrive within 30 s, or when one read of the body receives no data for 60 s. There is no limit on the total time, so a large dump on a slow link still completes. SIGINT and SIGTERM stop the downloads and psql, and the command exits non-zero. See docs/providers.md.

Scope

This tool fetches, verifies and places files. It does not write blocks into an archive database, and it does not repair gaps.

Inserting a precomputed block is not a file copy: the block is decoded, its hashes are derived, and about twenty related tables are written. That logic belongs to the archive writer, and a second implementation of it in another language would diverge silently.

Concern Owner
Where artifacts are, and whether they are genuine mina-provision
What is missing from a database, and writing it mina-archive
Verifying a block's proof mina-verify

Dumps are produced hourly, so a restored database is behind the chain tip. Fetch the difference with mina-provision blocks and apply it with mina-archive, which takes a local directory of block files as input.

Environment variables

Variable Flag
MINA_NETWORK --network
MINA_PROVIDER --provider
MINA_PROVISION_CONFIG --provider-config

A flag on the command line always wins over the environment, and an empty variable is treated as unset.

Building

go build ./...
go vet ./...
go test ./...                  # no network access
go test -tags integration ./...  # needs PostgreSQL and live publishers

The default test suite never touches the network. A daily workflow runs the live checks against the default provider, so a bucket that is renamed or a naming rule that changes is noticed in this repository rather than by an operator.

Releasing

Pushing a vX.Y.Z tag builds a static binary for amd64 and arm64, packages each as a .deb, attaches them to a GitHub Release, and publishes the signed packages to the stable component of stable.apt.packages.minaprotocol.com. amd64 goes to bullseye, focal, jammy, bookworm and noble; arm64 goes to bookworm and noble, the two distributions that declare it. The release also has a SHA256SUMS file and a provenance attestation for its files. Publication is described in docs/releasing.md.

Planned

  • ledger — genesis and epoch ledger tarballs. A ledger tarball's identity is its sha3 value, equal to s3_data_hash, while its file name carries a blake2 hash.
  • checkpoint — replayer checkpoints.
  • peers — seed and peer lists.
  • Signature verification, beyond the published checksum.
  • An s3 backend.

About

Fetch, verify and place the published artifacts a Mina node needs: archive dumps, precomputed blocks and runtime config files.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages