From 836ea2c032794c9f0a24e1d9dfefea0de1c8141b Mon Sep 17 00:00:00 2001 From: Aleksandr Bukata <96521086+bukata-sa@users.noreply.github.com> Date: Tue, 3 Dec 2024 10:39:15 +0000 Subject: [PATCH] CCIP-4403 lbtc onchain reader (#1555) Epic: https://smartcontract-it.atlassian.net/browse/CCIP-3488 ## Description Added LBTC smart contract reader --- .../plugins/ccip/ccipexec/initializers.go | 2 +- .../ocr2/plugins/ccip/config/config.go | 7 +- .../ocr2/plugins/ccip/exportinternal.go | 9 -- .../ccip/internal/ccipdata/lbtc_reader.go | 23 ----- .../ocr2/plugins/ccip/tokendata/lbtc/lbtc.go | 96 +++++++++++++++---- .../plugins/ccip/tokendata/lbtc/lbtc_test.go | 20 ++++ core/services/relay/evm/exec_provider.go | 15 --- 7 files changed, 102 insertions(+), 70 deletions(-) delete mode 100644 core/services/ocr2/plugins/ccip/internal/ccipdata/lbtc_reader.go create mode 100644 core/services/ocr2/plugins/ccip/tokendata/lbtc/lbtc_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 3558a8d807..be39346984 100644 --- a/core/services/ocr2/plugins/ccip/exportinternal.go +++ b/core/services/ocr2/plugins/ccip/exportinternal.go @@ -108,16 +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) -} - -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 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 d1c26f7d6c..0000000000 --- a/core/services/ocr2/plugins/ccip/internal/ccipdata/lbtc_reader.go +++ /dev/null @@ -1,23 +0,0 @@ -package ccipdata - -import ( - "github.com/ethereum/go-ethereum/common" - - "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" - "github.com/smartcontractkit/chainlink/v2/core/logger" -) - -// TODO: Implement lbtc token reader -type LBTCReader interface { -} - -type LBTCReaderImpl struct { -} - -func NewLBTCReader(lggr logger.Logger, jobID string, transmitter common.Address, lp logpoller.LogPoller, registerFilters bool) (*LBTCReaderImpl, error) { - return &LBTCReaderImpl{}, nil -} - -func CloseLBTCReader(lggr logger.Logger, jobID string, transmitter common.Address, lp logpoller.LogPoller) error { - return nil -} diff --git a/core/services/ocr2/plugins/ccip/tokendata/lbtc/lbtc.go b/core/services/ocr2/plugins/ccip/tokendata/lbtc/lbtc.go index 0060f99d95..dece927d02 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" @@ -15,6 +17,7 @@ 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/tokendata" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/tokendata/http" ) @@ -72,16 +75,52 @@ 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 +type attestationRequest struct { + PayloadHashes []string `json:"messageHash"` +} + type attestationResponse struct { Attestations []messageAttestationResponse `json:"attestations"` } // 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 +} + var _ tokendata.Reader = &TokenDataReader{} func NewLBTCTokenDataReader( @@ -149,17 +188,16 @@ func (s *TokenDataReader) ReadTokenData(ctx context.Context, msg cciptypes.EVM2E } } - messageBody, err := s.getLBTCMessageBody(ctx, msg, tokenIndex) + payloadHash, err := s.getLBTCPayloadHash(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 +209,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 +217,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 := hexutil.Decode(attestation.Attestation) if err != nil { - return nil, fmt.Errorf("failed to encode messageAndAttestation : %w", err) + return nil, err } - return messageAndAttestation, nil + return payloadAndProof, nil case attestationStatusPending: return nil, tokendata.ErrNotReady case attestationStatusSubmitted: @@ -194,12 +232,36 @@ 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) getLBTCPayloadHash(msg cciptypes.EVM2EVMOnRampCCIPSendRequestedWithMeta, tokenIndex int) ([32]byte, error) { + decodedSourceTokenData, err := abihelpers.DecodeAbiStruct[sourceTokenData](msg.SourceTokenData[tokenIndex]) + if err != nil { + 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. 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) + } + return payloadHash, nil } 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) @@ -207,11 +269,9 @@ func (s *TokenDataReader) callAttestationApi(ctx context.Context, lbtcMessageHas case err != nil: return attestationResponse{}, err } - return attestationResponse{}, nil -} - -func encodeMessageAndAttestation(messageBody []byte, attestation string) ([]byte, error) { - return nil, nil + var attestationResp attestationResponse + err = json.Unmarshal(respRaw, &attestationResp) + return attestationResp, err } 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..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 ccip.CloseLBTCReader(s.lggr, s.lggr.Name(), s.lbtcConfig.SourceMessageTransmitterAddress, s.lp) - }) var multiErr error for _, fn := range unregisterFuncs { if err := fn(); err != nil {