Skip to content

feat: getLogs since resume hint - #367

Open
andrevmatos wants to merge 34 commits into
mainfrom
feat/sinceLog
Open

feat: getLogs since resume hint#367
andrevmatos wants to merge 34 commits into
mainfrom
feat/sinceLog

Conversation

@andrevmatos

Copy link
Copy Markdown
Collaborator

Summary

Adds a partial since resume hint to LogFilter: pass the last log from a previous getLogs call, and the stream resumes just past it instead of re-scanning from startBlock/startTime. Aimed at watermark-driven pollers/watchers, where every poll currently re-walks pages (and, on TON, re-resolves shard floors) it has already seen.

Behavior

  • Start floors, all chains (withSinceStart): since.blockNumber/blockTimestamp stand in for startBlock/startTime — each effective floor is the larger of the explicit bound and the hint's, so since alone satisfies the start requirement. getMessageById and getExecutionReceipts accept since too.
  • Exclusive resume on cursor-capable chains — the hinted log is not re-emitted, while later same-tx/same-block followers still are:
    • EVM: queries from since.blockNumber (blocks always fetched whole), excluding that block's logs with index <= since.index; foreign-address hints ignored.
    • Solana: the hint signature seeds the native until cursor; since.blockNumber is an absolute slot floor for when the queried node doesn't know the signature (pruned/lagging). Watch polls also keep a resume cursor when the initial scan collects nothing.
    • TON: resumes from the lt embedded in the composite transactionHash (exclusive); skips the accountShardEndLt(startBlock - 1) shard-header lookup when the hint covers startBlock (saves 2 RPCs/scan on the steady-state poll path). Hints from a foreign account — by address field or the account embedded in the hash — are ignored.
    • Aptos: single-handle streams resume exactly from the hint's event sequence number; multi-handle streams floor at blockNumber + 1.
  • Sui: floors only (no cursor use). Canton: N/A — getLogs not implemented yet.

Bundled fixes (required by / uncovered by the above)

  • Aptos: multi-topic rounds are now version-atomic — the ceiling is exclusive, so a ledger version's events are never split across rounds (this is what makes the blockNumber + 1 floor sound). Also fixes catchedUp dropping the event exactly at a full page boundary (start > end, was >=).
  • TON: removes the per-address getTransactions window cache — redundant once since bounds rescans, and it retained parsed account history for the life of watch streams (production memory hazard).

Testing

  • New unit coverage per chain: floor merging (withSinceStart matrix), exclusive-resume semantics, stale/foreign/malformed hints, watch-mode empty-scan resume, Aptos batch-boundary atomicity, EVM index exclusion; changelog entry included.
  • Full ccip-sdk unit suite passes (1120 tests, tsc clean). Commits are bisect-clean: core plumbing first, then one commit per chain, TON cache removal split from the TON feature.

Lets a getLogs caller resume from the last emitted log instead of a fresh
startBlock/startTime: since.blockNumber/blockTimestamp merge as raising
start floors via withSinceStart, so the hint alone satisfies the start
requirement; cursor-capable chains additionally resume exclusively past
the hinted log. getMessageById and getExecutionReceipts accept it too.
Queries floor at the hint's block (blocks are always fetched whole), but
within that block logs with index <= since.index are not re-emitted, so a
partial hint is honored exactly while same-block followers still stream.
Foreign-address hints are ignored wholesale.
The hint signature seeds the native `until` cursor (exclusive); its
blockNumber is also an absolute slot floor, since `until` only has an
effect when the queried node actually knows the signature. Watch polls
now keep a resume cursor even when the initial scan collects nothing,
instead of re-emitting the unbounded head page.
Single-handle streams resume exactly from the hint's event sequence
number; multi-handle streams floor at blockNumber + 1. The +1 is safe
because versions are now emitted atomically: the round ceiling is
exclusive and a handle counts as caught up only past the tip seq —
previously >= dropped the event exactly at a full batch boundary.
The composite transactionHash carries an exact per-account lt cursor;
the resume floor is exclusive (sinceLt + 1) and only ever raises the
requested start. When the hint's block covers startBlock, the
shard-header floor lookup is skipped entirely. Hints from a foreign
account — by address field or by the account embedded in the hash — are
ignored, as are malformed ones.
The since resume hint now bounds rescans directly, making the
per-address cursor-page cache redundant. Long-lived watch streams
retained the account's parsed transaction history in memory (production
memory hazard); the walk also releases its initial batch once yielded.
@andrevmatos
andrevmatos requested review from a team, PabloMansanet and aelmanaa as code owners August 21, 2026 16:18
@vercel

vercel Bot commented Aug 21, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
ccip-tools-ts Ready Ready Preview Aug 25, 2026 1:39am

Request Review

aelmanaa
aelmanaa previously approved these changes Aug 21, 2026

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Adds cross-chain getLogs resume hints to avoid rescanning previously processed logs.

Changes:

  • Adds shared since floor handling and chain-specific exclusive cursors.
  • Improves Aptos batch atomicity and boundary handling.
  • Removes TON’s transaction cache and adds resume-focused tests.

Reviewed changes

Copilot reviewed 18 out of 18 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
CHANGELOG.md Documents resume behavior and fixes.
ccip-sdk/src/types.ts Makes generic log fields writable.
ccip-sdk/src/ton/txcache.test.ts Removes obsolete cache tests.
ccip-sdk/src/ton/logs.ts Releases retained transaction arrays.
ccip-sdk/src/ton/index.ts Removes caching and adds LT resume logic.
ccip-sdk/src/ton/index.test.ts Adds TON resume tests.
ccip-sdk/src/sui/index.test.ts Tests Sui resume floors.
ccip-sdk/src/sui/events.ts Applies shared resume floors.
ccip-sdk/src/solana/logs.ts Adds signature-based resume behavior.
ccip-sdk/src/solana/logs.test.ts Tests Solana resume semantics.
ccip-sdk/src/requests.ts Accepts hints when finding messages.
ccip-sdk/src/logs.test.ts Tests shared floor merging.
ccip-sdk/src/index.ts Exports the floor helper.
ccip-sdk/src/evm/logs.ts Adds block/index resume filtering.
ccip-sdk/src/evm/logs.test.ts Tests EVM resume behavior.
ccip-sdk/src/chain.ts Defines the hint API and receipt plumbing.
ccip-sdk/src/aptos/logs.ts Adds cursors and version-atomic batching.
ccip-sdk/src/aptos/logs.test.ts Tests Aptos resume and batching behavior.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread ccip-sdk/src/chain.ts
Comment thread ccip-sdk/src/ton/index.ts Outdated
@github-actions

github-actions Bot commented Aug 21, 2026

Copy link
Copy Markdown

Coverage Report

ℹ tests 1341
ℹ suites 323
ℹ pass 1341
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 216971.90648

> @chainlink/ccip-cli@1.12.0 test
> node --test

✔ src/commands/e2e-helpers.test.ts (110.100368ms)
Verbose mode enabled
▶ getCtx
  ▶ output — always stdout
    ✔ output.write goes to stdout (1.330557ms)
    ✔ output.table goes to stdout (1.231447ms)
  ✔ output — always stdout (3.708732ms)
  ▶ logger — always stderr
    ✔ logger.info goes to stderr (0.427514ms)
    ✔ logger.warn goes to stderr (0.280963ms)
    ✔ logger.error goes to stderr (0.262907ms)
  ✔ logger — always stderr (1.176784ms)
  ▶ verbose mode
    ✔ logger.debug is a no-op when verbose is false (1.428334ms)
    ✔ logger.debug goes to stderr when verbose is true (1.911533ms)
  ✔ verbose mode (3.594981ms)
  ▶ destroy signal
    ✔ returns a working destroy function (10.335295ms)
    ✔ calling destroy twice does not throw (0.604301ms)
  ✔ destroy signal (11.224966ms)
✔ getCtx (20.431219ms)
▶ lane-latency command
  ✔ should output JSON format correctly (12.139335ms)
  ✔ should resolve chain IDs to chain selectors (1.768998ms)
  ✔ should use custom API URL when provided (1.476006ms)
  ✔ should output log format correctly (1.270987ms)
  ✔ should handle chain IDs as input (1.254261ms)
  ✔ should handle chain selectors as input (1.289064ms)
  ✔ should throw CCIPApiClientNotAvailableError when --no-api flag is set (3.881664ms)
  ✔ should work normally when --no-api flag is false (0.970283ms)
  ✔ should forward blockConfirmations to API URL (0.816311ms)
  ✔ should not include numOfBlocks when blockConfirmations is not provided (0.757372ms)
  ▶ CCIP_API environment variable integration
    ✔ should respect CCIP_API=false environment variable (0.319322ms)
  ✔ CCIP_API environment variable integration (0.441595ms)
✔ lane-latency command (30.278317ms)
▶ e2e command lane EVM v1.5
  ✔ should show lane config Sepolia -> Fuji (v1.5) in JSON (4833.895156ms)
  ✔ should show lane config Sepolia -> Fuji (v1.5) in pretty format (3250.533667ms)
✔ e2e command lane EVM v1.5 (8086.144219ms)
▶ e2e command lane EVM v2.0
  ✔ should show lane config Sepolia -> Fuji (v2.0) in JSON (2261.259245ms)
✔ e2e command lane EVM v2.0 (2261.626029ms)
▶ e2e command lane EVM <-> Aptos (v1.6)
  ✔ should show lane config Sepolia -> Aptos (v1.6) (4513.530422ms)
  ✔ should show lane config Aptos -> Sepolia (v1.6) (3312.896678ms)
✔ e2e command lane EVM <-> Aptos (v1.6) (7826.854134ms)
▶ e2e command lane EVM <-> Solana (v1.6)
  ✔ should show lane config Sepolia -> Solana (v1.6) (4139.775506ms)
  ✔ should show lane config Solana -> Sepolia (v1.6) (7108.624561ms)
✔ e2e command lane EVM <-> Solana (v1.6) (11248.781812ms)
▶ e2e command lane EVM <-> TON (v1.6)
  ✔ should show lane config TON -> Sepolia (v1.6) (46295.112651ms)
  ✔ should show lane config Sepolia -> TON (v1.6) (15372.434425ms)
✔ e2e command lane EVM <-> TON (v1.6) (61667.905116ms)
▶ selectRequest non-interactive behavior
  ✔ returns the single request without prompting (0.843753ms)
  ✔ returns the request matching logIndex (0.150948ms)
  ✔ throws CCIPInteractiveRequiredError for multiple requests without logIndex (1.28688ms)
✔ selectRequest non-interactive behavior (3.424734ms)
▶ CCIPInteractiveRequiredError
  ✔ has correct code and is not transient (0.267393ms)
  ✔ uses default recovery when none provided (0.279461ms)
  ✔ preserves context fields (2.005565ms)
✔ CCIPInteractiveRequiredError (2.740582ms)
▶ preprocessArgv TTY auto-detection
  ✔ --interactive flag is defined in globalOpts (0.164858ms)
✔ preprocessArgv TTY auto-detection (0.283848ms)
▶ search messages command
  ✔ should throw CCIPApiClientNotAvailableError when --no-api flag is set (2.335362ms)
  ✔ should output JSON format correctly (10.206581ms)
  ✔ should pass sender filter to API (2.372848ms)
  ✔ should pass receiver filter to API (2.198145ms)
  ✔ should resolve source chain to selector (2.369363ms)
  ✔ should resolve dest chain to selector (5.009835ms)
  ✔ should pass manual-exec-only filter to API (2.430035ms)
  ✔ should treat limit 0 as unlimited (3.772309ms)
  ✔ should respect limit parameter (4.216297ms)
  ✔ should warn when no results found (0.865846ms)
  ✔ should use custom API URL when provided (1.617099ms)
  ✔ should warn on negative limit and fall back to default (1.519881ms)
  ✔ should output log format (1.24655ms)
✔ search messages command (42.09749ms)
▶ e2e command show EVM
  ▶ pretty format (default)
    ✔ should show complete CCIP transaction details EVM to EVM (6822.584261ms)
  ✔ pretty format (default) (6823.605601ms)
  ▶ json format
    ✔ should output a single valid JSON envelope with all expected fields (4923.883833ms)
  ✔ json format (4924.23369ms)
  ▶ log format
    ✔ should output in log format with object assignments (5955.666584ms)
  ✔ log format (5955.96887ms)
  ▶ verbose flag
    ✔ should work with verbose flag enabled (5936.223383ms)
  ✔ verbose flag (5936.487241ms)
  ▶ error handling
    ✔ should handle invalid transaction hash gracefully (8829.555987ms)
    ✔ should require transaction hash argument (1398.006566ms)
  ✔ error handling (10227.945441ms)
  ✔ should show complete CCIP transaction details EVM to Aptos (13155.055367ms)
  ✔ should show complete CCIP transaction details EVM to Solana (5940.123911ms)
  ✔ should show EVM to Solana v2 OffRamp execution without verifications (3016.922882ms)
✔ e2e command show EVM (55981.581611ms)
▶ e2e command show Solana
  ✔ should show complete CCIP transaction details Solana to EVM (13648.663292ms)
✔ e2e command show Solana (13648.870315ms)
▶ e2e command show Aptos
  ✔ should show complete CCIP transaction details Aptos to EVM (12701.136704ms)
✔ e2e command show Aptos (12701.384298ms)
﹣ e2e command show TON (0.054702ms) # SKIP
▶ formatCCIPError
  ✔ should return null for non-CCIPError instances (0.935871ms)
  ✔ should format CCIPError with code and message (0.419783ms)
  ✔ should include help section with recovery hint (0.164398ms)
  ✔ should include note section for transient errors (0.229246ms)
  ✔ should include retry timing for transient errors with retryAfterMs (0.316277ms)
  ✔ should not include note section for permanent errors (0.141924ms)
  ✔ should format error with structured output (0.189606ms)
  ✔ should include stack trace when verbose is true (0.315315ms)
  ✔ should not include stack trace when verbose is false (0.1858ms)
✔ formatCCIPError (4.36326ms)
▶ pretty display formatters
  ✔ formats TON addresses to friendly form, bare tx hashes, recursively (1.14717ms)
  ✔ formats remote-family values with the familyFor override (0.15921ms)
  ✔ leaves values without known address/hash keys untouched (0.109555ms)
  ✔ formatDisplayTxHash is identity for non-TON hashes (0.143185ms)
✔ pretty display formatters (1.792873ms)
▶ yieldResolved
  ✔ throws if a promise rejects while the generator is paused after a yield (8.324943ms)
✔ yieldResolved (8.552386ms)
▶ canton/signer
  ▶ Ed25519TransactionSigner constructor
    ✔ constructs with a valid 64-char hex seed (4.07898ms)
    ✔ constructs with a 0x-prefixed seed (0.516759ms)
    ✔ throws when seed is too short (0.478451ms)
    ✔ throws when seed is too long (0.225891ms)
    ✔ throws when seed contains non-hex characters (0.136786ms)
    ✔ computes the correct Canton fingerprint (0.78237ms)
    ✔ produces different fingerprints for different seeds (0.799195ms)
  ✔ Ed25519TransactionSigner constructor (8.346726ms)
  ▶ Ed25519TransactionSigner.sign()
    ✔ returns a PartySignatures structure (0.90868ms)
    ✔ includes the correct party ID (0.458402ms)
    ✔ produces a signature with correct properties (0.689961ms)
    ✔ produces a valid Ed25519 signature (64 bytes) (0.431ms)
    ✔ produces a cryptographically valid signature (1.155702ms)
    ✔ produces different signatures for different hashes (0.411591ms)
    ✔ produces different signatures for different signers (same hash) (0.538853ms)
    ✔ handles empty hash (edge case) (0.39139ms)
  ✔ Ed25519TransactionSigner.sign() (5.414845ms)
  ▶ Ed25519TransactionSigner.getFingerprint()
    ✔ returns the fingerprint computed during construction (0.343608ms)
    ✔ returns a 68-character hex string (0.338871ms)
  ✔ Ed25519TransactionSigner.getFingerprint() (0.806676ms)
  ▶ Integration: multiple signers with same party
    ✔ allows multiple signers for the same party with different keys (0.610511ms)
  ✔ Integration: multiple signers with same party (0.700406ms)
✔ canton/signer (16.022851ms)
▶ fetchChainsFromRpcs
  ✔ lets duplicate tx-hash race endpoints query before aborting losers (13.082166ms)
  ✔ chainGetter: returns chain from first winning endpoint and destroys late arrivals (50.729663ms)
  ✔ chainGetter: resolves a pending request once the endpoint connects (Branch 2) (21.60024ms)
  ✔ chainGetter: rejects with CCIPRpcNotFoundError when all endpoints fail to connect (1.579071ms)
  ✔ chainGetter: rejects immediately with CCIPRpcNotFoundError once family is already exhausted (0.712865ms)
  ✔ txHash: rejects with CCIPTransactionNotFoundError when tx not on any chain (0.750281ms)
  ✔ txHash: Branch-4 (txOnlyRacer) is destroyed via catch when it loses the tx race (11.250284ms)
  ✔ txHash: chain connecting after txFoundIn is set is immediately destroyed without calling getTransaction (23.672144ms)
  ✔ txHash: Branch-2 chain given to a pending chainGetter is NOT destroyed when it cannot find the tx (13.748501ms)
  ✔ txFoundIn shared across families: SVM txOnlyRacer is destroyed after EVM wins (34.270765ms)
✔ fetchChainsFromRpcs (173.693494ms)
▶ isCantonLedgerUrl
  ✔ matches Canton JSON Ledger API paths (0.139941ms)
  ✔ does not match EVM JSON-RPC URLs (0.071538ms)
✔ isCantonLedgerUrl (0.423459ms)
▶ filterEndpointsForFamily
  ✔ gives Canton only ledger URLs (0.110937ms)
  ✔ gives EVM only non-ledger URLs (0.084637ms)
✔ filterEndpointsForFamily (0.263757ms)
▶ resolveRouter
  ✔ returns explicit CLI -r for any source family (0.174223ms)
  ✔ returns undefined for EVM source without -r (0.061713ms)
✔ resolveRouter (0.30541ms)
▶ resolveCliIndexer
  ✔ prefers explicit CLI --indexer values (0.130657ms)
  ✔ falls back to canton-config indexerUrl when the lane involves Canton (0.072289ms)
  ✔ does not fall back to canton-config indexerUrl for EVM-only lanes (0.05973ms)
  ✔ returns undefined when neither CLI nor config provides an indexer (0.048343ms)
✔ resolveCliIndexer (0.408586ms)
▶ resolveCliRouter
  ✔ prefers explicit CLI -r on Canton source (0.135735ms)
  ✔ prefers explicit CLI -r on EVM source even when canton-config has senderInstanceId (0.063996ms)
  ✔ falls back to canton-config senderInstanceId for Canton source (0.075884ms)
  ✔ does not fall back to senderInstanceId for EVM source (0.055885ms)
✔ resolveCliRouter (0.44422ms)
ℹ tests 117
ℹ suites 39
ℹ pass 117
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 91380.755667
---------------------------------------|---------|----------|---------|---------|-------------------
File                                   | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
---------------------------------------|---------|----------|---------|---------|-------------------
All files                              |    77.7 |    79.18 |   67.17 |    77.7 |                   
 ccip-cli/src                          |   90.42 |    59.25 |      75 |   90.42 |                   
  index.ts                             |   90.42 |    59.25 |      75 |   90.42 | ...73-174,180-185 
 ccip-cli/src/commands                 |   52.31 |       75 |   61.29 |   52.31 |                   
  index.ts                             |     100 |      100 |     100 |     100 |                   
  lane-latency.ts                      |   70.76 |    83.33 |   33.33 |   70.76 | ...,76-83,119-128 
  lane.ts                              |   86.98 |    68.42 |     100 |   86.98 | ...41-144,148-150 
  manual-exec.ts                       |   20.34 |      100 |       0 |   20.34 | ...39-149,151-347 
  parse.ts                             |    55.2 |      100 |       0 |    55.2 | 47-51,58-65,67-96 
  search.ts                            |   81.25 |      100 |       0 |   81.25 | 24-29             
  send.ts                              |   14.13 |      100 |       0 |   14.13 | ...09-237,239-552 
  show.ts                              |   75.62 |    63.75 |     100 |   75.62 | ...15-318,398-400 
  supported-tokens.ts                  |   17.93 |      100 |       0 |   17.93 | ...80-353,355-368 
  token.ts                             |   22.72 |      100 |       0 |   22.72 | ...3,60-67,69-132 
  types.ts                             |     100 |      100 |     100 |     100 |                   
  utils.ts                             |   86.68 |    78.91 |   93.33 |   86.68 | ...11-721,729-737 
 ccip-cli/src/commands/search          |   53.89 |    76.19 |    12.5 |   53.89 |                   
  messages.ts                          |   53.89 |    76.19 |    12.5 |   53.89 | ...36-258,260-295 
 ccip-cli/src/providers                |   58.73 |    87.06 |   32.72 |   58.73 |                   
  aptos.ts                             |   51.47 |      100 |       0 |   51.47 | ...,95-96,105-136 
  canton.ts                            |   84.98 |    96.29 |      90 |   84.98 | 183-201,252-273   
  evm.ts                               |    36.3 |      100 |       0 |    36.3 | ...69-104,116-168 
  index.ts                             |   84.27 |    83.33 |      90 |   84.27 | ...30-131,289-337 
  solana.ts                            |   47.44 |      100 |       0 |   47.44 | ...02-103,112-137 
  sui.ts                               |    49.5 |      100 |       0 |    49.5 | ...57-258,267-301 
  ton.ts                               |   15.03 |      100 |       0 |   15.03 | 24-153            
 ccip-sdk/src                          |    95.2 |    87.44 |    92.7 |    95.2 |                   
  chain.ts                             |   96.27 |    80.97 |   84.37 |   96.27 | ...2403,2436-2437 
  commits.ts                           |    90.9 |    77.41 |     100 |    90.9 | ...06,115-118,120 
  execution.ts                         |   89.69 |    85.29 |     100 |   89.69 | ...65-172,192-199 
  explorer.ts                          |     100 |      100 |     100 |     100 |                   
  extra-args.ts                        |     100 |    89.47 |     100 |     100 | 225,260,297,312   
  fetch.ts                             |   98.16 |    93.92 |   97.56 |   98.16 | ...-784,1027-1029 
  gas.ts                               |   85.17 |    86.04 |      75 |   85.17 | ...83-294,403-452 
  http-status.ts                       |     100 |      100 |     100 |     100 |                   
  index.ts                             |     100 |      100 |     100 |     100 |                   
  messages.ts                          |    79.2 |    46.15 |      40 |    79.2 | ...99-310,314-327 
  networks.ts                          |     100 |      100 |     100 |     100 |                   
  offchain.ts                          |   84.64 |    76.92 |     100 |   84.64 | ...18-227,236-237 
  requests.ts                          |   95.32 |    90.47 |     100 |   95.32 | ...45-646,671-676 
  supported-chains.ts                  |     100 |      100 |     100 |     100 |                   
  types.ts                             |     100 |      100 |     100 |     100 |                   
  utils.ts                             |    99.2 |    95.97 |     100 |    99.2 | ...12,217-218,220 
 ccip-sdk/src/api                      |   93.22 |    83.44 |   94.73 |   93.22 |                   
  index.ts                             |   93.22 |    83.44 |   94.73 |   93.22 | ...92-895,898-901 
 ccip-sdk/src/aptos                    |   69.29 |    81.14 |   64.06 |   69.29 |                   
  exec.ts                              |   29.31 |      100 |       0 |   29.31 | 18-58             
  hasher.ts                            |   75.89 |       80 |   66.66 |   75.89 | 17-36,50-56       
  index.ts                             |   67.11 |    74.76 |   66.66 |   67.11 | ...1021,1025-1036 
  logs.ts                              |   95.77 |    84.83 |   83.33 |   95.77 | ...56,424,533-544 
  send.ts                              |    25.6 |      100 |       0 |    25.6 | ...2,63-80,93-125 
  token.ts                             |   23.75 |       75 |     100 |   23.75 | 35-156            
  types.ts                             |   65.97 |      100 |       0 |   65.97 | 26-33,65-89       
 ccip-sdk/src/canton                   |    44.7 |    76.56 |   31.66 |    44.7 |                   
  amount.ts                            |   92.59 |       70 |     100 |   92.59 | 22-23             
  ccv-addresses.ts                     |     100 |    91.07 |     100 |     100 | 13,30,64,67       
  defaults.ts                          |   99.28 |    90.24 |     100 |   99.28 | 93                
  events.ts                            |   45.74 |     52.5 |   23.52 |   45.74 | ...13-525,531-540 
  index.ts                             |   33.54 |     60.6 |   12.82 |   33.54 | ...2535,2538-2540 
  types.ts                             |   95.34 |      100 |      50 |   95.34 | 55-62             
  update-id.ts                         |   98.11 |    88.88 |     100 |   98.11 | 52                
 ccip-sdk/src/canton/client            |   56.53 |     61.9 |   20.58 |   56.53 |                   
  client.ts                            |   54.67 |       60 |   20.58 |   54.67 | ...16-735,750-770 
  index.ts                             |     100 |      100 |     100 |     100 |                   
 ...dk/src/canton/explicit-disclosures |   74.81 |    69.35 |    55.1 |   74.81 |                   
  acs.ts                               |   87.36 |     69.1 |      90 |   87.36 | ...20,568,606-607 
  eds.ts                               |   54.34 |      100 |       0 |   54.34 | ...86-393,397-402 
 ccip-sdk/src/canton/token-metadata    |   62.64 |      100 |       0 |   62.64 |                   
  client.ts                            |   62.64 |      100 |       0 |   62.64 | 104-163,170-174   
 ...dk/src/canton/transfer-instruction |    53.4 |      100 |       0 |    53.4 |                   
  client.ts                            |    53.4 |      100 |       0 |    53.4 | 97-180,187-191    
 ccip-sdk/src/errors                   |   89.19 |    76.66 |   54.81 |   89.19 |                   
  CCIPError.ts                         |     100 |      100 |     100 |     100 |                   
  codes.ts                             |     100 |      100 |     100 |     100 |                   
  index.ts                             |     100 |      100 |     100 |     100 |                   
  pure.ts                              |     100 |       75 |     100 |     100 | 32                
  recovery.ts                          |     100 |      100 |     100 |     100 |                   
  specialized.ts                       |   86.79 |    73.11 |   52.34 |   86.79 | ...3606,3629-3638 
  utils.ts                             |   94.44 |    81.48 |     100 |   94.44 | 15,17,22,24       
 ccip-sdk/src/evm                      |   91.35 |    82.17 |   92.98 |   91.35 |                   
  const.ts                             |   98.66 |    92.85 |     100 |   98.66 | 122-123           
  errors.ts                            |   91.98 |    81.57 |     100 |   91.98 | ...85,239-242,247 
  extra-args.ts                        |    94.5 |    61.01 |     100 |    94.5 | ...11-212,328-340 
  fork.test.data.ts                    |     100 |      100 |     100 |     100 |                   
  gas.ts                               |   98.19 |    63.15 |     100 |   98.19 | 90-91,93          
  hasher.ts                            |     100 |     92.3 |     100 |     100 | 135               
  index.ts                             |    85.4 |    80.07 |   90.54 |    85.4 | ...2682,2837-2868 
  logs.ts                              |   99.16 |    92.02 |      90 |   99.16 | 69-70,75-76       
  messageCodec.ts                      |     100 |      100 |     100 |     100 |                   
  messages.ts                          |     100 |      100 |     100 |     100 |                   
  offchain.ts                          |    87.5 |    71.42 |     100 |    87.5 | 13-14             
  simulate.ts                          |     100 |    94.82 |     100 |     100 | 109-111,425       
  types.ts                             |     100 |      100 |     100 |     100 |                   
 ccip-sdk/src/evm/viem                 |   79.76 |    90.62 |   69.23 |   79.76 |                   
  client-adapter.ts                    |     100 |       90 |     100 |     100 | 48,74             
  index.ts                             |     100 |      100 |     100 |     100 |                   
  wallet-adapter.ts                    |   63.09 |     90.9 |   55.55 |   63.09 | ...91-124,131-157 
 ccip-sdk/src/hasher                   |   94.29 |    78.94 |     100 |   94.29 |                   
  common.ts                            |     100 |      100 |     100 |     100 |                   
  hasher.ts                            |     100 |    66.66 |     100 |     100 | 19                
  index.ts                             |     100 |      100 |     100 |     100 |                   
  merklemulti.ts                       |   93.43 |       78 |     100 |   93.43 | ...06-307,315-316 
 ccip-sdk/src/shared                   |   85.35 |    84.33 |   82.35 |   85.35 |                   
  bcs-codecs.ts                        |   79.07 |    60.86 |   66.66 |   79.07 | ...42-252,260-269 
  codec.ts                             |   94.32 |    93.22 |     100 |   94.32 | 140-141,179-187   
  constants.ts                         |     100 |      100 |     100 |     100 |                   
 ccip-sdk/src/solana                   |   77.11 |    72.43 |    82.4 |   77.11 |                   
  cleanup.ts                           |   26.95 |    66.66 |   33.33 |   26.95 | ...59-101,114-227 
  exec.ts                              |   69.11 |    62.96 |   66.66 |   69.11 | ...69-473,513-514 
  extra-args.ts                        |   58.07 |       75 |      60 |   58.07 | 84-120,130-188    
  fork.test.data.ts                    |     100 |      100 |     100 |     100 |                   
  gas.ts                               |   92.02 |    68.57 |     100 |   92.02 | ...,80-89,169-170 
  hasher.ts                            |   96.49 |    81.81 |     100 |   96.49 | 64-67             
  index.ts                             |   80.51 |       78 |    84.9 |   80.51 | ...2095,2099-2132 
  logs.ts                              |   87.79 |    74.07 |     100 |   87.79 | ...50-151,163-164 
  offchain.ts                          |     100 |      100 |     100 |     100 |                   
  patchBorsh.ts                        |   78.31 |       50 |     100 |   78.31 | ...47,65-66,72-78 
  send.ts                              |    77.6 |    33.33 |      80 |    77.6 | ...52-360,403-442 
  signatures-cache.ts                  |   79.78 |    69.23 |    87.5 |   79.78 | ...51-260,274-275 
  types.ts                             |     100 |      100 |     100 |     100 |                   
  utils.ts                             |   77.44 |    64.17 |   81.25 |   77.44 | ...54-556,588-603 
 ccip-sdk/src/sui                      |   67.03 |    61.87 |   76.69 |   67.03 |                   
  discovery.ts                         |   47.49 |    48.27 |      60 |   47.49 | ...32-780,783-817 
  events.ts                            |   71.09 |     67.1 |    90.9 |   71.09 | ...30-443,470-481 
  exec.ts                              |   30.71 |      100 |       0 |   30.71 | 37-89,101-153     
  hasher.ts                            |   98.19 |    66.66 |     100 |   98.19 | 35,51             
  index.ts                             |   73.69 |    57.58 |   77.96 |   73.69 | ...2117,2133-2134 
  objects.ts                           |   55.24 |    85.45 |   83.33 |   55.24 | ...57-313,324-467 
  types.ts                             |     100 |      100 |     100 |     100 |                   
 ccip-sdk/src/sui/manuallyExec         |   53.36 |       75 |      50 |   53.36 |                   
  encoder.ts                           |    83.9 |    66.66 |     100 |    83.9 | 51-58,73-77,82    
  index.ts                             |   33.82 |      100 |       0 |   33.82 | 47-136            
 ccip-sdk/src/ton                      |   85.83 |    81.78 |   88.33 |   85.83 |                   
  exec.ts                              |     100 |      100 |     100 |     100 |                   
  extra-args.ts                        |   98.66 |    72.72 |     100 |   98.66 | 156-157,222       
  hasher.ts                            |   78.07 |    77.77 |      75 |   78.07 | 100-108,156-187   
  index.ts                             |    84.9 |    81.37 |   80.64 |    84.9 | ...2078,2085-2086 
  logs.ts                              |   94.53 |     86.2 |     100 |   94.53 | ...95-602,649-652 
  send.ts                              |   95.52 |    66.66 |     100 |   95.52 | 37-44,188         
  ton-cache.ts                         |     100 |    93.75 |     100 |     100 | 102               
  types.ts                             |   91.24 |    81.25 |     100 |   91.24 | ...61-63,71-74,92 
  utils.ts                             |    63.1 |    77.77 |    90.9 |    63.1 | ...37-395,397-400 
---------------------------------------|---------|----------|---------|---------|-------------------

The override scans the indexer directly (queryTransactionBlocks) and
derived its checkpoint/time bounds only from hints.startBlock/startTime,
so a since-only call scanned from genesis. Merge with withSinceStart
first.
The floor merge ran before the composite-hash account check, so a hint
with a matching address field but a foreign embedded account still raised
startBlock/startTime and could skip this account's logs. A provably
foreign hint is now dropped wholesale — cursor and floors alike; the
regression test uses a hint block above startBlock so a leaked floor
would be observable.
@andrevmatos

Copy link
Copy Markdown
Collaborator Author

Both Copilot review comments addressed in the two fix commits just pushed:

  1. Sui getExecutionReceipts override (chain.ts:1988 thread): valid — the override scans queryTransactionBlocks directly and derived its bounds only from hints.startBlock/hints.startTime, so a since-only call scanned from genesis. Now merges via withSinceStart first (49cc609), with a regression test covering the indexer path. (Left the pre-existing no-start behavior — scanning from checkpoint 0 — unchanged, as that's independent of since.)

  2. TON embedded-account check ran after the floor merge (ton/index.ts:588 thread): valid — a hint with a matching address field but a foreign embedded account still raised the floors. Validation now happens before withSinceStart, and a provably foreign hint is dropped wholesale, cursor and floors alike (2e1403d). The foreignEmbedded test now uses a hint block above startBlock, so a leaked floor would observably skip a block.

Full unit suite re-run after both fixes: 171/171 across the touched suites, tsc clean.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 31 out of 31 changed files in this pull request and generated 6 comments.

Comment thread ccip-sdk/src/chain.ts
Comment thread ccip-sdk/src/ton/index.ts Outdated
Comment on lines +688 to +690
} catch {
delete opts.since
}
Comment thread ccip-sdk/src/ton/logs.ts Outdated
Comment on lines +296 to +300
const startUtime = Math.max(0, Math.floor(Number(opts.startTime ?? 0)))
let firstPage: TonV3Message[]
try {
const [page, indexedTip] = await Promise.all([
fetchV3Messages(ctxV3, { source: acct.toRawString(), startUtime, limit }),
Comment on lines +203 to +207
export function prettyFormat(
value: unknown,
sameFamily: ChainFamily,
familyFor: Readonly<Record<string, ChainFamily>> = {},
): unknown {
Comment thread ccip-sdk/src/ton/index.ts
Comment on lines +1471 to 1473
if (/^-?\d+:[0-9a-fA-F]{64}$/.test(bytes)) {
return bytes
}
Comment thread ccip-sdk/src/ton/logs.ts
Comment on lines +303 to +307
if (indexedTip < cutoff - V3_MAX_INDEX_LAG) {
throw new CCIPHttpError(
0,
`TON v3 index lagging: tip ${indexedTip} vs requested cutoff ${cutoff}`,
)
CI (saw 97 on the public index) exceeded the 96 per-tx seqno RPC cap:
txs newer than the index's tip fall back to per-tx seqno resolution by
design, and the public index lags a few blocks, so a healthy walk
legitimately falls back for its most recent txs. Oracle engagement is
already enforced by the retry loop (v3transactions >= 1); keep the cap
as a loose guard against the pre-oracle ~2-3 RPCs per walked tx.
…STENT

The coverage test requires every error code to carry a recovery hint; the new stream-inconsistency code (index/RPC disagreement mid-scan) was missing both the hint and the transient-code registration.
…aded

CI runners' shared egress gets 429-stormed by the keyless public toncenter index, making the live scans flap. Add a preflight probe of the v3 tip (twice before skipping) and skip individual tests whose index-dependent assertions can't hold under a degraded index (every attempt truncated, or the meta path never engaged). Healthy networks still run the full suite; the mocked unit tests cover the logic hermetically.
The hint's signature used to seed the node's transaction-granular `until` cursor, which drops the WHOLE hinted tx — including logs after the hint's index (batch executions, multi-topic streams). The walk now pages to the hint's slot floor and SolanaChain.getLogs applies the per-log (transactionHash, index) exclusion, mirroring EVM's per-block behavior.
…fast path

The v2 walk's `to_lt` cursor is transaction-granular, so a tx-exclusive floor at the hint's lt silently skipped the hinted tx's own followers. With the composite tx lt AND the hint's per-log index (a log's created_lt), the walk now floors inclusively at the hinted tx and drops only its logs at/before the hint's index; index-carrying hints also seed the v3 /messages fast path at index + 1 (start_lt inclusive, message-granular), so same-tx and same-block logs always flow and the hinted log is never re-fetched. A composite-only hint stays tx-exclusive, an index-only one floors at the message lt. startTime is applied as an emit-time filter when the scan floor is block-derived.
…harden withSinceStart typing

An addressless (topic-/typeAndVersions-only) sweep's since hint is its own last emitted log — every log carries an address, and (blockNumber, index) is the previously-emitted cursor — so it is honored wholesale: the hinted block is fetched whole and its logs at or before the hinted index are skipped, exactly as on address-scoped filters. Only an address-scoped filter whose hint addresses another contract drops the hint wholesale (no floors, no exclusion). withSinceStart is retyped to accept LeanNumbers<LogFilter> callers, keeps its kind-preserving floor writes and drops the hint's tx backref; the merged startBlock remains the scan floor with startTime filtered at emission (contract wording updated). getMessagesInBatch destructures a leaked `since` out of its backward retry legs.
…ation

The v3 fast path assumed /messages rows are created_lt-ascending; the index actually orders them by created_at (lts regress across rows of the same second), so an lt-based page cursor could silently skip rows and the forward-only meta prune turned out-of-order joins into endless truncation. Pages now turn by created_at with a per-page created_lt re-sort, meta joins by tx hash without pruning, a boundary second overflowing one page is drained via offset (never cut), a row missing its tx hash truncates loudly, and the turn cursor is the page's max created_at so it never regresses. The scan is clamped to the index's ingested tip (blocks past it aren't indexed yet — emitting to the cutoff silently under-delivered, N2), and any truncation now surfaces as CCIPLogsStreamInconsistentError instead of ending silently (N3).
An all-empty fast-path scan (dormant address, nothing new) is almost always
clamped: the index tip always trails a near-tip cutoff (latest - 1) by a few
blocks, and busy streams only avoid the marker via the block > cutoff break.
Throwing CCIPLogsStreamInconsistentError on every such poll was a permanent
transient-error storm with zero silent-loss protection — nothing emitted
means no poller watermark can have moved. The N2 truncation signal now fires
only once a scan has EMITTED logs (whose watermark could be trusted past the
ingested tip); pre-emission truncation (clamp or mid-stream corruption) ends
quietly, as the tail is re-scanned on the next poll anyway.

Unit tests cover both sides (loud after emissions, quiet before); the live
integration suite treats the mid-stream throw as a retryable attempt instead
of a hard failure.
The previous 'clamp' was a boolean keyed on the probe-time tip (cached ~30s)
vs a fresh cutoff (latest - 1): stale-tip drift could mark a scan truncated
even when it missed nothing, throwing CCIPLogsStreamInconsistentError
intermittently on healthy near-tip scans (looked like flake in CI; the
quiet-empty fix removed the storm but not the spurious signal).

openV3EventStream now really clamps: it returns { stream, cutoff:
min(cutoff, indexedTip) } and the consumer seals there, so every fast-path
scan ends as a COMPLETE prefix by construction — a watermark poller resumes
from its own last emitted log, so the tail is simply fetched next poll. A
PINNED endBlock above the ingested tip cannot be clamped (a definite-window
request would be silently under-delivered), so the fast path declines and
the complete v2 walk serves it. The truncated marker is now reserved for
genuine index self-contradiction (a message row missing its tx hash, or an
unrecoverable page turn), surfacing loudly only once logs were emitted (no
watermark can have moved before that; quiet-empty semantics kept).

Unit coverage: sealed-prefix clamp (was: throws), pinned-endBlock decline
(v2 walk delivers the whole window), plus the existing loud/quiet truncation
pair. Integration comments aligned; CHANGELOG updated.
Turn queries carry start_utime only (a created_lt cursor would silently skip
regressing rows), so the boundary second re-serves rows BELOW a since hint's
seed floor: the hinted log's own row and earlier rows of the same second
(parallel shards). They were not in the seen set, so fresh() accepted them,
they were hydrated, and the consumer read the earlier block as a mid-stream
rewind (CCIPLogsStreamInconsistentError) on every multi-page hint scan.

Rows below the seed are now counted for the offset drain (they are part of
the server-side result set) but never hydrated or emitted, and fresh()
excludes them so a floor-excluded prefix cannot loop the turn forever. They
are deliberately NOT added to seen (keyed by tx hash): the hinted tx's own
followers share that hash and must still flow.

Repro test: page=2 hint scan whose boundary second carries the hinted tx's
own row -> threw 'block rewind 11 -> 10' before, emits ['11001','11002']
after. Also: regression test for the rewind diagnostic naming both blocks
(was interpolating undefined after the cursor reset), the deliberate-vs-silent
asymmetry with the v2 walk documented at its catch site, and a stale
'30 seconds' cache comment in solana fixed to the actual 5s TTL.
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.

3 participants