Skip to content

Commit 9bb0640

Browse files
authored
CCIP-5109 Add LBTC tokendata (#801)
* CCIP-5109 Add LBTC tokendata * review fixes
1 parent cb02e90 commit 9bb0640

16 files changed

+1608
-41
lines changed

execute/plugin_tokendata_test.go

Lines changed: 54 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ func runRoundAndGetOutcome(ctx context.Context, ocrTypeCodec ocrtypecodec.ExecCo
3232
return outcome
3333
}
3434

35-
func Test_USDC_Transfer(t *testing.T) {
35+
func Test_LBTC_USDC_Transfer(t *testing.T) {
3636
ocrTypeCodec := ocrtypecodec.DefaultExecCodec
3737
ctx := tests.Context(t)
3838

@@ -43,6 +43,13 @@ func Test_USDC_Transfer(t *testing.T) {
4343
usdcAddressBytes, err := cciptypes.NewUnknownAddressFromHex(usdcAddress)
4444
require.NoError(t, err)
4545

46+
lbtcAddress := "0xc791ec14ad1d566425f006eec12a300343164ab1"
47+
lbtcAddressBytes, err := cciptypes.NewUnknownAddressFromHex(lbtcAddress)
48+
require.NoError(t, err)
49+
lbtcMessageHash1 := internal.MustDecode("0xa9165956caf08b3da46db4cccdd58098b2c3a90e57372f3f28d7d46672e2091b")
50+
lbtcMessageHash2 := internal.MustDecode("0xc317b01e5a87000f8c51517227ea9ff07c9f4da646e8209c56424dc85ff50fe7")
51+
lbtcMessageHash3 := internal.MustDecode("0x5f9b38941ce144fad0b6890a3e15bc67c7b51bb89751ab6f672f088f44e36b91")
52+
4653
messages := []inmem.MessagesWithMetadata{
4754
makeMsgWithMetadata(102, sourceChain, destChain, false),
4855
makeMsgWithMetadata(103, sourceChain, destChain, false),
@@ -54,10 +61,21 @@ func Test_USDC_Transfer(t *testing.T) {
5461
SourcePoolAddress: usdcAddressBytes,
5562
ExtraData: readerpkg.NewSourceTokenDataPayload(2, 0).ToBytes(),
5663
})),
57-
makeMsgWithMetadata(106, sourceChain, destChain, false,
64+
makeMsgWithMetadata(106, sourceChain, destChain, false, withTokens(cciptypes.RampTokenAmount{
65+
SourcePoolAddress: lbtcAddressBytes,
66+
ExtraData: lbtcMessageHash1,
67+
})),
68+
makeMsgWithMetadata(107, sourceChain, destChain, false, withTokens(cciptypes.RampTokenAmount{
69+
SourcePoolAddress: lbtcAddressBytes,
70+
ExtraData: lbtcMessageHash2,
71+
})),
72+
makeMsgWithMetadata(108, sourceChain, destChain, false,
5873
withTokens(cciptypes.RampTokenAmount{
5974
SourcePoolAddress: usdcAddressBytes,
6075
ExtraData: readerpkg.NewSourceTokenDataPayload(3, 0).ToBytes(),
76+
}, cciptypes.RampTokenAmount{
77+
SourcePoolAddress: lbtcAddressBytes,
78+
ExtraData: lbtcMessageHash3,
6179
}),
6280
),
6381
}
@@ -68,7 +86,7 @@ func Test_USDC_Transfer(t *testing.T) {
6886
newMessageSentEvent(0, 6, 3, []byte{3}),
6987
}
7088

71-
usdcAttestation104_106 := map[string]string{
89+
usdcAttestation104_108 := map[string]string{
7290
"0x0f43587da5355551d234a2ba24dde8edfe0e385346465d6d53653b6aa642992e": `{
7391
"status": "complete",
7492
"attestation": "0x100001"
@@ -79,9 +97,23 @@ func Test_USDC_Transfer(t *testing.T) {
7997
}`,
8098
}
8199

100+
lbtcAttestation106_108 := map[string]string{
101+
lbtcMessageHash1.String(): `{
102+
"message_hash": "0xa9165956caf08b3da46db4cccdd58098b2c3a90e57372f3f28d7d46672e2091b",
103+
"status": "NOTARIZATION_STATUS_SESSION_APPROVED",
104+
"attestation": "0x200001"
105+
}`,
106+
lbtcMessageHash3.String(): `{
107+
"message_hash": "0x5f9b38941ce144fad0b6890a3e15bc67c7b51bb89751ab6f672f088f44e36b91",
108+
"status": "NOTARIZATION_STATUS_SESSION_APPROVED",
109+
"attestation": "0x200003"
110+
}`,
111+
}
112+
82113
intTest := SetupSimpleTest(t, logger2.Test(t), []cciptypes.ChainSelector{sourceChain}, destChain)
83114
intTest.WithMessages(messages, 1000, time.Now().Add(-4*time.Hour), 1, sourceChain)
84-
intTest.WithUSDC(usdcAddress, usdcAttestation104_106, events, sourceChain)
115+
intTest.WithUSDC(usdcAddress, usdcAttestation104_108, events, sourceChain)
116+
intTest.WithLBTC(lbtcAddress, lbtcAttestation106_108, sourceChain)
85117
runner := intTest.Start()
86118
defer intTest.Close()
87119

@@ -100,15 +132,19 @@ func Test_USDC_Transfer(t *testing.T) {
100132
require.Len(t, outcome.CommitReports, 1)
101133

102134
// Round 3 - Filter
103-
// Messages 102-104,106 are executed, 105 doesn't have token data ready
135+
// Messages 102-104,106,108 are executed, 105 and 107 don't have token data ready
104136
outcome = runRoundAndGetOutcome(ctx, ocrTypeCodec, t, runner)
105137
require.NoError(t, err)
106138
require.Len(t, outcome.Report.ChainReports, 1)
107139
sequenceNumbers := extractSequenceNumbers(outcome.Report.ChainReports[0].Messages)
108-
assert.ElementsMatch(t, sequenceNumbers, []cciptypes.SeqNum{102, 103, 104, 106})
140+
assert.ElementsMatch(t, sequenceNumbers, []cciptypes.SeqNum{102, 103, 104, 106, 108})
109141
//Attestation data added to the USDC
110142
assert.Equal(t, internal.MustDecodeRaw("0x100001"), outcome.Report.ChainReports[0].OffchainTokenData[2][0])
111-
assert.Equal(t, internal.MustDecodeRaw("0x100003"), outcome.Report.ChainReports[0].OffchainTokenData[3][0])
143+
//Attestation data added to the LBTC
144+
assert.Equal(t, internal.MustDecodeRaw("0x200001"), outcome.Report.ChainReports[0].OffchainTokenData[3][0])
145+
//Attestation data added to the USDC+LBTC
146+
assert.Equal(t, internal.MustDecodeRaw("0x100003"), outcome.Report.ChainReports[0].OffchainTokenData[4][0])
147+
assert.Equal(t, internal.MustDecodeRaw("0x200003"), outcome.Report.ChainReports[0].OffchainTokenData[4][1])
112148

113149
intTest.usdcServer.AddResponse(
114150
"0x70ef528624085241badbff913575c0ab50241e7cb6db183a5614922ab0bcba5d",
@@ -117,6 +153,14 @@ func Test_USDC_Transfer(t *testing.T) {
117153
"attestation": "0x100002"
118154
}`)
119155

156+
intTest.lbtcServer.AddResponse(
157+
lbtcMessageHash2.String(),
158+
`{
159+
"message_hash": "0xc317b01e5a87000f8c51517227ea9ff07c9f4da646e8209c56424dc85ff50fe7",
160+
"status": "NOTARIZATION_STATUS_SESSION_APPROVED",
161+
"attestation": "0x200002"
162+
}`)
163+
120164
// Run 3 more rounds to get all attestations
121165
for i := 0; i < 3; i++ {
122166
outcome = runRoundAndGetOutcome(ctx, ocrTypeCodec, t, runner)
@@ -125,7 +169,9 @@ func Test_USDC_Transfer(t *testing.T) {
125169
require.Len(t, outcome.Report.ChainReports, 1)
126170
sequenceNumbers = extractSequenceNumbers(outcome.Report.ChainReports[0].Messages)
127171
// 102, 103 and 104 are in the inflight message cache.
128-
assert.ElementsMatch(t, sequenceNumbers, []cciptypes.SeqNum{105})
172+
assert.ElementsMatch(t, sequenceNumbers, []cciptypes.SeqNum{105, 107})
129173
//Attestation data added to the remaining USDC messages
130174
assert.Equal(t, internal.MustDecodeRaw("0x100002"), outcome.Report.ChainReports[0].OffchainTokenData[0][0])
175+
//Attestation data added to the remaining LBTC messages
176+
assert.Equal(t, internal.MustDecodeRaw("0x200002"), outcome.Report.ChainReports[0].OffchainTokenData[1][0])
131177
}

execute/test_utils.go

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@ package execute
33
import (
44
"context"
55
"encoding/binary"
6+
"encoding/json"
7+
"io"
68
"net/http"
79
"net/http/httptest"
10+
"slices"
811
"strings"
912
"testing"
1013
"time"
@@ -55,6 +58,7 @@ type IntTest struct {
5558
msgHasher cciptypes.MessageHasher
5659
ccipReader *inmem.InMemoryCCIPReader
5760
usdcServer *ConfigurableAttestationServer
61+
lbtcServer *ConfigurableAttestationServer
5862
tokenObserverConfig []pluginconfig.TokenDataObserverConfig
5963
tokenChainReader map[cciptypes.ChainSelector]contractreader.ContractReaderFacade
6064
}
@@ -203,6 +207,29 @@ func (it *IntTest) WithUSDC(
203207
}
204208
}
205209

210+
func (it *IntTest) WithLBTC(
211+
sourcePoolAddress string,
212+
attestations map[string]string,
213+
srcSelector cciptypes.ChainSelector,
214+
) {
215+
it.lbtcServer = newConfigurableAttestationServer(attestations)
216+
it.tokenObserverConfig = append(it.tokenObserverConfig, pluginconfig.TokenDataObserverConfig{
217+
Type: "lbtc",
218+
Version: "1",
219+
LBTCObserverConfig: &pluginconfig.LBTCObserverConfig{
220+
AttestationConfig: pluginconfig.AttestationConfig{
221+
AttestationAPI: it.lbtcServer.server.URL,
222+
AttestationAPIInterval: commonconfig.MustNewDuration(1 * time.Millisecond),
223+
AttestationAPITimeout: commonconfig.MustNewDuration(1 * time.Second),
224+
},
225+
SourcePoolAddressByChain: map[cciptypes.ChainSelector]string{
226+
srcSelector: sourcePoolAddress,
227+
},
228+
AttestationAPIBatchSize: 1,
229+
},
230+
})
231+
}
232+
206233
func (it *IntTest) Start() *testhelpers.OCR3Runner[[]byte] {
207234
cfg := pluginconfig.ExecuteOffchainConfig{
208235
MessageVisibilityInterval: *commonconfig.MustNewDuration(8 * time.Hour),
@@ -280,6 +307,9 @@ func (it *IntTest) Close() {
280307
if it.usdcServer != nil {
281308
it.usdcServer.Close()
282309
}
310+
if it.lbtcServer != nil {
311+
it.lbtcServer.Close()
312+
}
283313
}
284314

285315
func (it *IntTest) newNode(
@@ -360,12 +390,57 @@ func newConfigurableAttestationServer(responses map[string]string) *Configurable
360390
}
361391
}
362392
}
393+
if r.Method == http.MethodPost {
394+
var request map[string]interface{}
395+
bodyRaw, err := io.ReadAll(r.Body)
396+
if err != nil {
397+
panic(err)
398+
}
399+
err = json.Unmarshal(bodyRaw, &request)
400+
if err != nil {
401+
panic(err)
402+
}
403+
payloadHashesUntyped := request["messageHash"].([]interface{})
404+
if len(payloadHashesUntyped) == 0 {
405+
w.WriteHeader(http.StatusBadRequest)
406+
}
407+
payloadHashes := make([]string, len(payloadHashesUntyped))
408+
for i, hash := range payloadHashesUntyped {
409+
payloadHashes[i] = hash.(string)
410+
}
411+
attestationResponse := attestationBatchByMessageHashes(payloadHashes, c.responses)
412+
responseBytes, err := json.Marshal(attestationResponse)
413+
if err != nil {
414+
panic(err)
415+
}
416+
_, err = w.Write(responseBytes)
417+
if err != nil {
418+
panic(err)
419+
}
420+
}
363421
w.WriteHeader(http.StatusNotFound)
364422
}))
365423
c.server = server
366424
return c
367425
}
368426

427+
func attestationBatchByMessageHashes(payloadHashes []string, responses map[string]string) map[string]interface{} {
428+
attestations := make([]interface{}, 0, len(responses))
429+
for payloadHash, attestationRaw := range responses {
430+
if slices.Contains(payloadHashes, payloadHash) {
431+
var attestation map[string]interface{}
432+
err := json.Unmarshal([]byte(attestationRaw), &attestation)
433+
if err != nil {
434+
panic(err)
435+
}
436+
attestations = append(attestations, attestation)
437+
}
438+
}
439+
attestationResponse := make(map[string]interface{})
440+
attestationResponse["attestations"] = attestations
441+
return attestationResponse
442+
}
443+
369444
func (c *ConfigurableAttestationServer) AddResponse(key, response string) {
370445
c.responses[key] = response
371446
}

execute/tokendata/http/http.go

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package http
22

33
import (
4+
"bytes"
45
"context"
56
"errors"
67
"io"
@@ -39,6 +40,7 @@ type HTTPClient interface {
3940
// https://developers.circle.com/stablecoins/reference/getattestation
4041
// https://developers.circle.com/stablecoins/docs/transfer-usdc-on-testnet-from-ethereum-to-avalanche
4142
Get(ctx context.Context, path string) (cciptypes.Bytes, HTTPStatus, error)
43+
Post(ctx context.Context, path string, requestData cciptypes.Bytes) (cciptypes.Bytes, HTTPStatus, error)
4244
}
4345

4446
// httpClient is a client for the USDC attestation API. It encapsulates all the details specific to the Attestation API:
@@ -130,6 +132,27 @@ func (h *httpClient) Get(ctx context.Context, requestPath string) (cciptypes.Byt
130132
return response, httpStatus, err
131133
}
132134

135+
func (h *httpClient) Post(
136+
ctx context.Context,
137+
requestPath string,
138+
requestData cciptypes.Bytes,
139+
) (cciptypes.Bytes, HTTPStatus, error) {
140+
lggr := logutil.WithContextValues(ctx, h.lggr)
141+
142+
requestURL := *h.apiURL
143+
requestURL.Path = path.Join(requestURL.Path, requestPath)
144+
145+
response, httpStatus, err := h.callAPI(ctx, lggr, http.MethodPost, *h.apiURL, bytes.NewBuffer(requestData))
146+
h.lggr.Debugw(
147+
"Response from attestation API",
148+
"requestURL", requestURL.String(),
149+
"requestBody", string(requestData),
150+
"status", httpStatus,
151+
"err", err,
152+
)
153+
return response, httpStatus, err
154+
}
155+
133156
func (h *httpClient) callAPI(
134157
ctx context.Context,
135158
lggr logger.Logger,
@@ -209,7 +232,6 @@ func (h *httpClient) setCoolDownPeriod(lggr logger.Logger, headers http.Header)
209232
}
210233
}
211234
}
212-
213235
coolDownDuration = min(coolDownDuration, maxCoolDownDuration)
214236
//Logging on the error level, because we should always self-rate limit before hitting the API rate limit
215237
lggr.Errorw(

execute/tokendata/http/http_test.go

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ func Test_NewHTTPClient_New(t *testing.T) {
4949

5050
for _, tc := range tt {
5151
t.Run(tc.api, func(t *testing.T) {
52-
client, err := newHTTPClient(logger.Test(t), tc.api, 1*time.Millisecond, longTimeout, maxCoolDownDuration)
52+
client, err := newHTTPClient(logger.Test(t), tc.api, 1*time.Millisecond, longTimeout, 0)
5353
if tc.wantErr {
5454
require.Error(t, err)
5555
} else {
@@ -156,7 +156,7 @@ func Test_HTTPClient_Get(t *testing.T) {
156156
attestationURI, err := url.ParseRequestURI(ts.URL)
157157
require.NoError(t, err)
158158

159-
client, err := newHTTPClient(logger.Test(t), attestationURI.String(), tc.timeout, tc.timeout, maxCoolDownDuration)
159+
client, err := newHTTPClient(logger.Test(t), attestationURI.String(), tc.timeout, tc.timeout, 0)
160160
require.NoError(t, err)
161161
response, statusCode, err := client.Get(tests.Context(t), tc.messageHash.String())
162162

@@ -187,8 +187,7 @@ func Test_HTTPClient_Cooldown(t *testing.T) {
187187
attestationURI, err := url.ParseRequestURI(ts.URL)
188188
require.NoError(t, err)
189189

190-
client, err := newHTTPClient(logger.Test(t), attestationURI.String(),
191-
1*time.Millisecond, longTimeout, maxCoolDownDuration)
190+
client, err := newHTTPClient(logger.Test(t), attestationURI.String(), time.Millisecond, longTimeout, time.Minute)
192191
require.NoError(t, err)
193192
_, _, err = client.Get(tests.Context(t), cciptypes.Bytes32{1, 2, 3}.String())
194193
require.EqualError(t, err, tokendata.ErrUnknownResponse.Error())
@@ -208,13 +207,13 @@ func Test_HTTPClient_GetInstance(t *testing.T) {
208207
}))
209208
defer ts.Close()
210209

211-
client1, err := GetHTTPClient(logger.Test(t), ts.URL, 1*time.Hour, longTimeout, maxCoolDownDuration)
210+
client1, err := GetHTTPClient(logger.Test(t), ts.URL, 1*time.Hour, longTimeout, 0)
212211
require.NoError(t, err)
213212

214-
client2, err := GetHTTPClient(logger.Test(t), ts.URL, 1*time.Hour, longTimeout, maxCoolDownDuration)
213+
client2, err := GetHTTPClient(logger.Test(t), ts.URL, 1*time.Hour, longTimeout, 0)
215214
require.NoError(t, err)
216215

217-
client3, err := newHTTPClient(logger.Test(t), ts.URL, 1*time.Hour, longTimeout, maxCoolDownDuration)
216+
client3, err := newHTTPClient(logger.Test(t), ts.URL, 1*time.Hour, longTimeout, 0)
218217
require.NoError(t, err)
219218

220219
assert.True(t, client1 == client2)
@@ -251,9 +250,7 @@ func Test_HTTPClient_CoolDownWithRetryHeader(t *testing.T) {
251250
attestationURI, err := url.ParseRequestURI(ts.URL)
252251
require.NoError(t, err)
253252

254-
client, err := newHTTPClient(
255-
logger.Test(t), attestationURI.String(), 1*time.Millisecond, time.Hour, maxCoolDownDuration,
256-
)
253+
client, err := newHTTPClient(logger.Test(t), attestationURI.String(), 1*time.Millisecond, time.Hour, 0)
257254
require.NoError(t, err)
258255
_, _, err = client.Get(tests.Context(t), cciptypes.Bytes32{1, 2, 3}.String())
259256
require.EqualError(t, err, tokendata.ErrUnknownResponse.Error())
@@ -321,7 +318,7 @@ func Test_HTTPClient_RateLimiting_Parallel(t *testing.T) {
321318
attestationURI, err := url.ParseRequestURI(ts.URL)
322319
require.NoError(t, err)
323320

324-
client, err := newHTTPClient(lggr, attestationURI.String(), tc.rateConfig, longTimeout, maxCoolDownDuration)
321+
client, err := newHTTPClient(lggr, attestationURI.String(), tc.rateConfig, longTimeout, 0)
325322
require.NoError(t, err)
326323

327324
ctx := context.Background()

0 commit comments

Comments
 (0)