Skip to content

fix(sui): process multiple gateway events per tx#4596

Open
alan747271363-art wants to merge 2 commits into
zeta-chain:mainfrom
alan747271363-art:codex/sui-multiple-gateway-events
Open

fix(sui): process multiple gateway events per tx#4596
alan747271363-art wants to merge 2 commits into
zeta-chain:mainfrom
alan747271363-art:codex/sui-multiple-gateway-events

Conversation

@alan747271363-art
Copy link
Copy Markdown

@alan747271363-art alan747271363-art commented May 13, 2026

Summary

  • allow Sui inbound observer to process Gateway events with non-zero event indexes
  • keep each Gateway event mapped to its own inbound vote using the existing EventIndex field
  • cover both block-scan and inbound-tracker recovery paths for multiple events in one Sui transaction

Tests

  • go test -tags pebbledb,ledger,test ./zetaclient/chains/sui/observer

Closes #4584

Greptile Summary

This PR fixes the Sui inbound observer to correctly handle transactions that emit more than one gateway event (e.g. a contract depositing to multiple accounts in a single PTB). Previously a hard guard rejected any event whose EventIndex was non-zero, which silently skipped all but the first event in such transactions.

  • Removes the event.EventIndex != 0 early-return in processInboundEvent, allowing every gateway event in a transaction to be parsed, validated, and voted on independently using its own EventIndex.
  • Adds test coverage for the block-scan (ObserveInbound) and recovery (ProcessInboundTrackers) paths, verifying that two distinct inbound votes are produced with the correct receiver, amount, and event index for a two-event transaction.
  • Introduces SampleEventWithSeq test helper to construct mock events with an arbitrary sequence number.

Confidence Score: 4/5

Safe to merge; the fix is focused, cursor advancement and vote idempotency are handled correctly, and both recovery paths are covered by tests.

The production change is a five-line deletion of a guard that blocked multi-event transactions. Cursor management, compliance skipping, and errTxNotFound/errVoteInbound retry semantics all continue to work correctly for the multi-event case. The one non-critical concern is that the block-scan path issues a redundant SuiGetTransactionBlock call per event for the same digest; the test even hard-codes two identical OnGetTx registrations for the same hash, confirming the behaviour is present but benign at current scale.

No files require special attention; inbound.go is the only production change and it is small and well-tested.

Important Files Changed

Filename Overview
zetaclient/chains/sui/observer/inbound.go Removes the guard that rejected events with EventIndex != 0, enabling correct processing of multiple gateway events per transaction in both block-scan and tracker paths.
zetaclient/chains/sui/observer/observer_test.go Adds two new integration-style test cases for multi-event transactions covering both ObserveInbound and ProcessInboundTrackers, and introduces SampleEventWithSeq helper to construct events with a configurable sequence number.

Sequence Diagram

sequenceDiagram
    participant Observer
    participant SuiRPC
    participant ZetaCore

    Note over Observer: Block-scan path (ObserveInbound)
    Observer->>SuiRPC: QueryModuleEvents(cursor)
    SuiRPC-->>Observer: "[Event{TxA, seq=0}, Event{TxA, seq=1}, ...]"

    loop for each event
        Observer->>SuiRPC: SuiGetTransactionBlock(TxA)
        SuiRPC-->>Observer: tx (checkpoint, effects)
        Observer->>ZetaCore: "VoteInbound(TxA, EventIndex=N)"
        Observer->>Observer: setCursor(packageID, event.Id)
    end

    Note over Observer: Tracker path (ProcessInboundTrackers)
    Observer->>SuiRPC: "SuiGetTransactionBlock(TxA, ShowEvents=true)"
    SuiRPC-->>Observer: "tx with Events[seq=0, seq=1]"

    loop for each tx.Event
        Observer->>ZetaCore: "VoteInbound(TxA, EventIndex=N)"
    end
Loading

Comments Outside Diff (1)

  1. zetaclient/chains/sui/observer/inbound.go, line 73-102 (link)

    P2 Duplicate RPC call per event for same tx in block-scan path

    When a single transaction emits N gateway events, observeGatewayInbound will call SuiGetTransactionBlock once per event (N times in total), even though all events share the same TxDigest. The test explicitly registers OnGetTx twice for the same hash (ts.OnGetTx(txHash, "10000", true, false, nil) × 2) confirming the current behavior.

    Under normal operation this is just wasteful, but it becomes a concern at scale when the same tx produces many events and the RPC node is rate-limited or has latency. Consider caching the fetched *models.SuiTransactionBlockResponse by TxDigest within the scan loop (a simple map[string]*tx is enough) and passing the cached value to processInboundEvent on subsequent events for the same digest.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: zetaclient/chains/sui/observer/inbound.go
    Line: 73-102
    
    Comment:
    **Duplicate RPC call per event for same tx in block-scan path**
    
    When a single transaction emits N gateway events, `observeGatewayInbound` will call `SuiGetTransactionBlock` once per event (N times in total), even though all events share the same `TxDigest`. The test explicitly registers `OnGetTx` twice for the same hash (`ts.OnGetTx(txHash, "10000", true, false, nil)` × 2) confirming the current behavior.
    
    Under normal operation this is just wasteful, but it becomes a concern at scale when the same tx produces many events and the RPC node is rate-limited or has latency. Consider caching the fetched `*models.SuiTransactionBlockResponse` by `TxDigest` within the scan loop (a simple `map[string]*tx` is enough) and passing the cached value to `processInboundEvent` on subsequent events for the same digest.
    
    How can I resolve this? If you propose a fix, please make it concise.
Prompt To Fix All With AI
Fix the following 1 code review issue. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 1
zetaclient/chains/sui/observer/inbound.go:73-102
**Duplicate RPC call per event for same tx in block-scan path**

When a single transaction emits N gateway events, `observeGatewayInbound` will call `SuiGetTransactionBlock` once per event (N times in total), even though all events share the same `TxDigest`. The test explicitly registers `OnGetTx` twice for the same hash (`ts.OnGetTx(txHash, "10000", true, false, nil)` × 2) confirming the current behavior.

Under normal operation this is just wasteful, but it becomes a concern at scale when the same tx produces many events and the RPC node is rate-limited or has latency. Consider caching the fetched `*models.SuiTransactionBlockResponse` by `TxDigest` within the scan loop (a simple `map[string]*tx` is enough) and passing the cached value to `processInboundEvent` on subsequent events for the same digest.

Reviews (1): Last reviewed commit: "fix(sui): process multiple gateway event..." | Re-trigger Greptile

Summary by CodeRabbit

  • Performance

    • Optimized inbound event processing for the Sui chain by implementing per-scan transaction caching, reducing redundant RPC calls when multiple events reference the same transaction.
  • Tests

    • Expanded test coverage to verify proper handling of multiple gateway events within a single transaction on the Sui chain.

Review Change Stack

@alan747271363-art alan747271363-art requested a review from a team as a code owner May 13, 2026 17:15
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 13, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: bd7fe95f-1293-42c6-b9b8-7ebe4fe75dab

📥 Commits

Reviewing files that changed from the base of the PR and between 9a516a3 and be326be.

📒 Files selected for processing (2)
  • zetaclient/chains/sui/observer/inbound.go
  • zetaclient/chains/sui/observer/observer_test.go

📝 Walkthrough

Walkthrough

The PR implements transaction block caching within each inbound observer scan to enable efficient processing of multiple Gateway events from the same Sui transaction, and adds comprehensive test coverage for this multi-event scenario across both the main observer and tracker recovery paths.

Changes

Multi-event Sui Gateway processing

Layer / File(s) Summary
Per-scan transaction caching mechanism
zetaclient/chains/sui/observer/inbound.go
ObserveGatewayInbound creates a per-scan txCache map and passes it through the inbound event loop. processInboundEvent gains an optional txCache parameter; when tx is nil, it reuses cached transaction blocks by hash, and stores newly fetched blocks into the cache for subsequent events. The tracker integration path passes nil for txCache since it provides the transaction block directly.
Test infrastructure and multi-event coverage
zetaclient/chains/sui/observer/observer_test.go
Introduces SampleEventWithSeq to generate events with explicit sequence numbers, replacing inline hardcoding in SampleEvent. Two new subtests validate multi-event handling: ObserveInbound with two gateway events in a single transaction (asserting correct vote creation and cursor advancement), and ProcessInboundTrackers with two events in a tracker's transaction block (asserting correct EventIndex ordering and vote fields).

Sequence Diagram

sequenceDiagram
  participant Scan as Scan Loop
  participant Cache as txCache Map
  participant ProcessFunc as processInboundEvent
  participant RPC as Sui RPC
  
  Scan->>Cache: Create per-scan txCache
  
  loop For each inbound event
    Scan->>ProcessFunc: Call with event and txCache
    ProcessFunc->>Cache: Lookup by TxHash
    alt Transaction in cache
      Cache-->>ProcessFunc: Return cached block
    else Transaction not in cache
      ProcessFunc->>RPC: Fetch SuiTransactionBlockResponse
      RPC-->>ProcessFunc: Transaction block
      ProcessFunc->>Cache: Store by TxHash
    end
    ProcessFunc->>ProcessFunc: Process Gateway event
  end
Loading

🎯 2 (Simple) | ⏱️ ~12 minutes

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Out of Scope Changes check ❓ Inconclusive The PR includes a caching optimization noted in its objectives, but the actual implementation addresses only the core fix; cached transaction reuse is mentioned but the full scope versus delivered implementation is unclear. Clarify whether the transaction caching optimization mentioned in objectives has been fully implemented in inbound.go or remains as a noted future improvement.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: enabling the Sui observer to process multiple gateway events from a single transaction.
Description check ✅ Passed The description comprehensively covers the change rationale, test execution, and linked issue, exceeding the template's basic requirements.
Linked Issues check ✅ Passed The PR fully addresses issue #4584 by removing the EventIndex != 0 rejection guard, processing each gateway event independently with its own inbound vote, and covering both block-scan and tracker recovery paths.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link
Copy Markdown
Author

Follow-up pushed in be326be2 to address the Greptile note about duplicate SuiGetTransactionBlock calls in the block-scan path.

What changed:

  • Added a per-scan TxDigest cache in observeGatewayInbound.
  • Reused the cached transaction block when multiple gateway events come from the same Sui transaction.
  • Updated the multi-event regression so the shared transaction block is mocked once while still asserting two inbound votes with event indexes 0 and 1.

Validation:

  • git diff --check HEAD~1..HEAD passes.
  • I attempted the focused Go test, but this local Windows environment does not have Go installed, and Docker Desktop is not running, so I could not execute go test locally.

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.

Sui inbound observer rejects valid Gateway events with EventIndex != 0 and advances cursor past them

1 participant