Skip to content

Commit

Permalink
feat(devops): Release proposal (#213)
Browse files Browse the repository at this point in the history
# Motivation
Automation of proposals.

# Changes
- Add a command to create a proposal.

# Tests
This has been used (with minor modification) in the last release.

# TODO
- Builds are not yet reliably reproducible on Mac.
  • Loading branch information
bitdivine authored Dec 16, 2024
1 parent cc94db2 commit f44f155
Show file tree
Hide file tree
Showing 3 changed files with 172 additions and 0 deletions.
17 changes: 17 additions & 0 deletions HACKING.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,20 @@ dfx canister install signer --mode upgrade --upgrade-unchanged --network staging
```

If you are not a controller, you may request a canister upgrade via Orbit. Please contact Leon Tan for the latest Orbit deployment instructions.

## Deploy to Production

- Create a GitHub release with a tag such as `v0.1.2`
- Update the GitHub release text. It is recommended to ask the team to review the text.
- Ensure that the release has been published.
- Check out the release commit
- Delete any old release directory.
- Install the corresponding `ic-admin`: `./scripts/setup ic-admin`
- Run: `./scripts/proposal-assets -t $TAG`
- Verify that `release/ci` contains the release Wasm and arguments.
- Run a docker build locally and verify that the Wasm and argument file hashes match.
- Run: `./scripts/proposal-template -t $TAG`
- Verify that `release/PROPOSAL.md` has been created.
- Run: `./scripts/propose`
- Verify the proposal very carefully, then submit the proposal.
- Create an appointment with trusted neurons to vote on the proposal.
2 changes: 2 additions & 0 deletions scripts/clap.bash
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# clap - a BASH argument parser
# This parser aims to have similar parsing semantics as Rust's clap parser; if it doesn't look anything like clap it's not just you.
#
# Minimum supported bash version: 5

clap_usage=""
clap_flag_match=""
Expand Down
153 changes: 153 additions & 0 deletions scripts/propose
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
#!/usr/bin/env bash
set -euo pipefail
SOURCE_DIR="$(dirname "$(realpath "${BASH_SOURCE[0]}")")"
PATH="$SOURCE_DIR:$PATH"

print_help() {
cat <<-EOF
Creates an NNS proposal to upgrade the chain-fusion-signer.
EOF
}

# Source the clap.bash file ---------------------------------------------------
source "$SOURCE_DIR/clap.bash"
# Define options
clap.define short=n long=network desc="The dfx network to use" variable=DFX_NETWORK default="ic"
clap.define short=u long=nns_url desc="The NNS_URL; typically a network provider" variable=DFX_NNS_URL
clap.define short=N long=neuron desc="The neuron ID to propose with" variable=DFX_NEURON_ID
clap.define short=k long=hsm_key_id desc="The HSM key ID" variable=DFX_HSM_KEY_ID default=""
clap.define short=i long=identity desc="The dfx identity to use" variable=DFX_IDENTITY default="$(dfx identity whoami)"
clap.define long=save-proposal-id-to-file desc="Create a file with this name containing the proposal ID" variable=PROPOSAL_ID_FILE default="proposal_id.txt"
# Source the output file ----------------------------------------------------------
source "$(clap.build)"

cd "${SOURCE_DIR}/.."
echo "Preparing release for network: $DFX_NETWORK"

# Gets the neuron ID.
# ... if saved in a file, get it:
NEURON_PATH="$HOME/.config/dfx/prod-neuron"
if test -e "$NEURON_PATH"; then
DFX_NEURON_ID="$(cat "$NEURON_PATH")"
echo "Neuron ID: $DFX_NEURON_ID (from $NEURON_PATH)"
else
read -rp "Please enter your Neuron ID: " DFX_NEURON_ID
read -rp "... Store the Neuron ID for future use? (y/N)" STORE_NEURON
[[ "$STORE_NEURON" != [yY] ]] || {
mkdir -p "$(dirname "$NEURON_PATH")"
printf "%s" "$DFX_NEURON_ID" >"$NEURON_PATH"
}
fi
[[ "${DFX_NEURON_ID:-}" != "" ]] || {
echo "ERROR: Neuron ID not provided."
exit 1
} >&2

# Gets the HSM key id, if applicable
# ... for mainnet we use an HSM and the key ID in the standard setup is 01:
[[ "${DFX_HSM_KEY_ID:-}" != "" ]] || {
if [[ "${DFX_NETWORK:-}" =~ mainnet|ic ]]; then
DFX_HSM_KEY_ID="01"
fi
}
# ... else look for an HSM in the dfx identity:
[[ "${DFX_HSM_KEY_ID:-}" != "" ]] || {
DFX_HSM_KEY_ID="$(jq -re '.hsm.key_id // empty' "${HOME}/.config/dfx/identity/${DFX_IDENTITY}/identity.json")" || true
}
# ... else the neuron should be controlled by a pem file.

# Gets the authentication mechanism
PEM_PATH="$HOME/.config/dfx/identity/${DFX_IDENTITY}/identity.pem"
if [[ "${DFX_NETWORK:-}" =~ mainnet|ic ]] || [[ "${DFX_HSM_KEY_ID:-}" != "" ]]; then
# Authenticate with an HSM
read -rp "HSM Pin: " DFX_HSM_PIN
export DFX_HSM_PIN # Needed for dfx canister id, for some strange reason.
AUTH_ARGS=(--use-hsm --pin "$DFX_HSM_PIN" --key-id "${DFX_HSM_KEY_ID}" --slot 0)
elif test -e "$PEM_PATH"; then
# Authenticate with a pem file
[[ "$(jq .encryption "$HOME/.config/dfx/identity/${DFX_IDENTITY}/identity.json")" == "null" ]] || {
echo "ERROR: ic-admin does not support encrypted pem files."
exit 1
} >&2
AUTH_ARGS=(--secret-key-pem "$PEM_PATH")
else
{
echo "ERROR: Please specify the HSM key ID (usually 01) or use a dfx identity with an unencrypted pem file."
exit 1
} >&2
fi

# Gets the chain-fusion-signer canister ID
CANISTER_ID="$(dfx canister id signer --network "$DFX_NETWORK")"
# ... just checking...
if [[ "${DFX_NETWORK:-}" =~ mainnet|ic ]]; then
[[ "${CANISTER_ID:-}" == "grghe-syaaa-aaaar-qabyq-cai" ]] || {
echo "ERROR: Has the mainnet signer canister ID really changed?"
echo " If so, please update this script: '${BASH_SOURCE[0]}'"
exit 1
} >&2
fi

# Gets the NNS URL
[[ "${DFX_NNS_URL:-}" != "" ]] || {
if [[ "${DFX_NETWORK:-}" =~ mainnet|ic ]]; then
DFX_NNS_URL="https://ic0.app"
else
# Try to use the snsdemo script for getting the provider for testnets and local replicas.
DFX_NNS_URL="$(dfx-network-provider --network "$DFX_NETWORK")"
fi
}
[[ "${DFX_NNS_URL:-}" != "" ]] || {
echo "ERROR: Unable to determine the NNS URL."
echo " Please specify --nns_url as a command line argument."
exit 1
} >&2

# Gets the wasm
# Note: If the wasm is not provided, the SHA will fail.
# Note: Also, the template creation script will fail if the wasm is not provided.
WASM="release/ci/signer.wasm.gz"
WASM_SHA="$(sha256sum <"$WASM" | awk '{print $1}')"

# Gets the canister arguments
ARG_PATH=release/ci/signer.args.bin
test -e "$ARG_PATH" || {
echo "ERROR: Arguments need to be provided in $ARG_PATH"
exit 1
} >&2

# Gets the proposal text
SUMMARY_FILE=release/PROPOSAL.md
test -e "${SUMMARY_FILE:-}" || {
echo "ERROR: Proposal text not found at: ${SUMMARY_FILE}"
exit 1
} >&2

# Prepares the command
set ic-admin "${AUTH_ARGS[@]}" --nns-url "$DFX_NNS_URL" propose-to-change-nns-canister --proposer "$DFX_NEURON_ID" --canister-id "$CANISTER_ID" --mode upgrade --wasm-module-path "$WASM" --summary-file "$SUMMARY_FILE" --wasm-module-sha256 "$WASM_SHA" --arg "$ARG_PATH"
# ... just checking...
echo
echo PLEASE REVIEW THIS COMMAND:
echo
echo "${@}"
echo

read -rp "Execute? (y/N) " COMMAND_OK
if [[ "$COMMAND_OK" = [yY] ]]; then
if [[ "${PROPOSAL_ID_FILE:-}" ]]; then
# The ic-admin command writes 1 line (among others) to stdout with the following format:
# proposal proposal <proposal_id>
# We want to parse the proposal ID out of that. First we tee stdout to
# /dev/tty so the user can also see the output, then we use grep and sed to
# extract the proposal ID and write it to a file.
"${@}" |
tee /dev/tty |
(grep -E 'proposal [0-9]+' || true) |
head -1 |
sed -E 's/.*proposal ([0-9]+)/\1/' >"$PROPOSAL_ID_FILE"
else
"${@}"
fi
fi

0 comments on commit f44f155

Please sign in to comment.