diff --git a/README.md b/README.md index cdf7c4b..de24d16 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ This repository holds shell scripts that Intersect uses to engage in Cardano on- - [metadata-create.sh](./scripts/metadata-create.sh) - Creates a governance-action JSON-LD file (CIP-108 body + CIP-169 extension + CIP-116 ProposalProcedure on-chain format, including [Intersect CIP108 schemas](https://github.com/IntersectMBO/governance-actions/tree/main/schemas)) - Requires a `.md` input file with H2 sections (`## Title`, `## Abstract`, `## Motivation`, `## Rationale`, `## References`, `## Authors`) - - Requires `--governance-action-type ` and `--deposit-return-addr ` + - Requires `--type ` and `--deposit-return-addr ` - Optional `--language ` sets the JSON-LD `@context.@language` (default: `en`) - [metadata-validate.sh](./scripts/metadata-validate.sh) diff --git a/docs/info-action-procedure.md b/docs/info-action-procedure.md index 74ea286..fd4bc46 100644 --- a/docs/info-action-procedure.md +++ b/docs/info-action-procedure.md @@ -40,7 +40,7 @@ With the `metadata-create` script taking the data from the doc and creating a `. The input file must be a `.md` file structured with H2 headers (`## Title`, `## Abstract`, `## Motivation`, `## Rationale`, `## References`, `## Authors`). Pass `--language ` to override the default `en` in the generated `@context.@language`. ```shell -./scripts/metadata-create.sh my-metadata.md --governance-action-type info --deposit-return-addr $DEPOSIT_RETURN_ADDR +./scripts/metadata-create.sh my-metadata.md --type info --deposit-return-addr $DEPOSIT_RETURN_ADDR ``` ### 5. Sanity check the metadata diff --git a/docs/treasury-withdrawal-procedure.md b/docs/treasury-withdrawal-procedure.md index 1191006..76c1f35 100644 --- a/docs/treasury-withdrawal-procedure.md +++ b/docs/treasury-withdrawal-procedure.md @@ -43,7 +43,7 @@ With the `metadata-create` script taking the data from the doc and creating a `. The input file must be a `.md` file structured with H2 headers (`## Title`, `## Abstract`, `## Motivation`, `## Rationale`, `## References`, `## Authors`). Pass `--language ` to override the default `en` in the generated `@context.@language`. ```shell -./scripts/metadata-create.sh my-metadata.md --governance-action-type treasury --deposit-return-addr $DEPOSIT_RETURN_ADDR +./scripts/metadata-create.sh my-metadata.md --type treasury --deposit-return-addr $DEPOSIT_RETURN_ADDR ``` ### 5. Sanity check the metadata diff --git a/scripts/action-create-hf.sh b/scripts/action-create-hf.sh new file mode 100755 index 0000000..1fd35f9 --- /dev/null +++ b/scripts/action-create-hf.sh @@ -0,0 +1,455 @@ +#!/bin/bash + +################################################## + +# Default configuration values + +################################################## + +# Exit immediately if a command exits with a non-zero status, +# treat unset variables as an error, and fail if any command in a pipeline fails +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=lib/messages.sh +source "$SCRIPT_DIR/lib/messages.sh" + +# Check if cardano-cli is installed +if ! command -v cardano-cli >/dev/null 2>&1; then + print_fail "cardano-cli is not installed or not in your PATH." + exit 1 +fi + +# Check if ipfs cli is installed +if ! command -v ipfs >/dev/null 2>&1; then + print_fail "ipfs cli is not installed or not in your PATH." + exit 1 +fi + +# Usage message + +usage() { + printf '%s%sCreate a Hard Fork Initiation action from a given JSON-LD metadata file%s\n\n' "$UNDERLINE" "$BOLD" "$NC" + printf 'Syntax:%s %s %s%s [%s--deposit-return-addr%s ] [%s--prev-governance-action-id%s #]\n' "$BOLD" "$0" "$GREEN" "$NC" "$GREEN" "$NC" "$GREEN" "$NC" + print_usage_option "" "Path to the JSON-LD metadata file" + print_usage_option "[--deposit-return-addr ]" "Optional check that metadata deposit return address matches provided one (Bech32)" + print_usage_option "[--prev-governance-action-id #]" "Optional check that metadata previous-HF action id matches provided one" + print_usage_option "-h, --help" "Show this help message and exit" + exit 1 +} + +# Initialize variables with defaults +input_file="" + +# Optional variables +deposit_return_address_input="" +prev_action_id_input="" +prev_tx_input="" +prev_idx_input="" + +# Parse command line arguments +while [[ $# -gt 0 ]]; do + case $1 in + --deposit-return-addr) + if [ -n "${2:-}" ]; then + deposit_return_address_input="$2" + shift 2 + else + print_fail "--deposit-return-addr requires a value" + usage + fi + ;; + --prev-governance-action-id) + if [ -n "${2:-}" ]; then + prev_action_id_input="$2" + shift 2 + else + print_fail "--prev-governance-action-id requires a value" + usage + fi + ;; + -h|--help) + usage + ;; + *) + if [ -z "$input_file" ]; then + input_file="$1" + else + print_fail "Input file already specified. Unexpected argument: $1" + usage + fi + shift + ;; + esac +done + +# If no input file provided, show usage +if [ -z "$input_file" ]; then + print_fail "No input file specified" + usage +fi + +# Enforce .jsonld extension +if [[ "$input_file" != *.jsonld ]]; then + print_fail "Input file $(fmt_path "$input_file") must be a JSON-LD metadata file with a .jsonld extension." + print_hint "This script expects a CIP-108 metadata document whose body.onChain.gov_action.tag is 'hard_fork_initiation_action'." + exit 1 +fi + +# Check if input file exists +if [ ! -f "$input_file" ]; then + print_fail "Input file not found: $(fmt_path "$input_file")" + exit 1 +fi + +# Parse --prev-governance-action-id if supplied: <64hex># +if [ -n "$prev_action_id_input" ]; then + if [[ ! "$prev_action_id_input" =~ ^[0-9a-fA-F]{64}#[0-9]+$ ]]; then + print_fail "--prev-governance-action-id must be of the form <64-hex tx-id>#. Got: $(fmt_path "$prev_action_id_input")" + exit 1 + fi + prev_tx_input="${prev_action_id_input%%#*}" + prev_idx_input="${prev_action_id_input##*#}" +fi + +print_banner "Creating a Hard Fork Initiation governance action from a given metadata file" +print_info "This script assumes compliance with Intersect's hard-fork-initiation action schema" +print_info "This script assumes that CARDANO_NODE_SOCKET_PATH, CARDANO_NODE_NETWORK_ID and IPFS_GATEWAY_URI are set" + +# Exit if socket path is not set +if [ -z "${CARDANO_NODE_SOCKET_PATH:-}" ]; then + print_fail "CARDANO_NODE_SOCKET_PATH environment variable is not set." + exit 1 +fi + +# Exit if network id is not set +if [ -z "${CARDANO_NODE_NETWORK_ID:-}" ]; then + print_fail "CARDANO_NODE_NETWORK_ID environment variable is not set." + exit 1 +fi + +# Get if mainnet or testnet +if [ "$CARDANO_NODE_NETWORK_ID" = "764824073" ] || [ "$CARDANO_NODE_NETWORK_ID" = "mainnet" ]; then + print_info "Local node is using mainnet" + protocol_magic="mainnet" +else + print_info "Local node is using a testnet" + protocol_magic="testnet" +fi + +# Do some basic validation checks on metadata +print_section "Doing some basic validation and checks on metadata" + +# Function to check if jq query returned null or empty +check_field() { + local field_name="$1" + local field_value="$2" + + if [ -z "$field_value" ] || [ "$field_value" = "null" ]; then + print_fail "Required field '$field_name' not found in metadata" + exit 1 + fi +} + +# Extract and validate required fields +title=$(jq -r '.body.title' "$input_file") +check_field "title" "$title" + +ga_type=$(jq -r '.body.onChain.gov_action.tag' "$input_file") +check_field "tag" "$ga_type" + +deposit_return=$(jq -r '.body.onChain.reward_account' "$input_file") +check_field "reward_account" "$deposit_return" + +deposit=$(jq -r '.body.onChain.deposit' "$input_file") +check_field "deposit" "$deposit" + +# HF-specific fields. All four are required by our contract — for a "very +# first hard fork in the chain" you'd omit gov_action_id, but Conway is well +# past that point and the metadata-create.sh prompt requires it. +target_major=$(jq -r '.body.onChain.gov_action.protocol_version.major' "$input_file") +check_field "protocol_version.major" "$target_major" +target_minor=$(jq -r '.body.onChain.gov_action.protocol_version.minor' "$input_file") +check_field "protocol_version.minor" "$target_minor" +prev_tx=$(jq -r '.body.onChain.gov_action.gov_action_id.transaction_id' "$input_file") +check_field "gov_action_id.transaction_id" "$prev_tx" +prev_idx=$(jq -r '.body.onChain.gov_action.gov_action_id.gov_action_index' "$input_file") +check_field "gov_action_id.gov_action_index" "$prev_idx" + +# Sanity-check the deposit magnitude. The current Cardano governance action +# deposit is 100,000 ada = 100_000_000_000 lovelace. +EXPECTED_DEPOSIT_LOVELACE="100000000000" +if [ "$deposit" != "$EXPECTED_DEPOSIT_LOVELACE" ]; then + print_warn "body.onChain.deposit = ${BRIGHTWHITE}${deposit}${NC} lovelace, expected ${BRIGHTWHITE}${EXPECTED_DEPOSIT_LOVELACE}${NC} (100,000 ADA, the current governance action deposit). Verify this is intentional before submitting." +fi + +# Authoritative deposit check against the live protocol parameter +print_info "Checking that deposit matches the current protocol parameter" +onchain_deposit=$(cardano-cli conway query protocol-parameters | jq -r '.govActionDeposit') +if [ "$deposit" = "$onchain_deposit" ]; then + print_pass "Metadata has expected deposit amount" +else + print_fail "Metadata does not have expected deposit amount" + print_hint "Expected: $onchain_deposit found: $deposit" + exit 1 +fi + +authors=$(jq -r '.authors' "$input_file") +check_field "authors" "$authors" +witness=$(jq -r '.authors[0].witness' "$input_file") +check_field "witness" "$witness" + +if [ "$ga_type" = "hard_fork_initiation_action" ]; then + print_pass "Metadata has correct governance action tag" +else + print_fail "Metadata does not have the correct governance action tag" + print_hint "Expected: hard_fork_initiation_action found: $ga_type" + exit 1 +fi + +# Shape-check HF-specific fields per CIP-116. +if [[ ! "$target_major" =~ ^[0-9]+$ ]]; then + print_fail "body.onChain.gov_action.protocol_version.major must be a non-negative integer. Got: $target_major" + exit 1 +fi +if [[ ! "$target_minor" =~ ^[0-9]+$ ]]; then + print_fail "body.onChain.gov_action.protocol_version.minor must be a non-negative integer. Got: $target_minor" + exit 1 +fi +if [[ ! "$prev_tx" =~ ^[0-9a-fA-F]{64}$ ]]; then + print_fail "body.onChain.gov_action.gov_action_id.transaction_id must be 64 hex characters. Got: $prev_tx" + exit 1 +fi +if [[ ! "$prev_idx" =~ ^[0-9]+$ ]]; then + print_fail "body.onChain.gov_action.gov_action_id.gov_action_index must be a non-negative integer. Got: $prev_idx" + exit 1 +fi +print_pass "HF-specific fields have valid shapes" + +# if return address passed in check against metadata +if [ -n "$deposit_return_address_input" ]; then + print_info "Comparing provided deposit return address to metadata" + if [ "$deposit_return_address_input" = "$deposit_return" ]; then + print_pass "Metadata has expected deposit return address" + else + print_fail "Metadata does not have expected deposit return address" + exit 1 + fi +fi + +# if previous-action id passed in check against metadata +if [ -n "$prev_action_id_input" ]; then + print_info "Comparing provided previous-HF action id to metadata" + if [ "$prev_tx_input" = "$prev_tx" ] && [ "$prev_idx_input" = "$prev_idx" ]; then + print_pass "Metadata has expected previous-HF action id" + else + print_fail "Metadata does not have expected previous-HF action id" + print_hint "Provided: ${prev_tx_input}#${prev_idx_input}" + print_hint "Metadata: ${prev_tx}#${prev_idx}" + exit 1 + fi +fi + +# Verify bech32 integrity (checksum + address type) of stake address +validate_stake_address() { + local label="$1" + local address="$2" + local info + if ! info=$(cardano-cli address info --address "$address" 2>&1); then + print_fail "$label is not a valid bech32 address: $(fmt_path "$address")" + print_hint "cardano-cli rejected it: $info" + exit 1 + fi + local addr_type + addr_type=$(echo "$info" | jq -r '.type // ""') + if [ "$addr_type" != "stake" ]; then + print_fail "$label is bech32-valid but is not a stake address (type=${addr_type:-unknown}): $(fmt_path "$address")" + exit 1 + fi +} + +validate_stake_address "metadata body.onChain.reward_account" "$deposit_return" + +# use bech32 prefix to determine if addresses are mainnet or testnet +is_stake_address_mainnet() { + local address="$1" + # Check if address starts with stake1 (mainnet) + if [[ "$address" =~ ^stake1 ]]; then + return 0 + # Check if address starts with stake_test1 (testnet) + elif [[ "$address" =~ ^stake_test1 ]]; then + return 1 + else + print_fail "Invalid stake address format: $address" + exit 1 + fi +} + +# if mainnet node then expect addresses to be mainnet +if [ "$protocol_magic" = "mainnet" ]; then + if is_stake_address_mainnet "$deposit_return"; then + print_pass "Deposit return address is a valid mainnet stake address" + else + print_fail "Deposit return address is not a valid mainnet stake address" + exit 1 + fi +else + if ! is_stake_address_mainnet "$deposit_return"; then + print_pass "Deposit return address is a valid testnet stake address" + else + print_fail "Deposit return address is not a valid testnet stake address" + exit 1 + fi +fi + +# use header byte to determine if stake address is script-based or key-based +is_stake_address_script() { + local address="$1" + + address_hex=$(cardano-cli address info --address "$address"| jq -r ".base16") + first_char="${address_hex:0:1}" + + if [ "$first_char" = "f" ]; then + return 0 # true + elif [ "$first_char" = "e" ]; then + return 1 # false + else + print_fail "Invalid stake address header byte" + exit 1 + fi +} + +is_stake_address_registered(){ + local address="$1" + stake_address_deposit=$(cardano-cli conway query stake-address-info --address "$address" | jq -r '.[0].stakeRegistrationDeposit') + if [ "$stake_address_deposit" != "null" ]; then + return 0 + else + return 1 + fi +} + +# check if stake addresses are registered +if is_stake_address_registered "$deposit_return"; then + print_pass "Deposit return stake address is registered" +else + print_fail "Deposit return stake address is not registered" + exit 1 +fi + +print_pass "Automatic validations passed" + +# HF-specific chain cross-checks: previous-action id (hard-fail) and target +# version sanity (warn-only). +print_section "Cross-checking chain state for hard-fork action" + +# Cache gov-state once — used for both checks below. +gov_state_json=$(cardano-cli conway query gov-state) + +chain_prev_hf=$(echo "$gov_state_json" | jq -c '.nextRatifyState.nextEnactState.prevGovActionIds.HardFork') +if [ "$chain_prev_hf" = "null" ] || [ -z "$chain_prev_hf" ]; then + print_fail "Chain reports no previous hard fork in prevGovActionIds.HardFork, but metadata claims one (${prev_tx}#${prev_idx})." + print_hint "If this is genuinely the first hard fork in the chain, the metadata must omit body.onChain.gov_action.gov_action_id — but our contract requires it. Resolve the contradiction before continuing." + exit 1 +fi +chain_prev_tx=$(echo "$chain_prev_hf" | jq -r '.txId') +chain_prev_idx=$(echo "$chain_prev_hf" | jq -r '.govActionIx') +if [ "$chain_prev_tx" != "$prev_tx" ] || [ "$chain_prev_idx" != "$prev_idx" ]; then + print_fail "Metadata's previous-HF gov_action_id does not match chain state." + print_hint "Metadata: ${prev_tx}#${prev_idx}" + print_hint "Chain: ${chain_prev_tx}#${chain_prev_idx}" + exit 1 +fi +print_pass "Previous-HF action id matches chain state" + +# Target version sanity. HARDFORK-01 says the new major must be either equal +# to or one greater than the previous; if one greater, minor must be zero. +chain_major=$(echo "$gov_state_json" | jq -r '.currentPParams.protocolVersion.major') +chain_minor=$(echo "$gov_state_json" | jq -r '.currentPParams.protocolVersion.minor') +if [ "$target_major" = "$((chain_major + 1))" ] && [ "$target_minor" = "0" ]; then + print_pass "Target version ${target_major}.${target_minor} is currentMajor+1 with minor=0 (HARDFORK-01 happy path)" +elif [ "$target_major" = "$chain_major" ] && [ "$target_minor" -gt "$chain_minor" ]; then + print_pass "Target version ${target_major}.${target_minor} is a minor bump on currentMajor" +else + print_warn "Target version ${target_major}.${target_minor} is unusual relative to chain ${chain_major}.${chain_minor}. Verify HARDFORK-01/-02/-03 before submitting." +fi + +print_section "Computing details" + +# Compute the hash and IPFS URI +file_hash=$(b2sum -l 256 "$input_file" | awk '{print $1}') +print_info "Metadata file hash: ${YELLOW}${file_hash}${NC}" + +ipfs_cid=$(ipfs add -Q --cid-version 1 "$input_file") +print_info "IPFS URI: ${YELLOW}ipfs://${ipfs_cid}${NC}" + +# Make user manually confirm the choices +print_section "Creating hard-fork-initiation action" +print_info "Title: ${YELLOW}${title}${NC}" + +print_info "Deposit return address: ${YELLOW}${deposit_return}${NC}" +if is_stake_address_script "$deposit_return"; then + print_info "(this is a script-based address)" +else + print_info "(this is a key-based address)" +fi +if ! confirm "Do you want to proceed with this deposit return address?"; then + print_fail "Cancelled by user" + exit 1 +fi + +print_info "Target protocol version: ${YELLOW}${chain_major}.${chain_minor}${NC} -> ${YELLOW}${target_major}.${target_minor}${NC}" +if ! confirm "Do you want to proceed with this protocol version?"; then + print_fail "Cancelled by user" + exit 1 +fi + +print_info "Previous HF action: ${YELLOW}${prev_tx}#${prev_idx}${NC}" +if ! confirm "Do you want to proceed with this previous-action id?"; then + print_fail "Cancelled by user" + exit 1 +fi + +# Create the action +print_section "Creating action file" + +action_file="$input_file.action" +action_json="$input_file.action.json" + +gov_action_deposit=$(echo "$gov_state_json" | jq -r '.currentPParams.govActionDeposit') +require_nonnull "$gov_action_deposit" "governance action deposit (gov-state .currentPParams.govActionDeposit)" + +cardano-cli conway governance action create-hardfork \ + --$protocol_magic \ + --governance-action-deposit "$gov_action_deposit" \ + --deposit-return-stake-address "$deposit_return" \ + --anchor-url "ipfs://$ipfs_cid" \ + --anchor-data-hash "$file_hash" \ + --check-anchor-data \ + --protocol-major-version "$target_major" \ + --protocol-minor-version "$target_minor" \ + --prev-governance-action-tx-id "$prev_tx" \ + --prev-governance-action-index "$prev_idx" \ + --out-file "$action_file" + +print_pass "Action file created at $(fmt_path "$action_file")" + +print_section "Creating JSON representation of action file" + +cardano-cli conway governance action view --action-file "$action_file" > "$action_json" +print_pass "JSON file created at $(fmt_path "$action_json")" + +print_section "Summary" +print_pass "Hard Fork Initiation governance action created" +print_kv "Input" "$(fmt_path "$input_file")" +print_kv "Action" "$(fmt_path "$action_file")" +print_kv "JSON" "$(fmt_path "$action_json")" +print_kv "Hash" "$file_hash" +print_kv "IPFS" "ipfs://$ipfs_cid" +print_kv "Version" "${target_major}.${target_minor}" +print_kv "Prev" "${prev_tx}#${prev_idx}" +print_next "Include the action file in a transaction:" \ + " cardano-cli latest transaction build \\" \ + " --tx-in --change-address \\" \ + " --proposal-file '$action_file' \\" \ + " --out-file tx.raw" diff --git a/scripts/action-create-tw.sh b/scripts/action-create-tw.sh index 36f96a3..2214e3f 100755 --- a/scripts/action-create-tw.sh +++ b/scripts/action-create-tw.sh @@ -124,14 +124,15 @@ print_info "This script assumes compliance Intersect's treasury withdrawal actio print_info "This script assumes that CARDANO_NODE_SOCKET_PATH, CARDANO_NODE_NETWORK_ID and IPFS_GATEWAY_URI are set" # Exit if socket path is not set -if [ -z "$CARDANO_NODE_SOCKET_PATH" ]; then +if [ -z "${CARDANO_NODE_SOCKET_PATH:-}" ]; then print_fail "CARDANO_NODE_SOCKET_PATH environment variable is not set." exit 1 fi # Exit if network id is not set -if [ -z "$CARDANO_NODE_NETWORK_ID" ]; then +if [ -z "${CARDANO_NODE_NETWORK_ID:-}" ]; then print_fail "CARDANO_NODE_NETWORK_ID environment variable is not set." + exit 1 fi # Get if mainnet or testnet diff --git a/scripts/metadata-create.sh b/scripts/metadata-create.sh index e5dcb39..4f515fd 100755 --- a/scripts/metadata-create.sh +++ b/scripts/metadata-create.sh @@ -12,7 +12,7 @@ resolve_context_url() { ppu) echo "${INTERSECT_SCHEMAS_BASE}/parameter-changes/common.jsonld" ;; hf) echo "${INTERSECT_SCHEMAS_BASE}/hard-fork-initiation/common.jsonld" ;; committee) echo "${INTERSECT_SCHEMAS_BASE}/update-committee/common.jsonld" ;; - *) print_fail "No @context mapping for --governance-action-type '$1'"; exit 1 ;; + *) print_fail "No @context mapping for --type '$1'"; exit 1 ;; esac } @@ -41,9 +41,9 @@ fi # Usage message usage() { printf '%s%sCreate JSON-LD metadata from a Markdown file%s\n\n' "$UNDERLINE" "$BOLD" "$NC" - printf 'Syntax:%s %s %s<.md-file> --governance-action-type%s %s--deposit-return-addr%s [%s--inline-context%s]\n' "$BOLD" "$0" "$GREEN" "$NC" "$GREEN" "$NC" "$GREEN" "$NC" + printf 'Syntax:%s %s %s<.md-file> --type%s %s--deposit-return-addr%s [%s--inline-context%s]\n' "$BOLD" "$0" "$GREEN" "$NC" "$GREEN" "$NC" "$GREEN" "$NC" print_usage_option "<.md-file>" "Path to the .md file as input" - print_usage_option "--governance-action-type " "Type of governance action" + print_usage_option "--type " "Type of governance action" print_usage_option "--deposit-return-addr " "Stake address for deposit return (bech32)" print_usage_option "[--inline-context]" "Embed the full @context object in the document instead of referencing the URL" print_usage_option "-h, --help" "Show this help message and exit" @@ -80,12 +80,12 @@ trap cleanup EXIT # Parse command line arguments while [[ $# -gt 0 ]]; do case $1 in - --governance-action-type) + --type) if [ -n "${2:-}" ]; then governance_action_type="$2" shift 2 else - print_fail "--governance-action-type requires a value" + print_fail "--type requires a value" usage fi ;; @@ -138,7 +138,7 @@ fi # If no governance action type provided, show usage if [ -z "$governance_action_type" ]; then - print_fail "--governance-action-type is required" + print_fail "--type is required" usage fi @@ -281,6 +281,33 @@ resolve_ppu_prev_gov_action_id() { printf '%s' "$reshaped" } +# Resolve the previously enacted hard-fork-initiation governance action id +resolve_hf_prev_gov_action_id() { + local gov_state="$1" + local prev + + prev=$(printf '%s' "$gov_state" \ + | jq '.nextRatifyState.nextEnactState.prevGovActionIds.HardFork') + + if [ -z "$prev" ]; then + print_fail "Could not read prevGovActionIds.HardFork from gov-state (cardano-cli output may have changed)" >&2 + exit 1 + fi + + if [ "$prev" = "null" ]; then + print_warn "No previously enacted hard-fork-initiation action on chain; gov_action_id will be null" >&2 + printf 'null' + return 0 + fi + + # Reshape ledger {txId, govActionIx} into CIP-116 {transaction_id, gov_action_index}. + local reshaped + reshaped=$(printf '%s' "$prev" \ + | jq '{ transaction_id: .txId, gov_action_index: (.govActionIx | tostring) }') + print_pass "Resolved previous hard-fork-initiation gov_action_id: $(printf '%s' "$reshaped" | jq -c .)" >&2 + printf '%s' "$reshaped" +} + # Resolve the guardrails script hash used as the on-chain resolve_policy_hash() { local script_hash="" @@ -510,30 +537,12 @@ hf_collect_inputs() { exit 1 fi - # Previous hard-fork-initiation action ID - printf 'Please enter previous hard fork action transaction id (64 hex chars): ' >&2 - IFS= read -r HF_PREV_TX &2 - IFS= read -r HF_PREV_IDX &2 print_info " Target protocol version: ${YELLOW}${HF_MAJOR}.${HF_MINOR}${NC}" >&2 - print_info " Previous HF action: ${YELLOW}${HF_PREV_TX}#${HF_PREV_IDX}${NC}" >&2 + print_info " Previous HF action: ${YELLOW}$(printf '%s' "$HF_GOV_ACTION_ID" | jq -c .)${NC}" >&2 if ! confirm "Is this correct?"; then print_fail "Cancelled by user" @@ -549,8 +558,7 @@ generate_hf_onchain() { jq -n \ --arg deposit "$GOV_ACTION_DEPOSIT" \ --arg reward_account "$deposit_return_address" \ - --arg prev_tx "$HF_PREV_TX" \ - --arg prev_idx "$HF_PREV_IDX" \ + --argjson gov_action_id "$HF_GOV_ACTION_ID" \ --argjson major "$HF_MAJOR" \ --argjson minor "$HF_MINOR" \ '{ @@ -558,7 +566,7 @@ generate_hf_onchain() { reward_account: $reward_account, gov_action: { tag: "hard_fork_initiation_action", - gov_action_id: { transaction_id: $prev_tx, gov_action_index: $prev_idx }, + gov_action_id: $gov_action_id, protocol_version: { major: $major, minor: $minor } } }' @@ -631,6 +639,15 @@ if [ "$governance_action_type" = "ppu" ]; then PPU_GOV_ACTION_ID=$(resolve_ppu_prev_gov_action_id "$GOV_STATE") || exit 1 fi +# Resolve the previously enacted hard-fork-initiation gov_action_id (hf only). +# Resolved here at the top level so a failure reliably aborts the script — doing +# it inside the generator (a command-substitution subshell) would not propagate +# the exit. +HF_GOV_ACTION_ID="null" +if [ "$governance_action_type" = "hf" ]; then + HF_GOV_ACTION_ID=$(resolve_hf_prev_gov_action_id "$GOV_STATE") || exit 1 +fi + # Generate onChain property based on governance action type print_info "Generating onChain property for $governance_action_type" ONCHAIN_PROPERTY=$(generate_onchain_property "$governance_action_type") diff --git a/scripts/metadata-validate.sh b/scripts/metadata-validate.sh index c4f6a11..2567e6b 100755 --- a/scripts/metadata-validate.sh +++ b/scripts/metadata-validate.sh @@ -439,12 +439,7 @@ fi # JSON-LD safe-mode expansion check. Surfaces "dropping property that did not # expand into an absolute IRI or keyword" warnings caused by missing term -# mappings inside the document's @context. This is the failure mode that the -# v1.1.0 hard-fork-initiation schema hit on protocol_version.{major,minor}: -# the context only declared an outer @type, with no inner @context to map the -# inner keys, so safe-mode JSON-LD processors silently drop them. We use Node -# + the 'jsonld' npm package because pyld's behaviour matches it byte-for-byte -# in safe mode (same warning event shape). +# mappings inside the document's @context. JSONLD_CHECK_FAILED=0 if [ "$check_jsonld" = "true" ]; then print_section "Applying JSON-LD safe-mode expansion check"