Skip to content
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

Merged
merged 7 commits into from
Sep 25, 2024
Merged

Conversation

mateusz-sekara
Copy link
Contributor

No description provided.

@@ -90,136 +89,6 @@ func (e ExecuteOffchainConfig) Validate() error {
return nil
}

// TokenDataObserverConfig is the base struct for token data observers. Every token data observer
Copy link
Contributor Author

@mateusz-sekara mateusz-sekara Sep 25, 2024

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) {
Copy link
Contributor Author

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 {
Copy link
Contributor Author

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) {
Copy link
Contributor Author

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

@mateusz-sekara mateusz-sekara marked this pull request as ready for review September 25, 2024 08:44
@mateusz-sekara mateusz-sekara requested a review from a team as a code owner September 25, 2024 08:44
@mateusz-sekara mateusz-sekara changed the title Implementing attestation API CCIP-2882 Implementing attestation API Sep 25, 2024
outcome[chainSelector] = make(map[exectypes.MessageTokenID]AttestationStatus)

for tokenID, messageHash := range hashes {
// TODO sequential processing
Copy link
Contributor Author

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)
Copy link
Collaborator

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?

Copy link
Contributor Author

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
Copy link
Collaborator

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

Copy link
Contributor Author

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 {
Copy link
Collaborator

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

Copy link
Contributor Author

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 ;)

//nolint:revive
func NewHTTPClient(api string, apiInterval time.Duration, apiTimeout time.Duration) *httpClient {
return &httpClient{
apiURL: api,
Copy link
Collaborator

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?

Copy link
Contributor Author

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

Copy link
Contributor Author

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

Copy link

Test Coverage

Branch Coverage
attestation-api 72.6%
main 72.0%

@@ -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)
Copy link
Contributor

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()
Copy link
Contributor

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
Copy link
Contributor

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 {
Copy link
Contributor

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 ->
Copy link
Contributor

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?

Copy link
Contributor Author

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
Copy link
Contributor

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))
Copy link
Contributor

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
Copy link
Contributor

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 {
Copy link
Contributor

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()
Copy link
Contributor

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
Copy link
Contributor

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
Copy link
Contributor

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?

Copy link
Contributor Author

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

@mateusz-sekara mateusz-sekara merged commit 7eba4f9 into main Sep 25, 2024
3 checks passed
@mateusz-sekara mateusz-sekara deleted the attestation-api branch September 25, 2024 13:51
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 {
Copy link
Contributor

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)
Copy link
Contributor

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 {
Copy link
Contributor

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
Copy link
Contributor

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

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.

4 participants