Skip to content

index escrow events#1378

Merged
giurgiur99 merged 7 commits into
mainfrom
escrow-index
May 26, 2026
Merged

index escrow events#1378
giurgiur99 merged 7 commits into
mainfrom
escrow-index

Conversation

@giurgiur99
Copy link
Copy Markdown
Contributor

@giurgiur99 giurgiur99 commented May 21, 2026

Fixes #1355

Changes proposed in this PR:

  • Index all 6 Escrow events (Auth, Lock, Claimed, Canceled, Deposit, Withdraw) via a new EscrowEventProcessor
  • Store them in a new escrow collection (Typesense + Elasticsearch).
  • Add getEscrowEvents query (P2P + GET /api/services/escrow/events)

@giurgiur99
Copy link
Copy Markdown
Contributor Author

/run-security-scan

Copy link
Copy Markdown
Member

@alexcos20 alexcos20 left a comment

Choose a reason for hiding this comment

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

AI automated code review (Gemini 3).

Overall risk: low

Summary:
This PR successfully introduces indexing and querying for Ocean Protocol Escrow events. The implementation is well-architected, cleanly extending the existing Command Handler, Indexer, and Database patterns (both Elasticsearch and Typesense). Excellent use of data normalization and robust parsing logic for blockchain events.

Comments:
• [INFO][performance] When constructing Typesense filter_by queries, it's a recommended best practice to wrap string values in backticks to prevent syntax errors if the string ever contains spaces, hyphens, or other special characters. While the fields currently being filtered (addresses, hashes) are typically safe hex strings, adding backticks improves query robustness.

-        .map(([field, value]) => `${field}:=${value}`)
+        .map(([field, value]) => `${field}:=${typeof value === 'string' ? `\`${value}\`` : value}`)

• [WARNING][other] By default, Typesense indexes all string fields for searching and enforces a maximum length limit (often 2048 bytes per field) on indexed strings. The proof field from the Escrow Claimed event could potentially contain a large hex string exceeding this limit, which would cause record insertion to fail. Since it is highly unlikely you will need to search/filter by the proof value itself, consider disabling indexing for this field to avoid length restrictions.

-      { name: 'proof', type: 'string', optional: true },
+      { name: 'proof', type: 'string', optional: true, index: false },

• [INFO][style] Great job utilizing safe parsing helper functions (addr and num) to consistently handle missing arguments, enforce lowercase addresses, and prevent precision loss with uint256 values. The switch-case mapping for different escrow events is also very clean.

@giurgiur99
Copy link
Copy Markdown
Contributor Author

/run-security-scan

Copy link
Copy Markdown
Member

@alexcos20 alexcos20 left a comment

Choose a reason for hiding this comment

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

AI automated code review (Gemini 3).

Overall risk: medium

Summary:
This PR implements escrow event indexing and querying, which cleanly extends the current architecture. It appropriately segregates database interactions and properly implements pagination. However, there is a NoSQL injection risk in the Typesense query builder and potential runtime crashes in the command handler when processing untrusted inputs.

Comments:
• [ERROR][security] User inputs (e.g., query string parameters) are passed directly into backticks within Typesense's filter_by syntax. If an attacker includes backticks in their payload, it breaks the string literal boundaries, resulting in a NoSQL injection vulnerability. Backticks should be stripped or escaped from the input values.

-          typeof value === 'string' ? `${field}:=\`${value}\`` : `${field}:=${value}`
+          typeof value === 'string' ? `${field}:=\`${value.replace(/`/g, '')}\`` : `${field}:=${value}`

• [WARNING][bug] P2P commands can send loosely structured JSON. If task.payer, task.payee, or task.token are provided as objects or arrays instead of strings, calling .toLowerCase() will throw a TypeError and cause an unhandled runtime error. Defensively check the types before calling string prototype methods.

-        payer: task.payer ? task.payer.toLowerCase() : undefined,
-        payee: task.payee ? task.payee.toLowerCase() : undefined,
-        token: task.token ? task.token.toLowerCase() : undefined,
+        payer: typeof task.payer === 'string' ? task.payer.toLowerCase() : undefined,
+        payee: typeof task.payee === 'string' ? task.payee.toLowerCase() : undefined,
+        token: typeof task.token === 'string' ? task.token.toLowerCase() : undefined,

• [INFO][performance] Good call using index: false for the proof field. This successfully avoids hitting Typesense's indexed-field length limitation for large cryptographic hex strings and preserves memory space.

@giurgiur99
Copy link
Copy Markdown
Contributor Author

/run-security-scan

Copy link
Copy Markdown
Member

@alexcos20 alexcos20 left a comment

Choose a reason for hiding this comment

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

AI automated code review (Gemini 3).

Overall risk: low

Summary:
This PR introduces Escrow event indexing functionality. It successfully sets up Database schemas, an event processor, a P2P command handler, and the relevant API routes. Overall, the implementation is high-quality, defensively coded, and integrates well with the current architecture. A few minor robustness improvements are suggested to prevent edge-case TypeErrors and handle potentially malformed inputs.

Comments:
• [WARNING][bug] The addr helper uses optional chaining for toString() but directly calls .toLowerCase(). If v is null or undefined, v?.toString() evaluates to undefined, and calling .toLowerCase() on it will throw a TypeError. Although ethers usually guarantees the presence of event fields, it's safer to properly guard against undefined.

-const addr = (v: any): string => v?.toString().toLowerCase()
+const addr = (v: any): string | undefined => v ? v.toString().toLowerCase() : undefined

• [INFO][security] Since GetEscrowEventsCommand can be sent over P2P directly as a JSON payload, an attacker could theoretically supply objects or arrays for eventType, jobId, or txId (e.g., jobId: { foo: 'bar' }). This could cause syntax errors in Typesense or malformed queries in Elasticsearch. Although these errors are currently caught and safely handled (returning 500 or an empty array), strictly typing them as strings is more robust and consistent with how payer, payee, and token are handled.

-        eventType: task.eventType,
+        eventType: typeof task.eventType === 'string' ? task.eventType : undefined,
         payer: typeof task.payer === 'string' ? task.payer.toLowerCase() : undefined,
         payee: typeof task.payee === 'string' ? task.payee.toLowerCase() : undefined,
         token: typeof task.token === 'string' ? task.token.toLowerCase() : undefined,
-        jobId: task.jobId,
-        txHash: task.txId
+        jobId: typeof task.jobId === 'string' ? task.jobId : undefined,
+        txHash: typeof task.txId === 'string' ? task.txId : undefined

• [INFO][security] Excellent defensive programming here by using backticks and explicitly stripping any backticks from user input. This safely mitigates any risk of Typesense filter query syntax injection.

@giurgiur99 giurgiur99 marked this pull request as ready for review May 25, 2026 13:36
Copy link
Copy Markdown

@claude claude Bot left a comment

Choose a reason for hiding this comment

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

Claude Code Review

This repository is configured for manual code reviews. Comment @claude review to trigger a review and subscribe this PR to future pushes, or @claude review once for a one-time review.

Tip: disable this comment in your organization's Code Review settings.

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented May 25, 2026

Greptile Summary

This PR introduces full indexing of the six Escrow contract events (Auth, Lock, Claimed, Canceled, Deposit, Withdraw) and exposes them through a new getEscrowEvents endpoint available via HTTP GET, P2P command, and POST /directCommand.

  • New EscrowEventProcessor decodes Escrow logs using the contract ABI, applies an address guard to reject false positives from the generic event signatures (Deposit, Withdraw, Lock), and upserts one row per txHash-logIndex into the new escrow collection in both Typesense and Elasticsearch.
  • New EscrowEventsHandler serves queries with optional filters (chainId, eventType, payer, payee, token, jobId, txHash, pagination) and includes an allowlist validation for eventType; a null return from search() is currently silently converted to an empty 200 response (see inline comment).
  • Schema additions cover both backends; the Typesense schema is correct, while the Elasticsearch schema maps several uint256 string fields as text instead of keyword (raised in prior thread).

Confidence Score: 4/5

Safe to merge after addressing the silent error masking in the query handler.

The indexing and storage logic is solid, and the address guard correctly prevents false positives from generic event names. The one concrete defect is in escrowHandler.ts: when the database search() call fails internally (returning null), the handler converts that to a 200 [] response, so callers cannot distinguish a successful empty result from a failed query. This should be fixed before the endpoint is used in production.

src/components/core/handler/escrowHandler.ts — null DB result masquerades as an empty success response.

Important Files Changed

Filename Overview
src/components/Indexer/processors/EscrowEventProcessor.ts New processor that decodes all 6 Escrow events using the contract ABI, applies an address guard against the chain's Escrow contract to avoid false positives from generic event signatures, and upserts into the escrow collection.
src/components/core/handler/escrowHandler.ts New query handler with eventType allowlist validation; a null return from search() is silently converted to an empty 200 response, masking DB errors from callers.
src/components/database/ElasticSchemas.ts Adds escrow index schema; numeric-string fields (amount, expiry, maxLockedAmount, maxLockSeconds, maxLockCounts) and proof are mapped as text instead of keyword, which will break exact-match term queries on those fields (discussed in prior review thread).
src/components/database/ElasticSearchDatabase.ts Adds ElasticsearchEscrowDatabase with standard CRUD and a term-query-based search; uses upsert semantics via index() with explicit ID which is appropriate for this append-only log.
src/components/database/TypesenseDatabase.ts Adds TypesenseEscrowDatabase; filter_by values are properly backtick-quoted with backtick stripping to prevent injection, numeric fields use :=value syntax correctly.
src/components/httpRoutes/escrow.ts New HTTP GET route; chainId is validated for NaN but offset/size are not (harmless — the DB layer falls back to defaults on NaN). eventType validation is delegated to the handler layer.
src/utils/constants.ts Adds 6 Escrow event hashes, ESCROW_EVENTS allowlist array, and GET_ESCROW_EVENTS protocol command; all consistent with the rest of the constants file.
src/test/integration/escrow.test.ts Integration test covering Deposit, Auth and Lock indexing plus handler query and pagination; uses waitForCondition correctly and skips gracefully when Escrow is not deployed on the test chain.

Sequence Diagram

sequenceDiagram
    participant Chain as Blockchain
    participant Indexer as OceanIndexer
    participant EP as EscrowEventProcessor
    participant DB as EscrowDatabase(Typesense/ES)
    participant HTTP as GET /api/services/escrow/events
    participant Handler as EscrowEventsHandler

    Chain->>Indexer: emits Escrow log (Auth/Lock/Claimed/Canceled/Deposit/Withdraw)
    Indexer->>EP: processEvent(log, chainId, eventName)
    EP->>EP: "verify log.address == escrowAddress"
    EP->>EP: escrowInterface.parseLog(log)
    EP->>EP: "build EscrowEvent record (id = txHash-logIndex)"
    EP->>DB: create(record)
    DB-->>EP: stored event

    HTTP->>Handler: handle(GetEscrowEventsCommand)
    Handler->>Handler: validate(eventType allowlist)
    Handler->>DB: search(filters, offset, size)
    DB-->>Handler: EscrowEvent[] or null
    Handler-->>HTTP: 200 JSON array (or 503 on DB error)
Loading

Reviews (2): Last reviewed commit: "address pr comments" | Re-trigger Greptile

Comment thread src/components/Indexer/processors/EscrowEventProcessor.ts
Comment thread src/components/database/ElasticSchemas.ts
Comment thread src/components/httpRoutes/escrow.ts
Comment thread src/components/core/handler/escrowHandler.ts
@giurgiur99 giurgiur99 merged commit 515f477 into main May 26, 2026
15 of 30 checks passed
@giurgiur99 giurgiur99 deleted the escrow-index branch May 26, 2026 15:43
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.

Index all escrow events in the indexer

3 participants