Program Details
Last Revised: May 27, 2026
The primary goal for Archive Node Operators in the Trailblazers program (Mesa Upgrade Testnet) is to validate the upgrade path for existing Berkeley-era Rosetta deployments, ensuring they function correctly under the new Mesa protocol rules. Archive Node Operators run an Archive Node, Rosetta API, and Archive Node API.
There were no significant Rosetta changes introduced in Mesa so this is primarily regression testing.
Please refer to Archive Node API github repository for setup guide: https://github.com/o1-labs/Archive-Node-API
If something goes wrong at any stage — missing blocks, unexpected fork point, replayer failure, Rosetta reconciliation drift, etc. — find detailed information at https://docs.minaprotocol.com/network-upgrades/mesa and post in the operator Discord channel: #mesa-upgrade-testnet. Do not try to recover silently; other operators may be hitting the same issue.
Rosetta Operator Responsibilities
Rosetta Operators participating in the MUT program are expected to execute the following steps:
|
STAGE 1: Berkeley-based Test Network |
|
Goals:
Runbook:
1. One shot mina-missing-blocks-auditor --archive-uri "$PG_URI" echo "exit=$?" # 0 means clean 2. As Daemon DB_USERNAME=.. \
PGPASSWORD=... \
DB_HOST=... \
DB_PORT=... \
DB_NAME=.. \
MINA_NETWORK=... \
PRECOMPUTED_BLOCKS_URL=.... \
TIMEOUT=600 \
mina-missing-blocks-guardian daemon
ii. Ensure successful replayer run. 1. First time # Convert genesis ledger to replayer input file. genesis_ledger.json can be obtained from daemon node docker or debian (by default stored in /var/lib/coda/config_{commit}.json) jq '{genesis_ledger: .ledger}' genesis_ledger.json > replayer_input.json mina-replayer \ --input-file replayer_input.json \ --archive-uri {CONNECTION_STRING_TO_DB} \ --checkpoint-interval 10000 \ --checkpoint-output-folder ./checkpoints \ --log-level Info --log-json | tee replayer.log 2. Nth time (it is a good idea to run it every 12 hours to replay new canonical blocks) # replayer checkpoint will be produced after first time run jq '{genesis_ledger: .ledger}' genesis_ledger.json > replayer_input.json mina-replayer \ --input-file ./checkpoints/replayer_checkpoint_XXXX.json \ --archive-uri {CONNECTION_STRING_TO_DB} \ --output-file replayer_output.json \ --checkpoint-interval 10000 \ --checkpoint-output-folder ./checkpoints \ --log-level Info --log-json | tee replayer.log b. Rosetta i. Ensure you are connected to correct network # ROSETTA_URL env var points to base rosetta url (e.g. http://localhost:3087) curl -s -X POST $ROSETTA_URL/network/list -H 'Content-Type: application/json' -d '{}' | jq . # Expect: { "network_identifiers":[{"blockchain":"mina","network":"testnet"}] } curl -s -X POST $ROSETTA_URL/network/status -H 'Content-Type: application/json' \ -d "{\"network_identifier\":{\"blockchain\":\"mina\",\"network\":\"testnet\"}}" | jq '.current_block_identifier, .genesis_block_identifier' ⚠ Gotcha: Rosetta reports the network identifier that the daemon advertises, which is not always the value you expect. Always resolve the real network name via /network/list before using it anywhere else. ii. Query some transactions and blocks # Get network id NID=$("curl -s -X POST $ROSETTA_URL/network/status -H 'Content-Type: application/json' \ -d "{\"network_identifier\":{\"blockchain\":\"mina\",\"network\":\"testnet\"}}") # Latest block curl -s -X POST $ROSETTA_URL/block -H 'Content-Type: application/json' \ -d "{\"network_identifier\":$NID,\"block_identifier\":{}}" | \ jq '.block.block_identifier, .block.transactions | length' # Block by height curl -s -X POST $ROSETTA_URL/block -H 'Content-Type: application/json' \ -d "{\"network_identifier\":$NID,\"block_identifier\":{\"index\":100}}" | jq '.block.block_identifier' # Specific tx in a block curl -s -X POST $ROSETTA_URL/block/transaction -H 'Content-Type: application/json' \ -d "{\"network_identifier\":$NID,\"block_identifier\":{\"index\":100,\"hash\":\"<state_hash>\"},\"transaction_identifier\":{\"hash\":\"<tx_hash>\"}}" | jq . ⚠ ROSETTA_URL: Should point to URL of your rosetta instance. E.g. http://localhost:3087 Find more detailed information in the docs (https://docs.minaprotocol.com/network-upgrades/mesa) and reach out in Discord if you experience any issues. |
|
STAGE 2: UMT testing Step 1 – State Finalization Period – Stop-slot release (day 2) |
|
Goals:
Runbook:
a. Run again missing block auditor mina-missing-blocks-auditor --archive-uri "$PG_URI"; echo "exit=$?" b. Ensure height of canonical blocks psql "$PG_URI" -c "SELECT MAX(height), MAX(global_slot_since_genesis) FROM blocks WHERE chain_status='canonical';"
⚠ This check can be performed just couple times before stop-transaction-slot 3. MUT Network stops accepting new transactions at the predefined stop-transaction-slot a. Monitor no new transaction after stop-transaction-slot # Fetch slots from runtime config: STOP_TX_SLOT=$(jq -r '.daemon.stop_transaction_slot' runtime_config.json) STOP_NET_SLOT=$(jq -r '.daemon.stop_network_slot' runtime_config.json) # No new *user* txs after stop_transaction_slot psql "$PG_URI" <<SQL SELECT COUNT(*) FROM user_commands uc JOIN blocks_user_commands buc ON buc.user_command_id=uc.id JOIN blocks b ON b.id=buc.block_id WHERE b.global_slot_since_genesis > $STOP_TX_SLOT; -- want 0 SQL ⚠ This check can be performed a couple of times between stop-transaction-slot and stop-network-slot. Periodic spot checks are sufficient. A single check shortly before the stop slot is enough — you do not need to watch this continuously for the full stop window. Confirm the result once before the slot and once after. b. Ensure no new blocks until the network comes to a halt and stop-network-slot # No new blocks past stop_network_slot watch -n 30 "psql '$PG_URI' -tAc \"SELECT MAX(global_slot_since_genesis) FROM blocks;\"" # Value should plateau at <= $STOP_NET_SLOT # Optional GraphQL liveness check on the daemon: curl -s $GRAPHQL_URL -H 'Content-Type: application/json' \ -d '{"query":"{ bestChain(maxLength:1){ protocolState{ consensusState{ slotSinceGenesis blockHeight } } } }"}' | jq . This check can be performed a couple of times between stop-network-slot and mesa genesis 4. Archive Operators upgrade archives to Mesa schema after the stop-network-slot. a. Run upgrade script: psql "$PG_URI" -v ON_ERROR_STOP=1 \ -f /etc/mina/archive/upgrade_to_mesa.sql 2>&1 | tee mesa_upgrade.log # Verify migration recorded success psql "$PG_URI" -c "SELECT * FROM migration_history ORDER BY commit_start_at DESC LIMIT 1;" # current_protocol_version='3.0.0' target='4.0.0' end_at IS NOT NULL AND success=true Note: the upgrade script ships inside the Mesa archive Docker image as well as debian package at /etc/mina/archive/upgrade_to_mesa.sql (underscores, not hyphens). If the image is not mounted on the archive host, extract it: docker run –rm <MINA_ARCHIVE_IMAGE> \ Find more detailed information in the docs and reach out in Discord if you experience any issues. |
| Step 2 – Hard Fork Transition using Legacy Mode (day 3) |
|
Goals:
Runbook:
a. Archive Node i. Ensure you have fork point block as canonical # Distinct protocol versions seen in the archive psql "$PG_URI" -c " SELECT pv.transaction, pv.network, pv.patch, COUNT(*) AS blocks FROM blocks b JOIN protocol_versions pv ON pv.id = b.protocol_version_id GROUP BY 1,2,3 ORDER BY 1,2,3;" # Post-fork blocks must use transaction=4 psql "$PG_URI" -c " SELECT MIN(b.height) AS first_mesa_block, MAX(b.height) AS latest_mesa_block FROM blocks b JOIN protocol_versions pv ON pv.id = b.protocol_version_id WHERE pv.transaction = 4 AND b.chain_status = 'canonical';" Expected after Step 3 (post-fork): a non-empty row with transaction = 4. Pre-fork blocks keep transaction = 3 — that's correct, history isn't rewritten. ⚠ PG_URI: stands for db connection string e.g. postgres://postgres:pass@localhost:5432/archive ii. Ensure no missing blocks mina-missing-blocks-auditor --archive-uri "$PG_URI"; echo "exit=$?" ⚠ PG_URI: stands for db connection string e.g. postgres://postgres:pass@localhost:5432/archive
|
| Step 3 – Post Hard fork Network Monitoring (day 3 – end of Stage 2) |
|
Goals:
Runbook:
a. Archive nodes have no gaps and show correct data (e.g. new coinbase, slot duration) i. Ensure mesa blocks correctness (360 MINA for Coinbase) ii. Ensure 90 seconds slot duration iii. Ensure archive data consistency (no missing blocks , chain continuity) ⚠ According to the trailblazer plan Archive/Rosetta testers will be split into 3 category regarding how fast they will join Mesa network after hardfork: a) immediately mesa release is published, b) post-HF, between 100-290 blocks c) post-HF, between 291~400 blocks
See: https://docs.minaprotocol.com/node-operators/archive-node/backfilling-missing-blocks for more details # Coinbase: expect 360000000000 nanomina on post-fork blocks psql "$PG_URI" <<'SQL' SELECT b.height, ic.hash, ic.command_type, ic.fee FROM blocks b JOIN blocks_internal_commands bic ON bic.block_id=b.id JOIN internal_commands ic ON ic.id=bic.internal_command_id WHERE ic.command_type='coinbase' AND b.global_slot_since_hard_fork IS NOT NULL AND b.global_slot_since_hard_fork > 0 ORDER BY b.height DESC LIMIT 20; SQL ⚠ PG_URI: stands for db connection string e.g. postgres://postgres:pass@localhost:5432/archive 1. Slot duration equal to 90 seconds # Slot duration: timestamps between consecutive canonical blocks should step by ~90 * slots_delta seconds psql "$PG_URI" <<'SQL' WITH c AS ( SELECT height, global_slot_since_hard_fork AS slot, timestamp::bigint AS ts FROM blocks WHERE chain_status='canonical' AND global_slot_since_hard_fork IS NOT NULL ORDER BY height DESC LIMIT 50 ) SELECT height, slot, (ts - LAG(ts) OVER (ORDER BY height))/1000 AS delta_sec, (slot - LAG(slot) OVER (ORDER BY height)) AS delta_slots, ((ts - LAG(ts) OVER (ORDER BY height))/1000) / NULLIF(slot - LAG(slot) OVER (ORDER BY height),0) AS sec_per_slot FROM c ORDER BY height; SQL # sec_per_slot should be 90
⚠ PG_URI: stands for db connection string e.g. postgres://postgres:pass@localhost:5432/archive iv. Ensure no missing blocks. mina-missing-blocks-auditor --archive-uri "$PG_URI"; echo "exit=$?" ⚠ PG_URI: stands for db connection string e.g. postgres://postgres:pass@localhost:5432/archive
v. Ensure successful replayer run # Replayer from the fork point onwards mina-replayer \ --input-file post_fork_replayer_input.json \ --archive-uri "$PG_URI" \ --output-file post_fork_replayer_output.json \ --checkpoint-interval 10000 --checkpoint-output-folder ./checkpoints-mesa \ --log-level Info --log-json | tee replayer-mesa.log # NOTE post_fork_replayer_input.json file is produced by berkeley replayer with options --hard-fork-target mesa \ --stop-slot-config-file "$STOP_SLOT_CONFIG" \ --hard-fork-output-file post_fork_replayer_input.json \ Best way to do it is via docker (with assumption that stop slot config is stored at cwd ) docker run -v .:/workdir minaprocotol/mina-archive:{BERKELEY_VERION} mina-replayer \ --archive-uri "$PG_CONN" \ --input-file "$LAST_CHECKPOINT_FILE" \ --hard-fork-target mesa \ --stop-slot-config-file "$STOP_SLOT_CONFIG" \ --hard-fork-output-file post_fork_replayer_input.json \ --log-json \ --log-level spam ⚠ PG_URI: stands for db connection string e.g. postgres://postgres:pass@localhost:5432/archive b. Rosetta 1. Ensure you are connected to correct network # ROSETTA_URL env var point to base rosetta url (e.g. http://localhost:5000) curl -s -X POST $ROSETTA_URL/network/list -H 'Content-Type: application/json' -d '{}' | jq . # Expect: { "network_identifiers":[{"blockchain":"mina","network":"devnet"}] } curl -s -X POST $ROSETTA_URL/network/status -H 'Content-Type: application/json' \ -d "{\"network_identifier\":{\"blockchain\":\"mina\",\"network\":\"$MINA_NETWORK\"}}" | jq '.current_block_identifier, .genesis_block_identifier'
2. You can query transactions and blocks # Get network id NID=$("curl -s -X POST $ROSETTA_URL/network/status -H 'Content-Type: application/json' \ -d "{\"network_identifier\":{\"blockchain\":\"mina\",\"network\":\"testnet\"}}") # Latest block curl -s -X POST $ROSETTA_URL/block -H 'Content-Type: application/json' \ -d "{\"network_identifier\":$NID,\"block_identifier\":{}}" | jq '.block.block_identifier, .block.transactions | length' # Block by height curl -s -X POST $ROSETTA_URL/block -H 'Content-Type: application/json' \ -d "{\"network_identifier\":$NID,\"block_identifier\":{\"index\":100}}" | jq '.block.block_identifier' # Specific tx in a block curl -s -X POST $ROSETTA_URL/block/transaction -H 'Content-Type: application/json' \ -d "{\"network_identifier\":$NID,\"block_identifier\":{\"index\":100,\"hash\":\"<state_hash>\"},\"transaction_identifier\":{\"hash\":\"<tx_hash>\"}}" | jq . ⚠ ROSETTA_URL: Should point to URL of your rosetta instance. E.g. http://localhost:3087 Find more detailed information in the docs and reach out in Discord if you experience any issues. |
About Mina Protocol
Mina is the world’s lightest blockchain, powered by participants. Rather than apply brute computing force, Mina uses advanced cryptography and recursive zk-SNARKs to design an entire blockchain that is about 22kb, the size of a couple of tweets. It is the first layer-1 to enable efficient implementation and easy programmability of zero knowledge smart contracts (zkApps). With its unique privacy features and ability to connect to any website, Mina is building a private gateway between the real world and crypto—and the secure, democratic future we all deserve.