Skip to content

Commit a325bb9

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

4 files changed

Lines changed: 163 additions & 7 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: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

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

12-
# Sets CONTRACT, NEAR_NET, MEMBER_ACCOUNTS, SIGN_DEPOSIT, and
12+
# Sets CONTRACT, NEAR_NET, MEMBER_ACCOUNTS, SIGN_DEPOSIT, PROPOSE_DEPOSIT, and
1313
# re-points the endpoint vars from any exported per-cluster ones
1414
# (NOMAD_ADDR_DEV_TESTNET, ...), so the network choice drives every step.
1515
# Addresses themselves stay out of this repo.
@@ -26,6 +26,10 @@ resolve_dev_cluster() {
2626
suffix="MAINNET" ;;
2727
*) die "Unknown dev cluster '$1' (expected testnet|mainnet)." ;;
2828
esac
29+
# Over propose_update_required_deposit_yoctonear; excess is refunded.
30+
# Read by upgrade-dev-contract.sh.
31+
PROPOSE_DEPOSIT="16 NEAR"
32+
2933
var="NOMAD_ADDR_DEV_${suffix}"; [[ -z "${!var:-}" ]] || export NOMAD_ADDR="${!var}"
3034
var="MPC_NODE_ADDRS_DEV_${suffix}"; [[ -z "${!var:-}" ]] || export MPC_NODE_ADDRS="${!var}"
3135
# +set: an intentionally empty value still disables the prompt.
@@ -79,6 +83,12 @@ nomad_auth_state() {
7983
else echo "(none)"; fi
8084
}
8185

86+
# Read-only contract query against the resolved cluster.
87+
near_view() {
88+
run_cmd near contract call-function as-read-only "$CONTRACT" "$1" \
89+
json-args '{}' network-config "$NEAR_NET" now
90+
}
91+
8292
# Check every MPC_NODE_ADDRS node reports release="<version>".
8393
verify_nodes() {
8494
local version=$1

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
# The Nomad IP address, its credentials, and the node metrics addresses are prompted
@@ -60,7 +60,7 @@ confirm "Proceed?" || { echo "Aborted."; exit 0; }
6060

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

6565
step "### Verify"
6666
if [[ -n "${MPC_NODE_ADDRS:-}" ]]; then
@@ -70,5 +70,20 @@ else
7070
fi
7171
run_step test_sign "$NETWORK" || true
7272

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

0 commit comments

Comments
 (0)