- 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
246 lines
7.0 KiB
Bash
Executable File
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."
|