-
Notifications
You must be signed in to change notification settings - Fork 0
feat: add dkg injection script for dappnode with guide #33
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
blockchainluffy
wants to merge
1
commit into
main
Choose a base branch
from
dkg-injection
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,64 @@ | ||
| # Dappnode Keypers: How to use DKG injection script | ||
|
|
||
| This guide describes the process for how **Dappnode** keypers can use the DKG injection script in the **shutter-api-1002** deployment. | ||
|
|
||
| ## Purpose | ||
|
|
||
| To restore key material generated during previous deployment, necessary to fulfill pending decryption tasks. | ||
|
|
||
| --- | ||
|
|
||
| **Initial Keypers**: Keypers who were active during **eon 11**. Timestamp range: Mar-24-2025 01:03:45 PM UTC (1742821425) - Dec-01-2025 11:25:35 AM UTC (1764588335). | ||
|
|
||
| --- | ||
|
|
||
| ## Prerequisites | ||
|
|
||
| - Fully synced keyper running the shutter-api-1002 deployment version on Dappnode | ||
| - The same signing keys used for initial keyper deployment | ||
| - Dappnode backup from the initial keyper | ||
| - Access to Dappnode instance via shell | ||
|
|
||
| --- | ||
|
|
||
| ## Process Steps | ||
|
|
||
| ### 1. Run Keypers with Same Signing Keys | ||
|
|
||
| In the **shutter-api-1002** deployment, run the keypers with the **same signing keys** that were used previously for the initial keypers deployment and wait for them to sync with the network. | ||
|
|
||
| Sync can be confirmed by this log line: | ||
|
|
||
| ``` | ||
| synced registry contract end-block=20044460 num-discarded-events=0 num-inserted-events=0 start-block=20044460 | ||
| ``` | ||
|
|
||
| The **end-block** should be (or greater than) the current head of the chain in the explorer. | ||
|
|
||
| ### 2. Ensure the backup is copied to the same instance | ||
|
|
||
| Copy the backup to the same instance where the keyper is running. | ||
|
|
||
| ### 3. Run DKG Injection Script (Dappnode) | ||
|
|
||
| After a keyperset transition is done, run the Dappnode DKG injection script with the backup path: | ||
|
|
||
| ```bash | ||
| curl -fsSL https://raw.githubusercontent.com/DAppnodePackage-shutter-api | ||
| /dkg-injection/dkg_injection/inject_dkg_result_dappnode.sh | bash -s -- <path_to_dappnode_backup> | ||
| ``` | ||
|
|
||
| Replace `<path_to_dappnode_backup>` with the actual path to your Dappnode backup archive. | ||
|
|
||
| Check if there is no error in running the script. | ||
|
|
||
| --- | ||
|
|
||
| ## Summary Checklist | ||
|
|
||
| | Step | Action | | ||
| |------|--------| | ||
| | 1 | Run keypers in shutter-api-1002 with same signing keys as initial keypers and wait for keypers to sync | | ||
| | 2 | Ensure the Dappnode backup archive is copied to the same instance | | ||
| | 3 | Run the Dappnode DKG injection script with the backup archive path | | ||
|
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,276 @@ | ||
| #!/usr/bin/env bash | ||
|
|
||
| # This script overrides a selected DKG result in the live keyper database | ||
| # with the corresponding data from a Dappnode backup. The following tables are | ||
| # affected: | ||
| # - dkg_result (columns: success, error, pure_result) | ||
| # - keyper_set (columns: keypers, threshold) | ||
| # - tendermint_batch_config (columns: keypers, threshold) | ||
| # | ||
| # Usage: | ||
| # ./inject_dkg_result_dappnode.sh <path-to-backup.tar> | ||
| # | ||
| # Ensure the node is sufficiently synced before running. If the keyper | ||
| # service is running, it will be stopped during the operation and | ||
| # restarted afterwards. The database service will be started if not | ||
| # already running, and stopped again afterwards if it was not running. | ||
|
|
||
| set -euo pipefail | ||
|
|
||
| EON="11" | ||
| KEYPER_CONFIG_INDEX="11" | ||
| MIN_TENDERMINT_CURRENT_BLOCK="0" | ||
|
|
||
| BACKUP_CONTAINER="backup-db" | ||
| BACKUP_IMAGE="postgres:16" | ||
| BACKUP_DB="postgres" | ||
| BACKUP_USER="postgres" | ||
| BACKUP_PASSWORD="postgres" | ||
| KEYPER_DB="keyper" | ||
| BACKUP_TABLE_SUFFIX="_backup" | ||
|
|
||
| TMP_DIR="$(mktemp -d 2>/dev/null || mktemp -d -t inject-dkg-result)" | ||
| TABLES=( | ||
| "dkg_result:eon:${EON}:success, error, pure_result" | ||
| "tendermint_batch_config:keyper_config_index:${KEYPER_CONFIG_INDEX}:keypers, threshold" | ||
| "keyper_set:keyper_config_index:${KEYPER_CONFIG_INDEX}:keypers, threshold" | ||
| ) | ||
|
|
||
| log() { | ||
| echo "==> $1" | ||
| } | ||
|
|
||
| usage() { | ||
| echo "Usage: $(basename "$0") <path-to-backup.tar|path-to-backup.tar.xz>" >&2 | ||
| exit 1 | ||
| } | ||
|
|
||
| if [[ "$#" -ne 1 ]]; then | ||
| usage | ||
| fi | ||
|
|
||
| if ! command -v tar >/dev/null 2>&1; then | ||
| echo "ERROR: required command 'tar' not found in PATH" >&2 | ||
| exit 1 | ||
| fi | ||
|
|
||
| BACKUP_TARBALL_PATH="$1" | ||
|
|
||
| if [[ ! -f "$BACKUP_TARBALL_PATH" ]]; then | ||
| echo "ERROR: tarball not found: $BACKUP_TARBALL_PATH" >&2 | ||
| exit 1 | ||
| fi | ||
|
|
||
| if docker ps -a --format '{{.Names}}' | grep -q "^${BACKUP_CONTAINER}\$"; then | ||
| echo "ERROR: container '${BACKUP_CONTAINER}' already exists. Aborting." >&2 | ||
| exit 1 | ||
| fi | ||
|
|
||
| DB_WAS_RUNNING=0 | ||
| KEYPER_WAS_RUNNING=0 | ||
|
|
||
| LIVE_DB_CONTAINER="${LIVE_DB_CONTAINER:-DAppNodePackage-db.shutter-api-gnosis.dnp.dappnode.eth}" | ||
| if docker ps --format '{{.Names}}' | grep -q "^${LIVE_DB_CONTAINER}\$"; then | ||
| DB_WAS_RUNNING=1 | ||
| fi | ||
|
|
||
| LIVE_KEYPER_CONTAINER="${LIVE_KEYPER_CONTAINER:-DAppNodePackage-shutter.shutter-api-gnosis.dnp.dappnode.eth}" | ||
| if docker ps --format '{{.Names}}' | grep -q "^${LIVE_KEYPER_CONTAINER}\$"; then | ||
| KEYPER_WAS_RUNNING=1 | ||
| fi | ||
|
|
||
| cleanup() { | ||
| rv=$? | ||
| if [[ "$rv" -ne 0 ]]; then | ||
| echo "Aborting due to error (exit code $rv)" >&2 | ||
| fi | ||
|
|
||
| log "Stopping backup container" | ||
| docker stop "$BACKUP_CONTAINER" >/dev/null 2>&1 || true | ||
|
|
||
| if [[ "$KEYPER_WAS_RUNNING" -eq 1 ]]; then | ||
| log "Restarting keyper service (was running before)" | ||
| docker start "$LIVE_KEYPER_CONTAINER" >/dev/null 2>&1 || true | ||
| else | ||
| log "Leaving keyper service stopped (was not running before)" | ||
| fi | ||
|
|
||
| if [[ "$DB_WAS_RUNNING" -eq 0 ]]; then | ||
| log "Stopping db service (was not running before)" | ||
| docker stop "$LIVE_DB_CONTAINER" >/dev/null 2>&1 || true | ||
| else | ||
| log "Keeping db service running (was running before)" | ||
| fi | ||
|
|
||
| if [[ -d "$TMP_DIR" ]]; then | ||
| log "Removing temporary directory ${TMP_DIR}" | ||
| rm -rf "$TMP_DIR" | ||
| fi | ||
|
|
||
| exit "$rv" | ||
| } | ||
| trap cleanup EXIT | ||
|
|
||
| if [[ "$DB_WAS_RUNNING" -eq 0 ]]; then | ||
| log "Starting db service (was not running)" | ||
| docker start "$LIVE_DB_CONTAINER" >/dev/null | ||
| fi | ||
|
|
||
| log "Checking shuttermint sync block number >= ${MIN_TENDERMINT_CURRENT_BLOCK}" | ||
| CURRENT_BLOCK=$(docker exec -i "$LIVE_DB_CONTAINER" sh -lc \ | ||
| "psql -t -A -U postgres -d ${KEYPER_DB} -c \"SELECT current_block FROM tendermint_sync_meta ORDER BY current_block DESC LIMIT 1\"" \ | ||
| 2>/dev/null | tr -d '[:space:]') | ||
|
|
||
| if [[ -z "$CURRENT_BLOCK" ]]; then | ||
| echo "ERROR: failed to read shuttermint sync block number" >&2 | ||
| exit 1 | ||
| fi | ||
|
|
||
| if ! [[ "$CURRENT_BLOCK" =~ ^[0-9]+$ ]]; then | ||
| echo "ERROR: shuttermint sync block number is not an integer: $CURRENT_BLOCK" >&2 | ||
| exit 1 | ||
| fi | ||
|
|
||
| if (( CURRENT_BLOCK < MIN_TENDERMINT_CURRENT_BLOCK )); then | ||
| echo "ERROR: shuttermint sync block number ($CURRENT_BLOCK) is below MIN_TENDERMINT_CURRENT_BLOCK ($MIN_TENDERMINT_CURRENT_BLOCK); aborting. Please wait until the node is sufficiently synced and try again." >&2 | ||
| exit 1 | ||
| fi | ||
|
|
||
| log "Stopping keyper service" | ||
| docker stop "$LIVE_KEYPER_CONTAINER" >/dev/null 2>&1 || true | ||
|
|
||
| log "Extracting keyper DB from backup" | ||
| TAR_WARNING_FLAGS=() | ||
| if tar --help 2>/dev/null | grep -q -- '--warning'; then | ||
| TAR_WARNING_FLAGS+=(--warning=no-unknown-keyword) | ||
| fi | ||
|
|
||
| TAR_ERROR_FILE="${TMP_DIR}/tar-extract.err" | ||
| if ! tar "${TAR_WARNING_FLAGS[@]}" -xf "$BACKUP_TARBALL_PATH" -C "$TMP_DIR" 2>"$TAR_ERROR_FILE"; then | ||
| if ! tar "${TAR_WARNING_FLAGS[@]}" -xJf "$BACKUP_TARBALL_PATH" -C "$TMP_DIR" 2>"$TAR_ERROR_FILE"; then | ||
| echo "ERROR: failed to extract backup tarball: $BACKUP_TARBALL_PATH" >&2 | ||
| if [[ -s "$TAR_ERROR_FILE" ]]; then | ||
| cat "$TAR_ERROR_FILE" >&2 | ||
| fi | ||
| exit 1 | ||
| fi | ||
| fi | ||
|
|
||
| if [[ -z "$(find "$TMP_DIR" -mindepth 1 -print -quit 2>/dev/null)" ]]; then | ||
| echo "ERROR: backup tarball extracted no files: $BACKUP_TARBALL_PATH" >&2 | ||
| exit 1 | ||
| fi | ||
|
|
||
| DB_DATA_DIR="" | ||
| while IFS= read -r -d '' d; do | ||
| if [[ -d "$d" && -f "$d/PG_VERSION" ]]; then | ||
| DB_DATA_DIR="$d" | ||
| break | ||
| fi | ||
| done < <(find "$TMP_DIR" -type d -name "db-data" -print0 2>/dev/null) | ||
|
|
||
| if [[ -z "$DB_DATA_DIR" || ! -d "$DB_DATA_DIR" ]]; then | ||
| echo "ERROR: could not find db-data directory (Postgres data) inside backup" >&2 | ||
| exit 1 | ||
| fi | ||
|
|
||
| log "Starting backup container" | ||
| docker run -d --rm \ | ||
| --name "$BACKUP_CONTAINER" \ | ||
| -e POSTGRES_USER="$BACKUP_USER" \ | ||
| -e POSTGRES_PASSWORD="$BACKUP_PASSWORD" \ | ||
| -e POSTGRES_DB="$BACKUP_DB" \ | ||
| -v "$DB_DATA_DIR:/var/lib/postgresql/data" \ | ||
| "$BACKUP_IMAGE" >/dev/null | ||
|
|
||
| log "Waiting for backup DB to become ready" | ||
| for i in {1..30}; do | ||
| if docker exec "$BACKUP_CONTAINER" pg_isready -U "$BACKUP_USER" -d "$BACKUP_DB" >/dev/null 2>&1; then | ||
| break | ||
| fi | ||
| sleep 1 | ||
| done | ||
| if ! docker exec "$BACKUP_CONTAINER" pg_isready -U "$BACKUP_USER" -d "$BACKUP_DB" >/dev/null 2>&1; then | ||
| echo "ERROR: backup DB did not become ready after 30 seconds" >&2 | ||
| exit 1 | ||
| fi | ||
|
|
||
| for entry in "${TABLES[@]}"; do | ||
| IFS=: read -r TABLE KEY_COLUMN KEY_VALUE SELECT_COLUMNS <<<"$entry" | ||
| BACKUP_CSV_FILE="${TMP_DIR}/${TABLE}_backup_${KEY_COLUMN}_${KEY_VALUE}.csv" | ||
| LIVE_CSV_FILE="${TMP_DIR}/${TABLE}_live_${KEY_COLUMN}_${KEY_VALUE}.csv" | ||
| SELECT_COLUMN_LIST=() | ||
|
|
||
| for col in ${SELECT_COLUMNS//,/ }; do | ||
| [[ -z "$col" ]] && continue | ||
| if [[ "$col" == "$KEY_COLUMN" ]]; then | ||
| echo "ERROR: column list for ${TABLE} must not include key column ${KEY_COLUMN}" >&2 | ||
| exit 1 | ||
| fi | ||
| SELECT_COLUMN_LIST+=("$col") | ||
| done | ||
|
|
||
| if [[ "${#SELECT_COLUMN_LIST[@]}" -eq 0 ]]; then | ||
| echo "ERROR: no non-key columns specified for update in ${TABLE}" >&2 | ||
| exit 1 | ||
| fi | ||
|
|
||
| SELECT_COLUMN_LIST_WITH_KEY=("$KEY_COLUMN" "${SELECT_COLUMN_LIST[@]}") | ||
| SELECT_COLUMNS_WITH_KEY=$(IFS=', '; echo "${SELECT_COLUMN_LIST_WITH_KEY[*]}") | ||
|
|
||
| log "Extracting ${TABLE} row ${KEY_COLUMN}=${KEY_VALUE} from backup DB" | ||
| docker exec "$BACKUP_CONTAINER" bash -lc \ | ||
| "psql -v ON_ERROR_STOP=1 -U '$BACKUP_USER' -d '$KEYPER_DB' -c \"COPY (SELECT ${SELECT_COLUMNS_WITH_KEY} FROM ${TABLE} WHERE ${KEY_COLUMN} = '${KEY_VALUE}' LIMIT 1) TO STDOUT WITH CSV\"" \ | ||
| >"$BACKUP_CSV_FILE" 2>/dev/null | ||
|
|
||
| if [[ ! -s "$BACKUP_CSV_FILE" ]]; then | ||
| echo "ERROR: no data extracted from backup DB (no row with ${KEY_COLUMN}=${KEY_VALUE} in ${TABLE})" >&2 | ||
| exit 1 | ||
| fi | ||
|
|
||
| log "Extracting ${TABLE} row ${KEY_COLUMN}=${KEY_VALUE} from live DB" | ||
| docker exec -i "$LIVE_DB_CONTAINER" sh -lc \ | ||
| "psql -v ON_ERROR_STOP=1 -U postgres -d ${KEYPER_DB} -c \"COPY (SELECT ${SELECT_COLUMNS_WITH_KEY} FROM ${TABLE} WHERE ${KEY_COLUMN} = '${KEY_VALUE}' LIMIT 1) TO STDOUT WITH CSV\"" \ | ||
| >"$LIVE_CSV_FILE" 2>/dev/null || true | ||
|
|
||
| if [[ ! -s "$LIVE_CSV_FILE" ]]; then | ||
| echo "ERROR: no data extracted from live DB (no row with ${KEY_COLUMN}=${KEY_VALUE} in ${TABLE})" >&2 | ||
| exit 1 | ||
| fi | ||
|
|
||
| if [[ -s "$LIVE_CSV_FILE" && -s "$BACKUP_CSV_FILE" && "$(cat "$LIVE_CSV_FILE")" == "$(cat "$BACKUP_CSV_FILE")" ]]; then | ||
| log "Live row for ${TABLE} already matches backup, nothing to do" | ||
| continue | ||
| fi | ||
|
|
||
| BACKUP_TABLE_NAME="${TABLE}${BACKUP_TABLE_SUFFIX}" | ||
|
|
||
| log "Backing up table ${TABLE} to ${BACKUP_TABLE_NAME} in live DB" | ||
| { | ||
| echo "CREATE TABLE IF NOT EXISTS ${BACKUP_TABLE_NAME} (LIKE ${TABLE} INCLUDING ALL);" | ||
| echo "TRUNCATE ${BACKUP_TABLE_NAME};" | ||
| echo "INSERT INTO ${BACKUP_TABLE_NAME} SELECT * FROM ${TABLE};" | ||
| } | docker exec -i "$LIVE_DB_CONTAINER" psql -U postgres -d "${KEYPER_DB}" >/dev/null 2>&1 | ||
|
|
||
| UPDATE_SET="" | ||
| for col in "${SELECT_COLUMN_LIST[@]}"; do | ||
| if [[ -z "$UPDATE_SET" ]]; then | ||
| UPDATE_SET="${col} = u.${col}" | ||
| else | ||
| UPDATE_SET="${UPDATE_SET}, ${col} = u.${col}" | ||
| fi | ||
| done | ||
|
|
||
| log "Restoring ${TABLE} row ${KEY_COLUMN}=${KEY_VALUE}" | ||
| { | ||
| echo "BEGIN;" | ||
| echo "CREATE TEMP TABLE tmp_update AS SELECT ${SELECT_COLUMNS_WITH_KEY} FROM ${TABLE} WHERE 1=0;" | ||
| echo "COPY tmp_update FROM STDIN WITH CSV;" | ||
| cat "$BACKUP_CSV_FILE" | ||
| echo '\.' | ||
| echo "UPDATE ${TABLE} AS t SET ${UPDATE_SET} FROM tmp_update u WHERE t.${KEY_COLUMN} = u.${KEY_COLUMN};" | ||
| echo "COMMIT;" | ||
| } | docker exec -i "$LIVE_DB_CONTAINER" psql -U postgres -d "${KEYPER_DB}" >/dev/null 2>&1 | ||
| done | ||
|
|
||
| log "Done" | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it would be better if we extract not in into TMP_DIR, but into a subdirectory, for two reasons:
-Jflag. Currently, the first extraction attempt might leave the directory in an undefined statetar-extract.errfile is created