From 8cf4caa16b37c97bc18533d691d3535aaf66174c Mon Sep 17 00:00:00 2001 From: dimitris Date: Fri, 28 Jun 2024 15:19:53 +0300 Subject: [PATCH] ocr3 - performance optimize msghasher (#1115) Performance optimizations of msgHasher, ~4 times faster by parsing ABIs once. --- .../ocr3/plugins/ccipevm/msghasher.go | 73 ++++++++++++------- .../ocr3/plugins/ccipevm/msghasher_test.go | 35 ++++++++- 2 files changed, 79 insertions(+), 29 deletions(-) diff --git a/core/services/ocr3/plugins/ccipevm/msghasher.go b/core/services/ocr3/plugins/ccipevm/msghasher.go index 9a6b97aff3..0b68ba4675 100644 --- a/core/services/ocr3/plugins/ccipevm/msghasher.go +++ b/core/services/ocr3/plugins/ccipevm/msghasher.go @@ -20,12 +20,42 @@ import ( type MessageHasherV1 struct { metaDataHash [32]byte leafDomainSeparator [32]byte + + // ABIs and types for encoding the message data similar to on-chain implementation: + // https://github.com/smartcontractkit/ccip/blob/54ee4f13143d3e414627b6a0b9f71d5dfade76c5/contracts/src/v0.8/ccip/libraries/Internal.sol#L135 + bytesArrayType abi.Type + tokensAbi abi.ABI + fixedSizeValuesAbi abi.ABI + packedValuesAbi abi.ABI } func NewMessageHasherV1(metaDataHash [32]byte) *MessageHasherV1 { + bytesArray, err := abi.NewType("bytes[]", "bytes[]", nil) + if err != nil { + panic(fmt.Sprintf("failed to create bytes[] type: %v", err)) + } + return &MessageHasherV1{ metaDataHash: metaDataHash, leafDomainSeparator: [32]byte{}, + + bytesArrayType: bytesArray, + tokensAbi: mustParseInputsAbi(`[{"components": [{"name":"token","type":"address"}, + {"name":"amount","type":"uint256"}], "type":"tuple[]"}]`), + fixedSizeValuesAbi: mustParseInputsAbi(`[{"name": "sender", "type":"address"}, + {"name": "receiver", "type":"address"}, + {"name": "sequenceNumber", "type":"uint64"}, + {"name": "gasLimit", "type":"uint256"}, + {"name": "strict", "type":"bool"}, + {"name": "nonce", "type":"uint64"}, + {"name": "feeToken","type": "address"}, + {"name": "feeTokenAmount","type": "uint256"}]`), + packedValuesAbi: mustParseInputsAbi(`[{"name": "leafDomainSeparator","type":"bytes32"}, + {"name": "metadataHash", "type":"bytes32"}, + {"name": "fixedSizeValuesHash", "type":"bytes32"}, + {"name": "dataHash", "type":"bytes32"}, + {"name": "tokenAmountsHash", "type":"bytes32"}, + {"name": "sourceTokenDataHash", "type":"bytes32"}]`), } } @@ -41,31 +71,19 @@ func (h *MessageHasherV1) Hash(_ context.Context, msg cciptypes.CCIPMsg) (ccipty Amount: ta.Amount, } } - encodedTokens, err := h.abiEncode(`[{"components": [{"name":"token","type":"address"}, - {"name":"amount","type":"uint256"}], "type":"tuple[]"}]`, tokenAmounts) + encodedTokens, err := h.abiEncode(h.tokensAbi, tokenAmounts) if err != nil { return [32]byte{}, fmt.Errorf("abi encode token amounts: %w", err) } - bytesArray, err := abi.NewType("bytes[]", "bytes[]", nil) - if err != nil { - return [32]byte{}, fmt.Errorf("new abi type bytes[]: %w", err) - } - - encodedSourceTokenData, err := abi.Arguments{abi.Argument{Type: bytesArray}}.PackValues([]interface{}{msg.SourceTokenData}) + encodedSourceTokenData, err := abi.Arguments{abi.Argument{Type: h.bytesArrayType}}. + PackValues([]interface{}{msg.SourceTokenData}) if err != nil { return [32]byte{}, fmt.Errorf("pack source token data: %w", err) } packedFixedSizeValues, err := h.abiEncode( - `[{"name": "sender", "type":"address"}, - {"name": "receiver", "type":"address"}, - {"name": "sequenceNumber", "type":"uint64"}, - {"name": "gasLimit", "type":"uint256"}, - {"name": "strict", "type":"bool"}, - {"name": "nonce", "type":"uint64"}, - {"name": "feeToken","type": "address"}, - {"name": "feeTokenAmount","type": "uint256"}]`, + h.fixedSizeValuesAbi, common.HexToAddress(string(msg.Sender)), common.HexToAddress(string(msg.Receiver)), uint64(msg.SeqNum), @@ -81,12 +99,7 @@ func (h *MessageHasherV1) Hash(_ context.Context, msg cciptypes.CCIPMsg) (ccipty fixedSizeValuesHash := utils.Keccak256Fixed(packedFixedSizeValues) packedValues, err := h.abiEncode( - `[{"name": "leafDomainSeparator","type":"bytes32"}, - {"name": "metadataHash", "type":"bytes32"}, - {"name": "fixedSizeValuesHash", "type":"bytes32"}, - {"name": "dataHash", "type":"bytes32"}, - {"name": "tokenAmountsHash", "type":"bytes32"}, - {"name": "sourceTokenDataHash", "type":"bytes32"}]`, + h.packedValuesAbi, h.leafDomainSeparator, h.metaDataHash, fixedSizeValuesHash, @@ -101,17 +114,21 @@ func (h *MessageHasherV1) Hash(_ context.Context, msg cciptypes.CCIPMsg) (ccipty return utils.Keccak256Fixed(packedValues), nil } -func (h *MessageHasherV1) abiEncode(abiStr string, values ...interface{}) ([]byte, error) { - inDef := fmt.Sprintf(`[{ "name" : "method", "type": "function", "inputs": %s}]`, abiStr) - inAbi, err := abi.JSON(strings.NewReader(inDef)) +func (h *MessageHasherV1) abiEncode(theAbi abi.ABI, values ...interface{}) ([]byte, error) { + res, err := theAbi.Pack("method", values...) if err != nil { return nil, err } - res, err := inAbi.Pack("method", values...) + return res[4:], nil +} + +func mustParseInputsAbi(s string) abi.ABI { + inDef := fmt.Sprintf(`[{ "name" : "method", "type": "function", "inputs": %s}]`, s) + inAbi, err := abi.JSON(strings.NewReader(inDef)) if err != nil { - return nil, err + panic(fmt.Errorf("failed to create %s ABI: %v", s, err)) } - return res[4:], nil + return inAbi } // Interface compliance check diff --git a/core/services/ocr3/plugins/ccipevm/msghasher_test.go b/core/services/ocr3/plugins/ccipevm/msghasher_test.go index 4585005dbf..3a49d78f9a 100644 --- a/core/services/ocr3/plugins/ccipevm/msghasher_test.go +++ b/core/services/ocr3/plugins/ccipevm/msghasher_test.go @@ -156,7 +156,7 @@ func testSetup(t *testing.T) *testSetupData { func TestMessageHasher_Hash(t *testing.T) { ctx := testutils.Context(t) - largeNumber, ok := big.NewInt(0).SetString("1000000000000000000", 10) //1e18 + largeNumber, ok := big.NewInt(0).SetString("1000000000000000000", 10) // 1e18 require.True(t, ok) msgData, err := hex.DecodeString("64617461") @@ -294,3 +294,36 @@ func TestMessageHasher_Hash(t *testing.T) { }) } } + +func BenchmarkMessageHasher_Hash(b *testing.B) { + ctx := testutils.Context(b) + msg := cciptypes.CCIPMsg{ + CCIPMsgBaseDetails: cciptypes.CCIPMsgBaseDetails{ + SourceChain: 1, + SeqNum: 1, + }, + ChainFeeLimit: cciptypes.NewBigIntFromInt64(400000), + Nonce: 1, + Sender: types.Account(utils.RandomAddress().String()), + Receiver: types.Account(utils.RandomAddress().String()), + Strict: false, + FeeToken: types.Account(utils.RandomAddress().String()), + FeeTokenAmount: cciptypes.NewBigIntFromInt64(1234567890), + Data: make([]byte, 2048), + TokenAmounts: []cciptypes.TokenAmount{ + { + Token: types.Account(utils.RandomAddress().String()), + Amount: utils.RandUint256(), + }, + }, + SourceTokenData: [][]byte{make([]byte, 2048)}, + } + metadataHash := utils.RandomBytes32() + + m := NewMessageHasherV1(metadataHash) + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, err := m.Hash(ctx, msg) + require.NoError(b, err) + } +}