Skip to content

Commit 1f4395a

Browse files
feat(scripts): add dev-cluster contract upgrade tooling
1 parent b168de6 commit 1f4395a

4 files changed

Lines changed: 163 additions & 8 deletions

File tree

RELEASES.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -171,9 +171,11 @@ scripted parts of a release. It offers two things:
171171
cluster via [`scripts/ops/dev-cluster/dev-menu.sh`](./scripts/ops/dev-cluster/dev-menu.sh).
172172

173173
The dev-cluster flow asks for the network (testnet first, then mainnet), the
174-
version, and the cluster's Nomad IP and credentials, then swaps each
175-
`mpc-node-*` Nomad job to the release image (plan, confirm, run) and checks the
176-
nodes report the new `release=` in their build info.
174+
version, and the cluster's Nomad IP and credentials, then runs the upgrade in
175+
runbook order: swap each `mpc-node-*` Nomad job to the release image (plan,
176+
confirm, run), check the nodes report the new `release=` in their build info,
177+
offer a test signature, and finally — only for releases that change
178+
`crates/contract` — propose and vote the contract update.
177179

178180
Every command is printed before it runs and every write is behind a
179181
confirmation prompt, so a run can be stopped at any step. Nothing

scripts/ops/dev-cluster/dev-common.sh

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@
99

1010
SIGN_WITH="${MPC_SIGN_WITH:-sign-with-keychain}"
1111

12-
# Sets CONTRACT, NEAR_NET, MEMBER_ACCOUNTS, SIGN_DEPOSIT and re-points
13-
# endpoint vars from per-cluster exports (NOMAD_ADDR_DEV_TESTNET, ...)
12+
# Sets CONTRACT, NEAR_NET, MEMBER_ACCOUNTS, SIGN_DEPOSIT, PROPOSE_DEPOSIT and
13+
# re-points endpoint vars from per-cluster exports (NOMAD_ADDR_DEV_TESTNET, ...)
1414
# so the network choice drives every step; addresses stay out of this repo.
1515
resolve_dev_cluster() {
1616
local suffix var
@@ -25,6 +25,10 @@ resolve_dev_cluster() {
2525
suffix="MAINNET" ;;
2626
*) die "Unknown dev cluster '$1' (expected testnet|mainnet)." ;;
2727
esac
28+
# Over propose_update_required_deposit_yoctonear; excess is refunded.
29+
# Read by upgrade-dev-contract.sh.
30+
PROPOSE_DEPOSIT="16 NEAR"
31+
2832
var="NOMAD_ADDR_DEV_${suffix}"; [[ -z "${!var:-}" ]] || export NOMAD_ADDR="${!var}"
2933
var="MPC_NODE_ADDRS_DEV_${suffix}"; [[ -z "${!var:-}" ]] || export MPC_NODE_ADDRS="${!var}"
3034
# +set: an intentionally empty value still disables the prompt.
@@ -82,6 +86,12 @@ nomad_auth_state() {
8286
else echo "(none)"; fi
8387
}
8488

89+
# Read-only contract query against the resolved cluster.
90+
near_view() {
91+
run_cmd near contract call-function as-read-only "$CONTRACT" "$1" \
92+
json-args '{}' network-config "$NEAR_NET" now
93+
}
94+
8595
# Check every MPC_NODE_ADDRS node reports release="<version>". Retries per
8696
# node — a node can still be warming up right after its allocation starts.
8797
verify_nodes() {

scripts/ops/dev-cluster/dev-menu.sh

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#!/usr/bin/env bash
22
#
33
# dev-menu.sh — entry point for dev-cluster work. Picks the network and
4-
# version, then upgrades the cluster nodes and verifies them.
4+
# version, then runs the upgrade in runbook order: nodes, verify, contract.
55
#
66
# Usage: ./scripts/ops/dev-cluster/dev-menu.sh [testnet|mainnet] [VERSION]
77
# Prompts for the Nomad IP, credentials, and node metrics addresses; exporting
@@ -62,7 +62,7 @@ confirm "Proceed?" || { echo "Aborted."; exit 0; }
6262

6363
step "### Step 1 — nodes"
6464
run_cmd "${SCRIPT_DIR}/migrate-dev-nodes.sh" "$VERSION" \
65-
|| die "Node upgrade did not complete."
65+
|| die "Node upgrade did not complete — stopping before the contract step."
6666

6767
step "### Verify"
6868
if [[ -n "${MPC_NODE_ADDRS:-}" ]]; then
@@ -72,5 +72,20 @@ else
7272
fi
7373
run_step test_sign "$NETWORK" || true
7474

75+
step "### Step 2 — contract"
76+
echo "Only for releases that change crates/contract (diff it between the two tags)."
77+
CONTRACT_RESULT="skipped — nodes only"
78+
if confirm "Upgrade the contract too?"; then
79+
if run_cmd "${SCRIPT_DIR}/upgrade-dev-contract.sh" "$VERSION" "$NETWORK"; then
80+
CONTRACT_RESULT="upgraded"
81+
else
82+
CONTRACT_RESULT="FAILED — see the output above"
83+
fi
84+
fi
85+
7586
echo
76-
ok "Done. Testnet first — upgrade the mainnet dev cluster only once this one is healthy."
87+
if [[ "$CONTRACT_RESULT" == FAILED* ]]; then
88+
warn "Nodes done; contract step ${CONTRACT_RESULT}. Do not upgrade the mainnet dev cluster yet."
89+
else
90+
ok "Done (contract: ${CONTRACT_RESULT}). Testnet first — upgrade the mainnet dev cluster only once this one is healthy."
91+
fi
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
#!/usr/bin/env bash
2+
#
3+
# upgrade-dev-contract.sh — Step 2 of a dev-cluster upgrade: get the contract
4+
# WASM (published release or a local build), borsh-serialize it, propose the
5+
# update and vote it in with the cluster's member accounts. Run only after the
6+
# nodes are on the new version.
7+
#
8+
# Usage: ./scripts/ops/dev-cluster/upgrade-dev-contract.sh <VERSION> <testnet|mainnet>
9+
# Env: MPC_WASM_SOURCE=release|build skips the source prompt;
10+
# MPC_SIGN_WITH overrides the signing method (default sign-with-keychain);
11+
# MPC_OPS_CACHE (default ~/.cache/mpc-ops) holds the artifacts.
12+
#
13+
14+
set -euo pipefail
15+
16+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
17+
# shellcheck source=../common.sh
18+
source "${SCRIPT_DIR}/../common.sh"
19+
# shellcheck source=dev-common.sh
20+
source "${SCRIPT_DIR}/dev-common.sh"
21+
22+
write_u32_le() {
23+
local n=$1 i
24+
for i in 0 8 16 24; do
25+
# shellcheck disable=SC2059
26+
printf "\\x$(printf '%02x' $(( (n >> i) & 0xFF )))"
27+
done
28+
}
29+
30+
# Echoes the wasm path; progress goes to stderr so it stays capturable.
31+
fetch_wasm() {
32+
local version=$1 dir=$2 source=${MPC_WASM_SOURCE:-}
33+
local wasm="${dir}/mpc-contract-v${version}.wasm"
34+
35+
if [[ -z "$source" ]]; then
36+
local choice
37+
read -rp "WASM source — (r)eleased ${version} or local (b)uild? [r] " choice >&2
38+
case "${choice:-r}" in
39+
r|R) source=release ;;
40+
b|B) source=build ;;
41+
*) die "Unknown source '${choice}'." ;;
42+
esac
43+
fi
44+
45+
if [[ "$source" == release ]]; then
46+
if [[ -f "$wasm" ]]; then
47+
step "==> Reusing ${wasm}" >&2
48+
else
49+
step "==> Downloading contract WASM from release ${version}..." >&2
50+
require_cmds gh tar
51+
run_cmd gh release download "$version" --repo near/mpc \
52+
--pattern "mpc-contract-v${version}.tar.gz" --dir "$dir" --clobber >&2
53+
run_cmd tar xzf "${dir}/mpc-contract-v${version}.tar.gz" -C "$dir" >&2
54+
[[ -f "$wasm" ]] || die "Expected ${wasm} after extracting the tarball."
55+
fi
56+
else
57+
require_cmds cargo git
58+
local root built
59+
root=$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel)
60+
step "==> Building the contract from ${root} (local build — not a released artifact)..." >&2
61+
( cd "$root" && run_cmd cargo near build non-reproducible-wasm --features abi \
62+
--profile=release-contract --manifest-path crates/contract/Cargo.toml --locked >&2 )
63+
# Named, not globbed: target/near also holds tee_verifier and
64+
# test_parallel_contract.
65+
built="${root}/target/near/mpc_contract/mpc_contract.wasm"
66+
[[ -f "$built" ]] || die "Expected ${built} after the cargo-near build."
67+
cp "$built" "$wasm"
68+
fi
69+
echo "$wasm"
70+
}
71+
72+
[[ $# -eq 2 ]] || die "Usage: $0 <VERSION> <testnet|mainnet>"
73+
VERSION=$1
74+
check_version "$VERSION"
75+
resolve_dev_cluster "$2"
76+
require_cmds near
77+
78+
CACHE="${MPC_OPS_CACHE:-$HOME/.cache/mpc-ops}/${VERSION}"
79+
mkdir -p "$CACHE"
80+
81+
WASM=$(fetch_wasm "$VERSION" "$CACHE")
82+
echo " wasm sha256: $(sha256_of "$WASM")"
83+
84+
SERIALIZED="${CACHE}/serialized.bin"
85+
WASM_SIZE=$(wc -c < "$WASM")
86+
# borsh ProposeUpdateArgs { code: Some(wasm), config: None }
87+
{
88+
printf '\x01'
89+
write_u32_le "$WASM_SIZE"
90+
cat "$WASM"
91+
printf '\x00'
92+
} > "$SERIALIZED"
93+
[[ "$(wc -c < "$SERIALIZED")" -eq $((WASM_SIZE + 6)) ]] \
94+
|| die "serialized.bin has an unexpected length."
95+
step "==> ${SERIALIZED} ready ($(wc -c < "$SERIALIZED") bytes)"
96+
97+
PROPOSER=${MEMBER_ACCOUNTS%% *}
98+
PROPOSE_CMD=(near contract call-function as-transaction "$CONTRACT" propose_update
99+
file-args "$SERIALIZED" prepaid-gas '100.0 Tgas' attached-deposit "$PROPOSE_DEPOSIT"
100+
sign-as "$PROPOSER" network-config "$NEAR_NET" "$SIGN_WITH" send)
101+
102+
step "About to propose the ${VERSION} contract on ${CONTRACT} (${NEAR_NET})"
103+
echo " proposer: ${PROPOSER}, deposit ${PROPOSE_DEPOSIT}"
104+
show_cmd "${PROPOSE_CMD[@]}"
105+
confirm "Send propose_update?" || { echo "Aborted before proposing."; exit 0; }
106+
107+
"${PROPOSE_CMD[@]}" \
108+
|| die "propose_update failed (an account low on NEAR is the usual cause — top it up)."
109+
110+
step "==> Pending proposals:"
111+
near_view proposed_updates || true
112+
113+
# near-cli's result format is too unstable to parse an id out of.
114+
read -rp "UpdateId to vote on: " UPDATE_ID
115+
[[ "$UPDATE_ID" =~ ^[0-9]+$ ]] || die "'${UPDATE_ID}' is not a numeric UpdateId."
116+
117+
# The deciding vote deploys + migrates inline, hence 300 Tgas.
118+
for account in $MEMBER_ACCOUNTS; do
119+
vote_cmd=(near contract call-function as-transaction "$CONTRACT" vote_update
120+
json-args "{\"id\": ${UPDATE_ID}}" prepaid-gas '300.0 Tgas' attached-deposit '0 NEAR'
121+
sign-as "$account" network-config "$NEAR_NET" "$SIGN_WITH" send)
122+
show_cmd "${vote_cmd[@]}"
123+
confirm "Vote for update ${UPDATE_ID} as ${account}?" || { echo " skipped."; continue; }
124+
"${vote_cmd[@]}" || echo " vote failed for ${account}."
125+
done
126+
127+
step "==> Contract version (expect ${VERSION} once threshold was reached):"
128+
near_view version || true

0 commit comments

Comments
 (0)