ERC-3668 edge case for clientside http/ssl errors

Under ERC-3668 “Client Lookup Protocol”

…
…
5. If the sender field does not match the address of the contract that was called, return an error to the caller and stop.
6. Construct a request URL by replacing sender with the lowercase 0x-prefixed hexadecimal formatted sender parameter, and replacing data with the 0x-prefixed hexadecimal formatted callData parameter. The client may choose which URLs to try in which order, but SHOULD prioritise URLs earlier in the list over those later in the list.
7. Make an HTTP GET request to the request URL.
8. If the response code from step (5) is in the range 400-499, return an error to the caller and stop.
9. If the response code from step (5) is in the range 500-599, go back to step (5) and pick a different URL, or stop if there are no further URLs to try.
10. Otherwise, replace data with an ABI-encoded call to the contract function specified by the 4-byte selector callbackFunction, supplying the data returned from step (7) and extraData from step (4), and return to step (1).

  • I think (5) mentioned in 8 & 9 is typo from draft, that should be (7)??

During CCIP read IF (7) request fails with clientside/network errors (server rejects or ssl errors) without any http status code, that’s preventing auto fallback to secondary gateway url. For failsafe scenario CCIP clients should try all fallback gateways until status code == 200 before giving up.

cc @ethlimo.eth here we go :pray:

2 Likes

Good catch, that’s correct.

Yes, correct.

2 Likes

@ethlimo.eth opened this issue for ethers.js.
it’ll take one more try catch in ccip clients.

2 Likes

reporting with more test in ethers.js & viem

Viem Test : It’s handling CCIP fallback correctly. :white_check_mark:

try {
  const result = await viem.ccipRequest({
      data: '0xc0de4c0ffee',
      sender: '0xc0de4c0ffeeec0de4c0ffeeec0de4c0ffeeec0de',
      urls: [
        `https://down.gateway/{sender}/{data}`,
        `https://fallback.gateway/{sender}/{data}`
      ],
    })
} catch (e){
    console.log(e)
}

>> HttpRequestError: HTTP request failed.
URL: https://fallback.gateway/{sender}/{data}
Details: Failed to fetch
Version: viem@2.17.4

Viem looks ok as it’s using universal resolver

On ethers.js it’s sending fetch request outside of try catch. No Universal Resolver. :x:

Edit : almost forgot ethers.js error logs, we’ve listed total primary+3 IPFS gateways for CCIP read.

ethers.min.js:1 
 GET https://e501017….99f7d5d….eab2c38eedc50…1a8bd0def.ipfs2.eth.limo/.well-known/eth/freetib/contenthash.json?t=0x5824 
net::ERR_HTTP2_PROTOCOL_ERROR

ethers.min.js:1 
 Uncaught (in promise) TypeError: Failed to fetch
    at FetchRequest.getUrl (ethers.min.js:1:15378)
    at #send (ethers.min.js:1:21605)
    ....
1 Like

To clarify, you mean status 4XX shouldn’t abort the gateway iteration?
What about 2XX with junk?


I’d go one step further and say termination should be decided by the contract.

OffchainNext.sol lets the contract decide if it wants to accept the response. It does randomized gateway iteration too. It works today w/o any framework modification. It only does 1-of-n but it could be extended to do m-of-n.

I have a pretty cool demo where I have an setup with various endpoints + one good one, and forcibly try all permutations and request always succeeds (at the expense of extra latency.)

I also have a demo where I use this with an EVMGateway, where it blocks a malicious gateway from supplying it invalid proofs (which would appear like normal 200 properly formatted response.) This illustrates the nuance in the error types:

  1. if 200 but "kek" or {data: "0x"}, that’s an invalid response → next
  2. if 200 with valid looking proofs but they were wrong, that’s an invalid response → next
  3. if 200 with valid proofs that assert you can’t do something or divide by zero, that’s a valid response but an execution error → fail
  4. if 200 with valid proofs and success → success

Additionally, we should also emphasize that {sender} is not necessarily actual requestor, and that requesting identity should be encoded into the endpoint URL to indicate the chain and contract (eg. signing relative to sender is bad practice.)

2 Likes

Tagging @ricmoo for Ethers support.

Thanks. Looking into this now.

I think it is likely the same root cause as this issue, if you could check and see if you agree?

Do you have a simple test case I can use to test this and the confirm the fix addresses it?

lol. I should have clicked through the link in the issue first; looks like it references this post. le sigh…

Aborting after 4xx error will give full power to first gateway in list to halt whole lookup process.
eg, in a randomized gateway list of N =5, if 1 gateway is throwing 4XX that’s 20% failed offchain lookup as there’s no fallback.

while(request.status !== 200){
    //.. try catch next gateway
}

erc3668 is strict with that {data:"..."}, if a bad gateway wants resolver to handle that junk? it’s not breaking anything on erc3668, jus do recurcise ccip lookup directly from resolver like m-of-n multisig :stuck_out_tongue: .

I’m not sure how far can we go without breaking ERC3668 specs. it’s cleverly designed to fit in between web2 & web3, so whole looklup process is trying to mimic http but we want more failsafe scenario for web3/decentralization…

– I’m re/thinking erc7700 draft with this erc3668 & gateways stuffs in mind… I’ll share that erc7700 redesign/suggestion here soon. we can’t change anything big in erc3668 after 4 years.

1 Like

Did you look at OffchainNext and my example? It lets the contract decide w/o any modification–but it can’t circumvent the 4XX ethers issue.

1 Like

I see you’re using data uri to act like final failsafe gateway… :vulcan_salute:

_shouldTryNext should also be triggered after signature/length check fails.

there’s one more thing to check from ERC3668.

This protocol can result in multiple lookups being requested by the same contract. Clients MUST implement a limit on the number of lookups they permit for a single contract call, and this limit SHOULD be at least 4.

1 Like

FYI. The issue has been fixed in Ethers. See the v6.13.3 notes for details.

Thanks for letting me know. :slight_smile:

5 Likes