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
40 changes: 33 additions & 7 deletions scripts/devnet.sh
Original file line number Diff line number Diff line change
Expand Up @@ -101,10 +101,36 @@ ensure_built() {

start_hardhat() {
local pidfile="$DEVNET_DIR/hardhat.pid"
local marker="$DEVNET_DIR/hardhat/deployed"
local artifacts_dir="$REPO_ROOT/packages/evm-module/artifacts/contracts"

if [ -f "$pidfile" ] && kill -0 "$(cat "$pidfile")" 2>/dev/null; then
log "Hardhat node already running (PID $(cat "$pidfile"))"
return 0
# Stale-bytecode guard. When contracts are recompiled while hardhat
# keeps running, the on-chain bytecode lags the artifacts on disk.
# The next `devnet start` short-circuits hardhat reuse here, then
# `deploy_contracts` skips on the still-present marker, so daemons
# connect to addresses whose code is older than the source. View
# methods added since the live deploy revert with "function selector
# not recognized" (e.g. CSS.getNodeStakeV10 in the staking loop).
# Detect the mismatch by comparing artifact mtimes to the marker;
# if any artifact is newer, kill hardhat so the fresh-start path
# below clears the marker + deployments and `deploy_contracts`
# re-deploys onto a fresh chain.
if [ -f "$marker" ] && [ -d "$artifacts_dir" ] \
&& [ -n "$(find "$artifacts_dir" -name '*.json' -newer "$marker" -print -quit 2>/dev/null)" ]; then
local stale_pid
stale_pid=$(cat "$pidfile")
log "Hardhat (PID $stale_pid) holds outdated contracts (artifacts newer than deploy marker) — restarting for fresh deploy"
kill "$stale_pid" 2>/dev/null || true
for _ in 1 2 3 4 5; do
kill -0 "$stale_pid" 2>/dev/null || break
sleep 1
done
rm -f "$pidfile"
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Issue: This restart path removes hardhat.pid and immediately starts a replacement even if the old Hardhat process is still alive after the 5s wait. On a slow shutdown, the new hardhat node can fail to bind :$HARDHAT_PORT, turning the stale-artifact recovery into a flaky devnet start. Re-check kill -0 after the loop and either keep waiting/escalate the kill or return non-zero before deleting the pidfile and launching a new node.

else
log "Hardhat node already running (PID $(cat "$pidfile"))"
return 0
fi
fi

log "Starting Hardhat node on port $HARDHAT_PORT..."
Expand Down Expand Up @@ -739,7 +765,7 @@ cmd_start() {
const opSigners = new Array(n).fill(null);
const nodeRoles = new Array(n).fill('edge');
// Codex round 4 on PR #368: read config.json FIRST and INDEPENDENTLY
// of wallets.json. The previous loop did wallets first and `continue`d
// of wallets.json. The previous loop did wallets first and \`continue\`d
// on parse failure BEFORE reading config, so an intended core whose
// wallets.json was malformed silently kept the 'edge' default,
// dropped out of coreIdxs, and the lostCores guard never fired.
Expand Down Expand Up @@ -865,7 +891,7 @@ cmd_start() {
// this code path exists to prevent.
//
// Codex round 2 on PR #368: re-read 'pending' before EVERY tx
// (not once per node loop). The previous code did `nonce++` at
// (not once per node loop). The previous code did \`nonce++\` at
// call site so a failed approve advanced the local counter even
// though the chain nonce did not, leaving createConviction +
// updateAsk to send with an inflated nonce that would either
Expand All @@ -878,7 +904,7 @@ cmd_start() {
return tx.wait();
};
// Codex round 4 on PR #368: skip createConviction if the daemon
// already opened a position. PR 366 wired `EVMChainAdapter.ensureProfile()`
// already opened a position. PR 366 wired \`EVMChainAdapter.ensureProfile()\`
// to open a default 50k V10 conviction during agent startup, so by
// the time this script reaches the staking loop the daemon has
// (usually) already staked. Running createConviction again would
Expand All @@ -895,8 +921,8 @@ cmd_start() {
// updateAsk is INDEPENDENT of stake state, so we still attempt
// it below regardless of the probe outcome.
//
// Codex round 7 on PR #368: probe `getNodeStakeV10(idId)` instead
// of NFT `balanceOf(opSigner.address)`. The op wallet could hold
// Codex round 7 on PR #368: probe \`getNodeStakeV10(idId)\` instead
// of NFT \`balanceOf(opSigner.address)\`. The op wallet could hold
// positions for OTHER identities (not in our devnet today, but
// a wallet-scoped probe is the wrong semantic check); we want
// to know whether THIS identity is staked.
Expand Down
Loading