Files
proxmox/scripts/deployment/recover-chain138-eoa-nonce-gaps.sh
defiQUG dbd517b279 Sync workspace: config, docs, scripts, CI, operator rules, and submodule pointers.
- Update dbis_core, cross-chain-pmm-lps, explorer-monorepo, metamask-integration, pr-workspace/chains
- Omit embedded publish git dirs and empty placeholders from index

Made-with: Cursor
2026-04-12 06:12:20 -07:00

246 lines
7.0 KiB
Bash
Executable File

#!/usr/bin/env bash
# Diagnose and optionally fill nonce gaps for a Chain 138 EOA whose future txs are
# stranded in Besu txpool_besuPendingTransactions.
#
# Default mode is dry-run. Use --apply to actually send gap-filler self-transfers.
#
# Example:
# PRIVATE_KEY=0xabc... bash scripts/deployment/recover-chain138-eoa-nonce-gaps.sh \
# --rpc http://192.168.11.217:8545
#
# bash scripts/deployment/recover-chain138-eoa-nonce-gaps.sh \
# --private-key 0xabc... \
# --rpc http://192.168.11.217:8545 \
# --apply
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
if [[ -f "${PROJECT_ROOT}/scripts/lib/load-project-env.sh" ]]; then
# shellcheck source=/dev/null
source "${PROJECT_ROOT}/scripts/lib/load-project-env.sh" 2>/dev/null || true
fi
RPC_URL="${RPC_URL_138:-http://192.168.11.211:8545}"
PRIVATE_KEY_INPUT="${PRIVATE_KEY:-${DEPLOYER_PRIVATE_KEY:-}}"
APPLY=0
CHAIN_ID=138
GAS_LIMIT=21000
GAS_BUMP_MULTIPLIER=2
MIN_GAS_WEI=2000
usage() {
cat <<'EOF'
Usage: recover-chain138-eoa-nonce-gaps.sh [options]
Options:
--private-key <hex> Private key for the EOA owner.
--rpc <url> Chain 138 RPC URL. Default: RPC_URL_138 or http://192.168.11.211:8545
--apply Actually send self-transfers for missing nonces.
--min-gas-wei <n> Floor gas price / max fee in wei. Default: 2000
--gas-multiplier <n> Multiply current eth_gasPrice by this factor. Default: 2
--help Show this help.
Behavior:
1. Derives the owner address from the provided private key.
2. Reads latest nonce and txpool_besuPendingTransactions for that address.
3. Finds any missing nonce gaps between latest nonce and the highest pending nonce.
4. In --apply mode, sends 0-value self-transfers for those missing nonces.
Notes:
- Dry-run is the default and is recommended first.
- This only fills missing nonce gaps. It does not cancel every future tx.
- Requires: cast, curl, jq
EOF
}
while [[ $# -gt 0 ]]; do
case "$1" in
--private-key)
PRIVATE_KEY_INPUT="${2:-}"
shift 2
;;
--rpc)
RPC_URL="${2:-}"
shift 2
;;
--apply)
APPLY=1
shift
;;
--min-gas-wei)
MIN_GAS_WEI="${2:-}"
shift 2
;;
--gas-multiplier)
GAS_BUMP_MULTIPLIER="${2:-}"
shift 2
;;
--help|-h)
usage
exit 0
;;
*)
echo "Unknown argument: $1" >&2
usage
exit 1
;;
esac
done
for cmd in cast curl jq; do
command -v "$cmd" >/dev/null 2>&1 || {
echo "Missing required command: $cmd" >&2
exit 1
}
done
if [[ -z "$PRIVATE_KEY_INPUT" ]]; then
echo "Missing private key. Pass --private-key or set PRIVATE_KEY." >&2
exit 1
fi
OWNER_ADDRESS="$(cast wallet address --private-key "$PRIVATE_KEY_INPUT" 2>/dev/null || true)"
if [[ -z "$OWNER_ADDRESS" ]]; then
echo "Could not derive owner address from private key." >&2
exit 1
fi
OWNER_ADDRESS_LC="$(printf '%s' "$OWNER_ADDRESS" | tr '[:upper:]' '[:lower:]')"
rpc_call() {
local method="$1"
local params_json="$2"
curl -fsS "$RPC_URL" \
-H 'content-type: application/json' \
--data "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"${method}\",\"params\":${params_json}}"
}
hex_to_dec() {
cast --to-dec "$1"
}
latest_hex="$(rpc_call eth_getTransactionCount "[\"${OWNER_ADDRESS}\",\"latest\"]" | jq -r '.result // "0x0"')"
pending_hex="$(rpc_call eth_getTransactionCount "[\"${OWNER_ADDRESS}\",\"pending\"]" | jq -r '.result // "0x0"')"
gas_hex="$(rpc_call eth_gasPrice "[]" | jq -r '.result // "0x0"')"
latest_nonce="$(hex_to_dec "$latest_hex")"
pending_nonce="$(hex_to_dec "$pending_hex")"
network_gas_wei="$(hex_to_dec "$gas_hex")"
pending_json="$(rpc_call txpool_besuPendingTransactions "[]")"
owner_pending_json="$(printf '%s' "$pending_json" | jq --arg owner "$OWNER_ADDRESS_LC" '
[
.result[]?
| select(((.from // .sender // "") | ascii_downcase) == $owner)
| {
hash,
nonce_hex: .nonce,
to,
gas,
gasPrice,
maxFeePerGas,
maxPriorityFeePerGas,
type
}
]
')"
owner_pending_json="$(
printf '%s' "$owner_pending_json" | jq '
map(. + {
nonce: (.nonce_hex | sub("^0x"; "") | if . == "" then "0" else . end)
})
'
)"
owner_pending_count="$(printf '%s' "$owner_pending_json" | jq 'length')"
highest_pending_nonce="$latest_nonce"
if [[ "$owner_pending_count" -gt 0 ]]; then
highest_pending_nonce="$(
printf '%s' "$owner_pending_json" \
| jq -r '.[].nonce' \
| while read -r nonce_hex; do cast --to-dec "0x${nonce_hex}"; done \
| sort -n \
| tail -n1
)"
fi
present_nonce_csv=","
if [[ "$owner_pending_count" -gt 0 ]]; then
while read -r nonce_dec; do
present_nonce_csv+="${nonce_dec},"
done < <(
printf '%s' "$owner_pending_json" \
| jq -r '.[].nonce' \
| while read -r nonce_hex; do cast --to-dec "0x${nonce_hex}"; done
)
fi
missing_nonces=()
for ((nonce = latest_nonce; nonce <= highest_pending_nonce; nonce++)); do
if [[ "$present_nonce_csv" != *",$nonce,"* ]]; then
missing_nonces+=("$nonce")
fi
done
effective_gas_wei="$(( network_gas_wei * GAS_BUMP_MULTIPLIER ))"
if (( effective_gas_wei < MIN_GAS_WEI )); then
effective_gas_wei="$MIN_GAS_WEI"
fi
echo "=== Chain 138 EOA nonce-gap recovery ==="
echo "Owner address: $OWNER_ADDRESS"
echo "RPC URL: $RPC_URL"
echo "Latest nonce: $latest_nonce"
echo "Pending nonce view: $pending_nonce"
echo "Network gas price: $network_gas_wei wei"
echo "Planned gas price: $effective_gas_wei wei"
echo "Pending tx count: $owner_pending_count"
echo "Highest pending nonce:${highest_pending_nonce}"
echo ""
if [[ "$owner_pending_count" -gt 0 ]]; then
echo "--- Pending txs for owner in txpool_besuPendingTransactions ---"
printf '%s\n' "$owner_pending_json" | jq '.'
echo ""
else
echo "No owner txs found in txpool_besuPendingTransactions."
echo ""
fi
if [[ "${#missing_nonces[@]}" -eq 0 ]]; then
echo "No nonce gaps detected between latest nonce and highest pending nonce."
exit 0
fi
echo "--- Missing nonce gaps ---"
printf 'Missing nonces: %s\n' "${missing_nonces[*]}"
echo ""
if [[ "$APPLY" -ne 1 ]]; then
echo "Dry-run only. Re-run with --apply to send 0-value self-transfers for the missing nonces above."
exit 0
fi
echo "--- Applying gap fillers ---"
for nonce in "${missing_nonces[@]}"; do
echo "Sending self-transfer for nonce $nonce ..."
cast send "$OWNER_ADDRESS" \
--private-key "$PRIVATE_KEY_INPUT" \
--rpc-url "$RPC_URL" \
--nonce "$nonce" \
--value 0 \
--gas-limit "$GAS_LIMIT" \
--gas-price "$effective_gas_wei" \
>/tmp/recover-chain138-eoa-nonce-gaps.out 2>/tmp/recover-chain138-eoa-nonce-gaps.err || {
echo "Failed to send nonce $nonce" >&2
cat /tmp/recover-chain138-eoa-nonce-gaps.err >&2 || true
exit 1
}
cat /tmp/recover-chain138-eoa-nonce-gaps.out
done
echo ""
echo "Submitted gap-filler transactions. Wait for inclusion, then rerun this script in dry-run mode."