Skip to content
This repository was archived by the owner on Mar 14, 2025. It is now read-only.

offchain - resurrect multi usdc transfers per msg#633

Merged
dimkouv merged 11 commits intoccip-developfrom
dk/ressurrect-multiusdc-transfers
Mar 28, 2024
Merged

offchain - resurrect multi usdc transfers per msg#633
dimkouv merged 11 commits intoccip-developfrom
dk/ressurrect-multiusdc-transfers

Conversation

@dimkouv
Copy link
Contributor

@dimkouv dimkouv commented Mar 22, 2024

Knowledge-sharing for reviewers

  1. For each token of a msg we find the token pool and call lock_or_burn.
  2. For USDC pool, the usdc contract we call emits a UsdcMessageSent event.
  3. This events are picked up by the log poller.

The problem

For a given ccip msg (tx hash, log index) we called:

usdcreader.GetLastUSDCMessagePriorToLogIndexInTx(logIndex, txHash)

But this only gives us the Last usdc message log.
A ccip message can contain multiple usdc token transfers with one log emitted for each.

The fix

I am updating the method above to also accept usdcTokenIndexOffset

usdcreader.GetUSDCMessagePriorToLogIndexInTx(logIndex, usdcTokenIndexOffset, txHash) ([]byte, error)

Here is an example:

msg.tokenAmounts = [
   {token: "0xusdc", amount: 12} // offset=2
   {token: "0xSomehingElse", amount: 3123}
   {token: "0xusdc", amount: 14} // offset=1
   {token: "0xusdc", amount: 114} // offset=0 (that's what we would return without this fix)
]

The reason that we essentially ignore non-usdc tokens is that for a message containing multiple usdc transfers the logs returned by the logPoller do not contain non-usdc logs, so we need to have that offset to pick the correct log.

@github-actions
Copy link
Contributor

I see you updated files related to core. Please run pnpm changeset to add a changeset.

@dimkouv dimkouv changed the title offchain - ressurrect multi usdc transfers per msg offchain - resurrect multi usdc transfers per msg Mar 22, 2024
Copy link
Collaborator

@RensR RensR left a comment

Choose a reason for hiding this comment

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

I feel like the test coverage around actually using multiple usdc tokens in a single msg is missing

@AnieeG
Copy link
Contributor

AnieeG commented Mar 28, 2024

I feel like the test coverage around actually using multiple usdc tokens in a single msg is missing

Just updated the test to include this scenario. one msg 2 usdc tokens, one non-usdc token

usdcTokenIndex := (len(allUsdcTokensData) - 1) - usdcTokenIndexOffset

if usdcTokenIndex < 0 || usdcTokenIndex >= len(allUsdcTokensData) {
return nil, errors.Errorf("no USDC message found prior to log index %d, usdc token:%d in tx %s, usdcTokenIndex=%d",
Copy link
Contributor

Choose a reason for hiding this comment

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

IMO it's better to log errors with so many details because it's easier to search in Loki then. The returned error could be simplified then

current := logs[len(logs)-i-1]
// collect the logs with log index less than the provided log index
allUsdcTokensData := make([][]byte, 0)
for _, current := range logs {
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 we miss tests covering this logic, no?

}

func (u *USDCReaderImpl) GetLastUSDCMessagePriorToLogIndexInTx(ctx context.Context, logIndex int64, txHash string) ([]byte, error) {
func (u *USDCReaderImpl) GetUSDCMessagePriorToLogIndexInTx(ctx context.Context, logIndex int64, usdcTokenIndexOffset int, txHash string) ([]byte, error) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Correct me if I'm wrong, but the only difference is that we fetch all the USDC logs from a single TX and the we pick the right one based on the usdcTokenIndexOffset, no?

IIUC, having a CCIP message with multiple USDC transfers will always fetch all the logs from a single TX (u.lp.IndexedLogsByTxHash), pick the correct one, and then discard the rest of them. Also, it would be done multiple times, right?

Copy link
Contributor Author

@dimkouv dimkouv Mar 28, 2024

Choose a reason for hiding this comment

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

Very nice observation, we can improve it as a follow up, by passing a list of tokens probably.
I would prefer not to include it here as it requires changes in several places not related to this change.

Copy link
Contributor

Choose a reason for hiding this comment

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

Sounds good, please make sure that ticket for that followup is created ;)

// if usdcTokenIndexOffset is 0 we select usdc2
// if usdcTokenIndexOffset is 1 we select usdc1
// The message logs are found using the provided transaction hash.
GetUSDCMessagePriorToLogIndexInTx(ctx context.Context, logIndex int64, usdcTokenIndexOffset int, txHash string) ([]byte, error)
Copy link
Contributor

Choose a reason for hiding this comment

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

PriorToLogIndex? IMO it's more like GetUSDCMessageBasedOnTheLogIndex

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't like both names :D Let's rename it in the follow-up with the performance improvement.

Copy link
Contributor

Choose a reason for hiding this comment

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

GetUSDCMessageByLogIndex? This is the convention we have in LogPoller (but not in all places :P). PriorToIndex is very misleading to me, because it sounds like "give me all logs from [0, tokenIndex]"

Copy link
Collaborator

@RensR RensR Mar 28, 2024

Choose a reason for hiding this comment

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

Message singular and PriorToIndex implies the last singular log before the given index 🤷🏼

return nil, fmt.Errorf("invalid token index %d for msg with %d tokens", tokenIndex, len(msg.TokenAmounts))
}

offsetFromLastUsdcToken := 0
Copy link
Contributor

Choose a reason for hiding this comment

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

I would extract that to a separate method that just returns a proper index of usdc token

// GetLastUSDCMessagePriorToLogIndexInTx returns the last USDC message that was sent
// before the provided log index in the given transaction.
GetLastUSDCMessagePriorToLogIndexInTx(ctx context.Context, logIndex int64, txHash string) ([]byte, error)
// GetUSDCMessagePriorToLogIndexInTx returns the specified USDC message data.
Copy link
Contributor

Choose a reason for hiding this comment

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

Correct me if I'm wrong, but it works as follows

for i := range msg.tokens {
    if usdc  { 
        log := fetchLogFromDatabase(i)
        attestation := fetchAttestation(log, i)
    }
}

This means that 100 usdc messages will equal 100 database calls, right?
Whereas we could be more efficient here probably by doing sth like this. This assumes that all logs are in the same TX, I'm not sure if this is a valid assumption

// grouping by type first
usdcMsgs := []msgs
for i := range msg.tokens {
   if usdc {
     usdcMsgs = append(usdcMsgs, msg.tokens[i])
  }
}

logs := fetchLogFromDatabase()
for i:= range usdcMessages {
   fetchLogFromDatabase(logs[i]. i)
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

sounds like a nice improvement, we can do as a follow up since it requires more interface changes that I don't want to include in this pr

Copy link
Collaborator

Choose a reason for hiding this comment

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

While nice in theory, the odds of multiple usdc txs in 1 tx is so low it's totally fine to punt imo

func (s *TokenDataReader) getUSDCMessageBody(
ctx context.Context,
msg cciptypes.EVM2EVMOnRampCCIPSendRequestedWithMeta,
tokenIndex int,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why isnt this a uint, it should never be <0 correct?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

A bit less casting for comparisons e.g. with len. Imo not really important

Copy link
Contributor

@jasonmci jasonmci left a comment

Choose a reason for hiding this comment

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

Approved

@dimkouv dimkouv closed this Mar 28, 2024
asoliman92 pushed a commit that referenced this pull request Jul 31, 2024
## Knowledge-sharing for reviewers
1. For each token of a msg we find the token pool and call lock_or_burn.
2. For USDC pool, the usdc contract we call emits a `UsdcMessageSent`
event.
3. This events are picked up by the log poller.  

## The problem
For a given ccip msg (tx hash, log index) we called:
```
usdcreader.GetLastUSDCMessagePriorToLogIndexInTx(logIndex, txHash)
```

But this only gives us the `Last` usdc message log.
A ccip message can contain multiple usdc token transfers with one log
emitted for each.

## The fix
I am updating the method above to also accept `usdcTokenIndexOffset`
```
usdcreader.GetUSDCMessagePriorToLogIndexInTx(logIndex, usdcTokenIndexOffset, txHash) ([]byte, error)
```

Here is an example:
```
msg.tokenAmounts = [
   {token: "0xusdc", amount: 12} // offset=2
   {token: "0xSomehingElse", amount: 3123}
   {token: "0xusdc", amount: 14} // offset=1
   {token: "0xusdc", amount: 114} // offset=0 (that's what we would return without this fix)
]
```

The reason that we essentially ignore non-usdc tokens is that for a
message containing multiple usdc transfers the logs returned by the
logPoller do not contain non-usdc logs, so we need to have that offset
to pick the correct log.

---------

Co-authored-by: AnieeG <anindita.ghosh@smartcontract.com>
Co-authored-by: Anindita Ghosh <88458927+AnieeG@users.noreply.github.com>
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants