-
Notifications
You must be signed in to change notification settings - Fork 7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
CCIP-2882 Implementing attestation API #162
Conversation
@@ -90,136 +89,6 @@ func (e ExecuteOffchainConfig) Validate() error { | |||
return nil | |||
} | |||
|
|||
// TokenDataObserverConfig is the base struct for token data observers. Every token data observer |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Extracted to a separate file, no changes
@@ -184,274 +181,3 @@ func TestExecuteOffchainConfig_EncodeDecode(t *testing.T) { | |||
}) | |||
} | |||
} | |||
|
|||
func Test_TokenDataObserver_Unmarshall(t *testing.T) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Extracted to a separate file, no changes
// in the TokenDataObserverConfig similarly to how USDCCCTPObserverConfig is embedded in the TokenDataObserverConfig. | ||
// There are two additional checks for the TokenDataObserverConfig to enforce that it's semantically (Validate) | ||
// and syntactically correct (WellFormed). | ||
type TokenDataObserverConfig struct { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Copied from exectypes, no changes here
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func Test_TokenDataObserver_Unmarshall(t *testing.T) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Copied from exectypes, no changes here
outcome[chainSelector] = make(map[exectypes.MessageTokenID]AttestationStatus) | ||
|
||
for tokenID, messageHash := range hashes { | ||
// TODO sequential processing |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Next PR, I'm optimizing for having working usdc flow asap. We can optimize the code by caching or parallel processing later
} | ||
} | ||
|
||
timeoutCtx, cancel := context.WithTimeoutCause(ctx, h.apiTimeout, ErrTimeout) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
q: if ctx is canceled before the timeout, does timeoutCtx cancel?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
have you checked the tests? :P yeah, it cancels
return response, http.StatusRequestTimeout, ErrTimeout | ||
} | ||
// On error, res is nil in most cases, do not read res.StatusCode, return BadRequest | ||
return response, http.StatusBadRequest, err |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we just return nil
here instead of response
to ensure its never accessed? I think we had a bug around this in <V1.5
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
response
is not an http object, it's just a payload, no risk
|
||
func (h *httpClient) setCoolDownPeriod(headers http.Header) { | ||
coolDownDuration := defaultCoolDownDuration | ||
if retryAfterHeader, exists := headers["Retry-After"]; exists && len(retryAfterHeader) > 0 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
consider: Retry-After
as a constant
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not sure if worth it, it's used in a single place and it's rather obvious what it is ;)
execute/tokendata/usdc/http.go
Outdated
//nolint:revive | ||
func NewHTTPClient(api string, apiInterval time.Duration, apiTimeout time.Duration) *httpClient { | ||
return &httpClient{ | ||
apiURL: api, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
q: Do we wanna do some kind of parsing on the passed in URL so that we don't construct garbage when doing the fmt.Sprintf
below?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah, probably makes sense to be defensive here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
added parsing during object construction and tests
30e8ecf
to
6ae1f66
Compare
Test Coverage
|
@@ -60,7 +60,7 @@ func NewConfigBasedCompositeObservers( | |||
// e.g. observers[i] := config.CreateTokenDataObserver() | |||
switch { | |||
case c.USDCCCTPObserverConfig != nil: | |||
observer, err1 := createUSDCTokenObserver(lggr, destChainSelector, c.USDCCCTPObserverConfig.Tokens, readers) | |||
observer, err1 := createUSDCTokenObserver(lggr, destChainSelector, *c.USDCCCTPObserverConfig, readers) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why err1
and not err
?
@@ -60,7 +60,7 @@ func NewConfigBasedCompositeObservers( | |||
// e.g. observers[i] := config.CreateTokenDataObserver() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NewConfigBasedCompositeObservers
is an exported function that returns an unexported type. In general it's not a good approach. Either return interface or exported struct.
) | ||
|
||
type AttestationStatus struct { | ||
// MessageHash is the hash of the message that the attestation was fetched for. It's going to be MessageSent event hash | ||
MessageHash []byte |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can replace []byte
with cciptypes.Bytes
, that way you also get cool hex encoding out of the box in logs.
ErrNotReady = errors.New("token data not ready") | ||
ErrRateLimit = errors.New("token data API is being rate limited") | ||
ErrTimeout = errors.New("token data API timed out") | ||
ErrUnknownResponse = errors.New("unexpected response from attestation API") | ||
) | ||
|
||
type AttestationStatus struct { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would be nice to have comment for AttestationStatus
.
e.g.
// AttestationStatus is returned from CCTP api / is used internally to keep track of / ...
@@ -30,15 +45,62 @@ type AttestationStatus struct { | |||
// Ethereum -> | |||
// | |||
// 12 -> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this mean for 12
there is no entry in the map?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's nested map :D So 12 is a chainSelector, next line contains padded messageTokenIds
func (r httpResponse) attestationToBytes() ([]byte, error) { | ||
attestationBytes, err := hex.DecodeString(strings.TrimPrefix(r.Attestation, "0x")) | ||
if err != nil { | ||
return nil, err |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
wrap err
defer cancel() | ||
|
||
requestURL := *h.apiURL | ||
requestURL.Path = path.Join(requestURL.Path, apiVersion, attestationPath, fmt.Sprintf("0x%x", messageHash)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can replace fmt.Sprintf("0x%x".....
with cciptypes.Bytes(messageHash).String()
// failing to make any progress. | ||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url.String(), nil) | ||
if err != nil { | ||
return response, http.StatusBadRequest, err |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
wrap err
return nil, res.StatusCode, fmt.Errorf("failed to decode json: %w", err) | ||
} | ||
|
||
if err1 := response.validate(); err1 != nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i think err1
can be just err
return nil, res.StatusCode, err1 | ||
} | ||
|
||
attestationBytes, err := response.attestationToBytes() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe better to wrap this err
if err != nil { return fmt.Errorf("convert attestation to bytes: %w")
return bytes, code, nil
defaultCoolDownDuration = 5 * time.Minute | ||
|
||
// maxCoolDownDuration defines the maximum duration we can wait till firing the next request | ||
maxCoolDownDuration = 10 * time.Minute |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's have this configurable, provide them in the constructor.
@@ -12,6 +12,7 @@ require ( | |||
golang.org/x/crypto v0.27.0 | |||
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 | |||
golang.org/x/sync v0.8.0 | |||
golang.org/x/time v0.3.0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We want this for ratelimiting?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, this is exactly how it's done for v1.5, let's keep it as it
func (h *httpClient) setCoolDownPeriod(headers http.Header) { | ||
coolDownDuration := defaultCoolDownDuration | ||
if retryAfterHeader, exists := headers["Retry-After"]; exists && len(retryAfterHeader) > 0 { | ||
if retryAfterSec, errParseInt := strconv.ParseInt(retryAfterHeader[0], 10, 64); errParseInt == nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it always an array of size 1? Wondering if we know what could be in the array?
|
||
h.coolDownMu.Lock() | ||
defer h.coolDownMu.Unlock() | ||
coolDownDuration = min(coolDownDuration, maxCoolDownDuration) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does it mean that if Circle told us to cooldown for >10m we still going to fire requests? Why not just relying on coolDownDuration
?
func (h *httpClient) setCoolDownPeriod(headers http.Header) { | ||
coolDownDuration := defaultCoolDownDuration | ||
if retryAfterHeader, exists := headers["Retry-After"]; exists && len(retryAfterHeader) > 0 { | ||
if retryAfterSec, errParseInt := strconv.ParseInt(retryAfterHeader[0], 10, 64); errParseInt == nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it always an array of size 1? Wondering if we know what could be in the array?
// Wait blocks until it the attestation API can be called or the | ||
// context is Done. | ||
if waitErr := h.rate.Wait(ctx); waitErr != nil { | ||
return empty, http.StatusTooManyRequests, ErrRateLimit |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wondering if we want to differentiate the 2 err. Something like ErrRateLimit
and ErrCoolDown
No description provided.