Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

@mani99brar
Copy link
Contributor

@mani99brar mani99brar commented Oct 27, 2025

Added logic to handle failed resolution scenario and fallback for missed claims in validator-cli

Files updated:

  • snapshot.ts: updated to save snapshot for now new messages if the inbox stateroot is not claimed on outbox.
  • claim.ts: previously sent snapshot's claim is verified againt the outbox claim.
  • validator.ts: outscoped challenge withdrawal transaction before resolving to prevent sending snapshots after a claim is resolved due to claimHash changes.
  • Test files
  • Newly added events and logs
  • graphQueries.ts: added stateRoot field for lastSavedSnapshot query

PR-Codex overview

This PR introduces enhancements to the validator-cli project, focusing on improving claim resolution handling, snapshot management, and event emissions for better bot functionality.

Detailed summary

  • Added event emission for already resolved claims in logger.ts.
  • Introduced CLAIM_ALREADY_RESOLVED event in botEvents.ts.
  • Updated README with clearer bot usage instructions and flags.
  • Reduced RPC_BLOCK_LIMIT from 1000 to 100 in watcher.ts.
  • Enhanced claim resolution logic to emit events for already resolved claims.
  • Updated claim.ts to verify claim hashes against sent snapshots.
  • Modified transaction handlers to streamline transaction submission checks.
  • Added veaOutbox support in various functions and interfaces.
  • Improved snapshot management with new snapshotSavingPeriod constants.
  • Added tests for snapshot functionality, including missed claims handling.

✨ Ask PR-Codex anything about this PR by commenting with /codex {your question}

Summary by CodeRabbit

  • New Features

    • Added snapshot saving period configuration with new --saveSnapshot and --path flags supporting challenger, bridger, or both modes.
    • Enhanced claim resolution state detection with improved handling of already-resolved claims.
  • Bug Fixes

    • Improved transaction submission logic for more reliable claim and challenge operations.
  • Documentation

    • Updated README with clearer validator bot descriptions and configuration examples.
  • Optimization

    • Reduced RPC query block range for improved performance.

@netlify
Copy link

netlify bot commented Oct 27, 2025

Deploy Preview for veascan ready!

Name Link
🔨 Latest commit 4a35176
🔍 Latest deploy log https://app.netlify.com/projects/veascan/deploys/68ff6fd35b5863000806ce9f
😎 Deploy Preview https://deploy-preview-435--veascan.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 27, 2025

Walkthrough

This pull request introduces support for VEA outbox channels in snapshot and claim resolution logic, adjusts epoch period configurations, adds snapshot saving period constraints, refactors transaction submission checks, and includes snapshot hash verification against stored onchain data.

Changes

Cohort / File(s) Change Summary
Documentation
validator-cli/README.md
Updated header from "bots" to "Validator bot", removed watcher file reference, added detailed descriptions of watcher functions (bridger submits snapshots, challenger challenges claims), documented new flags (--saveSnapshot, --path with options challenger/bridger/both), and included example usage for combined and challenger-only modes.
Configuration Constants
validator-cli/src/consts/bridgeRoutes.ts
Changed interface visibility and export structure (Bridge now exported, Network made non-exported), lowered DEVNET epochPeriod from 1800 to 300, added new exported constant snapshotSavingPeriod with per-network values, updated public export list to include bridges, Network, and snapshotSavingPeriod.
Snapshot Logic
validator-cli/src/helpers/snapshot.ts
validator-cli/src/helpers/snapshot.test.ts
Added veaOutbox parameter to SnapshotCheckParams and SaveSnapshotParams; introduced epoch-based timing using snapshotSavingPeriod; replaced hardcoded DEVNET time gating with dynamic epoch-aware calculations; extended test setup to mock veaOutbox and include snapshot state verification; added new test case for missed claims.
Validator Resolution Flow
validator-cli/src/helpers/validator.ts
Added early-exit path in challengeAndResolveClaim when claim is already resolved; extended ResolveFlowParams and handleResolveFlow to include veaOutbox parameter; propagated veaOutbox through claim resolution state fetch chain.
Bot Event Tracking
validator-cli/src/utils/botEvents.ts
validator-cli/src/utils/logger.ts
Added CLAIM_ALREADY_RESOLVED event member to BotEvents enum; added corresponding event listener in logger to emit epoch-specific resolved claim messages.
Claim Verification & Resolution
validator-cli/src/utils/claim.ts
validator-cli/src/utils/claim.test.ts
Introduced claimHash verification between Outbox and SentSnapshot data; added veaOutbox and fetchSentSnapshotData to ClaimResolveStateParams; created helper function getSentSnapshotData to extract and hash snapshot claims; updated snapshots to only be marked sent if hashes match; extended test coverage to validate incorrect snapshot verification.
Graph Query Updates
validator-cli/src/utils/graphQueries.ts
Added timestamp ordering to getSnapshotSentForEpoch fallback; extended SnapshotSavedResponse to include stateRoot field; updated getLastMessageSaved return type from Promise<string> to Promise<{ id: string; stateRoot: string }> with corresponding GraphQL selection changes.
Transaction Submission Handlers
validator-cli/src/utils/transactionHandlers/baseTransactionHandler.ts
validator-cli/src/utils/transactionHandlers/arbToEthHandler.ts
validator-cli/src/utils/transactionHandlers/arbToGnosisHandler.ts
Added new public method toSubmitTransaction to centralize submission feasibility logic; replaced per-method status checks (NOT_MADE/EXPIRED) with unified toSubmitTransaction gate returning boolean; applied consistently to makeClaim, challengeClaim, sendSnapshot, resolveChallengedClaim, and devnetAdvanceState across all handlers.
Watcher Integration
validator-cli/src/watcher.ts
Decreased RPC_BLOCK_LIMIT from 1000 to 100; extended saveSnapshot call to include veaOutbox parameter.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Claim verification logic (claim.ts): Hash verification between multiple data sources requires careful validation of correctness and edge cases
  • Transaction handler refactoring (baseTransactionHandler, arbToEthHandler, arbToGnosisHandler): Centralized submission logic change affects multiple execution paths; verify no regressions in transaction flow
  • Snapshot timing logic (snapshot.ts): Epoch-based calculations replacing hardcoded values need verification across DEVNET and TESTNET configurations
  • Interface propagation: veaOutbox parameter added across multiple signatures (validator.ts, snapshot.ts, claim.ts); verify all call sites properly pass the parameter

Possibly related PRs

Suggested labels

Type: Maintenance :construction:, Package: Bots

Suggested reviewers

  • alcercu
  • jaybuidl

Poem

🐰 Snapshots now dance with outbox grace,
Epochs tick in measured pace,
Claims verified before they're sealed,
Transaction gates more tightly wheeled,
The validator's watch runs deep,
While state roots safely wake from sleep!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title "Fix/handle sent snapshots" directly relates to core functionality changes in the PR, particularly the verification logic in claim.ts that validates previously sent snapshot claims against the outbox claim, and the snapshot-saving improvements in snapshot.ts. The PR description indicates that snapshot handling and verification are central objectives, with supporting changes across multiple files (veaOutbox parameter propagation, transaction handling refactoring, and event logging) designed to enable proper snapshot claim resolution. While the PR includes broader architectural improvements, the title captures the primary focus without being vague or misleading about the changeset.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/handle-sent-snapshots

Comment @coderabbitai help to get the list of available commands and usage tips.

@sonarqubecloud
Copy link

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
validator-cli/src/watcher.ts (1)

144-170: Handler key mismatch causes repeated re-instantiation (state loss).

You read with transactionHandlers[currentEpoch] (line 146) but store with transactionHandlers[txnHandlerKey] (line 168). This drops state between iterations and can trigger duplicate submissions.

     const txnHandlerKey = `${routeConfig[network].veaInbox.address}_${currentEpoch}`;
     const transactionHandler =
-      transactionHandlers[currentEpoch] ||
+      transactionHandlers[txnHandlerKey] ||
       new TransactionHandler({
         network,
         epoch: currentEpoch,
         veaInbox,
         veaOutbox,
         veaInboxProvider,
         veaOutboxProvider,
         veaRouterProvider,
         emitter,
       });
validator-cli/src/helpers/snapshot.test.ts (1)

103-116: fetchLastSavedMessage mock shape likely incorrect vs production.

isSnapshotNeeded expects an object with { id, stateRoot } (used to derive message index and last saved snapshot). Returning a string like "message-0" can mask issues.

Please update test mocks to the expected shape.

- fetchLastSavedMessage = jest.fn().mockResolvedValue("message-0");
+ fetchLastSavedMessage = jest.fn().mockResolvedValue({
+   id: "message-0",
+   stateRoot: "0xdeadbeef",
+});

Based on snapshot.ts snippet.

🧹 Nitpick comments (15)
validator-cli/src/utils/graphQueries.ts (2)

164-171: Guard against empty results and limit nested results to 1 item.

Accessing result.snapshots[0].fallback[0] will throw when no snapshots/fallbacks exist. Also, you can ask The Graph for only the latest fallback.

Apply:

-          snapshots(where: {epoch: ${epoch}, inbox_: { id: "${veaInbox}" }}) {
-            fallback(orderBy: timestamp, orderDirection: desc){
+          snapshots(where: {epoch: ${epoch}, inbox_: { id: "${veaInbox}" }}) {
+            fallback(first: 1, orderBy: timestamp, orderDirection: desc){
               txHash
             }
           }

And add a null-safe return:

-    return result.snapshots[0].fallback[0];
+    const [firstSnapshot] = result.snapshots ?? [];
+    const [firstFallback] = firstSnapshot?.fallback ?? [];
+    return firstFallback;

178-185: Consider backward-compatibility for stateRoot.

If older snapshots lack stateRoot, the static type should reflect that to avoid accidental undefined access at runtime.

 type SnapshotSavedResponse = {
   snapshots: {
-    stateRoot: string;
+    stateRoot?: string;
     messages: {
       id: string;
     }[];
   }[];
 };
validator-cli/src/consts/bridgeRoutes.ts (3)

90-95: Type snapshotSavingPeriod explicitly and document units.

Improves clarity and prevents accidental key drift.

-// For the remaining time in an epoch the bot should save snapshots
-const snapshotSavingPeriod = {
+// For the remaining time (seconds) in an epoch the bot should save snapshots
+const snapshotSavingPeriod: Record<Network, number> = {
   [Network.DEVNET]: 90, // 1m 30s
   [Network.TESTNET]: 600, // 10 mins
 };

96-100: Improve error message and return the local variable.

Tiny polish; aids debugging without adding new validation.

 const getBridgeConfig = (chainId: number): Bridge => {
   const bridge = bridges[chainId];
-  if (!bridge) throw new Error(`Bridge not found for chain`);
-  return bridges[chainId];
+  if (!bridge) throw new Error(`Bridge not found for chainId=${chainId}`);
+  return bridge;
 };

Based on learnings.


14-23: Optional: tighten Bridge shapes (env-var fields).

If strictNullChecks is on, process.env.* are string | undefined. Consider asserting at load time or widening types here to string | undefined and validate upstream. Leaving as-is is acceptable if upper layers already validate.

Based on learnings.

validator-cli/src/watcher.ts (2)

17-17: Make RPC block range configurable (and possibly per-chain).

100 may be too conservative/too aggressive depending on provider. Suggest env override and per-network defaults.

-const RPC_BLOCK_LIMIT = 100; // RPC_BLOCK_LIMIT is the limit of blocks that can be queried at once
+const RPC_BLOCK_LIMIT = Number(process.env.RPC_BLOCK_LIMIT ?? 100); // blocks per getLogs/query

Optionally, add a per-chain map if providers differ.


139-141: Optional: derive currentEpoch from chain time, not local clock.

Local clock skew can misalign epoch. You already have outbox timestamps available.

-const currentEpoch = Math.floor(Date.now() / (1000 * routeConfig[network].epochPeriod));
+const latestOutboxBlock = await veaOutboxProvider.getBlock("latest");
+const currentEpoch = Math.floor(latestOutboxBlock.timestamp / routeConfig[network].epochPeriod);
validator-cli/README.md (2)

32-33: Example command should include the script path for consistency.

Use the same invocation style in both examples to avoid confusion.

-`pm2 start -- --saveSnapshot`
+`pm2 start dist/watcher.js -- --saveSnapshot`

18-27: Clarify defaults and flag interactions.

Document the default for --path and how --saveSnapshot behaves in challenger-only mode (ignored or error).

  • Add: “Default: --path=both.”
  • Add: “--saveSnapshot is only used when the bridger path is active.”
validator-cli/src/utils/transactionHandlers/arbToEthHandler.ts (1)

63-67: Guard against zero/too‑low fee caps from profitability heuristic.

If deposit/(gas*6) underflows to 0, tx may never land. Provide a sane floor or fallback to provider fee data.

-const maxFeePerGas = deposit / (toBigInt(gasEstimate) * BigInt(6));
-let maxPriorityFeePerGas = BigInt(6_667_000_000_000);
+const profitabilityCap = deposit / (toBigInt(gasEstimate) * BigInt(6));
+const minFee = BigInt(1_000_000_000); // 1 gwei floor; tune per network
+const maxFeePerGas = profitabilityCap > 0n ? profitabilityCap : minFee;
+let maxPriorityFeePerGas = 6_667_000_000_000n;
 if (maxPriorityFeePerGas > maxFeePerGas) {
   maxPriorityFeePerGas = maxFeePerGas;
 }

Confirm deposit is in wei (bigint) for all routes so the division stays in consistent units.

validator-cli/src/utils/claim.test.ts (1)

97-99: Typo in comment.

There’s a stray “ß” in the VerificationStarted comment; remove to avoid noise.

-        .mockImplementationOnce(() => Promise.resolve([])); // For VerificationStartedß
+        .mockImplementationOnce(() => Promise.resolve([])); // For VerificationStarted
validator-cli/src/utils/transactionHandlers/arbToGnosisHandler.ts (1)

68-75: Fee cap heuristic: add floor/ceil to avoid non‑includable txs.

Same concern as Arb→Eth: profitability division can yield 0; set a minimum and optionally cap priority fee.

-const maxFeePerGasProfitable = deposit / (toBigInt(gasEstimate) * BigInt(6));
-let maxPriorityFeePerGasMEV = BigInt(6667000000000);
+const cap = deposit / (toBigInt(gasEstimate) * 6n);
+const minFee = 1_000_000_000n; // 1 gwei floor
+const maxFeePerGasProfitable = cap > 0n ? cap : minFee;
+let maxPriorityFeePerGasMEV = 6_667_000_000_000n;
 if (maxPriorityFeePerGasMEV > maxFeePerGasProfitable) {
   maxPriorityFeePerGasMEV = maxFeePerGasProfitable;
 }

Verify deposit unit is wei across networks to ensure fee math correctness.

validator-cli/src/helpers/snapshot.test.ts (2)

124-125: Prefer ethers.ZeroHash over "0x0" to match contract zeros.

Literal "0x0" may not equal ZeroHash. Use the canonical constant to prevent false negatives.

-      veaInbox.snapshots.mockResolvedValue("0x0");
+      const { ZeroHash } = await import("ethers");
+      veaInbox.snapshots.mockResolvedValue(ZeroHash);

193-201: Test name reads opposite of its assertion.

It asserts that no snapshot is saved when snapshotNeeded is false. Rename for clarity.

-it("should not save snapshot if snapshot is needed", async () => {
+it("should not save snapshot if snapshot is NOT needed", async () => {
validator-cli/src/helpers/validator.ts (1)

77-85: Early-exit is fine; add stale-claim guard before withdrawing.

claim.honest !== 0 short‑circuits correctly, but claim may be stale. Before calling withdrawChallengeDeposit (Line 81), re‑fetch honest or rely on a quick resolve-state check to avoid unnecessary reverts if state flipped post-fetch.

Would you prefer a lightweight re-query of claimHashes/claim here, or to let the handler’s idempotency handle no-ops?

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 571a1eb and 4a35176.

📒 Files selected for processing (14)
  • validator-cli/README.md (2 hunks)
  • validator-cli/src/consts/bridgeRoutes.ts (6 hunks)
  • validator-cli/src/helpers/snapshot.test.ts (11 hunks)
  • validator-cli/src/helpers/snapshot.ts (4 hunks)
  • validator-cli/src/helpers/validator.ts (5 hunks)
  • validator-cli/src/utils/botEvents.ts (1 hunks)
  • validator-cli/src/utils/claim.test.ts (5 hunks)
  • validator-cli/src/utils/claim.ts (5 hunks)
  • validator-cli/src/utils/graphQueries.ts (3 hunks)
  • validator-cli/src/utils/logger.ts (1 hunks)
  • validator-cli/src/utils/transactionHandlers/arbToEthHandler.ts (5 hunks)
  • validator-cli/src/utils/transactionHandlers/arbToGnosisHandler.ts (5 hunks)
  • validator-cli/src/utils/transactionHandlers/baseTransactionHandler.ts (5 hunks)
  • validator-cli/src/watcher.ts (2 hunks)
🧰 Additional context used
🧠 Learnings (11)
📚 Learning: 2024-12-02T10:16:56.825Z
Learnt from: madhurMongia
PR: kleros/vea#359
File: validator-cli/src/ArbToEth/watcherArbToGnosis.ts:829-840
Timestamp: 2024-12-02T10:16:56.825Z
Learning: In the `validator-cli/src/ArbToEth/watcherArbToGnosis.ts` file, avoid adding redundant error handling in functions like `reconstructChallengeProgress` when error handling is already adequately managed.

Applied to files:

  • validator-cli/src/utils/transactionHandlers/arbToEthHandler.ts
  • validator-cli/src/utils/transactionHandlers/arbToGnosisHandler.ts
📚 Learning: 2025-06-05T12:17:22.931Z
Learnt from: mani99brar
PR: kleros/vea#424
File: validator-cli/src/utils/transactionHandlers/arbToEthHandler.ts:128-131
Timestamp: 2025-06-05T12:17:22.931Z
Learning: In the BaseTransactionHandler class, all transaction properties are expected to be initialized to null. When subclasses like ArbToEthDevnetTransactionHandler use spread syntax to extend the transactions object (e.g., `{ ...this.transactions, devnetAdvanceStateTxn: null }`), there's no issue with state loss since the base transactions are null initially.

Applied to files:

  • validator-cli/src/utils/transactionHandlers/arbToEthHandler.ts
📚 Learning: 2024-12-09T09:42:34.067Z
Learnt from: mani99brar
PR: kleros/vea#370
File: bridger-cli/src/utils/transactionHandler.ts:64-77
Timestamp: 2024-12-09T09:42:34.067Z
Learning: In the `TransactionHandler` class (`bridger-cli/src/utils/transactionHandler.ts`), it's acceptable to let methods like `makeClaim` fail without additional error handling.

Applied to files:

  • validator-cli/src/utils/transactionHandlers/arbToEthHandler.ts
📚 Learning: 2024-11-27T04:18:05.872Z
Learnt from: mani99brar
PR: kleros/vea#344
File: validator-cli/src/ArbToEth/watcherArbToEth.ts:732-745
Timestamp: 2024-11-27T04:18:05.872Z
Learning: In `validator-cli/src/ArbToEth/watcherArbToEth.ts`, input validation inside the `hashClaim` function is unnecessary because the upper layer ensures valid input before calling it.

Applied to files:

  • validator-cli/src/utils/transactionHandlers/arbToEthHandler.ts
📚 Learning: 2025-06-05T12:17:21.782Z
Learnt from: mani99brar
PR: kleros/vea#424
File: validator-cli/src/helpers/claimer.ts:72-76
Timestamp: 2025-06-05T12:17:21.782Z
Learning: In validator-cli/src/helpers/claimer.ts, the outboxStateRoot captured at the beginning of the checkAndClaim function won't change for the epoch being claimed, so there's no race condition concern when reusing it in makeClaim/makeClaimDevnet functions.

Applied to files:

  • validator-cli/src/utils/transactionHandlers/arbToEthHandler.ts
  • validator-cli/src/helpers/snapshot.test.ts
  • validator-cli/src/watcher.ts
  • validator-cli/src/utils/claim.ts
  • validator-cli/src/utils/transactionHandlers/arbToGnosisHandler.ts
  • validator-cli/src/utils/claim.test.ts
  • validator-cli/src/helpers/validator.ts
📚 Learning: 2024-11-20T11:50:15.304Z
Learnt from: madhurMongia
PR: kleros/vea#359
File: contracts/src/test/ArbToGnosis/VeaInboxArbToGnosisMock.sol:27-38
Timestamp: 2024-11-20T11:50:15.304Z
Learning: In `VeaInboxArbToGnosisMock.sendSnapshot`, `epochPeriod` is guaranteed to be non-zero, so a check for `epochPeriod > 0` is unnecessary.

Applied to files:

  • validator-cli/src/helpers/snapshot.ts
  • validator-cli/src/helpers/snapshot.test.ts
📚 Learning: 2024-12-09T10:54:57.068Z
Learnt from: mani99brar
PR: kleros/vea#370
File: bridger-cli/src/utils/claim.ts:56-69
Timestamp: 2024-12-09T10:54:57.068Z
Learning: In the TypeScript file 'bridger-cli/src/utils/claim.ts', the upper layers handle input validation for the 'hashClaim' function. Therefore, explicit input validation within 'hashClaim' is not necessary.

Applied to files:

  • validator-cli/src/utils/claim.ts
📚 Learning: 2024-12-10T04:59:10.224Z
Learnt from: mani99brar
PR: kleros/vea#370
File: bridger-cli/src/utils/claim.ts:23-32
Timestamp: 2024-12-10T04:59:10.224Z
Learning: In `bridger-cli/src/utils/claim.ts`, within the `fetchClaim` function, when `claimData` is undefined, avoid initializing it with default values, as this can result in incorrect claim values and make issues harder to identify.

Applied to files:

  • validator-cli/src/utils/claim.ts
📚 Learning: 2024-11-27T10:48:48.433Z
Learnt from: madhurMongia
PR: kleros/vea#359
File: validator-cli/src/ArbToEth/watcherArbToGnosis.ts:24-24
Timestamp: 2024-11-27T10:48:48.433Z
Learning: In the `validator-cli` codebase, both `arbitrumToEth` and `arbitrumToGnosis` modules use the same `ClaimStruct`, so importing `ClaimStruct` from either module is acceptable.

Applied to files:

  • validator-cli/src/utils/claim.ts
  • validator-cli/src/utils/claim.test.ts
📚 Learning: 2024-12-10T08:00:35.645Z
Learnt from: mani99brar
PR: kleros/vea#370
File: bridger-cli/src/consts/bridgeRoutes.ts:28-30
Timestamp: 2024-12-10T08:00:35.645Z
Learning: In `bridger-cli/src/consts/bridgeRoutes.ts`, additional validation in the `getBridgeConfig` function is unnecessary because error handling and validation are managed by upper layers in the application.

Applied to files:

  • validator-cli/src/consts/bridgeRoutes.ts
📚 Learning: 2024-12-09T09:40:28.400Z
Learnt from: mani99brar
PR: kleros/vea#370
File: bridger-cli/src/utils/transactionHandler.ts:13-13
Timestamp: 2024-12-09T09:40:28.400Z
Learning: In `bridger-cli/src/utils/transactionHandler.ts`, the `veaOutbox` implementations differ for each chain, but a common interface should be defined for type checks to enhance type safety.

Applied to files:

  • validator-cli/src/consts/bridgeRoutes.ts
🧬 Code graph analysis (2)
validator-cli/src/utils/transactionHandlers/baseTransactionHandler.ts (1)
validator-cli/src/utils/errors.ts (1)
  • ClaimNotSetError (70-70)
validator-cli/src/helpers/snapshot.test.ts (2)
validator-cli/src/helpers/snapshot.ts (2)
  • isSnapshotNeeded (59-98)
  • saveSnapshot (28-57)
validator-cli/src/consts/bridgeRoutes.ts (2)
  • snapshotSavingPeriod (102-102)
  • Network (102-102)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Analyze (javascript)
  • GitHub Check: dependency-review
  • GitHub Check: test
🔇 Additional comments (13)
validator-cli/src/utils/logger.ts (1)

144-146: LGTM: added log for already-resolved claims.

Event wiring and message are clear and consistent.

validator-cli/src/utils/botEvents.ts (1)

35-35: LGTM: new BotEvents member.

CLAIM_ALREADY_RESOLVED is well-named and used by logger/validator.

validator-cli/src/watcher.ts (1)

160-165: LGTM: pass veaOutbox into saveSnapshot.

Matches the broader PR changes adding outbox-aware snapshot logic.

validator-cli/src/utils/claim.test.ts (2)

235-279: Good: pass veaOutbox and fetchSentSnapshotData through resolve-state paths.

This aligns tests with production signatures and covers the cross‑channel check.


299-307: Nice negative test for incorrect snapshot hash.

Covers the regression where a wrong snapshot was sent but execution remains pending.

validator-cli/src/utils/transactionHandlers/arbToGnosisHandler.ts (1)

44-46: Unified gating applied correctly.

Early returns keep methods idempotent and avoid duplicate submissions.

Also applies to: 60-62, 88-90, 101-103, 137-139

validator-cli/src/helpers/snapshot.test.ts (2)

7-7: Good: include veaOutbox in tests.

State root checks across inbox/outbox are now exercised.

Also applies to: 21-23


220-247: Devnet time-window logic LGTM.

Dynamic calculation using snapshotSavingPeriod improves robustness across config changes.

validator-cli/src/utils/transactionHandlers/arbToEthHandler.ts (1)

29-31: Centralized gating pattern verified—no legacy status checks found outside base class.

The script output confirms that direct status comparisons are properly encapsulated within baseTransactionHandler.ts, and no orphaned TransactionStatus.NOT_MADE|EXPIRED or direct checkTransactionStatus() calls exist in handler implementations. The early-return pattern via toSubmitTransaction() is consistently applied as intended.

validator-cli/src/helpers/validator.ts (1)

106-112: veaOutbox propagation through resolve flow looks good.

Signature and call-site updates are consistent, enabling cross‑channel checks during resolution.

Also applies to: 154-160, 168-174

validator-cli/src/helpers/snapshot.ts (2)

40-46: Time gating reads well.

Epoch boundary wait using snapshotSavingPeriod[network] is sensible to avoid early saves.


47-53: Good: include epochPeriod in decision; cross‑check with current outbox stateRoot.

Passing epochPeriod and checking inbox.snapshots(epochNow) vs outbox.stateRoot() is a solid additional guard.

Also applies to: 89-96

validator-cli/src/utils/claim.ts (1)

66-74: Reject the review comment - it conflicts with the established codebase pattern.

The current implementation is correct and aligns with the documented learning for this codebase. Here's why:

  1. Contract Type: veaOutbox is typed as any (line 24), not a properly typed contract interface.

  2. Current Implementation is Correct: The direct log property access correctly extracts event data:

    • claimLogs[0].data_stateRoot (not indexed, stored in data field) ✓
    • claimLogs[0].topics[1]_claimer (indexed, topics[1]) ✓
    • challengeLogs[0].topics[2]_challenger (indexed, topics[2]) ✓
  3. Learning Applies: Per the retrieved learning from mani99brar (PR #396), "direct access to log properties is preferred when the contract variable is typed as any, rather than using interface parsing methods like parseLog()." This is exactly the case here.

  4. Why parseLog() Isn't Recommended Here:

    • The contract is any typed, so calling veaOutbox.interface.parseLog() would fail or be unreliable
    • Direct property access is the practical solution for any typed variables
    • The current implementation correctly handles the indexed/non-indexed field distinction

The code does not need changes. The review comment misses the specific context that this codebase intentionally uses direct log access for any typed contracts.

Likely an incorrect or invalid review comment.

Comment on lines +117 to 132
it.only("should return true if claim was missed in previous epoch", async () => {
count = 1;
let currentCount = 3;
veaInbox.count.mockResolvedValue(currentCount);
fetchLastSavedMessage = jest.fn().mockResolvedValue("message-3");
veaInbox.queryFilter.mockRejectedValue(new Error("queryFilter failed"));
veaOutbox.stateRoot.mockResolvedValue("0xabcde");
veaInbox.snapshots.mockResolvedValue("0x0");
const params = {
chainId,
veaInbox,
veaOutbox,
count,
fetchLastSavedMessage,
} as any;
expect(isSnapshotNeeded(params)).resolves.toEqual({
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Remove it.only to avoid running a single test only.

This will cripple the test suite.

-it.only("should return true if claim was missed in previous epoch", async () => {
+it("should return true if claim was missed in previous epoch", async () => {

Run this to detect any lingering “only” in tests:


🏁 Script executed:

#!/bin/bash
rg -nP '\b(it|describe|test)\.only\(' -g 'validator-cli/**' -S

Length of output: 132


Remove it.only() from test at line 117 to restore full test suite execution.

The it.only() prevents other tests from running and will break the test suite. Change line 117 from it.only(...) to it(...).

-it.only("should return true if claim was missed in previous epoch", async () => {
+it("should return true if claim was missed in previous epoch", async () => {
📝 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
it.only("should return true if claim was missed in previous epoch", async () => {
count = 1;
let currentCount = 3;
veaInbox.count.mockResolvedValue(currentCount);
fetchLastSavedMessage = jest.fn().mockResolvedValue("message-3");
veaInbox.queryFilter.mockRejectedValue(new Error("queryFilter failed"));
veaOutbox.stateRoot.mockResolvedValue("0xabcde");
veaInbox.snapshots.mockResolvedValue("0x0");
const params = {
chainId,
veaInbox,
veaOutbox,
count,
fetchLastSavedMessage,
} as any;
expect(isSnapshotNeeded(params)).resolves.toEqual({
it("should return true if claim was missed in previous epoch", async () => {
count = 1;
let currentCount = 3;
veaInbox.count.mockResolvedValue(currentCount);
fetchLastSavedMessage = jest.fn().mockResolvedValue("message-3");
veaInbox.queryFilter.mockRejectedValue(new Error("queryFilter failed"));
veaOutbox.stateRoot.mockResolvedValue("0xabcde");
veaInbox.snapshots.mockResolvedValue("0x0");
const params = {
chainId,
veaInbox,
veaOutbox,
count,
fetchLastSavedMessage,
} as any;
expect(isSnapshotNeeded(params)).resolves.toEqual({
🤖 Prompt for AI Agents
In validator-cli/src/helpers/snapshot.test.ts around lines 117 to 132, the test
is marked with it.only which prevents the rest of the test suite from running;
change the test declaration from it.only(...) to it(...) so the test runs
normally with the full suite, save the file, and run the test runner to verify
all tests execute.

Comment on lines +73 to 88
let lastSavedSnapshot: string;
try {
const saveSnapshotLogs = await veaInbox.queryFilter(veaInbox.filters.SnapshotSaved());
lastSavedCount = Number(saveSnapshotLogs[saveSnapshotLogs.length - 1].args[2]);
lastSavedSnapshot = saveSnapshotLogs[saveSnapshotLogs.length - 1].args[1];
} catch {
const veaInboxAddress = await veaInbox.getAddress();
const lastSavedMessageId = await fetchLastSavedMessage(veaInboxAddress, chainId);
const { id: lastSavedMessageId, stateRoot: lastSavedStateRoot } = await fetchLastSavedMessage(
veaInboxAddress,
chainId
);
const messageIndex = extractMessageIndex(lastSavedMessageId);
lastSavedSnapshot = lastSavedStateRoot;
// adding 1 to the message index to get the last saved count
lastSavedCount = messageIndex + 1;
lastSavedCount = messageIndex;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Handle empty Graph response and align comment with logic.

  • If fetchLastSavedMessage returns null/undefined, destructuring throws. Provide safe defaults (lastSavedCount = 0, lastSavedSnapshot = ZeroHash).
  • The comment says “adding 1 to the message index,” but code doesn’t add 1. Either remove the note or implement the +1 if correct.

Apply this diff:

   } catch {
     const veaInboxAddress = await veaInbox.getAddress();
-    const { id: lastSavedMessageId, stateRoot: lastSavedStateRoot } = await fetchLastSavedMessage(
-      veaInboxAddress,
-      chainId
-    );
-    const messageIndex = extractMessageIndex(lastSavedMessageId);
-    lastSavedSnapshot = lastSavedStateRoot;
-    // adding 1 to the message index to get the last saved count
-    lastSavedCount = messageIndex;
+    const lastSaved = await fetchLastSavedMessage(veaInboxAddress, chainId);
+    if (!lastSaved) {
+      lastSavedSnapshot = ZeroHash;
+      lastSavedCount = 0;
+    } else {
+      const messageIndex = extractMessageIndex(lastSaved.id);
+      lastSavedSnapshot = lastSaved.stateRoot;
+      // If the index is zero-based, keep as-is; otherwise, add +1.
+      lastSavedCount = messageIndex; // update comment accordingly if needed
+    }
   }

Confirm whether the graph’s message index is zero- or one-based so we can fix the comment or add +1 appropriately.


I need to verify the concerns raised in the review comment. Let me explore the codebase to understand the context.


🏁 Script executed:

#!/bin/bash
# Find and display the snapshot.ts file content around the target lines
fd snapshot.ts --type f | head -5

Length of output: 37


🏁 Script executed:

#!/bin/bash
# Look for fetchLastSavedMessage function definition
rg -n "fetchLastSavedMessage" --type ts -A 10 -B 2

Length of output: 12707


🏁 Script executed:

#!/bin/bash
# Search for ZeroHash constant
rg -n "ZeroHash" --type ts -B 2 -A 2

Length of output: 5106


🏁 Script executed:

#!/bin/bash
# Search for tests related to snapshot
fd -e test.ts -e spec.ts | xargs grep -l "snapshot" 2>/dev/null | head -3

Length of output: 128


🏁 Script executed:

#!/bin/bash
# Check for Graph-related imports and message index usage
rg -n "messageIndex\|message.*index" --type ts -C 3

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Find getLastMessageSaved implementation
rg -n "getLastMessageSaved" --type ts -A 15 -B 2

Length of output: 5164


🏁 Script executed:

#!/bin/bash
# Find extractMessageIndex implementation
rg -n "extractMessageIndex" --type ts -A 10 -B 2

Length of output: 2109


🏁 Script executed:

#!/bin/bash
# Check graphQueries file
fd graphQueries --type f

Length of output: 39


Fix unsafe destructuring and misleading comment in catch block.

The code at lines 80-88 has two issues:

  1. Unsafe destructuring: getLastMessageSaved can return undefined when the graph query returns fewer than 2 snapshots or no messages, but the current code destructures without checking for null, which will throw an error.

  2. Misleading comment: The comment at line 86 states "adding 1 to the message index" but the code assigns lastSavedCount = messageIndex directly without incrementing.

Apply the suggested diff to add a null check and provide safe defaults (using the already-imported ZeroHash constant and initializing count to 0). Update the comment to reflect the actual logic or clarify if +1 is needed based on message index semantics.

Comment on lines +167 to +176
sentSnapshotLogs.sort((a, b) =>
a.blockNumber !== b.blockNumber ? b.blockNumber - a.blockNumber : b.logIndex - a.logIndex
);
// Add logic to check if the sent message has the actual claimHash or not
const expectedClaimHash = await fetchSentSnapshotData(
sentSnapshotLogs[0].transactionHash,
veaInboxProvider,
veaInbox.interface
);
const claimHash = await veaOutbox.claimHashes(epoch);
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Guard against null expectedClaimHash and fail fast when mismatched.

If getSentSnapshotData can return null, comparisons must short‑circuit. Also, return early on mismatch to avoid ambiguous state.

Apply this diff:

-      const expectedClaimHash = await fetchSentSnapshotData(
+      const expectedClaimHash = await fetchSentSnapshotData(
         sentSnapshotLogs[0].transactionHash,
         veaInboxProvider,
         veaInbox.interface
       );
-      const claimHash = await veaOutbox.claimHashes(epoch);
-
-      if (claimHash === expectedClaimHash) {
+      if (!expectedClaimHash) return claimResolveState;
+      const claimHash = await veaOutbox.claimHashes(epoch);
+      if (claimHash === expectedClaimHash) {
         claimResolveState.sendSnapshot.status = true;
         claimResolveState.sendSnapshot.txHash = sentSnapshotLogs[0].transactionHash;
       } else {
         return claimResolveState;
       }
@@
-      const expectedClaimHash = await fetchSentSnapshotData(
+      const expectedClaimHash = await fetchSentSnapshotData(
         sentSnapshotFromGraph.txHash,
         veaInboxProvider,
         veaInbox.interface
       );
-      const claimHash = await veaOutbox.claimHashes(epoch);
-      if (claimHash === expectedClaimHash) {
+      if (!expectedClaimHash) return claimResolveState;
+      const claimHash = await veaOutbox.claimHashes(epoch);
+      if (claimHash === expectedClaimHash) {
         claimResolveState.sendSnapshot.status = true;
         claimResolveState.sendSnapshot.txHash = sentSnapshotFromGraph.txHash;
       } else {
         return claimResolveState;
       }

Also applies to: 178-184, 190-201

🤖 Prompt for AI Agents
In validator-cli/src/utils/claim.ts around lines 167-176 (also apply same checks
to 178-184 and 190-201), guard against a null expectedClaimHash returned from
fetchSentSnapshotData by checking if expectedClaimHash is falsy and immediately
throwing or returning an error; then compare expectedClaimHash to the claimHash
and if they differ fail fast (throw or return) instead of proceeding, ensuring
any log messages include both values for clarity and avoiding further processing
when the hashes don’t match.

Comment on lines +236 to +247
const getSentSnapshotData = async (txHash: string, provider: JsonRpcProvider, inboxInterface: any): Promise<string> => {
const tx = await provider.getTransaction(txHash);
if (!tx) return null;

// Parse the transaction calldata to identify function + args
const parsed = inboxInterface.parseTransaction({ data: tx.data });
const args = parsed.args;
const claimTuple = args[1] as ClaimStruct;
const expectedClaimHash = hashClaim(claimTuple);
return expectedClaimHash;
};

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Fix return type and make parsing robust in getSentSnapshotData.

It returns null on missing tx but the signature promises string. Also, assuming the claim tuple at args[1] is brittle.

Apply this diff:

-const getSentSnapshotData = async (txHash: string, provider: JsonRpcProvider, inboxInterface: any): Promise<string> => {
+const getSentSnapshotData = async (
+  txHash: string,
+  provider: JsonRpcProvider,
+  inboxInterface: any
+): Promise<string | null> => {
   const tx = await provider.getTransaction(txHash);
-  if (!tx) return null;
+  if (!tx) return null;
 
-  // Parse the transaction calldata to identify function + args
-  const parsed = inboxInterface.parseTransaction({ data: tx.data });
-  const args = parsed.args;
-  const claimTuple = args[1] as ClaimStruct;
-  const expectedClaimHash = hashClaim(claimTuple);
-  return expectedClaimHash;
+  try {
+    // Parse the transaction calldata to identify function + args
+    const parsed = inboxInterface.parseTransaction({ data: tx.data });
+    const args = parsed.args;
+    // Support both sendSnapshot(epoch, claim) and sendSnapshot(claim) shapes
+    const maybeClaim = Array.isArray(args) && args.length > 0
+      ? (args.length > 1 ? args[1] : args[0])
+      : null;
+    if (!maybeClaim) return null;
+    const expectedClaimHash = hashClaim(maybeClaim as ClaimStruct);
+    return expectedClaimHash;
+  } catch {
+    return null;
+  }
 };
🤖 Prompt for AI Agents
In validator-cli/src/utils/claim.ts around lines 236 to 247, the function
getSentSnapshotData declares a Promise<string> but returns null when the
transaction is missing and assumes args[1] exists; update the signature to
Promise<string | null>, return null when tx is not found, wrap parsing in a
try/catch to handle parseTransaction errors, verify parsed and parsed.args exist
and that args.length > 1 before accessing args[1], validate that args[1] matches
the expected ClaimStruct shape (or bail with null), and only then compute and
return hashClaim(claimTuple).

Comment on lines +192 to 207
const getLastMessageSaved = async (veaInbox: string, chainId: number): Promise<{ id: string; stateRoot: string }> => {
const subgraph = getInboxSubgraphUrl(chainId);
const result: SnapshotSavedResponse = await request(
`${subgraph}`,
`{
snapshots(first:2, orderBy:timestamp,orderDirection:desc, where:{inbox:"${veaInbox}"}) {
stateRoot
messages(first: 1,orderBy:timestamp,orderDirection:desc){
id
}
}
}`
);
if (result.snapshots.length < 2 || result.snapshots[1].messages.length === 0) return;
return result.snapshots[1].messages[0].id;
return { id: result.snapshots[1].messages[0].id, stateRoot: result.snapshots[1].stateRoot };
};
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Return type mismatch and inconsistent where filter; add null-safety.

Function can return undefined, but signature forbids it. Also align where with the other query (inbox_: { id: ... }) and guard array access.

-const getLastMessageSaved = async (veaInbox: string, chainId: number): Promise<{ id: string; stateRoot: string }> => {
+const getLastMessageSaved = async (
+  veaInbox: string,
+  chainId: number
+): Promise<{ id: string; stateRoot: string } | undefined> => {
   const subgraph = getInboxSubgraphUrl(chainId);
   const result: SnapshotSavedResponse = await request(
     `${subgraph}`,
     `{
-      snapshots(first:2, orderBy:timestamp,orderDirection:desc, where:{inbox:"${veaInbox}"}) {
+      snapshots(first: 2, orderBy: timestamp, orderDirection: desc, where: { inbox_: { id: "${veaInbox}" } }) {
         stateRoot
         messages(first: 1,orderBy:timestamp,orderDirection:desc){
           id 
         }
       }
     }`
   );
-  if (result.snapshots.length < 2 || result.snapshots[1].messages.length === 0) return;
-  return { id: result.snapshots[1].messages[0].id, stateRoot: result.snapshots[1].stateRoot };
+  const secondLatest = result.snapshots?.[1];
+  const messageId = secondLatest?.messages?.[0]?.id;
+  const stateRoot = secondLatest?.stateRoot;
+  if (!messageId || !stateRoot) return undefined;
+  return { id: messageId, stateRoot };
 };

Run to find call sites expecting the old string return to update them:


🏁 Script executed:

#!/bin/bash
rg -nP --type=ts -C2 '\bgetLastMessageSaved\s*\('

Length of output: 0


I need to verify whether the file and function exist in the codebase, and locate the call sites. Let me search more broadly:


---

🏁 Script executed:

```shell
#!/bin/bash
# Find the graphQueries.ts file
fd -type f -name "graphQueries.ts"

Length of output: 170


🏁 Script executed:

#!/bin/bash
# Search for getLastMessageSaved function definition and calls
rg -nP "getLastMessageSaved" --type=ts

Length of output: 513


🏁 Script executed:

#!/bin/bash
# Check file structure around validator-cli
fd -type f "graphQueries" 

Length of output: 170



---

🏁 Script executed:

```shell
#!/bin/bash
# View the snapshot.ts file to understand how getLastMessageSaved is used
cat validator-cli/src/helpers/snapshot.ts

Length of output: 3668


🏁 Script executed:

#!/bin/bash
# Get context around the function definition and the return statement
cat validator-cli/src/utils/graphQueries.ts | sed -n '192,207p'

Length of output: 657


🏁 Script executed:

#!/bin/bash
# Search for how the result of getLastMessageSaved or fetchLastSavedMessage is used
rg -nP "fetchLastSavedMessage" validator-cli/src/helpers/snapshot.ts -A5 -B2

Length of output: 874



---

🏁 Script executed:

```shell
#!/bin/bash
# Verify snapshot.ts is the only direct or indirect caller
rg -n "getLastMessageSaved\|fetchLastSavedMessage" validator-cli/src --type=ts -C1

Length of output: 0



---

🏁 Script executed:

```shell
#!/bin/bash
# Simple search for all references to getLastMessageSaved
rg "getLastMessageSaved" validator-cli/src

Length of output: 497


🏁 Script executed:

#!/bin/bash
# Also search for fetchLastSavedMessage usage patterns
rg "fetchLastSavedMessage" validator-cli/src

Length of output: 1529


Return type mismatch requires updating caller; function can return undefined.

The function getLastMessageSaved (graphQueries.ts:192) can return undefined at line 203, but its type signature forbids this. The caller in snapshot.ts (line 80–83) destructures the result without null-checks and will crash at runtime.

Update the return type to Promise<{ id: string; stateRoot: string } | undefined> and guard the caller:

const getLastMessageSaved = async (veaInbox: string, chainId: number): Promise<{ id: string; stateRoot: string } | undefined> => {

In snapshot.ts, add null-safety where fetchLastSavedMessage is called (line 80–83):

-  const { id: lastSavedMessageId, stateRoot: lastSavedStateRoot } = await fetchLastSavedMessage(
+  const result = await fetchLastSavedMessage(
     veaInboxAddress,
     chainId
   );
+  if (!result) return { snapshotNeeded: false, latestCount: currentCount };
+  const { id: lastSavedMessageId, stateRoot: lastSavedStateRoot } = result;

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In validator-cli/src/utils/graphQueries.ts around lines 192 to 207 the function
getLastMessageSaved can return undefined (it returns nothing when snapshots are
missing) but its signature is Promise<{ id: string; stateRoot: string }>, so
update the signature to Promise<{ id: string; stateRoot: string } | undefined>
and keep the existing behavior; then in snapshot.ts where fetchLastSavedMessage
is consumed (around lines 80–83) add a null-check before destructuring the
result (handle the undefined case by early return or error handling) to avoid
runtime crashes.

Comment on lines +192 to +201
public async toSubmitTransaction(
trnx: Transaction | null,
contract: ContractType,
currentTime: number
): Promise<boolean> {
const status = await this.checkTransactionStatus(trnx, contract, currentTime);
if (status === TransactionStatus.PENDING || status === TransactionStatus.NOT_FINAL) return false;
return true;
}

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Prevent re‑submitting after a FINAL tx (gas burn / revert risk).

toSubmitTransaction returns true for FINAL, enabling duplicate submissions of already-finalized actions (e.g., withdraws, verifications, saves). Block on FINAL instead; only allow when NOT_MADE or EXPIRED.

Apply this diff:

   public async toSubmitTransaction(
     trnx: Transaction | null,
     contract: ContractType,
     currentTime: number
   ): Promise<boolean> {
     const status = await this.checkTransactionStatus(trnx, contract, currentTime);
-    if (status === TransactionStatus.PENDING || status === TransactionStatus.NOT_FINAL) return false;
-    return true;
+    // Submit only if nothing was sent yet or the previous attempt expired.
+    if (status === TransactionStatus.NOT_MADE || status === TransactionStatus.EXPIRED) return true;
+    // Block while pending, not-final, or already final.
+    return false;
   }

No call‑site changes needed; behavior becomes safe by default.

Also applies to: 206-209, 235-237, 259-265, 279-285, 298-300

🤖 Prompt for AI Agents
In validator-cli/src/utils/transactionHandlers/baseTransactionHandler.ts around
lines 192-201 (and similarly update the other occurrences at 206-209, 235-237,
259-265, 279-285, 298-300), the method currently treats FINAL as allowable and
returns true, which permits re-submission of already-finalized transactions;
change the condition so the function only returns true when the status is
NOT_MADE or EXPIRED and returns false for PENDING, NOT_FINAL, and FINAL; update
each affected toSubmitTransaction (and equivalent) locations to explicitly check
for NOT_MADE || EXPIRED -> return true, otherwise return false.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants