From 38badc58767b869e8d1c938fb721a2b6b94e4f0c Mon Sep 17 00:00:00 2001 From: Aleksandr Bukata Date: Mon, 2 Dec 2024 13:40:09 +0000 Subject: [PATCH 1/4] CCIP-4403 lbtc onchain reader --- .../ocr2/plugins/ccip/exportinternal.go | 4 - .../ccip/internal/ccipdata/lbtc_reader.go | 122 +++++++++++- .../internal/ccipdata/lbtc_reader_test.go | 185 ++++++++++++++++++ .../ocr2/plugins/ccip/tokendata/lbtc/lbtc.go | 112 +++++++++-- .../plugins/ccip/tokendata/lbtc/lbtc_test.go | 20 ++ core/services/relay/evm/exec_provider.go | 3 +- 6 files changed, 424 insertions(+), 22 deletions(-) create mode 100644 core/services/ocr2/plugins/ccip/internal/ccipdata/lbtc_reader_test.go create mode 100644 core/services/ocr2/plugins/ccip/tokendata/lbtc/lbtc_test.go diff --git a/core/services/ocr2/plugins/ccip/exportinternal.go b/core/services/ocr2/plugins/ccip/exportinternal.go index 3558a8d807..1fab843830 100644 --- a/core/services/ocr2/plugins/ccip/exportinternal.go +++ b/core/services/ocr2/plugins/ccip/exportinternal.go @@ -112,10 +112,6 @@ func NewLBTCReader(lggr logger.Logger, jobID string, transmitter common.Address, return ccipdata.NewLBTCReader(lggr, jobID, transmitter, lp, registerFilters) } -func CloseLBTCReader(lggr logger.Logger, jobID string, transmitter common.Address, lp logpoller.LogPoller) error { - return ccipdata.CloseLBTCReader(lggr, jobID, transmitter, lp) -} - type USDCReaderImpl = ccipdata.USDCReaderImpl type LBTCReaderImpl = ccipdata.LBTCReaderImpl diff --git a/core/services/ocr2/plugins/ccip/internal/ccipdata/lbtc_reader.go b/core/services/ocr2/plugins/ccip/internal/ccipdata/lbtc_reader.go index d1c26f7d6c..5a22b76809 100644 --- a/core/services/ocr2/plugins/ccip/internal/ccipdata/lbtc_reader.go +++ b/core/services/ocr2/plugins/ccip/internal/ccipdata/lbtc_reader.go @@ -1,23 +1,137 @@ package ccipdata import ( + "bytes" + "context" + "fmt" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/patrickmn/go-cache" + "github.com/pkg/errors" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils" "github.com/smartcontractkit/chainlink/v2/core/logger" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/abihelpers" +) + +var ( + _ LBTCReader = &LBTCReaderImpl{} ) -// TODO: Implement lbtc token reader +const ( + LBTC_DEPOSIT_FILTER_NAME = "LBTC deposited" + LBTC_PAYLOAD_ABI = `[{"type": "bytes"}]` +) + +type lbtcPayload []byte + +func (d lbtcPayload) AbiString() string { + return LBTC_PAYLOAD_ABI +} + +func (d lbtcPayload) Validate() error { + if len(d) == 0 { + return errors.New("must be non-empty") + } + return nil +} + type LBTCReader interface { + GetLBTCMessageInTx(ctx context.Context, payloadHash []byte, txHash string) ([]byte, error) + Close() error } type LBTCReaderImpl struct { + eventID common.Hash + lp logpoller.LogPoller + filter logpoller.Filter + lggr logger.Logger + transmitterAddress common.Address + + // shortLivedInMemLogs is a short-lived cache (items expire every few seconds) + // used to prevent frequent log fetching from the log poller + shortLivedInMemLogs *cache.Cache } func NewLBTCReader(lggr logger.Logger, jobID string, transmitter common.Address, lp logpoller.LogPoller, registerFilters bool) (*LBTCReaderImpl, error) { - return &LBTCReaderImpl{}, nil + return NewLBTCReaderWithCache(lggr, jobID, transmitter, lp, cache.New(shortLivedInMemLogsCacheExpiration, 2*shortLivedInMemLogsCacheExpiration), registerFilters) } -func CloseLBTCReader(lggr logger.Logger, jobID string, transmitter common.Address, lp logpoller.LogPoller) error { - return nil +func NewLBTCReaderWithCache(lggr logger.Logger, jobID string, transmitter common.Address, lp logpoller.LogPoller, cache *cache.Cache, registerFilters bool) (*LBTCReaderImpl, error) { + eventSig := utils.Keccak256Fixed([]byte("DepositToBridge(address,bytes32,bytes32,bytes)")) + r := &LBTCReaderImpl{ + lggr: lggr, + lp: lp, + eventID: eventSig, + filter: logpoller.Filter{ + Name: logpoller.FilterName(LBTC_DEPOSIT_FILTER_NAME, jobID, transmitter.Hex()), + EventSigs: []common.Hash{eventSig}, + Addresses: []common.Address{transmitter}, + Retention: CommitExecLogsRetention, + }, + transmitterAddress: transmitter, + shortLivedInMemLogs: cache, + } + + if registerFilters { + if err := r.RegisterFilters(); err != nil { + return nil, fmt.Errorf("register filters: %w", err) + } + } + return r, nil +} + +func (r *LBTCReaderImpl) GetLBTCMessageInTx(ctx context.Context, payloadHash []byte, txHash string) ([]byte, error) { + var lpLogs []logpoller.Log + + // fetch all the lbtc logs for the provided tx hash + key := fmt.Sprintf("lbtc-%s", txHash) + if rawLogs, foundInMem := r.shortLivedInMemLogs.Get(key); foundInMem { + inMemLogs, ok := rawLogs.([]logpoller.Log) + if !ok { + return nil, errors.Errorf("unexpected in-mem logs type %T", rawLogs) + } + r.lggr.Debugw("found logs in memory", "key", key, "len", len(inMemLogs)) + lpLogs = inMemLogs + } + if len(lpLogs) == 0 { + r.lggr.Debugw("fetching logs from lp") + var err error + lpLogs, err = r.lp.IndexedLogsByTxHash( + ctx, + r.eventID, + r.transmitterAddress, + common.HexToHash(txHash), + ) + if err != nil { + return nil, err + } + r.shortLivedInMemLogs.Set(key, lpLogs, cache.DefaultExpiration) + r.lggr.Debugw("fetched logs from lp", "logs", len(lpLogs)) + } + for _, log := range lpLogs { + topics := log.GetTopics() + if currentPayloadHash := topics[3]; bytes.Equal(currentPayloadHash[:], payloadHash) { + return parseLBTCDepositPayload(log.Data) + } + } + return nil, fmt.Errorf("payload with hash=%s not found in logs", hexutil.Encode(payloadHash)) +} + +func parseLBTCDepositPayload(logData []byte) ([]byte, error) { + decodeAbiStruct, err := abihelpers.DecodeAbiStruct[lbtcPayload](logData) + if err != nil { + return nil, err + } + return decodeAbiStruct, nil +} + +func (r *LBTCReaderImpl) RegisterFilters() error { + return r.lp.RegisterFilter(context.Background(), r.filter) +} + +func (r *LBTCReaderImpl) Close() error { + return r.lp.UnregisterFilter(context.Background(), r.filter.Name) } diff --git a/core/services/ocr2/plugins/ccip/internal/ccipdata/lbtc_reader_test.go b/core/services/ocr2/plugins/ccip/internal/ccipdata/lbtc_reader_test.go new file mode 100644 index 0000000000..f102ae31fb --- /dev/null +++ b/core/services/ocr2/plugins/ccip/internal/ccipdata/lbtc_reader_test.go @@ -0,0 +1,185 @@ +package ccipdata + +import ( + "context" + "crypto/sha256" + "math/big" + "testing" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/patrickmn/go-cache" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + + evmclimocks "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client/mocks" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/headtracker" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" + lpmocks "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller/mocks" + types2 "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils" + ubig "github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils/big" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" + "github.com/smartcontractkit/chainlink/v2/core/logger" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/abihelpers" +) + +func TestLBTCParse(t *testing.T) { + encodedPayload, err := hexutil.Decode("0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000e45c70a5050000000000000000000000000000000000000000000000000000000000aa36a7000000000000000000000000845f8e3c214d8d0e4d83fc094f302aa26a12a0bc0000000000000000000000000000000000000000000000000000000000014a34000000000000000000000000845f8e3c214d8d0e4d83fc094f302aa26a12a0bc00000000000000000000000062f10ce5b727edf787ea45776bd050308a61150800000000000000000000000000000000000000000000000000000000000003e6000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000") + require.NoError(t, err) + payload, err := parseLBTCDepositPayload(encodedPayload) + require.NoError(t, err) + expected := "0x5c70a5050000000000000000000000000000000000000000000000000000000000aa36a7000000000000000000000000845f8e3c214d8d0e4d83fc094f302aa26a12a0bc0000000000000000000000000000000000000000000000000000000000014a34000000000000000000000000845f8e3c214d8d0e4d83fc094f302aa26a12a0bc00000000000000000000000062f10ce5b727edf787ea45776bd050308a61150800000000000000000000000000000000000000000000000000000000000003e60000000000000000000000000000000000000000000000000000000000000006" + assert.Equal(t, expected, hexutil.Encode(payload)) +} + +func Test_MockLogPoller(t *testing.T) { + lggr := logger.TestLogger(t) + payload := []byte("0x1111") + payloadHash := sha256.Sum256(payload) + t.Run("found one", func(t *testing.T) { + lp := lpmocks.NewLogPoller(t) + reader, err := NewLBTCReader(lggr, "job_1", utils.RandomAddress(), lp, false) + require.NoError(t, err) + lp.On("IndexedLogsByTxHash", mock.Anything, reader.eventID, reader.transmitterAddress, mock.Anything). + Return([]logpoller.Log{ + LogWithPayload(t, 20, payload), + }, nil) + + data, err := reader.GetLBTCMessageInTx(context.Background(), payloadHash[:], "0x0001") + assert.NoError(t, err) + assert.Equal(t, payload, data) + }) + + t.Run("found multiple", func(t *testing.T) { + lp := lpmocks.NewLogPoller(t) + reader, err := NewLBTCReader(lggr, "job_1", utils.RandomAddress(), lp, false) + require.NoError(t, err) + lp.On("IndexedLogsByTxHash", mock.Anything, reader.eventID, reader.transmitterAddress, mock.Anything). + Return([]logpoller.Log{ + LogWithPayload(t, 10, []byte("0x1110")), + LogWithPayload(t, 20, payload), + LogWithPayload(t, 30, []byte("0x2222")), + }, nil) + + data, err := reader.GetLBTCMessageInTx(context.Background(), payloadHash[:], "0x0001") + assert.NoError(t, err) + assert.Equal(t, payload, data) + }) + + t.Run("found multiple none match", func(t *testing.T) { + lp := lpmocks.NewLogPoller(t) + reader, err := NewLBTCReader(lggr, "job_1", utils.RandomAddress(), lp, false) + require.NoError(t, err) + lp.On("IndexedLogsByTxHash", mock.Anything, reader.eventID, reader.transmitterAddress, mock.Anything). + Return([]logpoller.Log{ + LogWithPayload(t, 10, []byte("0x1110")), + LogWithPayload(t, 30, []byte("0x2222")), + }, nil) + + data, err := reader.GetLBTCMessageInTx(context.Background(), payloadHash[:], "0x0001") + assert.Nil(t, data) + assert.Errorf(t, err, "payload with hash=%s not found in logs", payloadHash) + }) + + t.Run("no logs found", func(t *testing.T) { + lp := lpmocks.NewLogPoller(t) + reader, err := NewLBTCReader(lggr, "job_1", utils.RandomAddress(), lp, false) + require.NoError(t, err) + lp.On("IndexedLogsByTxHash", mock.Anything, reader.eventID, reader.transmitterAddress, mock.Anything). + Return([]logpoller.Log{}, nil) + + data, err := reader.GetLBTCMessageInTx(context.Background(), payloadHash[:], "0x0001") + assert.Nil(t, data) + assert.Errorf(t, err, "payload with hash=%s not found in logs", payloadHash) + }) + + t.Run("cache hit", func(t *testing.T) { + rCache := cache.New(cache.NoExpiration, cache.NoExpiration) + err := rCache.Add("lbtc-0x0001", []logpoller.Log{LogWithPayload(t, 20, payload)}, cache.NoExpiration) + require.NoError(t, err) + r, err := NewLBTCReaderWithCache(lggr, "job_1", utils.RandomAddress(), nil, rCache, false) + require.NoError(t, err) + data, err := r.GetLBTCMessageInTx(context.Background(), payloadHash[:], "0x0001") + assert.NoError(t, err) + assert.Equal(t, payload, data) + }) +} + +func Test_SimulatedLogPoller_FoundMultiple(t *testing.T) { + lggr := logger.TestLogger(t) + chainID := testutils.NewRandomEVMChainID() + db := pgtest.NewSqlxDB(t) + o := logpoller.NewORM(chainID, db, lggr) + + transmitter := utils.RandomAddress() + payload := []byte("0x1111") + payloadHash := sha256.Sum256(payload) + logs := []types.Log{ + EthLogWithPayload(t, 10, transmitter, []byte("0x2222")), + EthLogWithPayload(t, 20, utils.RandomAddress(), payload), + EthLogWithPayload(t, 30, transmitter, payload), + } + + ec := evmclimocks.NewClient(t) + head := types2.NewHead(big.NewInt(1), common.Hash{}, common.Hash{}, 0, ubig.New(chainID)) + ec.On("HeadByNumber", mock.Anything, mock.Anything).Return(&head, nil) + ec.On("FilterLogs", mock.Anything, mock.Anything).Return(logs, nil) + ec.On("ConfiguredChainID").Return(chainID, nil) + + lpOpts := logpoller.Opts{ + PollPeriod: time.Hour, + FinalityDepth: 1, + BackfillBatchSize: 1, + RpcBatchSize: 1, + KeepFinalizedBlocksDepth: 100, + } + headTracker := headtracker.NewSimulatedHeadTracker(ec, lpOpts.UseFinalityTag, lpOpts.FinalityDepth) + lp := logpoller.NewLogPoller(o, ec, lggr, headTracker, lpOpts) + lp.PollAndSaveLogs(context.Background(), 1) + + reader, err := NewLBTCReader(lggr, "job_1", transmitter, lp, true) + require.NoError(t, err) + + data, err := reader.GetLBTCMessageInTx(context.Background(), payloadHash[:], common.Hash{}.Hex()) + assert.NoError(t, err) + assert.Equal(t, payload, data) +} + +func EthLogWithPayload(t *testing.T, logIndex uint, transmitter common.Address, payload []byte) types.Log { + encodedPayload, err := abihelpers.ABIEncode(LBTC_PAYLOAD_ABI, payload) + require.NoError(t, err) + payloadHash := sha256.Sum256(payload) + topics := make([]common.Hash, 4) + topics[0] = crypto.Keccak256Hash([]byte("DepositToBridge(address,bytes32,bytes32,bytes)")) + topics[3] = common.BytesToHash(payloadHash[:]) + return types.Log{ + Address: transmitter, + Topics: topics, + Data: encodedPayload, + BlockNumber: 1, + TxHash: common.Hash{}, + TxIndex: 1, + BlockHash: common.Hash{}, + Index: logIndex, + Removed: false, + } +} + +func LogWithPayload(t *testing.T, index int64, payload []byte) logpoller.Log { + payloadHash := sha256.Sum256(payload) + topics := make([][]byte, 4) + topics[3] = payloadHash[:] + logData, err := abihelpers.ABIEncode(LBTC_PAYLOAD_ABI, payload) + require.NoError(t, err) + return logpoller.Log{ + LogIndex: index, + Topics: topics, + Data: logData, + } +} diff --git a/core/services/ocr2/plugins/ccip/tokendata/lbtc/lbtc.go b/core/services/ocr2/plugins/ccip/tokendata/lbtc/lbtc.go index 0060f99d95..74c61fae82 100644 --- a/core/services/ocr2/plugins/ccip/tokendata/lbtc/lbtc.go +++ b/core/services/ocr2/plugins/ccip/tokendata/lbtc/lbtc.go @@ -1,6 +1,7 @@ package lbtc import ( + "bytes" "context" "crypto/sha256" "fmt" @@ -15,6 +16,8 @@ import ( cciptypes "github.com/smartcontractkit/chainlink-common/pkg/types/ccip" "github.com/smartcontractkit/chainlink/v2/core/logger" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/abihelpers" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/ccipdata" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/tokendata" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/tokendata/http" ) @@ -61,6 +64,7 @@ type TokenDataReader struct { httpClient http.IHttpClient attestationApi *url.URL attestationApiTimeout time.Duration + lbtcReader ccipdata.LBTCReader lbtcTokenAddress common.Address rate *rate.Limiter @@ -82,10 +86,69 @@ type attestationResponse struct { // TODO: Implement encoding/decoding +type sourceTokenData struct { + SourcePoolAddress []byte + DestTokenAddress []byte + ExtraData []byte + DestGasAmount uint32 +} + +func (m sourceTokenData) AbiString() string { + return `[{ + "components": [ + {"name": "sourcePoolAddress", "type": "bytes"}, + {"name": "destTokenAddress", "type": "bytes"}, + {"name": "extraData", "type": "bytes"}, + {"name": "destGasAmount", "type": "uint32"} + ], + "type": "tuple" + }]` +} + +func (m sourceTokenData) Validate() error { + if len(m.SourcePoolAddress) == 0 { + return errors.New("sourcePoolAddress must be non-empty") + } + if len(m.DestTokenAddress) == 0 { + return errors.New("destTokenAddress must be non-empty") + } + if len(m.ExtraData) == 0 { + return errors.New("extraData must be non-empty") + } + return nil +} + +type payloadAndProof struct { + Payload []byte + Proof []byte +} + +func (m payloadAndProof) AbiString() string { + return ` + [{ + "components": [ + {"name": "payload", "type": "bytes"}, + {"name": "proof", "type": "bytes"} + ], + "type": "tuple" + }]` +} + +func (m payloadAndProof) Validate() error { + if len(m.Payload) == 0 { + return errors.New("payload must be non-empty") + } + if len(m.Proof) == 0 { + return errors.New("proof must be non-empty") + } + return nil +} + var _ tokendata.Reader = &TokenDataReader{} func NewLBTCTokenDataReader( lggr logger.Logger, + lbtcReader ccipdata.LBTCReader, lbtcAttestationApi *url.URL, lbtcAttestationApiTimeoutSeconds int, lbtcTokenAddress common.Address, @@ -107,6 +170,7 @@ func NewLBTCTokenDataReader( httpClient: http.NewObservedIHttpClient(&http.HttpClient{}), attestationApi: lbtcAttestationApi, attestationApiTimeout: timeout, + lbtcReader: lbtcReader, lbtcTokenAddress: lbtcTokenAddress, coolDownMu: &sync.RWMutex{}, rate: rate.NewLimiter(rate.Every(requestInterval), 1), @@ -149,17 +213,16 @@ func (s *TokenDataReader) ReadTokenData(ctx context.Context, msg cciptypes.EVM2E } } - messageBody, err := s.getLBTCMessageBody(ctx, msg, tokenIndex) + payload, payloadHash, err := s.getLBTCPayloadAndHash(ctx, msg, tokenIndex) if err != nil { return []byte{}, errors.Wrap(err, "failed getting the LBTC message body") } msgID := hexutil.Encode(msg.MessageID[:]) - messageBodyHash := sha256.Sum256(messageBody) - messageBodyHashHex := hexutil.Encode(messageBodyHash[:]) - s.lggr.Infow("Calling attestation API", "messageBodyHash", messageBodyHashHex, "messageID", msgID) + payloadHashHex := hexutil.Encode(payloadHash[:]) + s.lggr.Infow("Calling attestation API", "messageBodyHash", payloadHashHex, "messageID", msgID) - attestationResp, err := s.callAttestationApi(ctx, messageBodyHash) + attestationResp, err := s.callAttestationApi(ctx, payloadHash) if err != nil { return nil, err } @@ -171,7 +234,7 @@ func (s *TokenDataReader) ReadTokenData(ctx context.Context, msg cciptypes.EVM2E } var attestation messageAttestationResponse for _, attestationCandidate := range attestationResp.Attestations { - if attestationCandidate.MessageHash == messageBodyHashHex { + if attestationCandidate.MessageHash == payloadHashHex { attestation = attestationCandidate } } @@ -179,11 +242,11 @@ func (s *TokenDataReader) ReadTokenData(ctx context.Context, msg cciptypes.EVM2E "attestationStatus", attestation.Status, "attestation", attestation) switch attestation.Status { case attestationStatusSessionApproved: - messageAndAttestation, err := encodeMessageAndAttestation(messageBody, attestation.Attestation) + payloadAndProof, err := encodePayloadAndProof(payload, attestation.Attestation) if err != nil { - return nil, fmt.Errorf("failed to encode messageAndAttestation : %w", err) + return nil, fmt.Errorf("failed to encode payloadAndProof : %w", err) } - return messageAndAttestation, nil + return payloadAndProof, nil case attestationStatusPending: return nil, tokendata.ErrNotReady case attestationStatusSubmitted: @@ -194,8 +257,24 @@ func (s *TokenDataReader) ReadTokenData(ctx context.Context, msg cciptypes.EVM2E } } -func (s *TokenDataReader) getLBTCMessageBody(ctx context.Context, msg cciptypes.EVM2EVMOnRampCCIPSendRequestedWithMeta, tokenIndex int) ([]byte, error) { - return nil, nil +func (s *TokenDataReader) getLBTCPayloadAndHash(ctx context.Context, msg cciptypes.EVM2EVMOnRampCCIPSendRequestedWithMeta, tokenIndex int) ([]byte, [32]byte, error) { + decodedSourceTokenData, err := abihelpers.DecodeAbiStruct[sourceTokenData](msg.SourceTokenData[tokenIndex]) + if err != nil { + return nil, [32]byte{}, err + } + payloadHash := decodedSourceTokenData.ExtraData + if len(payloadHash) != 32 { + s.lggr.Warnw("SourceTokenData.extraData is not 32 bytes. LBTC Attestation probably disabled onchain", "payloadHash", payloadHash) + } + payload, err := s.lbtcReader.GetLBTCMessageInTx(ctx, payloadHash, msg.TxHash) + if err != nil { + return nil, [32]byte{}, err + } + actualPayloadHash := sha256.Sum256(payload) + if bytes.Equal(actualPayloadHash[:], payloadHash) { + return payload, [32]byte(payloadHash), nil + } + return nil, [32]byte{}, fmt.Errorf("payload hash mismatch: expected %x, got %x", payloadHash, actualPayloadHash) } func (s *TokenDataReader) callAttestationApi(ctx context.Context, lbtcMessageHash [32]byte) (attestationResponse, error) { @@ -210,8 +289,15 @@ func (s *TokenDataReader) callAttestationApi(ctx context.Context, lbtcMessageHas return attestationResponse{}, nil } -func encodeMessageAndAttestation(messageBody []byte, attestation string) ([]byte, error) { - return nil, nil +func encodePayloadAndProof(payload []byte, attestation string) ([]byte, error) { + proofBytes, err := hexutil.Decode(attestation) + if err != nil { + return nil, fmt.Errorf("failed to decode response attestation: %w", err) + } + return abihelpers.EncodeAbiStruct[payloadAndProof](payloadAndProof{ + Payload: payload, + Proof: proofBytes, + }) } func (s *TokenDataReader) setCoolDownPeriod(d time.Duration) { diff --git a/core/services/ocr2/plugins/ccip/tokendata/lbtc/lbtc_test.go b/core/services/ocr2/plugins/ccip/tokendata/lbtc/lbtc_test.go new file mode 100644 index 0000000000..e91ca77c6d --- /dev/null +++ b/core/services/ocr2/plugins/ccip/tokendata/lbtc/lbtc_test.go @@ -0,0 +1,20 @@ +package lbtc + +import ( + "testing" + + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/abihelpers" +) + +func Test_DecodeSourceTokenData(t *testing.T) { + input, err := hexutil.Decode("0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000249f00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000267d40f64ecc4d95f3e8b2237df5f37b10812c250000000000000000000000000000000000000000000000000000000000000020000000000000000000000000c47e4b3124597fdf8dd07843d4a7052f2ee80c3000000000000000000000000000000000000000000000000000000000000000e45c70a5050000000000000000000000000000000000000000000000000000000000aa36a7000000000000000000000000845f8e3c214d8d0e4d83fc094f302aa26a12a0bc0000000000000000000000000000000000000000000000000000000000014a34000000000000000000000000845f8e3c214d8d0e4d83fc094f302aa26a12a0bc00000000000000000000000062f10ce5b727edf787ea45776bd050308a61150800000000000000000000000000000000000000000000000000000000000003e6000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000") + require.NoError(t, err) + decoded, err := abihelpers.DecodeAbiStruct[sourceTokenData](input) + require.NoError(t, err) + expected, err := hexutil.Decode("0x5c70a5050000000000000000000000000000000000000000000000000000000000aa36a7000000000000000000000000845f8e3c214d8d0e4d83fc094f302aa26a12a0bc0000000000000000000000000000000000000000000000000000000000014a34000000000000000000000000845f8e3c214d8d0e4d83fc094f302aa26a12a0bc00000000000000000000000062f10ce5b727edf787ea45776bd050308a61150800000000000000000000000000000000000000000000000000000000000003e60000000000000000000000000000000000000000000000000000000000000006") + require.NoError(t, err) + require.Equal(t, expected, decoded.ExtraData) +} diff --git a/core/services/relay/evm/exec_provider.go b/core/services/relay/evm/exec_provider.go index 2b167ed74f..5c8723d2e0 100644 --- a/core/services/relay/evm/exec_provider.go +++ b/core/services/relay/evm/exec_provider.go @@ -131,7 +131,7 @@ func (s *SrcExecProvider) Close() error { if s.lbtcConfig.AttestationAPI == "" { return nil } - return ccip.CloseLBTCReader(s.lggr, s.lggr.Name(), s.lbtcConfig.SourceMessageTransmitterAddress, s.lp) + return s.lbtcReader.Close() }) var multiErr error for _, fn := range unregisterFuncs { @@ -233,6 +233,7 @@ func (s *SrcExecProvider) NewTokenDataReader(ctx context.Context, tokenAddress c } return lbtc.NewLBTCTokenDataReader( s.lggr, + s.lbtcReader, attestationURI, int(s.lbtcConfig.AttestationAPITimeoutSeconds), tokenAddr, From 51f9182ea08d606420ca0d7754521eb12596bc88 Mon Sep 17 00:00:00 2001 From: Aleksandr Bukata Date: Mon, 2 Dec 2024 15:18:43 +0000 Subject: [PATCH 2/4] handle extra-data --- .../ccip/internal/ccipdata/lbtc_reader.go | 9 ++++---- .../internal/ccipdata/lbtc_reader_test.go | 12 +++++------ .../ocr2/plugins/ccip/tokendata/lbtc/lbtc.go | 21 ++++++++++++------- 3 files changed, 23 insertions(+), 19 deletions(-) diff --git a/core/services/ocr2/plugins/ccip/internal/ccipdata/lbtc_reader.go b/core/services/ocr2/plugins/ccip/internal/ccipdata/lbtc_reader.go index 5a22b76809..f183c32950 100644 --- a/core/services/ocr2/plugins/ccip/internal/ccipdata/lbtc_reader.go +++ b/core/services/ocr2/plugins/ccip/internal/ccipdata/lbtc_reader.go @@ -1,7 +1,6 @@ package ccipdata import ( - "bytes" "context" "fmt" @@ -39,7 +38,7 @@ func (d lbtcPayload) Validate() error { } type LBTCReader interface { - GetLBTCMessageInTx(ctx context.Context, payloadHash []byte, txHash string) ([]byte, error) + GetLBTCMessageInTx(ctx context.Context, payloadHash [32]byte, txHash string) ([]byte, error) Close() error } @@ -83,7 +82,7 @@ func NewLBTCReaderWithCache(lggr logger.Logger, jobID string, transmitter common return r, nil } -func (r *LBTCReaderImpl) GetLBTCMessageInTx(ctx context.Context, payloadHash []byte, txHash string) ([]byte, error) { +func (r *LBTCReaderImpl) GetLBTCMessageInTx(ctx context.Context, payloadHash [32]byte, txHash string) ([]byte, error) { var lpLogs []logpoller.Log // fetch all the lbtc logs for the provided tx hash @@ -113,11 +112,11 @@ func (r *LBTCReaderImpl) GetLBTCMessageInTx(ctx context.Context, payloadHash []b } for _, log := range lpLogs { topics := log.GetTopics() - if currentPayloadHash := topics[3]; bytes.Equal(currentPayloadHash[:], payloadHash) { + if currentPayloadHash := topics[3]; currentPayloadHash == payloadHash { return parseLBTCDepositPayload(log.Data) } } - return nil, fmt.Errorf("payload with hash=%s not found in logs", hexutil.Encode(payloadHash)) + return nil, fmt.Errorf("payload with hash=%s not found in logs", hexutil.Encode(payloadHash[:])) } func parseLBTCDepositPayload(logData []byte) ([]byte, error) { diff --git a/core/services/ocr2/plugins/ccip/internal/ccipdata/lbtc_reader_test.go b/core/services/ocr2/plugins/ccip/internal/ccipdata/lbtc_reader_test.go index f102ae31fb..d1135e9b0a 100644 --- a/core/services/ocr2/plugins/ccip/internal/ccipdata/lbtc_reader_test.go +++ b/core/services/ocr2/plugins/ccip/internal/ccipdata/lbtc_reader_test.go @@ -51,7 +51,7 @@ func Test_MockLogPoller(t *testing.T) { LogWithPayload(t, 20, payload), }, nil) - data, err := reader.GetLBTCMessageInTx(context.Background(), payloadHash[:], "0x0001") + data, err := reader.GetLBTCMessageInTx(context.Background(), payloadHash, "0x0001") assert.NoError(t, err) assert.Equal(t, payload, data) }) @@ -67,7 +67,7 @@ func Test_MockLogPoller(t *testing.T) { LogWithPayload(t, 30, []byte("0x2222")), }, nil) - data, err := reader.GetLBTCMessageInTx(context.Background(), payloadHash[:], "0x0001") + data, err := reader.GetLBTCMessageInTx(context.Background(), payloadHash, "0x0001") assert.NoError(t, err) assert.Equal(t, payload, data) }) @@ -82,7 +82,7 @@ func Test_MockLogPoller(t *testing.T) { LogWithPayload(t, 30, []byte("0x2222")), }, nil) - data, err := reader.GetLBTCMessageInTx(context.Background(), payloadHash[:], "0x0001") + data, err := reader.GetLBTCMessageInTx(context.Background(), payloadHash, "0x0001") assert.Nil(t, data) assert.Errorf(t, err, "payload with hash=%s not found in logs", payloadHash) }) @@ -94,7 +94,7 @@ func Test_MockLogPoller(t *testing.T) { lp.On("IndexedLogsByTxHash", mock.Anything, reader.eventID, reader.transmitterAddress, mock.Anything). Return([]logpoller.Log{}, nil) - data, err := reader.GetLBTCMessageInTx(context.Background(), payloadHash[:], "0x0001") + data, err := reader.GetLBTCMessageInTx(context.Background(), payloadHash, "0x0001") assert.Nil(t, data) assert.Errorf(t, err, "payload with hash=%s not found in logs", payloadHash) }) @@ -105,7 +105,7 @@ func Test_MockLogPoller(t *testing.T) { require.NoError(t, err) r, err := NewLBTCReaderWithCache(lggr, "job_1", utils.RandomAddress(), nil, rCache, false) require.NoError(t, err) - data, err := r.GetLBTCMessageInTx(context.Background(), payloadHash[:], "0x0001") + data, err := r.GetLBTCMessageInTx(context.Background(), payloadHash, "0x0001") assert.NoError(t, err) assert.Equal(t, payload, data) }) @@ -146,7 +146,7 @@ func Test_SimulatedLogPoller_FoundMultiple(t *testing.T) { reader, err := NewLBTCReader(lggr, "job_1", transmitter, lp, true) require.NoError(t, err) - data, err := reader.GetLBTCMessageInTx(context.Background(), payloadHash[:], common.Hash{}.Hex()) + data, err := reader.GetLBTCMessageInTx(context.Background(), payloadHash, common.Hash{}.Hex()) assert.NoError(t, err) assert.Equal(t, payload, data) } diff --git a/core/services/ocr2/plugins/ccip/tokendata/lbtc/lbtc.go b/core/services/ocr2/plugins/ccip/tokendata/lbtc/lbtc.go index 74c61fae82..01dbb3bc6c 100644 --- a/core/services/ocr2/plugins/ccip/tokendata/lbtc/lbtc.go +++ b/core/services/ocr2/plugins/ccip/tokendata/lbtc/lbtc.go @@ -1,7 +1,6 @@ package lbtc import ( - "bytes" "context" "crypto/sha256" "fmt" @@ -262,17 +261,23 @@ func (s *TokenDataReader) getLBTCPayloadAndHash(ctx context.Context, msg cciptyp if err != nil { return nil, [32]byte{}, err } - payloadHash := decodedSourceTokenData.ExtraData - if len(payloadHash) != 32 { - s.lggr.Warnw("SourceTokenData.extraData is not 32 bytes. LBTC Attestation probably disabled onchain", "payloadHash", payloadHash) + destTokenData := decodedSourceTokenData.ExtraData + var payloadHash [32]byte + if len(destTokenData) != 32 { + payloadHash = sha256.Sum256(destTokenData) + s.lggr.Warnw("SourceTokenData.extraData size is not 32. It could be a LBTC payload, not LBTC payload sha256. "+ + "Probably this message is sent when LBTC attestation was disabled onchain. Will use sha256 from this value", + "destTokenData", destTokenData, "newPayloadHash", payloadHash) + } else { + payloadHash = [32]byte(destTokenData) } - payload, err := s.lbtcReader.GetLBTCMessageInTx(ctx, payloadHash, msg.TxHash) + actualPayload, err := s.lbtcReader.GetLBTCMessageInTx(ctx, payloadHash, msg.TxHash) if err != nil { return nil, [32]byte{}, err } - actualPayloadHash := sha256.Sum256(payload) - if bytes.Equal(actualPayloadHash[:], payloadHash) { - return payload, [32]byte(payloadHash), nil + actualPayloadHash := sha256.Sum256(actualPayload) + if actualPayloadHash == payloadHash { + return actualPayload, payloadHash, nil } return nil, [32]byte{}, fmt.Errorf("payload hash mismatch: expected %x, got %x", payloadHash, actualPayloadHash) } From 0ef7aec7224616239ac6e4b2648545a1c136b24e Mon Sep 17 00:00:00 2001 From: Aleksandr Bukata Date: Mon, 2 Dec 2024 17:30:50 +0000 Subject: [PATCH 3/4] finish attestation api chain of calls --- .../ocr2/plugins/ccip/tokendata/lbtc/lbtc.go | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/core/services/ocr2/plugins/ccip/tokendata/lbtc/lbtc.go b/core/services/ocr2/plugins/ccip/tokendata/lbtc/lbtc.go index 01dbb3bc6c..9d57f9bb3d 100644 --- a/core/services/ocr2/plugins/ccip/tokendata/lbtc/lbtc.go +++ b/core/services/ocr2/plugins/ccip/tokendata/lbtc/lbtc.go @@ -1,8 +1,10 @@ package lbtc import ( + "bytes" "context" "crypto/sha256" + "encoding/json" "fmt" "net/url" "sync" @@ -79,6 +81,10 @@ type messageAttestationResponse struct { } // TODO: Adjust after checking API docs +type attestationRequest struct { + PayloadHashes []string `json:"messageHash"` +} + type attestationResponse struct { Attestations []messageAttestationResponse `json:"attestations"` } @@ -283,7 +289,14 @@ func (s *TokenDataReader) getLBTCPayloadAndHash(ctx context.Context, msg cciptyp } func (s *TokenDataReader) callAttestationApi(ctx context.Context, lbtcMessageHash [32]byte) (attestationResponse, error) { - _, _, _, err := s.httpClient.Get(ctx, "", s.attestationApiTimeout) + attestationUrl := fmt.Sprintf("%s/bridge/%s/%s", s.attestationApi.String(), apiVersion, attestationPath) + request := attestationRequest{PayloadHashes: []string{hexutil.Encode(lbtcMessageHash[:])}} + encodedRequest, err := json.Marshal(request) + requestBuffer := bytes.NewBuffer(encodedRequest) + if err != nil { + return attestationResponse{}, err + } + respRaw, _, _, err := s.httpClient.Post(ctx, attestationUrl, requestBuffer, s.attestationApiTimeout) switch { case errors.Is(err, tokendata.ErrRateLimit): s.setCoolDownPeriod(defaultCoolDownDuration) @@ -291,7 +304,9 @@ func (s *TokenDataReader) callAttestationApi(ctx context.Context, lbtcMessageHas case err != nil: return attestationResponse{}, err } - return attestationResponse{}, nil + var attestationResp attestationResponse + err = json.Unmarshal(respRaw, &attestationResp) + return attestationResp, err } func encodePayloadAndProof(payload []byte, attestation string) ([]byte, error) { From a5b0ceec46e31d722337c216ee6d178ab8eb44dc Mon Sep 17 00:00:00 2001 From: Aleksandr Bukata Date: Mon, 2 Dec 2024 18:49:22 +0000 Subject: [PATCH 4/4] drop lbtc onchain reader --- .../plugins/ccip/ccipexec/initializers.go | 2 +- .../ocr2/plugins/ccip/config/config.go | 7 +- .../ocr2/plugins/ccip/exportinternal.go | 5 - .../ccip/internal/ccipdata/lbtc_reader.go | 136 ------------- .../internal/ccipdata/lbtc_reader_test.go | 185 ------------------ .../ocr2/plugins/ccip/tokendata/lbtc/lbtc.go | 70 ++----- core/services/relay/evm/exec_provider.go | 16 -- 7 files changed, 16 insertions(+), 405 deletions(-) delete mode 100644 core/services/ocr2/plugins/ccip/internal/ccipdata/lbtc_reader.go delete mode 100644 core/services/ocr2/plugins/ccip/internal/ccipdata/lbtc_reader_test.go diff --git a/core/services/ocr2/plugins/ccip/ccipexec/initializers.go b/core/services/ocr2/plugins/ccip/ccipexec/initializers.go index ccadd45d5b..d2d3d32ce9 100644 --- a/core/services/ocr2/plugins/ccip/ccipexec/initializers.go +++ b/core/services/ocr2/plugins/ccip/ccipexec/initializers.go @@ -131,7 +131,7 @@ func NewExecServices(ctx context.Context, lggr logger.Logger, jb job.Job, srcPro lbtcReader, err2 := srcProvider.NewTokenDataReader(ctx, ccip.EvmAddrToGeneric(pluginConfig.LBTCConfig.SourceTokenAddress)) if err2 != nil { - return nil, fmt.Errorf("new usdc reader: %w", err2) + return nil, fmt.Errorf("new lbtc reader: %w", err2) } tokenDataProviders[cciptypes.Address(pluginConfig.LBTCConfig.SourceTokenAddress.String())] = lbtcReader } diff --git a/core/services/ocr2/plugins/ccip/config/config.go b/core/services/ocr2/plugins/ccip/config/config.go index 16da4d3255..fbf8d590cf 100644 --- a/core/services/ocr2/plugins/ccip/config/config.go +++ b/core/services/ocr2/plugins/ccip/config/config.go @@ -121,10 +121,9 @@ type USDCConfig struct { } type LBTCConfig struct { - SourceTokenAddress common.Address - SourceMessageTransmitterAddress common.Address - AttestationAPI string - AttestationAPITimeoutSeconds uint + SourceTokenAddress common.Address + AttestationAPI string + AttestationAPITimeoutSeconds uint // AttestationAPIIntervalMilliseconds can be set to -1 to disable or 0 to use a default interval. AttestationAPIIntervalMilliseconds int } diff --git a/core/services/ocr2/plugins/ccip/exportinternal.go b/core/services/ocr2/plugins/ccip/exportinternal.go index 1fab843830..be39346984 100644 --- a/core/services/ocr2/plugins/ccip/exportinternal.go +++ b/core/services/ocr2/plugins/ccip/exportinternal.go @@ -108,12 +108,7 @@ func CloseUSDCReader(lggr logger.Logger, jobID string, transmitter common.Addres return ccipdata.CloseUSDCReader(lggr, jobID, transmitter, lp) } -func NewLBTCReader(lggr logger.Logger, jobID string, transmitter common.Address, lp logpoller.LogPoller, registerFilters bool) (*ccipdata.LBTCReaderImpl, error) { - return ccipdata.NewLBTCReader(lggr, jobID, transmitter, lp, registerFilters) -} - type USDCReaderImpl = ccipdata.USDCReaderImpl -type LBTCReaderImpl = ccipdata.LBTCReaderImpl var DefaultRpcBatchSizeLimit = rpclib.DefaultRpcBatchSizeLimit var DefaultRpcBatchBackOffMultiplier = rpclib.DefaultRpcBatchBackOffMultiplier diff --git a/core/services/ocr2/plugins/ccip/internal/ccipdata/lbtc_reader.go b/core/services/ocr2/plugins/ccip/internal/ccipdata/lbtc_reader.go deleted file mode 100644 index f183c32950..0000000000 --- a/core/services/ocr2/plugins/ccip/internal/ccipdata/lbtc_reader.go +++ /dev/null @@ -1,136 +0,0 @@ -package ccipdata - -import ( - "context" - "fmt" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/patrickmn/go-cache" - "github.com/pkg/errors" - - "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils" - "github.com/smartcontractkit/chainlink/v2/core/logger" - "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/abihelpers" -) - -var ( - _ LBTCReader = &LBTCReaderImpl{} -) - -const ( - LBTC_DEPOSIT_FILTER_NAME = "LBTC deposited" - LBTC_PAYLOAD_ABI = `[{"type": "bytes"}]` -) - -type lbtcPayload []byte - -func (d lbtcPayload) AbiString() string { - return LBTC_PAYLOAD_ABI -} - -func (d lbtcPayload) Validate() error { - if len(d) == 0 { - return errors.New("must be non-empty") - } - return nil -} - -type LBTCReader interface { - GetLBTCMessageInTx(ctx context.Context, payloadHash [32]byte, txHash string) ([]byte, error) - Close() error -} - -type LBTCReaderImpl struct { - eventID common.Hash - lp logpoller.LogPoller - filter logpoller.Filter - lggr logger.Logger - transmitterAddress common.Address - - // shortLivedInMemLogs is a short-lived cache (items expire every few seconds) - // used to prevent frequent log fetching from the log poller - shortLivedInMemLogs *cache.Cache -} - -func NewLBTCReader(lggr logger.Logger, jobID string, transmitter common.Address, lp logpoller.LogPoller, registerFilters bool) (*LBTCReaderImpl, error) { - return NewLBTCReaderWithCache(lggr, jobID, transmitter, lp, cache.New(shortLivedInMemLogsCacheExpiration, 2*shortLivedInMemLogsCacheExpiration), registerFilters) -} - -func NewLBTCReaderWithCache(lggr logger.Logger, jobID string, transmitter common.Address, lp logpoller.LogPoller, cache *cache.Cache, registerFilters bool) (*LBTCReaderImpl, error) { - eventSig := utils.Keccak256Fixed([]byte("DepositToBridge(address,bytes32,bytes32,bytes)")) - r := &LBTCReaderImpl{ - lggr: lggr, - lp: lp, - eventID: eventSig, - filter: logpoller.Filter{ - Name: logpoller.FilterName(LBTC_DEPOSIT_FILTER_NAME, jobID, transmitter.Hex()), - EventSigs: []common.Hash{eventSig}, - Addresses: []common.Address{transmitter}, - Retention: CommitExecLogsRetention, - }, - transmitterAddress: transmitter, - shortLivedInMemLogs: cache, - } - - if registerFilters { - if err := r.RegisterFilters(); err != nil { - return nil, fmt.Errorf("register filters: %w", err) - } - } - return r, nil -} - -func (r *LBTCReaderImpl) GetLBTCMessageInTx(ctx context.Context, payloadHash [32]byte, txHash string) ([]byte, error) { - var lpLogs []logpoller.Log - - // fetch all the lbtc logs for the provided tx hash - key := fmt.Sprintf("lbtc-%s", txHash) - if rawLogs, foundInMem := r.shortLivedInMemLogs.Get(key); foundInMem { - inMemLogs, ok := rawLogs.([]logpoller.Log) - if !ok { - return nil, errors.Errorf("unexpected in-mem logs type %T", rawLogs) - } - r.lggr.Debugw("found logs in memory", "key", key, "len", len(inMemLogs)) - lpLogs = inMemLogs - } - if len(lpLogs) == 0 { - r.lggr.Debugw("fetching logs from lp") - var err error - lpLogs, err = r.lp.IndexedLogsByTxHash( - ctx, - r.eventID, - r.transmitterAddress, - common.HexToHash(txHash), - ) - if err != nil { - return nil, err - } - r.shortLivedInMemLogs.Set(key, lpLogs, cache.DefaultExpiration) - r.lggr.Debugw("fetched logs from lp", "logs", len(lpLogs)) - } - for _, log := range lpLogs { - topics := log.GetTopics() - if currentPayloadHash := topics[3]; currentPayloadHash == payloadHash { - return parseLBTCDepositPayload(log.Data) - } - } - return nil, fmt.Errorf("payload with hash=%s not found in logs", hexutil.Encode(payloadHash[:])) -} - -func parseLBTCDepositPayload(logData []byte) ([]byte, error) { - decodeAbiStruct, err := abihelpers.DecodeAbiStruct[lbtcPayload](logData) - if err != nil { - return nil, err - } - return decodeAbiStruct, nil -} - -func (r *LBTCReaderImpl) RegisterFilters() error { - return r.lp.RegisterFilter(context.Background(), r.filter) -} - -func (r *LBTCReaderImpl) Close() error { - return r.lp.UnregisterFilter(context.Background(), r.filter.Name) -} diff --git a/core/services/ocr2/plugins/ccip/internal/ccipdata/lbtc_reader_test.go b/core/services/ocr2/plugins/ccip/internal/ccipdata/lbtc_reader_test.go deleted file mode 100644 index d1135e9b0a..0000000000 --- a/core/services/ocr2/plugins/ccip/internal/ccipdata/lbtc_reader_test.go +++ /dev/null @@ -1,185 +0,0 @@ -package ccipdata - -import ( - "context" - "crypto/sha256" - "math/big" - "testing" - "time" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/crypto" - "github.com/patrickmn/go-cache" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" - - evmclimocks "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client/mocks" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm/headtracker" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" - lpmocks "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller/mocks" - types2 "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils" - ubig "github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils/big" - "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" - "github.com/smartcontractkit/chainlink/v2/core/logger" - "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/abihelpers" -) - -func TestLBTCParse(t *testing.T) { - encodedPayload, err := hexutil.Decode("0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000e45c70a5050000000000000000000000000000000000000000000000000000000000aa36a7000000000000000000000000845f8e3c214d8d0e4d83fc094f302aa26a12a0bc0000000000000000000000000000000000000000000000000000000000014a34000000000000000000000000845f8e3c214d8d0e4d83fc094f302aa26a12a0bc00000000000000000000000062f10ce5b727edf787ea45776bd050308a61150800000000000000000000000000000000000000000000000000000000000003e6000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000") - require.NoError(t, err) - payload, err := parseLBTCDepositPayload(encodedPayload) - require.NoError(t, err) - expected := "0x5c70a5050000000000000000000000000000000000000000000000000000000000aa36a7000000000000000000000000845f8e3c214d8d0e4d83fc094f302aa26a12a0bc0000000000000000000000000000000000000000000000000000000000014a34000000000000000000000000845f8e3c214d8d0e4d83fc094f302aa26a12a0bc00000000000000000000000062f10ce5b727edf787ea45776bd050308a61150800000000000000000000000000000000000000000000000000000000000003e60000000000000000000000000000000000000000000000000000000000000006" - assert.Equal(t, expected, hexutil.Encode(payload)) -} - -func Test_MockLogPoller(t *testing.T) { - lggr := logger.TestLogger(t) - payload := []byte("0x1111") - payloadHash := sha256.Sum256(payload) - t.Run("found one", func(t *testing.T) { - lp := lpmocks.NewLogPoller(t) - reader, err := NewLBTCReader(lggr, "job_1", utils.RandomAddress(), lp, false) - require.NoError(t, err) - lp.On("IndexedLogsByTxHash", mock.Anything, reader.eventID, reader.transmitterAddress, mock.Anything). - Return([]logpoller.Log{ - LogWithPayload(t, 20, payload), - }, nil) - - data, err := reader.GetLBTCMessageInTx(context.Background(), payloadHash, "0x0001") - assert.NoError(t, err) - assert.Equal(t, payload, data) - }) - - t.Run("found multiple", func(t *testing.T) { - lp := lpmocks.NewLogPoller(t) - reader, err := NewLBTCReader(lggr, "job_1", utils.RandomAddress(), lp, false) - require.NoError(t, err) - lp.On("IndexedLogsByTxHash", mock.Anything, reader.eventID, reader.transmitterAddress, mock.Anything). - Return([]logpoller.Log{ - LogWithPayload(t, 10, []byte("0x1110")), - LogWithPayload(t, 20, payload), - LogWithPayload(t, 30, []byte("0x2222")), - }, nil) - - data, err := reader.GetLBTCMessageInTx(context.Background(), payloadHash, "0x0001") - assert.NoError(t, err) - assert.Equal(t, payload, data) - }) - - t.Run("found multiple none match", func(t *testing.T) { - lp := lpmocks.NewLogPoller(t) - reader, err := NewLBTCReader(lggr, "job_1", utils.RandomAddress(), lp, false) - require.NoError(t, err) - lp.On("IndexedLogsByTxHash", mock.Anything, reader.eventID, reader.transmitterAddress, mock.Anything). - Return([]logpoller.Log{ - LogWithPayload(t, 10, []byte("0x1110")), - LogWithPayload(t, 30, []byte("0x2222")), - }, nil) - - data, err := reader.GetLBTCMessageInTx(context.Background(), payloadHash, "0x0001") - assert.Nil(t, data) - assert.Errorf(t, err, "payload with hash=%s not found in logs", payloadHash) - }) - - t.Run("no logs found", func(t *testing.T) { - lp := lpmocks.NewLogPoller(t) - reader, err := NewLBTCReader(lggr, "job_1", utils.RandomAddress(), lp, false) - require.NoError(t, err) - lp.On("IndexedLogsByTxHash", mock.Anything, reader.eventID, reader.transmitterAddress, mock.Anything). - Return([]logpoller.Log{}, nil) - - data, err := reader.GetLBTCMessageInTx(context.Background(), payloadHash, "0x0001") - assert.Nil(t, data) - assert.Errorf(t, err, "payload with hash=%s not found in logs", payloadHash) - }) - - t.Run("cache hit", func(t *testing.T) { - rCache := cache.New(cache.NoExpiration, cache.NoExpiration) - err := rCache.Add("lbtc-0x0001", []logpoller.Log{LogWithPayload(t, 20, payload)}, cache.NoExpiration) - require.NoError(t, err) - r, err := NewLBTCReaderWithCache(lggr, "job_1", utils.RandomAddress(), nil, rCache, false) - require.NoError(t, err) - data, err := r.GetLBTCMessageInTx(context.Background(), payloadHash, "0x0001") - assert.NoError(t, err) - assert.Equal(t, payload, data) - }) -} - -func Test_SimulatedLogPoller_FoundMultiple(t *testing.T) { - lggr := logger.TestLogger(t) - chainID := testutils.NewRandomEVMChainID() - db := pgtest.NewSqlxDB(t) - o := logpoller.NewORM(chainID, db, lggr) - - transmitter := utils.RandomAddress() - payload := []byte("0x1111") - payloadHash := sha256.Sum256(payload) - logs := []types.Log{ - EthLogWithPayload(t, 10, transmitter, []byte("0x2222")), - EthLogWithPayload(t, 20, utils.RandomAddress(), payload), - EthLogWithPayload(t, 30, transmitter, payload), - } - - ec := evmclimocks.NewClient(t) - head := types2.NewHead(big.NewInt(1), common.Hash{}, common.Hash{}, 0, ubig.New(chainID)) - ec.On("HeadByNumber", mock.Anything, mock.Anything).Return(&head, nil) - ec.On("FilterLogs", mock.Anything, mock.Anything).Return(logs, nil) - ec.On("ConfiguredChainID").Return(chainID, nil) - - lpOpts := logpoller.Opts{ - PollPeriod: time.Hour, - FinalityDepth: 1, - BackfillBatchSize: 1, - RpcBatchSize: 1, - KeepFinalizedBlocksDepth: 100, - } - headTracker := headtracker.NewSimulatedHeadTracker(ec, lpOpts.UseFinalityTag, lpOpts.FinalityDepth) - lp := logpoller.NewLogPoller(o, ec, lggr, headTracker, lpOpts) - lp.PollAndSaveLogs(context.Background(), 1) - - reader, err := NewLBTCReader(lggr, "job_1", transmitter, lp, true) - require.NoError(t, err) - - data, err := reader.GetLBTCMessageInTx(context.Background(), payloadHash, common.Hash{}.Hex()) - assert.NoError(t, err) - assert.Equal(t, payload, data) -} - -func EthLogWithPayload(t *testing.T, logIndex uint, transmitter common.Address, payload []byte) types.Log { - encodedPayload, err := abihelpers.ABIEncode(LBTC_PAYLOAD_ABI, payload) - require.NoError(t, err) - payloadHash := sha256.Sum256(payload) - topics := make([]common.Hash, 4) - topics[0] = crypto.Keccak256Hash([]byte("DepositToBridge(address,bytes32,bytes32,bytes)")) - topics[3] = common.BytesToHash(payloadHash[:]) - return types.Log{ - Address: transmitter, - Topics: topics, - Data: encodedPayload, - BlockNumber: 1, - TxHash: common.Hash{}, - TxIndex: 1, - BlockHash: common.Hash{}, - Index: logIndex, - Removed: false, - } -} - -func LogWithPayload(t *testing.T, index int64, payload []byte) logpoller.Log { - payloadHash := sha256.Sum256(payload) - topics := make([][]byte, 4) - topics[3] = payloadHash[:] - logData, err := abihelpers.ABIEncode(LBTC_PAYLOAD_ABI, payload) - require.NoError(t, err) - return logpoller.Log{ - LogIndex: index, - Topics: topics, - Data: logData, - } -} diff --git a/core/services/ocr2/plugins/ccip/tokendata/lbtc/lbtc.go b/core/services/ocr2/plugins/ccip/tokendata/lbtc/lbtc.go index 9d57f9bb3d..dece927d02 100644 --- a/core/services/ocr2/plugins/ccip/tokendata/lbtc/lbtc.go +++ b/core/services/ocr2/plugins/ccip/tokendata/lbtc/lbtc.go @@ -18,7 +18,6 @@ import ( cciptypes "github.com/smartcontractkit/chainlink-common/pkg/types/ccip" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/abihelpers" - "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/ccipdata" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/tokendata" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/tokendata/http" ) @@ -65,7 +64,6 @@ type TokenDataReader struct { httpClient http.IHttpClient attestationApi *url.URL attestationApiTimeout time.Duration - lbtcReader ccipdata.LBTCReader lbtcTokenAddress common.Address rate *rate.Limiter @@ -77,7 +75,7 @@ type TokenDataReader struct { type messageAttestationResponse struct { MessageHash string `json:"message_hash"` Status attestationStatus `json:"status"` - Attestation string `json:"attestation"` + Attestation string `json:"attestation"` // Attestation represented by abi.encode(payload, proof) } // TODO: Adjust after checking API docs @@ -123,37 +121,10 @@ func (m sourceTokenData) Validate() error { return nil } -type payloadAndProof struct { - Payload []byte - Proof []byte -} - -func (m payloadAndProof) AbiString() string { - return ` - [{ - "components": [ - {"name": "payload", "type": "bytes"}, - {"name": "proof", "type": "bytes"} - ], - "type": "tuple" - }]` -} - -func (m payloadAndProof) Validate() error { - if len(m.Payload) == 0 { - return errors.New("payload must be non-empty") - } - if len(m.Proof) == 0 { - return errors.New("proof must be non-empty") - } - return nil -} - var _ tokendata.Reader = &TokenDataReader{} func NewLBTCTokenDataReader( lggr logger.Logger, - lbtcReader ccipdata.LBTCReader, lbtcAttestationApi *url.URL, lbtcAttestationApiTimeoutSeconds int, lbtcTokenAddress common.Address, @@ -175,7 +146,6 @@ func NewLBTCTokenDataReader( httpClient: http.NewObservedIHttpClient(&http.HttpClient{}), attestationApi: lbtcAttestationApi, attestationApiTimeout: timeout, - lbtcReader: lbtcReader, lbtcTokenAddress: lbtcTokenAddress, coolDownMu: &sync.RWMutex{}, rate: rate.NewLimiter(rate.Every(requestInterval), 1), @@ -218,7 +188,7 @@ func (s *TokenDataReader) ReadTokenData(ctx context.Context, msg cciptypes.EVM2E } } - payload, payloadHash, err := s.getLBTCPayloadAndHash(ctx, msg, tokenIndex) + payloadHash, err := s.getLBTCPayloadHash(msg, tokenIndex) if err != nil { return []byte{}, errors.Wrap(err, "failed getting the LBTC message body") } @@ -247,9 +217,9 @@ func (s *TokenDataReader) ReadTokenData(ctx context.Context, msg cciptypes.EVM2E "attestationStatus", attestation.Status, "attestation", attestation) switch attestation.Status { case attestationStatusSessionApproved: - payloadAndProof, err := encodePayloadAndProof(payload, attestation.Attestation) + payloadAndProof, err := hexutil.Decode(attestation.Attestation) if err != nil { - return nil, fmt.Errorf("failed to encode payloadAndProof : %w", err) + return nil, err } return payloadAndProof, nil case attestationStatusPending: @@ -262,30 +232,25 @@ func (s *TokenDataReader) ReadTokenData(ctx context.Context, msg cciptypes.EVM2E } } -func (s *TokenDataReader) getLBTCPayloadAndHash(ctx context.Context, msg cciptypes.EVM2EVMOnRampCCIPSendRequestedWithMeta, tokenIndex int) ([]byte, [32]byte, error) { +func (s *TokenDataReader) getLBTCPayloadHash(msg cciptypes.EVM2EVMOnRampCCIPSendRequestedWithMeta, tokenIndex int) ([32]byte, error) { decodedSourceTokenData, err := abihelpers.DecodeAbiStruct[sourceTokenData](msg.SourceTokenData[tokenIndex]) if err != nil { - return nil, [32]byte{}, err + return [32]byte{}, err } destTokenData := decodedSourceTokenData.ExtraData var payloadHash [32]byte + // We don't have better way to determine if the extraData is a payload or sha256(payload) + // Last parameter of the payload struct is 32-bytes nonce (see Lombard's Bridge._deposit(...) method), + // so we can assume that payload always exceeds 32 bytes if len(destTokenData) != 32 { payloadHash = sha256.Sum256(destTokenData) - s.lggr.Warnw("SourceTokenData.extraData size is not 32. It could be a LBTC payload, not LBTC payload sha256. "+ - "Probably this message is sent when LBTC attestation was disabled onchain. Will use sha256 from this value", + s.lggr.Warnw("SourceTokenData.extraData size is not 32. Probably this is deposit payload, not sha256(payload). "+ + "This message was sent when LBTC attestation was disabled onchain. Will use sha256 from this value", "destTokenData", destTokenData, "newPayloadHash", payloadHash) } else { payloadHash = [32]byte(destTokenData) } - actualPayload, err := s.lbtcReader.GetLBTCMessageInTx(ctx, payloadHash, msg.TxHash) - if err != nil { - return nil, [32]byte{}, err - } - actualPayloadHash := sha256.Sum256(actualPayload) - if actualPayloadHash == payloadHash { - return actualPayload, payloadHash, nil - } - return nil, [32]byte{}, fmt.Errorf("payload hash mismatch: expected %x, got %x", payloadHash, actualPayloadHash) + return payloadHash, nil } func (s *TokenDataReader) callAttestationApi(ctx context.Context, lbtcMessageHash [32]byte) (attestationResponse, error) { @@ -309,17 +274,6 @@ func (s *TokenDataReader) callAttestationApi(ctx context.Context, lbtcMessageHas return attestationResp, err } -func encodePayloadAndProof(payload []byte, attestation string) ([]byte, error) { - proofBytes, err := hexutil.Decode(attestation) - if err != nil { - return nil, fmt.Errorf("failed to decode response attestation: %w", err) - } - return abihelpers.EncodeAbiStruct[payloadAndProof](payloadAndProof{ - Payload: payload, - Proof: proofBytes, - }) -} - func (s *TokenDataReader) setCoolDownPeriod(d time.Duration) { s.coolDownMu.Lock() if d > maxCoolDownDuration { diff --git a/core/services/relay/evm/exec_provider.go b/core/services/relay/evm/exec_provider.go index 5c8723d2e0..e7454fc65b 100644 --- a/core/services/relay/evm/exec_provider.go +++ b/core/services/relay/evm/exec_provider.go @@ -39,7 +39,6 @@ type SrcExecProvider struct { maxGasPrice *big.Int usdcReader *ccip.USDCReaderImpl usdcConfig config.USDCConfig - lbtcReader *ccip.LBTCReaderImpl lbtcConfig config.LBTCConfig feeEstimatorConfig estimatorconfig.FeeEstimatorConfigProvider @@ -73,13 +72,6 @@ func NewSrcExecProvider( return nil, fmt.Errorf("new usdc reader: %w", err) } } - var lbtcReader *ccip.LBTCReaderImpl - if lbtcConfig.AttestationAPI != "" { - lbtcReader, err = ccip.NewLBTCReader(lggr, jobID, lbtcConfig.SourceMessageTransmitterAddress, lp, true) - if err != nil { - return nil, fmt.Errorf("new usdc reader: %w", err) - } - } return &SrcExecProvider{ lggr: lggr, @@ -91,7 +83,6 @@ func NewSrcExecProvider( startBlock: startBlock, usdcReader: usdcReader, usdcConfig: usdcConfig, - lbtcReader: lbtcReader, lbtcConfig: lbtcConfig, feeEstimatorConfig: feeEstimatorConfig, }, nil @@ -127,12 +118,6 @@ func (s *SrcExecProvider) Close() error { } return ccip.CloseUSDCReader(s.lggr, s.lggr.Name(), s.usdcConfig.SourceMessageTransmitterAddress, s.lp) }) - unregisterFuncs = append(unregisterFuncs, func() error { - if s.lbtcConfig.AttestationAPI == "" { - return nil - } - return s.lbtcReader.Close() - }) var multiErr error for _, fn := range unregisterFuncs { if err := fn(); err != nil { @@ -233,7 +218,6 @@ func (s *SrcExecProvider) NewTokenDataReader(ctx context.Context, tokenAddress c } return lbtc.NewLBTCTokenDataReader( s.lggr, - s.lbtcReader, attestationURI, int(s.lbtcConfig.AttestationAPITimeoutSeconds), tokenAddr,