But if we update the client to fix 4XX and invalid 2XX response, we need to ensure a JSON data-url is a valid endpoint, since OffchainTryNext() and OffchainLookupUnanswered() can be emulated using data-url and using recursive CCIP-Read w/pairs of endpoints:
endpoints = [a, b, c]
revert OffchainLookup w/ [a, data:0x]
if response == 0x or invalid, revert OffchainLookup w/ [b, data:0x]
if response == 0x or invalid, revert OffchainLookup w/[c, data: 0x]
if response == 0x or invalid, <No Answer>
My thinking was just that if we update the client, might as well make the spec better.
I mean itâs old âlockedâ ERC, if we can update more than basic fix Iâd like to push for direct ipfs://.., ipns://.., bzz://.. support as itâs limited to http/s only.
itâs already possible to do that with current specs, we shouldnât add all possible iteration in specs even if itâs possible to update old ERCs. Itâs up to devs how they want to use that callback as fallback.
gateways = [ https://gateway1/âŠ, datauri:hex(gateway1 failed)]
callback >> retry >> fallback to gateway 2.
Itâs difficult to evaluate an entirely new implementation against the current one. Can you describe how itâs different, and why the existing one canât have PRs instead? Tagging @taytems .
No general objections to this as a new EIP extending 3668. Forwards-compatibiltiy seems like an issue here: contracts may avoid using the new functionality because it will break legacy 3668-only clients, and instead opt for degraded support that they know wonât break things. Perhaps some method could be incorporated that lets the client signal to the contract that it supports this new EIP?
How do you send an error to a contract?
How responses are verified is entirely up to implementations; thatâs not specified by 3668.
How would the callback know it was being called for a 4xx? Intuitively this seems bad.
I only wrote it as a testbed for CCIP-Read, since its a complex example of wrapping and potentially recursive calls, but it turned out as a pretty nice implementation.
Initially I was thinking first URL, but after brainstorming with Premm a bit, the last URL could be a signal value (indicating the new feature set) and that would be maximally backwards compatible without requiring dataurl support.
Since callback(bytes response, bytes extraData) expects calldata, response can be supplied as just bytes4(selector) to indicate an error.
A successful call to f() returns (bytes4) via CCIP-Read would return 0xXXXXXXXX0000.....0000
I think the question is the difference between a CCIP-Read server that emits a 4XX and the contract is never told about the problem, only the client. And one that will pass {data: "0x..."} to the callback regardless of status code. For example, a 404 could supply {data: "0xc5723b51"} which is error NotFound().
I donât think this feature is necessary, it just seems more useful, since the new protocol would effectively silence the error, as it will just try the next endpoint rather than killing the session.
For new stuff: since the contract decides if it accepts the request or wants another response, the only issue I can think of would be some kind of HTTP middleware throwing an error between the server and the client, and responding with 4XX with malicious {data: "0x..."} that the contract somehow accepts.
For backwards compat, nearly every contract expects the response to ABI encoded, so bytes.length % 32 > 0 will likely revert.
The general idea here seems pretty useful in allowing the contract to be aware of and control the response in the case of an error. Iâve run into the issue before (specifically with the UniversalResolver sender).
Iâd note that the proposed solution would make it harder to debug a faulty endpoint since an HTTP error no longer becomes the source of the error to throw, but is instead given to the contractâs resulted handling of OffchainLookupUnanswered(). That isnât crucial though and could be fixed with better logging/etc.
I donât really see how this is an improvement over the pending UniversalResolver v3 changes. The implementation is more simple yes, but it also provides a significantly smaller feature set without any of the existing edge case handling.
Weâre working on new erc7700 draft to wrap all types of crosschain & dweb storage providers, @NameSys will push that new version soon.
Basic outline is to wrap everything with single new revert selector, eg.(bytes4(erc7700.selector)+bytes4(erc3668. selector)+âŠdata). Adding proper interface checks for read, write & callback usages, auto retry next gateway after 4xx & 5xx and support ipfs:// bzz:// ipns:// types in gateway *if 3668 revert data is wrapped in 7700 selector.
Okay, not calldata, but an abi-encoded response, which should be word-aligned. My repo implements this solution and I donât see any issue disambiguating a response from an error. Itâs the same as a returning an error over multicall as weâve discussed previously.
My design only needs to encode 1 signal value: OffchainLookupUnanswered(). If I canât assume this is word-aligned, there are other ways of communicating this information to the contract (see below).
Itâs perfectly fine if it doesnât do this, but that just means that either the status 2XX or the response is ignored. Servers would instead indicate an error to the contract by responding 200. These are equivalent to me.
I just think that the contract should dictate which response is accepted and ultimately decide what to do if no response is sufficient.
Recall, with this change, the contractâs callback:
can revert OffchainTryNext(sender) to reject a response w/o terminating the OffchainLookup session.
may be supplied bytes that are not word-aligned (this can already happen.)
will be supplied OffchainLookupUnanswered() if no response is accepted
Taking your feedback, 2 and 3 could be replaced with: if the hash of the response bytes are exactly 0x... then the CCIP-Read client is indicating OffchainLookupUnanswered(). Ideally, that response would be less than 32 bytes, causing any abi.decode to blow up, and therefore be backwards-compatible (since any existing CCIP-Read session that got no response is currently fatal.)
Okay. Still not a fan, though I see your point. It certainly shouldnât do this unless it can detect that the contract definitely implements this new standard, though.
Iâm not really sure what you mean by this.
Why not have 400s and 500s return an error in the same way you document above, with the response embedded?
This also seems fine but Iâm not sure what the contract would do with a human-readable error.
For an error, I see 3 possible responses:
400 âbad requestâ`
400: {"data": "0x..."} â my suggestion: just ignore status code and propagate whatever it sent, let the contract decide
200: {"data": "0x..."} â your suggestion (I thought): standard abi-encoded response that encodes there was an error, where the contract can decode it, but the HTTP layer isnât aware of it
My thinking was, unless the server returns {"data": "0x...."} the contract canât really digest it. It could wrap it in Error(string) but whatâs the contract going to do with it?
Currently, if itâs 4XX, the 3668 session just terminates. Instead of silently ignoring the error, it seemed useful (if it was properly formatted) to relay that information to the contract.
Iâm not against an HTTP status code wrapper, if thatâs what youâre suggesting, it just seems like if the server is functioning, it should be relaying errors via {"data": "0x..."} rather than status code. If the server isnât functioning, likely the error (and body) arenât usable by the contract.
For example, with a trusted gateway, if the error isnât signed, why would you trust it? And for an untrusted gateway, why trust anything (including the status code) about the response?
I am not a fan of any solution that involves ignoring the status code, or sending 200s when itâs actually an error.
What I was suggesting is that if you want to be able to handle errors in the contract, you can pass them in as error objects containing the data decoded from the response (if data could be decoded from the response).
Whatâs the alternative to trusting the error? The process canât proceed further without a valid response.
Both 4XX & 5XX errors should auto fallback to next gateway in list.
with current specs if first gateways is throwing 4xx, itâll stop whole lookup process.
this is more important as weâre selecting random gateways / shuffling gateway list.
& we also need that if we want to add data:application/json;charset=utf-8,%7B%22data%22%3A%220xfffffffff%22%7D data uris as 2nd gateway for callback to indicate that 1st gateway is throwing 4xx/5xx.
The feature I want is to allow untrusted/unreliable gateways in the gateway set.
To allow that, the arbiter of truth has to be the contract, since a malicious gateway can just fake its response and status code.
This implies that gateway errors at the HTTP level are meaningless to the protocol, so I concluded the status code can just be ignored, and all that matters is obtaining a correctly encoded response that the contract accepts, otherwise the call is a failure.
I say âignoreâ because it seems like nice developer UX if gateway errors also set their status code:
If a signing key is involved, errors should be signed. Any HTTP failure simply moves on to the next endpoint.
For a proving gateway, the appropriate proofs need supplied even in the failure case, otherwise, why trust it? Lineaâs gateway suffers from this problem: their gateway can claim any storage value doesnât exist and their verifier accepts it without proof.
Ignoring HTTP status codes is generally a bad practice, though. Iâm not sure why you object to just passing them through - along with response data - to the contract and letting it decide?
Right, but you still have to handle the case where an error response is returned without valid proof data.
Neither of these should kill the gateway iteration unless the contract says so.
HTTP errors certainly could be wrapped with HTTPError(uint16 code, string message) or w/e but I thought you were against passing error data to the contract.
I claim the common implementation would be the following since thereâs not much you can do with that error:
if bytes4(response) == HTTPError.selector) {
revert OffchainTryNext(address(this));
}
Unaware contracts (except for pass-through) would fail to decode that response, revert, and terminate the process, which is the current behavior.
I questioned the approach, but you provided justification. Iâm okay with it as long as thereâs some detection mechanism so that 3668-only contracts wonât be sent error data they may not understand.