Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
136 changes: 136 additions & 0 deletions scripts/test-upgrade.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
#!/bin/bash

# ========== Helper: Read named parameters ==========
for arg in "$@"; do
case $arg in
--chainId=*) CHAIN_ID="${arg#*=}" ;;
--nodeHome=*) NODE_HOME="${arg#*=}" ;;
--exportedState=*) EXPORTED_STATE="${arg#*=}" ;;
--version=*) VERSION="${arg#*=}" ;;
*) echo "⚠️ Unknown argument: $arg" ;;
esac
done

# ========== Fallback to env vars if not passed ==========
: "${CHAIN_ID:=${CHAIN_ID_ENV:-}}"
: "${NODE_HOME:=${NODE_HOME_ENV:-}}"
: "${EXPORTED_STATE:=${EXPORTED_STATE_ENV:-}}"
: "${VERSION:=${VERSION_ENV:-}}"

# ========== Validate dependencies ==========
echo "ℹ️ Checking required tools..."
for cmd in exrpd jq sponge; do
if ! command -v "$cmd" &> /dev/null; then
echo "❌ Error: Required tool '$cmd' not found. Please install it before running this script."
exit 1
fi
done
echo "✅ All tools found"

# ========== Validate required variables ==========
missing_vars=()
for var in CHAIN_ID NODE_HOME EXPORTED_STATE VERSION; do
if [ -z "${!var}" ]; then
missing_vars+=("$var")
fi
done

if [ ${#missing_vars[@]} -ne 0 ]; then
echo "❌ Error: The following required variables are missing:"
for var in "${missing_vars[@]}"; do echo " - $var"; done
echo
echo "👉 You can provide them as parameters or environment variables."
echo
echo "📌 Example usage:"
echo "./test-upgrade.sh \\"
echo " --chainId=\"xrplevm_1440000-1\" \\"
echo " --nodeHome=\"./.exrpd\" \\"
echo " --exportedState=\"./exported.json\" \\"
echo " --version=\"9.0.0\" \\"
echo
echo "Or set them as environment variables before running:"
echo "export CHAIN_ID=... && ./test-upgrade.sh"
exit 1
fi

echo "ℹ️ Initializing node home in $NODE_HOME..."
exrpd config set client chain-id "$CHAIN_ID" --home "$NODE_HOME"
exrpd config set client keyring-backend test --home "$NODE_HOME"
exrpd keys add key --key-type eth_secp256k1 --keyring-backend test --home "$NODE_HOME"
exrpd init localnode --chain-id "$CHAIN_ID" --home "$NODE_HOME" > /dev/null 2>&1
echo "✅ Node home initialized "

echo "ℹ️ Configuring genesis..."
cp $EXPORTED_STATE genesis.json
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Quote variable to prevent word splitting.

$EXPORTED_STATE should be quoted to handle paths containing spaces correctly.

Proposed fix
-cp $EXPORTED_STATE genesis.json
+cp "$EXPORTED_STATE" genesis.json
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
cp $EXPORTED_STATE genesis.json
cp "$EXPORTED_STATE" genesis.json
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/test-upgrade.sh` at line 64, The cp invocation in
scripts/test-upgrade.sh uses an unquoted shell variable which can break if
EXPORTED_STATE contains spaces; update the cp command that references
EXPORTED_STATE so the variable is quoted (i.e., use a quoted "$EXPORTED_STATE"
when calling cp) to prevent word-splitting and handle paths with spaces
correctly.

echo "ℹ️ Modifying bank ibc denoms..."
for ((i=1; i< $(jq '.app_state.bank.denom_metadata | length' genesis.json); i++)); do
jq --argjson i "$i" '.app_state.bank.denom_metadata[$i].denom_units[0].denom = .app_state.bank.denom_metadata[$i].base' genesis.json | sponge genesis.json
jq --argjson i "$i" '.app_state.bank.denom_metadata[$i].display = .app_state.bank.denom_metadata[$i].base' genesis.json | sponge genesis.json
done

echo "ℹ️ Modifying ratelimit initial_height..."
jq '.app_state.ratelimit.hour_epoch.epoch_start_height = .initial_height' genesis.json | sponge genesis.json

echo "ℹ️ Modifying gov voting periods..."
jq '.app_state.gov.params.voting_period = "30s"' genesis.json | sponge genesis.json
jq '.app_state.gov.params.expedited_voting_period = "20s"' genesis.json | sponge genesis.json

mv genesis.json "$NODE_HOME"/config/genesis.json
echo "✅ Genesis configured "

echo "ℹ️ Configuring local validator..."
echo "ℹ️ Adding local validator genesis account..."
exrpd add-genesis-account $(exrpd keys show key -a --keyring-backend test --home "$NODE_HOME") 100000000poa,10000xrp --home "$NODE_HOME"
echo "ℹ️ Creating local validator staking transaction..."
exrpd gentx key 100000000poa --fees 80000000000000000axrp --chain-id $CHAIN_ID --commission-rate 0 \
--commission-max-rate 0 --commission-max-change-rate 0 --timeout-height $(jq '.initial_height' $EXPORTED_STATE) \
--keyring-backend test --home "$NODE_HOME" \
--account-number $(jq '.app_state.auth.accounts | length' $EXPORTED_STATE) --sequence 0 --offline --gas 400000
Comment on lines +83 to +88
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Quote command substitutions and variables to prevent word splitting.

Multiple unquoted command substitutions and variables could cause issues with unexpected whitespace or special characters. Static analysis (SC2046) flagged these.

Proposed fix
-exrpd add-genesis-account $(exrpd keys show key -a --keyring-backend test --home "$NODE_HOME") 100000000poa,10000xrp --home "$NODE_HOME"
+exrpd add-genesis-account "$(exrpd keys show key -a --keyring-backend test --home "$NODE_HOME")" 100000000poa,10000xrp --home "$NODE_HOME"
 echo "ℹ️  Creating local validator staking transaction..."
 exrpd gentx key 100000000poa --fees 80000000000000000axrp --chain-id $CHAIN_ID --commission-rate 0 \
-   --commission-max-rate 0 --commission-max-change-rate 0 --timeout-height $(jq '.initial_height' $EXPORTED_STATE) \
+   --commission-max-rate 0 --commission-max-change-rate 0 --timeout-height "$(jq -r '.initial_height' "$EXPORTED_STATE")" \
    --keyring-backend test --home "$NODE_HOME" \
-   --account-number $(jq '.app_state.auth.accounts | length' $EXPORTED_STATE) --sequence 0 --offline --gas 400000
+   --account-number "$(jq -r '.app_state.auth.accounts | length' "$EXPORTED_STATE")" --sequence 0 --offline --gas 400000
🧰 Tools
🪛 Shellcheck (0.11.0)

[warning] 83-83: Quote this to prevent word splitting.

(SC2046)


[warning] 86-86: Quote this to prevent word splitting.

(SC2046)


[warning] 88-88: Quote this to prevent word splitting.

(SC2046)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/test-upgrade.sh` around lines 83 - 88, The commands exrpd
add-genesis-account and exrpd gentx use unquoted command substitutions and
variables which can break on whitespace or special chars; update both lines to
quote all substitutions and variable expansions (e.g., wrap $(exrpd keys show
key -a --keyring-backend test --home "$NODE_HOME") in quotes, quote "$(jq
'.initial_height' "$EXPORTED_STATE")", quote "$(jq '.app_state.auth.accounts |
length' "$EXPORTED_STATE")", and quote $CHAIN_ID and $NODE_HOME wherever used)
so that the exrpd add-genesis-account and exrpd gentx invocations reliably
receive single arguments.

exrpd collect-gentxs --home "$NODE_HOME" > /dev/null 2>&1
exrpd validate-genesis --home "$NODE_HOME"
echo "✅ Local validator configured "

jq '.initial_height = (.initial_height | tostring)' "$NODE_HOME"/config/genesis.json | sponge "$NODE_HOME"/config/genesis.json

echo "ℹ️ Starting the node in background process..."
exrpd start --home "$NODE_HOME" &

sleep 30

INITIAL_HEIGHT=$(jq '.initial_height' $NODE_HOME/config/genesis.json)
INITIAL_HEIGHT=${INITIAL_HEIGHT//\"/}
PROPOSAL_HEIGHT=$(( INITIAL_HEIGHT + 15 ))
echo "ℹ️ Creating upgrade proposal for version $VERSION and height $PROPOSAL_HEIGHT..."
cat <<EOF > upgrade-proposal.json
{
"messages": [
{
"@type": "/cosmos.upgrade.v1beta1.MsgSoftwareUpgrade",
"authority": "ethm10d07y265gmmuvt4z0w9aw880jnsr700jpva843",
"plan": {
"name": "v$VERSION",
"height": "$PROPOSAL_HEIGHT",
"info": "{\"binaries\":{\"linux/amd64\":\"https://github.com/xrplevm/node/releases/download/v$VERSION/node_$VERSION_Linux_amd64.tar.gz\",\"linux/arm64\":\"https://github.com/xrplevm/node/releases/download/v$VERSION/node_$VERSION_Linux_arm64.tar.gz\",\"darwin/amd64\":\"https://github.com/xrplevm/node/releases/download/v$VERSION/node_$VERSION_Darwin_amd64.tar.gz\",\"darwin/arm64\":\"https://github.com/xrplevm/node/releases/download/v$VERSION/node_$VERSION_Darwin_arm64.tar.gz\"}}"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical: Variable expansion bug produces malformed URLs.

$VERSION_Linux_amd64 is interpreted as a single variable name (which is undefined), not $VERSION followed by _Linux_amd64. This results in empty strings in the URLs, producing paths like node__Linux_amd64.tar.gz instead of node_9.0.0_Linux_amd64.tar.gz.

Use ${VERSION} with braces to disambiguate the variable boundary.

Proposed fix
-      "info": "{\"binaries\":{\"linux/amd64\":\"https://github.com/xrplevm/node/releases/download/v$VERSION/node_$VERSION_Linux_amd64.tar.gz\",\"linux/arm64\":\"https://github.com/xrplevm/node/releases/download/v$VERSION/node_$VERSION_Linux_arm64.tar.gz\",\"darwin/amd64\":\"https://github.com/xrplevm/node/releases/download/v$VERSION/node_$VERSION_Darwin_amd64.tar.gz\",\"darwin/arm64\":\"https://github.com/xrplevm/node/releases/download/v$VERSION/node_$VERSION_Darwin_arm64.tar.gz\"}}"
+      "info": "{\"binaries\":{\"linux/amd64\":\"https://github.com/xrplevm/node/releases/download/v${VERSION}/node_${VERSION}_Linux_amd64.tar.gz\",\"linux/arm64\":\"https://github.com/xrplevm/node/releases/download/v${VERSION}/node_${VERSION}_Linux_arm64.tar.gz\",\"darwin/amd64\":\"https://github.com/xrplevm/node/releases/download/v${VERSION}/node_${VERSION}_Darwin_amd64.tar.gz\",\"darwin/arm64\":\"https://github.com/xrplevm/node/releases/download/v${VERSION}/node_${VERSION}_Darwin_arm64.tar.gz\"}}"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"info": "{\"binaries\":{\"linux/amd64\":\"https://github.com/xrplevm/node/releases/download/v$VERSION/node_$VERSION_Linux_amd64.tar.gz\",\"linux/arm64\":\"https://github.com/xrplevm/node/releases/download/v$VERSION/node_$VERSION_Linux_arm64.tar.gz\",\"darwin/amd64\":\"https://github.com/xrplevm/node/releases/download/v$VERSION/node_$VERSION_Darwin_amd64.tar.gz\",\"darwin/arm64\":\"https://github.com/xrplevm/node/releases/download/v$VERSION/node_$VERSION_Darwin_arm64.tar.gz\"}}"
"info": "{\"binaries\":{\"linux/amd64\":\"https://github.com/xrplevm/node/releases/download/v${VERSION}/node_${VERSION}_Linux_amd64.tar.gz\",\"linux/arm64\":\"https://github.com/xrplevm/node/releases/download/v${VERSION}/node_${VERSION}_Linux_arm64.tar.gz\",\"darwin/amd64\":\"https://github.com/xrplevm/node/releases/download/v${VERSION}/node_${VERSION}_Darwin_amd64.tar.gz\",\"darwin/arm64\":\"https://github.com/xrplevm/node/releases/download/v${VERSION}/node_${VERSION}_Darwin_arm64.tar.gz\"}}"
🧰 Tools
🪛 Shellcheck (0.11.0)

[warning] 113-113: VERSION_Linux_amd64 is referenced but not assigned.

(SC2154)


[warning] 113-113: VERSION_Linux_arm64 is referenced but not assigned.

(SC2154)


[warning] 113-113: VERSION_Darwin_amd64 is referenced but not assigned.

(SC2154)


[warning] 113-113: VERSION_Darwin_arm64 is referenced but not assigned.

(SC2154)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/test-upgrade.sh` at line 113, The JSON "info" string uses unbraced
variable expansions like $VERSION_Linux_amd64 which are parsed as a single
(undefined) variable; change all occurrences to use brace syntax so the base
variable and suffix are distinct — e.g. replace $VERSION_Linux_amd64,
$VERSION_Linux_arm64, $VERSION_Darwin_amd64, $VERSION_Darwin_arm64 with
${VERSION}_Linux_amd64, ${VERSION}_Linux_arm64, ${VERSION}_Darwin_amd64,
${VERSION}_Darwin_arm64 respectively inside the "info" value to produce correct
URLs when VERSION is substituted.

}
}
],
"title": "Protocol upgrade to v$VERSION",
"summary": "This proposal will execute a protocol upgrade to the version $VERSION (https://github.com/xrplevm/node/releases/tag/v$VERSION)",
"metadata": "ipfs://QmRWbE8bibBjtacYaaJ1suRpxFiHuYCD1T4Uz6QV27GXKH",
"deposit": "500000000000000000000axrp"
}
EOF

exrpd --home "$NODE_HOME" tx gov submit-proposal ./upgrade-proposal.json --from key --gas-prices 800000000000axrp --gas 400000 --yes
echo "✅ Proposal created"

sleep 10
PROPOSAL_ID=$(exrpd q gov proposals --home $NODE_HOME -o json --page-count-total | jq '.pagination.total')
PROPOSAL_ID=${PROPOSAL_ID//\"/}
Comment on lines +128 to +129
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# First, let's examine the actual code around lines 128-129
cat -n scripts/test-upgrade.sh | sed -n '120,140p'

Repository: xrplevm/node

Length of output: 764


🏁 Script executed:

#!/bin/bash
# Search for exrpd gov proposals usage in the repo to understand the command better
rg "gov proposals" -A 3 -B 1 --type sh

Repository: xrplevm/node

Length of output: 371


🏁 Script executed:

#!/bin/bash
# Search for any references to exrpd command documentation or examples
rg "exrpd.*q.*gov" -A 2 --type sh | head -50

Repository: xrplevm/node

Length of output: 313


🏁 Script executed:

#!/bin/bash
# Search for any other proposal or governance-related queries in the repo
rg "proposals" --type sh -B 2 -A 2 | head -100

Repository: xrplevm/node

Length of output: 371


🏁 Script executed:

#!/bin/bash
# Check if there are any other examples of exrpd queries to understand the response structure
rg "exrpd q" --type sh -A 1 | grep -E "(exrpd q|jq)" | head -30

Repository: xrplevm/node

Length of output: 186


🏁 Script executed:

#!/bin/bash
# Search for documentation or comments about the exrpd command
find . -name "*.md" -o -name "*.txt" | xargs grep -l "exrpd" 2>/dev/null | head -10

Repository: xrplevm/node

Length of output: 38


🏁 Script executed:

#!/bin/bash
# Search for any README or documentation about the project
find . -maxdepth 2 -name "README*" -o -name "SETUP*" | head -10

Repository: xrplevm/node

Length of output: 38


🏁 Script executed:

#!/bin/bash
# Look for go.mod to understand what's being used
head -20 go.mod 2>/dev/null || echo "No go.mod at root"

Repository: xrplevm/node

Length of output: 584


🏁 Script executed:

#!/bin/bash
# Search for any Makefile or script that might show how exrpd is invoked
find . -name "Makefile" -o -name "*.sh" | xargs grep -l "exrpd" 2>/dev/null

Repository: xrplevm/node

Length of output: 135


🏁 Script executed:

#!/bin/bash
# Check the Makefile to understand the exrpd binary
head -50 Makefile

Repository: xrplevm/node

Length of output: 1708


🏁 Script executed:

#!/bin/bash
# Search for test output or any example JSON responses in the repo
find . -name "*.json" | xargs grep -l "pagination\|proposals" 2>/dev/null | head -5

Repository: xrplevm/node

Length of output: 38


🏁 Script executed:

#!/bin/bash
# Look for any cosmos SDK documentation or comments about query flags
rg "page-count-total|pagination" --type sh --type go -B 2 -A 2 | head -50

Repository: xrplevm/node

Length of output: 2502


🏁 Script executed:

#!/bin/bash
# Search for any test or script output that might show the actual JSON response structure
rg "\.proposals\[" --type sh --type go -B 1 -A 1

Repository: xrplevm/node

Length of output: 38


🏁 Script executed:

#!/bin/bash
# Check if there are any examples of exrpd output or test fixtures
find . -name "*.txt" -o -name "*.out" -o -name "*.log" 2>/dev/null | xargs grep -l "proposals" 2>/dev/null | head -5

Repository: xrplevm/node

Length of output: 38


🏁 Script executed:

#!/bin/bash
# Look at the local-node.sh to see how exrpd is used elsewhere
cat -n ./local-node.sh | grep -A 5 -B 5 "exrpd"

Repository: xrplevm/node

Length of output: 2860


🏁 Script executed:

#!/bin/bash
# Search for any cosmos sdk imports or gov module references
rg "cosmos/x/gov" --type go | head -20

Repository: xrplevm/node

Length of output: 38


🏁 Script executed:

#!/bin/bash
# Check the go.mod for cosmos-sdk version
grep "cosmos/cosmos-sdk" go.mod

Repository: xrplevm/node

Length of output: 239


🌐 Web query:

Cosmos SDK gov proposals query --reverse flag response structure

💡 Result:

In Cosmos SDK, query gov proposals --reverse is just the standard pagination “reverse order” switch (it maps to pagination.reverse=true in the underlying PageRequest). It does not change the shape of the response—it only changes the ordering of the returned list (descending vs ascending within the page). [1]

What --reverse actually does

  • CLI: simd query gov proposals ... --reverse (standard pagination flag)
  • REST (v1beta1 example): GET /cosmos/gov/v1beta1/proposals?...&pagination.reverse=true [1]
  • gRPC: QueryProposalsRequest.pagination.reverse = true (descending order) [1]

Response structure (unchanged by --reverse)

The protobuf/gRPC response type is:

  • QueryProposalsResponse:
    • proposals: []*Proposal
    • pagination: *PageResponse [2]

So JSON/YAML output is always shaped like:

  • top-level:
    • proposals: [...]
    • pagination: { next_key: ..., total: ... } (fields depend on how you paginate) [3]

--reverse simply reverses the order of elements in proposals compared to the default. [1]

Sources: [1] [2] [3]


Fragile proposal ID retrieval—using pagination count instead of actual proposal ID.

Line 128 retrieves .pagination.total, which is the count of all proposals, not the ID of the newly submitted proposal. This works accidentally in a fresh test environment (first proposal has ID 1, count is 1), but breaks if any proposals are deleted, cancelled, or if this script runs on a chain with existing proposals.

Query for the latest proposal by ID using --reverse to get the most recent proposal first:

Proposed fix
-PROPOSAL_ID=$(exrpd q gov proposals --home $NODE_HOME -o json --page-count-total | jq '.pagination.total')
-PROPOSAL_ID=${PROPOSAL_ID//\"/}
+PROPOSAL_ID=$(exrpd q gov proposals --home "$NODE_HOME" -o json --reverse | jq -r '.proposals[0].id')
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
PROPOSAL_ID=$(exrpd q gov proposals --home $NODE_HOME -o json --page-count-total | jq '.pagination.total')
PROPOSAL_ID=${PROPOSAL_ID//\"/}
PROPOSAL_ID=$(exrpd q gov proposals --home "$NODE_HOME" -o json --reverse | jq -r '.proposals[0].id')
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/test-upgrade.sh` around lines 128 - 129, The script currently sets
PROPOSAL_ID from '.pagination.total' which returns a count, not the latest
proposal ID; update the PROPOSAL_ID assignment in the script (the PROPOSAL_ID
variable near the exrpd q gov proposals call) to query proposals with the CLI
flag to list newest-first (use --reverse or equivalent) and extract the first
proposal's .proposal_id from the JSON output (via jq), stripping quotes as
before; replace the existing .pagination.total extraction with this
latest-proposal .proposal_id extraction so PROPOSAL_ID references the actual
most recent proposal ID.

echo "ℹ️ Voting for proposal $PROPOSAL_ID..."

exrpd --home "$NODE_HOME" tx gov vote "$PROPOSAL_ID" yes --from key --gas-prices 800000000000axrp --yes --broadcast-mode async
echo "✅ Proposal voted"

rm upgrade-proposal.json
wait