# Node Migration Guide This guide provides step-by-step instructions for node operators to migrate their MPC nodes between different hosts or cloud providers using the backup CLI. ## Overview Node migration allows you to move your MPC node from one host to another without requiring a full network resharing. This is accomplished using the `backup-cli` tool to securely backup and restore your node's keyshares. **Changing only your URL?** If your node keeps the same TLS key and you only need to point peers at a new address (e.g. fixing a typo or moving to a new domain), call `update_participant_url` on the contract instead of running a migration. It updates just your registered URL; peers pick it up without a resharing or reconnecting existing sessions. **Important:** This guide covers the **Soft Launch** migration process. For information about the architecture and future Hard Launch implementation, see [migration-service.md](./migration-service.md). ## Prerequisites Before starting a migration, ensure you have: 1. **An active MPC node** that is a current participant in the network 2. **A new host/machine** ready to run the migrated node 3. **The backup-cli tool** installed on a secure machine (can be your local machine or a dedicated backup server) 4. **NEAR CLI** installed for contract interactions 5. **Access to both nodes** (old and new) during the migration process ## Environment Variables Setup Set up the following environment variables at the beginning of your migration process. These will be used throughout the guide: ```bash # Your NEAR account ID that operates the MPC node export SIGNER_ACCOUNT_ID=your-account.testnet # The MPC contract account ID export MPC_CONTRACT_ACCOUNT_ID=v1.signer-prod.testnet # NEAR network configuration (testnet or mainnet) export NEAR_NETWORK=testnet ``` **Note:** Adjust these values based on your specific setup. For mainnet deployments, use `mainnet` for `NEAR_NETWORK` and `v1.signer` for `MPC_CONTRACT_ACCOUNT_ID`. ## Step 1: Setup the Backup CLI First, you'll need to set up the backup CLI tool and generate keys for the backup service. ### Install backup-cli Install the backup-cli tool using cargo (run from the repository root): ```bash cargo install --path crates/backup-cli --locked ``` This installs the `backup-cli` binary to your cargo bin directory (typically `~/.cargo/bin`), which should be in your `PATH`. ### Generate Backup Service Keys Create a home directory for the backup-cli and generate its keys: ```bash export BACKUP_HOME_DIR=/path/to/backup/home mkdir -p $BACKUP_HOME_DIR backup-cli \ --home-dir $BACKUP_HOME_DIR \ generate-keys ``` This creates a `secrets.json` file in your backup home directory containing: - `p2p_private_key`: Used for mutual TLS authentication with MPC nodes - `local_storage_aes_key`: Used to encrypt keyshares stored locally **Important:** Keep the `secrets.json` file secure. Anyone with access to this file can authenticate as your backup service and decrypt any keyshares stored locally. ## Step 2: Register the backup-cli Before you can backup keyshares, you must register your backup-cli's public key with the MPC contract. ### Get the Registration Command Run the following command to generate the NEAR CLI command for registration: ```bash backup-cli \ --home-dir $BACKUP_HOME_DIR \ register \ --mpc-contract-account-id $MPC_CONTRACT_ACCOUNT_ID \ --near-network $NEAR_NETWORK \ --signer-account-id $SIGNER_ACCOUNT_ID ``` This will output a complete `near` CLI command. Example output: ```bash Run the following command to register your backup service: near contract call-function as-transaction \ $MPC_CONTRACT_ACCOUNT_ID \ register_backup_service \ json-args '{"backup_service_info":{"public_key":"ed25519:AbC123..."}}' \ prepaid-gas '300.0 Tgas' \ attached-deposit '1 yoctoNEAR' \ sign-as $SIGNER_ACCOUNT_ID \ network-config $NEAR_NETWORK \ sign-with-keychain \ send ``` ### Execute the Registration Copy and run the generated command to register your backup-cli with the contract. **Note:** The "public key" in the registration corresponds to the `p2p_private_key` created in Step 1. ### Verify Registration ```bash near contract call-function as-read-only \ $MPC_CONTRACT_ACCOUNT_ID \ migration_info \ json-args {} \ network-config $NEAR_NETWORK \ now ``` You should see your account and registered backup_cli public key listed, something like this: ```json { "your-account.testnet": [ { "public_key": "ed25519:AbC123..." }, null ] } ``` ## Step 3: Generate and Set Encryption Key For additional security, the backup and restore process encrypts keyshares during transport using AES encryption. You need to generate a shared encryption key (32 bytes / 64 hex characters), configure it on both your old and new nodes, and pass it to the backup-cli commands. Where you set it on a node depends on the deployment (see [Step 5](#step-5-prepare-the-new-node) for exact placement): - **TDX / CVM node:** set `backup_encryption_key_hex` under `[mpc_node_config.secrets]` in `user-config.toml`. A CVM has no `.env` / `MPC_BACKUP_ENCRYPTION_KEY_HEX` pathway. - **Non-TEE node:** set the `MPC_BACKUP_ENCRYPTION_KEY_HEX` environment variable. **Important:** The key must match **exactly** between the backup-cli and the node it talks to (the old node for `get-keyshares`, the new node for `put-keyshares`) — a mismatch, including a stray trailing newline, makes the transfer fail. If set on the node it must be 64 hex characters (a malformed value stops the node from starting); if left unset, the node generates one itself (see below). ### Retrieve a key from an existing node. **Note:** If your node has been running without an encryption key configured, the node automatically generates one and stores it in a file called `backup_encryption_key.hex` in your `$MPC_HOME_DIR` directory. On a **non-TEE** node you can retrieve it with: ```bash export BACKUP_ENCRYPTION_KEY=$(cat $MPC_HOME_DIR/backup_encryption_key.hex) ``` Copy this key and set it as the `BACKUP_ENCRYPTION_KEY` environment variable for the backup-cli when running `get-keyshares`. **TEE (TDX/dstack) nodes:** `$MPC_HOME_DIR` (`/data`) is inside the CVM's encrypted disk, so you cannot read the auto-generated `backup_encryption_key.hex`. Provide the key yourself instead: set it in the `[mpc_node_config.secrets]` block of the node's `user-config.toml` (see [Prepare MPC Node Configuration](https://github.com/near/mpc/blob/main/docs/running-an-mpc-node-in-tdx-external-guide.md#prepare-mpc-node-configuration) in the operator guide) and keep a copy outside the CVM: ```toml [mpc_node_config.secrets] backup_encryption_key_hex = "" ``` This is the key you pass to the backup-cli — if the node is already deployed, it is the value you set in `backup_encryption_key_hex` at deploy time. The node reads it from the config on every start, so you can add or change it on a running node via `update-user-config` + restart. **Note on key differences:** - `BACKUP_ENCRYPTION_KEY` (this key) is used to encrypt keyshares during transport between nodes and the backup-cli - `local_storage_aes_key` (from Step 1) is used to encrypt keyshares stored on disk in the backup home directory - These are two different keys serving different purposes **TEE Migration Note:** This guide covers the Soft Launch migration process where the encryption key can be accessed from the file system. For TEE-to-TEE migrations in the Hard Launch phase, the backup service will run autonomously within a TEE and handle encryption keys securely without file system access. Refer to [migration-service.md](./migration-service.md) for Hard Launch details. ## Step 4: Backup Keyshares from Old Node Now backup the keyshares from your currently running node. ### Obtain Node Information You'll need: - **MPC node address**: The host where your node is running (e.g., `node.example.com`). Available from the contract — your participant entry's `url` in the `state` view. - **MPC node P2P public key**: The Ed25519 public key used for P2P communication. Available from the contract (your participant's `tls_public_key` in `state` / `get_tee_accounts`), or from the node's public-data endpoint: ```bash export P2P_KEY=$(curl -s http://:8080/public_data | jq -r ".near_p2p_public_key") ``` ### Get Contract State Before backing up keyshares, you need to query the current contract state and save it: ```bash near contract call-function as-read-only \ $MPC_CONTRACT_ACCOUNT_ID \ state \ json-args {} \ network-config $NEAR_NETWORK \ now > $BACKUP_HOME_DIR/contract_state.json ``` This saves the contract state to `contract_state.json`, which the backup-cli uses to determine the current epoch and which keyshares to request from the node (based on the domains in the current keyset). ### Run the Backup Port 8079 is the default port for the migration endpoint. ```bash backup-cli \ --home-dir $BACKUP_HOME_DIR \ get-keyshares \ --mpc-node-address node.example.com:8079 \ --mpc-node-p2p-key "ed25519:YourNodeP2PPublicKey..." \ --backup-encryption-key-hex $BACKUP_ENCRYPTION_KEY ``` The encrypted keyshares are now stored in `$BACKUP_HOME_DIR/permanent_keys/epoch__with__domains` (with a `key` hard-link in that directory). ## Step 5: Prepare the New Node Set up your new node on the new host with the following: 1. **Install and configure the MPC node software** on the new host (the new node should use the same NEAR account as the old node) 2. **Set the encryption key** on the backup-cli and the new node, using the same key you pass to `put-keyshares` in [Step 7](#step-7-transfer-keyshares-to-new-node) (it may differ from the old node's key, but re-using one key throughout is simplest). Where to set it on the new node: - **TDX / CVM node:** set it in `user-config.toml` under `[mpc_node_config.secrets]` before deploying. On a running CVM, apply it with `update-user-config` + restart (see [CVM management](https://github.com/near/mpc/blob/main/docs/running-an-mpc-node-in-tdx-external-guide.md#cvm-management)): ```toml [mpc_node_config.secrets] backup_encryption_key_hex = "" ``` - **Non-TEE node:** add it to the `.env` file: ```env MPC_BACKUP_ENCRYPTION_KEY_HEX= ``` 3. **Start the node and retrieve the new keys from the new node**: (P2P (TLS) key, NEAR account key) 4. **Add the node's `near_signer_public_key` to your account as a restricted access key** See more details on extracting key from the node and adding the keys to your account, in the [running an MPC node in TDX external guide](https://github.com/near/mpc/blob/main/docs/running-an-mpc-node-in-tdx-external-guide.md#add-the-node-account-key-to-your-account) **Note:** The keys can be retrieved using the node's public data endpoint: ```bash export near_signer_public_key=$(curl -s http://:8080/public_data | jq -r ".near_signer_public_key") export P2P_KEY=$(curl -s http://:8080/public_data | jq -r ".near_p2p_public_key") ``` ### Check that the new node's attestation is registered on the contract ```bash near contract call-function as-read-only \ $MPC_CONTRACT_ACCOUNT_ID \ get_tee_accounts \ json-args {} \ network-config $NEAR_NETWORK \ now ``` **Note:** If the new node's attestation was submitted successfully, you should see 2 attestations registered on the contract — one for the old node and one for the new node. Output should look like this: ```bash [ { "account_id": "your-account.testnet", "account_public_key": "ed25519:OldNodeAccountPublicKey...", "tls_public_key": "ed25519:OldNodeTlsPublicKey..." }, { "account_id": "your-account.testnet", "account_public_key": "ed25519:NewNodeAccountPublicKey...", "tls_public_key": "ed25519:NewNodeTlsPublicKey..." } ] ``` ## Step 6: Initiate Migration state in Contract ### Collect New Node Information You'll need: - **New node's P2P public key**: $P2P_KEY from step above. - **New node's signer account public key**: $near_signer_public_key from step above. - **New node's address**: The URL where the new node will be accessible (e.g., `new-node.example.com:80`) ### start_node_migration on contract Call the `start_node_migration` method on the MPC contract to register the new node as the migration target: ```bash near contract call-function as-transaction \ $MPC_CONTRACT_ACCOUNT_ID \ start_node_migration \ json-args "{ \"destination_node_info\": { \"signer_account_pk\": \"$near_signer_public_key\", \"destination_node_info\": { \"url\": \"http://new-node.example.com:80\", \"tls_public_key\": \"$P2P_KEY\" } } }" \ prepaid-gas '300.0 Tgas' \ attached-deposit '1 yoctoNEAR' \ sign-as $SIGNER_ACCOUNT_ID \ network-config $NEAR_NETWORK \ sign-with-keychain \ send ``` **Note:** The `url` in `destination_node_info` above must contain the `http://` prefix, please do not forget adding it. ### Verify Migration Was Registered on the Contract After calling `start_node_migration`, verify that the destination node was registered correctly on-chain: ```bash near contract call-function as-read-only \ $MPC_CONTRACT_ACCOUNT_ID \ migration_info \ json-args {} \ network-config $NEAR_NETWORK \ now ``` This will return migration information for all accounts, including your backup service info and destination node info. Look for your account in the output to confirm the migration was registered. ## Step 7: Transfer Keyshares to New Node ```bash backup-cli \ --home-dir $BACKUP_HOME_DIR \ put-keyshares \ --mpc-node-address new-node.example.com:8079 \ --mpc-node-p2p-key "ed25519:NewNodeP2PPublicKey..." \ --backup-encryption-key-hex $BACKUP_ENCRYPTION_KEY ``` The new node will: 1. Receive the encrypted keyshares 2. Decrypt them using its configured backup encryption key (Step 5) 3. Automatically call `conclude_node_migration` on the contract to finalize the migration 4. Begin participating in the MPC network with the restored keyshares ## Step 8: Verify Migration Success Check that the migration completed successfully: 1. **Check contract state**: Query the contract to verify your account now points to the new node's public key 2. **Monitor new node logs**: Ensure the new node is participating in signature and CKD requests 3. **Test functionality**: Send a test signature request to verify the network recognizes the new node ### Query Migration State You can check the current migration state using the contract's view methods: ```bash near contract call-function as-read-only \ $MPC_CONTRACT_ACCOUNT_ID \ migration_info \ json-args {} \ network-config $NEAR_NETWORK \ now ``` Look for your account in the output. Once the migration is complete, there should be no ongoing migration (destination_node_info should be null) for your account. ## Step 9: Decommission Old Node After verifying the migration was successful: 1. **Stop the old node** on the old host. 2. **Revoke the old node's signer key.** The function-call key you added in Step 5 of the previous migration persists on your account with `unlimited` allowance on the MPC contract until explicitly removed. Use `list-keys` to find the old signer's public key (distinct from the one you just added in Step 5), then `delete-keys`: ```bash near account list-keys \ $SIGNER_ACCOUNT_ID \ network-config $NEAR_NETWORK \ now near account delete-keys \ $SIGNER_ACCOUNT_ID \ public-keys \ network-config $NEAR_NETWORK \ sign-with-keychain \ send ``` The `public-keys` argument is a comma-separated list (`,,…`), so if more than one stale function-call key has accumulated from earlier migrations, you can revoke them all in a single call. Don't revoke the `backup-cli`'s registered key from Step 2 — that's the backup service registration, reused across migrations. 3. **Keep the backup** of keyshares (the contents of `$BACKUP_HOME_DIR`, including the `key` file and the `permanent_keys/` directory with `epoch_<...>_with_<...>_domains` files) for a reasonable period (in case you need to migrate again). 4. **Securely delete** the old node's data once you're confident the new node is functioning correctly. ## Troubleshooting ### Connection Errors with backup-cli If backup-cli cannot connect to your node: - **Verify firewall rules**: Ensure the backup service can reach the node's address and that port 8079 is open and accessible. ## Known Limitations The back-migration flow (returning to a previously-active node, i.e. A → B → A) has two operator-facing limitations: 1. **Restart Node A before initiating the back-migration.** Stop and start A so its migration service is reinitialized and ready to receive keyshares from B. The restart also forces A to submit a fresh on-chain attestation (see next bullet). 2. **A's on-chain attestation must be current.** The contract rejects the back-migration if A's attestation has expired or been revoked while A was outside the participant set. Restarting A (limitation 1) forces a fresh attestation submission immediately; otherwise, A's normal periodic resubmission updates the attestation roughly every hour.