Chore/decouple hashi executor#445
Conversation
✅ Deploy Preview for veascan ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
WalkthroughIntegrates lock-backed state files for Hashi runs, adds pending-message queuing with timestamps and time-window logic, moves nonce initialization to non-Hashi path, introduces per-message static-call filtering with a HASHI_MESSAGE_FAILING event, and updates logging/events and related tests/types/state files. Changes
Sequence DiagramsequenceDiagram
participant RC as Relayer (Main Loop)
participant HE as Hashi Executor
participant SF as State File
participant BE as Blockchain RPC
participant EM as Event Emitter
RC->>HE: runHashiExecutor(args)
HE->>SF: getStartBlockNumber(chainId, network)
SF->>EM: emit(LOCK_CLAIMED)
SF-->>HE: hashiBlockNumber
HE->>SF: readPendingMessages(chainId, network)
SF-->>HE: stored pending messages
HE->>BE: getAllMessageDispatchedLogs(fromBlock..toBlock)
EM->>EM: emit(INDEXING)
BE-->>HE: logs (with timestamps)
HE->>HE: determine isMessageExecutable for each
HE->>HE: enqueue THRESHOLD_NOT_MET within MAX_PENDING_TIME
HE->>HE: merge new + stored pending
alt no executable messages
HE->>SF: updateHashiStateFile(...pending...)
SF->>EM: emit(LOCK_RELEASED)
else executable messages
HE->>BE: staticCall per message (pre-filter)
BE-->>HE: staticCall results
HE->>EM: emit(HASHI_MESSAGE_FAILING) for failing msgs
HE->>BE: executeBatchOnHashi(filteredMessages)
BE-->>HE: tx submitted
HE->>SF: updateHashiStateFile(...pending...)
SF->>EM: emit(LOCK_RELEASED)
end
HE-->>RC: return result
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 inconclusive)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Review rate limit: 4/5 reviews remaining, refill in 12 minutes. Comment |
There was a problem hiding this comment.
Actionable comments posted: 6
🧹 Nitpick comments (1)
relayer-cli/state/hashi_10200.json (1)
2-4: Prefer template defaults for committed Hashi stateLine 3 (
hashiBlockNumber) and Line 4 (hashiMessages) are runtime progression state. Consider committing a template/default file and keeping live state files git-ignored to avoid environment-coupled replay checkpoints.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@relayer-cli/state/hashi_10200.json` around lines 2 - 4, The committed file relayer-cli/state/hashi_10200.json contains runtime fields (hashiBlockNumber, hashiMessages, ts) that should not be committed as live state; replace this with a template (e.g., relayer-cli/state/hashi_10200.template.json) containing neutral defaults (null/0/empty array for hashiBlockNumber, hashiMessages and a placeholder ts) and add the live state path/pattern (relayer-cli/state/*.json or the specific hashi_10200.json) to .gitignore so runtime updates are not tracked; update any code or docs that reference the file name to point at the template for initial values.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@relayer-cli/src/utils/hashi.ts`:
- Around line 175-188: The current loop treats every staticCall failure as a
permanent bad message and swallows transient RPC errors; update the loop around
yaru.executeMessages.staticCall so it distinguishes business-logic reverts from
transient/provider failures: call yaru.executeMessages.staticCall([message])
inside try, on success push to filteredMessages; in catch, inspect the error
(e.g., error.message contains "revert" / "execution reverted" or error.code ===
"CALL_EXCEPTION" / provider-specific revert indicator) and only for those known
revert cases emit BotEvents.HASHI_MESSAGE_FAILING and continue, otherwise
rethrow the error so the cycle aborts and the transient failure can be retried;
keep references to filteredMessages, emitter.emit,
BotEvents.HASHI_MESSAGE_FAILING and yaru.executeMessages.staticCall to locate
the code.
- Around line 71-83: The branch in the loop over txns drops messages with status
THRESHOLD_NOT_MET because isMessageExecutable can return that state but
toExecuteMessage currently only produces EXECUTABLE/EXECUTED, so
threshold-not-met messages never land in pendingMessages; update
toExecuteMessage (and/or isMessageExecutable's mapping) to return a proper
HashiExecutionStatus.THRESHOLD_NOT_MET case and ensure the calling loop (where
executableMessages and pendingMessages are appended) treats THRESHOLD_NOT_MET as
a valid non-executable pending state (respecting MAX_PENDING_TIME_SECONDS) so
those tx objects are pushed into pendingMessages instead of being skipped.
Ensure you reference the same HashiExecutionStatus enum values and the functions
isMessageExecutable and toExecuteMessage so the mapping and loop remain
consistent.
- Around line 291-293: The loop that accumulates logs uses the condition
all.length <= MAX_BATCH_SIZE which allows one extra fetch; change the loop guard
in the function that iterates blocks (the while using start, toBlock, and all)
to use all.length < MAX_BATCH_SIZE and also, when appending fetched logs to all,
trim the appended batch to at most (MAX_BATCH_SIZE - all.length) to guarantee we
never exceed MAX_BATCH_SIZE.
In `@relayer-cli/src/utils/hashiHelpers/stateFile.ts`:
- Around line 33-36: getStartBlockNumber() currently acquires the lock but then
calls syncStateFile() when the state file is missing; syncStateFile() ->
updateHashiStateFile() releases that lock, leaving a race where another executor
can start. Fix by keeping the lock held for the full bootstrap: modify
updateHashiStateFile() (and transitively syncStateFile()) to accept a flag like
retainLock/keepLock (default false) and when calling from getStartBlockNumber()
pass true so the lock is not released until after bootstrap/indexing completes,
or alternatively move the lock release out of updateHashiStateFile() and perform
it only in the caller (getStartBlockNumber()) after syncStateFile() returns;
ensure the same lock variable used by getStartBlockNumber(), syncStateFile(),
and updateHashiStateFile() is respected.
- Around line 74-79: The write of chain_state_file fails because JSON.stringify
on hashiMessages (HashiMessageExecutionVars) contains messageId: bigint; update
the serialization so bigints are converted before writing: either map
hashiMessages to a serializable form (replace messageId with
messageId.toString()) or pass a JSON.stringify replacer that converts bigint to
string, then call fileSystem.writeFileSync with the sanitized JSON; modify the
code around hashiMessages / fileSystem.writeFileSync to perform this
transformation so pending messages don't crash serialization.
In `@relayer-cli/state/testnet_10200.json`:
- Line 3: The committed default state sets the JSON key "nonce" to 12 which can
cause startup nonce drift; update the default value of "nonce" in the testnet
state file from 12 to 0 so fresh setups start with a neutral committed nonce and
let the runtime/environment manage actual nonces.
---
Nitpick comments:
In `@relayer-cli/state/hashi_10200.json`:
- Around line 2-4: The committed file relayer-cli/state/hashi_10200.json
contains runtime fields (hashiBlockNumber, hashiMessages, ts) that should not be
committed as live state; replace this with a template (e.g.,
relayer-cli/state/hashi_10200.template.json) containing neutral defaults
(null/0/empty array for hashiBlockNumber, hashiMessages and a placeholder ts)
and add the live state path/pattern (relayer-cli/state/*.json or the specific
hashi_10200.json) to .gitignore so runtime updates are not tracked; update any
code or docs that reference the file name to point at the template for initial
values.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: fb7e97eb-847e-4ab2-b58a-775ab6914c86
📒 Files selected for processing (20)
relayer-cli/src/relayer.tsrelayer-cli/src/utils/botEvents.tsrelayer-cli/src/utils/hashi.test.tsrelayer-cli/src/utils/hashi.tsrelayer-cli/src/utils/hashiHelpers/bridgeRoutes.tsrelayer-cli/src/utils/hashiHelpers/hashiTypes.tsrelayer-cli/src/utils/hashiHelpers/stateFile.tsrelayer-cli/src/utils/logger.tsrelayer-cli/src/utils/relayerHelpers.test.tsrelayer-cli/src/utils/relayerHelpers.tsrelayer-cli/state/devnet_10200.jsonrelayer-cli/state/devnet_11155111.jsonrelayer-cli/state/hashi_10200.jsonrelayer-cli/state/hashi_11155111.jsonrelayer-cli/state/hashi_1514.jsonrelayer-cli/state/hashi_42161.jsonrelayer-cli/state/testnet_10200.jsonrelayer-cli/state/testnet_11155111.jsonrelayer-cli/state/testnet_1514.jsonrelayer-cli/state/testnet_42161.json
… into chore/decouple-hashi-executor
|
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (5)
relayer-cli/src/utils/hashi.ts (1)
104-108: Prefer.at(-1)for accessing the last element.Per static analysis, using
.at(-1)is cleaner and more idiomatic than[length - 1].Suggested fix
emitter.emit( BotEvents.EXECUTING_HASHI, executableMessages[0].nonce, - executableMessages[executableMessages.length - 1].nonce + executableMessages.at(-1)!.nonce );🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@relayer-cli/src/utils/hashi.ts` around lines 104 - 108, Replace the manual last-element access with .at(-1): in the emitter.emit call that uses BotEvents.EXECUTING_HASHI and the array executableMessages, change the second argument from executableMessages[executableMessages.length - 1].nonce to executableMessages.at(-1).nonce so you access the final message idiomatically; keep the first argument as executableMessages[0].nonce and leave the event name emitter.emit(BotEvents.EXECUTING_HASHI, ...) intact.relayer-cli/src/utils/hashiHelpers/stateFile.ts (4)
74-74: Usepath.joinfor consistent path construction.Line 32 uses
path.join(stateDir, ...)but this line uses string concatenation. This inconsistency could cause issues with trailing slashes or cross-platform path handling.Suggested fix
- const chain_state_file = process.env.STATE_DIR + network + "_" + chainId + ".json"; + const chain_state_file = path.join(stateDir, `${network}_${chainId}.json`);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@relayer-cli/src/utils/hashiHelpers/stateFile.ts` at line 74, The code constructs chain_state_file using string concatenation which can break on different OSes; update the assignment for chain_state_file to use path.join (like the other usage of path.join(stateDir, ...)) by importing/using the path module and building the filename from STATE_DIR (process.env.STATE_DIR or stateDir), network and chainId (e.g., path.join(stateDir, `${network}_${chainId}.json`)) so path handling is consistent across platforms.
40-41: UsebigIntReviverfor consistent JSON parsing.
readPendingMessages(Line 109) usesbigIntReviver, but this parse does not. While currently onlyhashiBlockNumberis read (a regular number), adding the reviver maintains consistency and prevents future bugs if other fields are accessed.Suggested fix
const chain_state_raw = fileSystem.readFileSync(stateFile, { encoding: "utf8" }); - const chain_state = JSON.parse(chain_state_raw); + const chain_state = JSON.parse(chain_state_raw, bigIntReviver);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@relayer-cli/src/utils/hashiHelpers/stateFile.ts` around lines 40 - 41, The JSON.parse call that creates chain_state from chain_state_raw should use the existing bigIntReviver for consistent bigint handling; update the parse in stateFile.ts where chain_state and chain_state_raw are defined to call JSON.parse(chain_state_raw, bigIntReviver) so it matches readPendingMessages and prevents future bigint parsing issues (refer to the bigIntReviver symbol and the readPendingMessages usage as the pattern to follow).
1-7: Clean up imports and prefernode:prefix.Per static analysis:
HashiMessageis imported but unused- Prefer
node:fs,node:path,node:eventsover bare module names- Consider using
import 'dotenv/config'instead ofrequire("dotenv").config()for ESM consistencySuggested fix
-import * as fs from "fs"; -import * as path from "path"; -import { EventEmitter } from "events"; +import * as fs from "node:fs"; +import * as path from "node:path"; +import { EventEmitter } from "node:events"; import { claimLock, releaseLock } from "../lock"; import { BotEvents } from "../botEvents"; -import { HashiMessage, HashiMessageExecutionVars } from "./hashiTypes"; -require("dotenv").config(); +import { HashiMessageExecutionVars } from "./hashiTypes"; +import "dotenv/config";🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@relayer-cli/src/utils/hashiHelpers/stateFile.ts` around lines 1 - 7, Remove the unused HashiMessage import and switch to node-prefixed core imports: replace "fs" with "node:fs", "path" with "node:path", and "events" with "node:events" while keeping EventEmitter; keep imports for claimLock, releaseLock, BotEvents and HashiMessageExecutionVars as needed; and replace require("dotenv").config() with a top-level ESM import 'dotenv/config' to maintain ESM consistency in stateFile.ts.
59-69: Consider using an options object to reduce parameter count.The function has 9 parameters (SonarCloud flags max 7). Grouping optional/DI parameters into an options object improves readability and makes adding new options easier.
Example refactor
interface UpdateStateOptions { isIniting?: boolean; fileSystem?: typeof fs; removeLock?: typeof releaseLock; } async function updateHashiStateFile( chainId: number, createdTimestamp: number, hashiBlockNumberFrom: number, hashiMessages: HashiMessageExecutionVars[], network: string, emitter: EventEmitter, options: UpdateStateOptions = {} ) { const { isIniting = false, fileSystem = fs, removeLock = releaseLock } = options; // ... }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@relayer-cli/src/utils/hashiHelpers/stateFile.ts` around lines 59 - 69, The updateHashiStateFile function currently accepts nine parameters; refactor it to accept the required params (chainId, createdTimestamp, hashiBlockNumberFrom, hashiMessages, network, emitter) and a single options object for the remaining/DI params (isIniting, fileSystem, removeLock). Change the signature of updateHashiStateFile to take an options parameter (e.g., UpdateStateOptions) with defaults for isIniting = false, fileSystem = fs, and removeLock = releaseLock, then destructure those defaults at the top of the function and replace usages of the original parameters with the destructured names throughout the body so callers pass an options object instead of multiple trailing args.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@relayer-cli/src/utils/hashiHelpers/stateFile.ts`:
- Around line 81-84: The code currently emits BotEvents.LOCK_RELEASED regardless
of whether the lock was actually removed; update the logic so that
emitter.emit(BotEvents.LOCK_RELEASED) only runs when the lock is released (i.e.,
inside the same conditional that calls removeLock when isIniting is false).
Locate the isIniting check and removeLock(network, chainId) call and move or add
the emitter.emit(BotEvents.LOCK_RELEASED) into that branch so the event is not
emitted when isIniting is true.
---
Nitpick comments:
In `@relayer-cli/src/utils/hashi.ts`:
- Around line 104-108: Replace the manual last-element access with .at(-1): in
the emitter.emit call that uses BotEvents.EXECUTING_HASHI and the array
executableMessages, change the second argument from
executableMessages[executableMessages.length - 1].nonce to
executableMessages.at(-1).nonce so you access the final message idiomatically;
keep the first argument as executableMessages[0].nonce and leave the event name
emitter.emit(BotEvents.EXECUTING_HASHI, ...) intact.
In `@relayer-cli/src/utils/hashiHelpers/stateFile.ts`:
- Line 74: The code constructs chain_state_file using string concatenation which
can break on different OSes; update the assignment for chain_state_file to use
path.join (like the other usage of path.join(stateDir, ...)) by importing/using
the path module and building the filename from STATE_DIR (process.env.STATE_DIR
or stateDir), network and chainId (e.g., path.join(stateDir,
`${network}_${chainId}.json`)) so path handling is consistent across platforms.
- Around line 40-41: The JSON.parse call that creates chain_state from
chain_state_raw should use the existing bigIntReviver for consistent bigint
handling; update the parse in stateFile.ts where chain_state and chain_state_raw
are defined to call JSON.parse(chain_state_raw, bigIntReviver) so it matches
readPendingMessages and prevents future bigint parsing issues (refer to the
bigIntReviver symbol and the readPendingMessages usage as the pattern to
follow).
- Around line 1-7: Remove the unused HashiMessage import and switch to
node-prefixed core imports: replace "fs" with "node:fs", "path" with
"node:path", and "events" with "node:events" while keeping EventEmitter; keep
imports for claimLock, releaseLock, BotEvents and HashiMessageExecutionVars as
needed; and replace require("dotenv").config() with a top-level ESM import
'dotenv/config' to maintain ESM consistency in stateFile.ts.
- Around line 59-69: The updateHashiStateFile function currently accepts nine
parameters; refactor it to accept the required params (chainId,
createdTimestamp, hashiBlockNumberFrom, hashiMessages, network, emitter) and a
single options object for the remaining/DI params (isIniting, fileSystem,
removeLock). Change the signature of updateHashiStateFile to take an options
parameter (e.g., UpdateStateOptions) with defaults for isIniting = false,
fileSystem = fs, and removeLock = releaseLock, then destructure those defaults
at the top of the function and replace usages of the original parameters with
the destructured names throughout the body so callers pass an options object
instead of multiple trailing args.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: dbfd58f8-3100-4ddc-9c5f-d6355a86cfe2
📒 Files selected for processing (2)
relayer-cli/src/utils/hashi.tsrelayer-cli/src/utils/hashiHelpers/stateFile.ts


PR-Codex overview
This PR focuses on enhancing the
relayer-cliby updating state management, improving message execution handling, and refining the code structure for better maintainability and clarity.Detailed summary
HashiMessageExecutionVarsto include timestamp.Summary by CodeRabbit
New Features
Improvements