From be120a2c40e9dd8f569edea640498068c3e882e7 Mon Sep 17 00:00:00 2001 From: Makram Date: Thu, 18 Jul 2024 15:03:06 +0300 Subject: [PATCH] core/services/ccipcapability: oracle creator impls (#1180) ## Motivation We still need to create the plugin and bootstrap oracles, meaning there needs to be a concrete implementation of the `OracleCreator` interface. ## Solution Implement the `OracleCreator` interface. An end to end Integration test is also included in this PR, which tests commit reports. However, these commit reports do NOT contain merkle roots, some issues need to be resolved in upcoming PRs which involve changes in chainlink-common. ## Notes * This PR is using `legacyevm` instead of relayers because chain writer changes have not yet fully propagated to this repo. Once they have we can update the implementation to use the relayers. ## Requires * https://github.com/smartcontractkit/chainlink-ccip/pull/20 --------- Co-authored-by: Abdelrahman Soliman (Boda) <2677789+asoliman92@users.noreply.github.com> Co-authored-by: asoliman --- .../scripts/native_solc_compile_all_ccip | 1 + .../multi_ocr3_helper/multi_ocr3_helper.go | 1096 +++++++++++++++++ ...rapper-dependency-versions-do-not-edit.txt | 1 + core/gethwrappers/ccip/go_generate.go | 1 + core/scripts/go.mod | 1 + core/scripts/go.sum | 2 + core/services/ccipcapability/common/common.go | 23 + .../ccipcapability/common/common_test.go | 50 + .../configs/evm/chain_writer.go | 67 + .../configs/evm/contract_reader.go | 190 +++ core/services/ccipcapability/delegate.go | 292 +++++ core/services/ccipcapability/delegate_test.go | 1 + core/services/ccipcapability/launcher/diff.go | 31 +- .../ccipcapability/launcher/diff_test.go | 168 ++- .../launcher/integration_test.go | 2 +- .../ocrimpls/config_digester.go | 23 + .../ccipcapability/ocrimpls/config_tracker.go | 66 + .../ocrimpls/contract_transmitter.go | 188 +++ .../ocrimpls/contract_transmitter_test.go | 689 +++++++++++ .../ccipcapability/ocrimpls/keyring.go | 61 + .../ccipcapability/oraclecreator/inprocess.go | 339 +++++ .../oraclecreator/inprocess_test.go | 238 ++++ .../ccipcapability/validate/validate.go | 94 ++ .../ccipcapability/validate/validate_test.go | 57 + core/services/chainlink/application.go | 44 +- core/services/job/job_orm_test.go | 117 ++ core/services/job/models.go | 50 + core/services/job/orm.go | 64 +- .../plugins/ccip_integration_tests/helpers.go | 154 ++- .../ccip_integration_tests/home_chain_test.go | 21 +- .../integration_helpers.go | 43 +- .../ccip_integration_tests/ocr3_node_test.go | 169 ++- .../ccip_integration_tests/ocr_node_helper.go | 36 +- .../ccip_integration_tests/ping_pong_test.go | 4 +- core/services/pipeline/common.go | 1 + core/services/relay/evm/types/codec_entry.go | 8 +- core/services/synchronization/common.go | 3 + core/web/presenters/job.go | 23 + core/web/presenters/job_test.go | 98 +- go.mod | 3 +- go.sum | 4 +- integration-tests/go.mod | 1 + integration-tests/go.sum | 2 + integration-tests/load/go.mod | 1 + integration-tests/load/go.sum | 2 + 45 files changed, 4272 insertions(+), 257 deletions(-) create mode 100644 core/gethwrappers/ccip/generated/multi_ocr3_helper/multi_ocr3_helper.go create mode 100644 core/services/ccipcapability/common/common.go create mode 100644 core/services/ccipcapability/common/common_test.go create mode 100644 core/services/ccipcapability/configs/evm/chain_writer.go create mode 100644 core/services/ccipcapability/configs/evm/contract_reader.go create mode 100644 core/services/ccipcapability/delegate.go create mode 100644 core/services/ccipcapability/delegate_test.go create mode 100644 core/services/ccipcapability/ocrimpls/config_digester.go create mode 100644 core/services/ccipcapability/ocrimpls/config_tracker.go create mode 100644 core/services/ccipcapability/ocrimpls/contract_transmitter.go create mode 100644 core/services/ccipcapability/ocrimpls/contract_transmitter_test.go create mode 100644 core/services/ccipcapability/ocrimpls/keyring.go create mode 100644 core/services/ccipcapability/oraclecreator/inprocess.go create mode 100644 core/services/ccipcapability/oraclecreator/inprocess_test.go create mode 100644 core/services/ccipcapability/validate/validate.go create mode 100644 core/services/ccipcapability/validate/validate_test.go rename core/services/ocr3/plugins/ccip_integration_tests/{ => integrationhelpers}/integration_helpers.go (91%) diff --git a/contracts/scripts/native_solc_compile_all_ccip b/contracts/scripts/native_solc_compile_all_ccip index 40e9ea56ec..6228655d87 100755 --- a/contracts/scripts/native_solc_compile_all_ccip +++ b/contracts/scripts/native_solc_compile_all_ccip @@ -85,6 +85,7 @@ compileContract ccip/test/helpers/CommitStoreHelper.sol compileContract ccip/test/helpers/MessageHasher.sol compileContract ccip/test/helpers/ReportCodec.sol compileContract ccip/test/helpers/receivers/MaybeRevertMessageReceiver.sol +compileContract ccip/test/helpers/MultiOCR3Helper.sol compileContract ccip/test/mocks/MockRMN1_0.sol compileContract ccip/test/mocks/MockE2EUSDCTokenMessenger.sol compileContract ccip/test/mocks/MockE2EUSDCTransmitter.sol diff --git a/core/gethwrappers/ccip/generated/multi_ocr3_helper/multi_ocr3_helper.go b/core/gethwrappers/ccip/generated/multi_ocr3_helper/multi_ocr3_helper.go new file mode 100644 index 0000000000..d51e398b43 --- /dev/null +++ b/core/gethwrappers/ccip/generated/multi_ocr3_helper/multi_ocr3_helper.go @@ -0,0 +1,1096 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package multi_ocr3_helper + +import ( + "errors" + "fmt" + "math/big" + "strings" + + ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/event" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated" +) + +var ( + _ = errors.New + _ = big.NewInt + _ = strings.NewReader + _ = ethereum.NotFound + _ = bind.Bind + _ = common.Big1 + _ = types.BloomLookup + _ = event.NewSubscription + _ = abi.ConvertType +) + +type MultiOCR3BaseConfigInfo struct { + ConfigDigest [32]byte + F uint8 + N uint8 + IsSignatureVerificationEnabled bool +} + +type MultiOCR3BaseOCRConfig struct { + ConfigInfo MultiOCR3BaseConfigInfo + Signers []common.Address + Transmitters []common.Address +} + +type MultiOCR3BaseOCRConfigArgs struct { + ConfigDigest [32]byte + OcrPluginType uint8 + F uint8 + IsSignatureVerificationEnabled bool + Signers []common.Address + Transmitters []common.Address +} + +type MultiOCR3BaseOracle struct { + Index uint8 + Role uint8 +} + +var MultiOCR3HelperMetaData = &bind.MetaData{ + ABI: "[{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"expected\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"actual\",\"type\":\"bytes32\"}],\"name\":\"ConfigDigestMismatch\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"expected\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"actual\",\"type\":\"uint256\"}],\"name\":\"ForkedChain\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"enumMultiOCR3Base.InvalidConfigErrorType\",\"name\":\"errorType\",\"type\":\"uint8\"}],\"name\":\"InvalidConfig\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"NonUniqueSignatures\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"OracleCannotBeZeroAddress\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"SignaturesOutOfRegistration\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint8\",\"name\":\"ocrPluginType\",\"type\":\"uint8\"}],\"name\":\"StaticConfigCannotBeChanged\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"UnauthorizedSigner\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"UnauthorizedTransmitter\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"expected\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"actual\",\"type\":\"uint256\"}],\"name\":\"WrongMessageLength\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"WrongNumberOfSignatures\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"ocrPluginType\",\"type\":\"uint8\"}],\"name\":\"AfterConfigSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"ocrPluginType\",\"type\":\"uint8\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"configDigest\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"address[]\",\"name\":\"signers\",\"type\":\"address[]\"},{\"indexed\":false,\"internalType\":\"address[]\",\"name\":\"transmitters\",\"type\":\"address[]\"},{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"F\",\"type\":\"uint8\"}],\"name\":\"ConfigSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"OwnershipTransferRequested\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"OwnershipTransferred\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint8\",\"name\":\"ocrPluginType\",\"type\":\"uint8\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"configDigest\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"sequenceNumber\",\"type\":\"uint64\"}],\"name\":\"Transmitted\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"acceptOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint8\",\"name\":\"ocrPluginType\",\"type\":\"uint8\"},{\"internalType\":\"address\",\"name\":\"oracleAddress\",\"type\":\"address\"}],\"name\":\"getOracle\",\"outputs\":[{\"components\":[{\"internalType\":\"uint8\",\"name\":\"index\",\"type\":\"uint8\"},{\"internalType\":\"enumMultiOCR3Base.Role\",\"name\":\"role\",\"type\":\"uint8\"}],\"internalType\":\"structMultiOCR3Base.Oracle\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint8\",\"name\":\"ocrPluginType\",\"type\":\"uint8\"}],\"name\":\"latestConfigDetails\",\"outputs\":[{\"components\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"configDigest\",\"type\":\"bytes32\"},{\"internalType\":\"uint8\",\"name\":\"F\",\"type\":\"uint8\"},{\"internalType\":\"uint8\",\"name\":\"n\",\"type\":\"uint8\"},{\"internalType\":\"bool\",\"name\":\"isSignatureVerificationEnabled\",\"type\":\"bool\"}],\"internalType\":\"structMultiOCR3Base.ConfigInfo\",\"name\":\"configInfo\",\"type\":\"tuple\"},{\"internalType\":\"address[]\",\"name\":\"signers\",\"type\":\"address[]\"},{\"internalType\":\"address[]\",\"name\":\"transmitters\",\"type\":\"address[]\"}],\"internalType\":\"structMultiOCR3Base.OCRConfig\",\"name\":\"ocrConfig\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"configDigest\",\"type\":\"bytes32\"},{\"internalType\":\"uint8\",\"name\":\"ocrPluginType\",\"type\":\"uint8\"},{\"internalType\":\"uint8\",\"name\":\"F\",\"type\":\"uint8\"},{\"internalType\":\"bool\",\"name\":\"isSignatureVerificationEnabled\",\"type\":\"bool\"},{\"internalType\":\"address[]\",\"name\":\"signers\",\"type\":\"address[]\"},{\"internalType\":\"address[]\",\"name\":\"transmitters\",\"type\":\"address[]\"}],\"internalType\":\"structMultiOCR3Base.OCRConfigArgs[]\",\"name\":\"ocrConfigArgs\",\"type\":\"tuple[]\"}],\"name\":\"setOCR3Configs\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint8\",\"name\":\"ocrPluginType\",\"type\":\"uint8\"}],\"name\":\"setTransmitOcrPluginType\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32[3]\",\"name\":\"reportContext\",\"type\":\"bytes32[3]\"},{\"internalType\":\"bytes\",\"name\":\"report\",\"type\":\"bytes\"},{\"internalType\":\"bytes32[]\",\"name\":\"rs\",\"type\":\"bytes32[]\"},{\"internalType\":\"bytes32[]\",\"name\":\"ss\",\"type\":\"bytes32[]\"},{\"internalType\":\"bytes32\",\"name\":\"rawVs\",\"type\":\"bytes32\"}],\"name\":\"transmitWithSignatures\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32[3]\",\"name\":\"reportContext\",\"type\":\"bytes32[3]\"},{\"internalType\":\"bytes\",\"name\":\"report\",\"type\":\"bytes\"}],\"name\":\"transmitWithoutSignatures\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"typeAndVersion\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"pure\",\"type\":\"function\"}]", + Bin: "0x60a06040523480156200001157600080fd5b503380600081620000695760405162461bcd60e51b815260206004820152601860248201527f43616e6e6f7420736574206f776e657220746f207a65726f000000000000000060448201526064015b60405180910390fd5b600080546001600160a01b0319166001600160a01b03848116919091179091558116156200009c576200009c81620000a9565b5050466080525062000154565b336001600160a01b03821603620001035760405162461bcd60e51b815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c66000000000000000000604482015260640162000060565b600180546001600160a01b0319166001600160a01b0383811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b608051611db66200017760003960008181610efc0152610f480152611db66000f3fe608060405234801561001057600080fd5b50600436106100be5760003560e01c80637ac0aa1a11610076578063c673e5841161005b578063c673e584146101c5578063f2fde38b146101e5578063f716f99f146101f857600080fd5b80637ac0aa1a1461015b5780638da5cb5b1461019d57600080fd5b806334a9c92e116100a757806334a9c92e1461012057806344e65e551461014057806379ba50971461015357600080fd5b8063181f5a77146100c357806326bf9d261461010b575b600080fd5b604080518082018252601981527f4d756c74694f4352334261736548656c70657220312e302e300000000000000060208201529051610102919061153c565b60405180910390f35b61011e610119366004611603565b61020b565b005b61013361012e366004611691565b61023a565b60405161010291906116f3565b61011e61014e366004611766565b6102ca565b61011e61034d565b61011e610169366004611819565b600480547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660ff92909216919091179055565b60005460405173ffffffffffffffffffffffffffffffffffffffff9091168152602001610102565b6101d86101d3366004611819565b61044f565b604051610102919061188d565b61011e6101f3366004611920565b6105c7565b61011e610206366004611a8c565b6105db565b604080516000808252602082019092526004549091506102349060ff168585858580600061061d565b50505050565b6040805180820182526000808252602080830182905260ff86811683526003825284832073ffffffffffffffffffffffffffffffffffffffff871684528252918490208451808601909552805480841686529394939092918401916101009091041660028111156102ad576102ad6116c4565b60028111156102be576102be6116c4565b90525090505b92915050565b60045460408051602080880282810182019093528782526103439360ff16928c928c928c928c918c91829185019084908082843760009201919091525050604080516020808d0282810182019093528c82529093508c92508b9182918501908490808284376000920191909152508a925061061d915050565b5050505050505050565b60015473ffffffffffffffffffffffffffffffffffffffff1633146103d3576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4d7573742062652070726f706f736564206f776e65720000000000000000000060448201526064015b60405180910390fd5b60008054337fffffffffffffffffffffffff00000000000000000000000000000000000000008083168217845560018054909116905560405173ffffffffffffffffffffffffffffffffffffffff90921692909183917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a350565b6104926040805160e081019091526000606082018181526080830182905260a0830182905260c08301919091528190815260200160608152602001606081525090565b60ff808316600090815260026020818152604092839020835160e081018552815460608201908152600183015480881660808401526101008104881660a0840152620100009004909616151560c08201529485529182018054845181840281018401909552808552929385830193909283018282801561054857602002820191906000526020600020905b815473ffffffffffffffffffffffffffffffffffffffff16815260019091019060200180831161051d575b50505050508152602001600382018054806020026020016040519081016040528092919081815260200182805480156105b757602002820191906000526020600020905b815473ffffffffffffffffffffffffffffffffffffffff16815260019091019060200180831161058c575b5050505050815250509050919050565b6105cf6109a1565b6105d881610a24565b50565b6105e36109a1565b60005b81518110156106195761061182828151811061060457610604611bf5565b6020026020010151610b19565b6001016105e6565b5050565b60ff8781166000908152600260209081526040808320815160808101835281548152600190910154808616938201939093526101008304851691810191909152620100009091049092161515606083015287359061067c8760a4611c53565b90508260600151156106c4578451610695906020611c66565b86516106a2906020611c66565b6106ad9060a0611c53565b6106b79190611c53565b6106c19082611c53565b90505b368114610706576040517f8e1192e1000000000000000000000000000000000000000000000000000000008152600481018290523660248201526044016103ca565b508151811461074e5781516040517f93df584c0000000000000000000000000000000000000000000000000000000081526004810191909152602481018290526044016103ca565b610756610ef9565b60ff808a16600090815260036020908152604080832033845282528083208151808301909252805480861683529394919390928401916101009091041660028111156107a4576107a46116c4565b60028111156107b5576107b56116c4565b90525090506002816020015160028111156107d2576107d26116c4565b1480156108335750600260008b60ff1660ff168152602001908152602001600020600301816000015160ff168154811061080e5761080e611bf5565b60009182526020909120015473ffffffffffffffffffffffffffffffffffffffff1633145b610869576040517fda0f08e800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5081606001511561094b576020820151610884906001611c7d565b60ff168551146108c0576040517f71253a2500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b83518551146108fb576040517fa75d88af00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000878760405161090d929190611c96565b604051908190038120610924918b90602001611ca6565b6040516020818303038152906040528051906020012090506109498a82888888610f7a565b505b6040805182815260208a81013567ffffffffffffffff169082015260ff8b16917f198d6990ef96613a9026203077e422916918b03ff47f0be6bee7b02d8e139ef0910160405180910390a2505050505050505050565b60005473ffffffffffffffffffffffffffffffffffffffff163314610a22576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4f6e6c792063616c6c61626c65206279206f776e65720000000000000000000060448201526064016103ca565b565b3373ffffffffffffffffffffffffffffffffffffffff821603610aa3576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c6600000000000000000060448201526064016103ca565b600180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b806040015160ff16600003610b5d5760006040517f367f56a20000000000000000000000000000000000000000000000000000000081526004016103ca9190611cba565b60208082015160ff80821660009081526002909352604083206001810154929390928392169003610bca57606084015160018201805491151562010000027fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffff909216919091179055610c1f565b6060840151600182015460ff6201000090910416151590151514610c1f576040517f87f6037c00000000000000000000000000000000000000000000000000000000815260ff841660048201526024016103ca565b60a08401518051601f60ff82161115610c675760016040517f367f56a20000000000000000000000000000000000000000000000000000000081526004016103ca9190611cba565b610cda8585600301805480602002602001604051908101604052809291908181526020018280548015610cd057602002820191906000526020600020905b815473ffffffffffffffffffffffffffffffffffffffff168152600190910190602001808311610ca5575b50505050506111b2565b856060015115610e4857610d558585600201805480602002602001604051908101604052809291908181526020018280548015610cd05760200282019190600052602060002090815473ffffffffffffffffffffffffffffffffffffffff168152600190910190602001808311610ca55750505050506111b2565b60808601518051610d6f906002870190602084019061147e565b5080516001850180547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff1661010060ff841690810291909117909155601f1015610de85760026040517f367f56a20000000000000000000000000000000000000000000000000000000081526004016103ca9190611cba565b6040880151610df8906003611cd4565b60ff168160ff1611610e395760036040517f367f56a20000000000000000000000000000000000000000000000000000000081526004016103ca9190611cba565b610e458783600161124a565b50505b610e548583600261124a565b8151610e69906003860190602085019061147e565b506040868101516001850180547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660ff8316179055875180865560a089015192517fab8b1b57514019638d7b5ce9c638fe71366fe8e2be1c40a7a80f1733d0e9f54793610ee0938a939260028b01929190611cf7565b60405180910390a1610ef185611445565b505050505050565b467f000000000000000000000000000000000000000000000000000000000000000014610a22576040517f0f01ce850000000000000000000000000000000000000000000000000000000081527f000000000000000000000000000000000000000000000000000000000000000060048201524660248201526044016103ca565b610f82611508565b835160005b81811015610343576000600188868460208110610fa657610fa6611bf5565b610fb391901a601b611c7d565b898581518110610fc557610fc5611bf5565b6020026020010151898681518110610fdf57610fdf611bf5565b60200260200101516040516000815260200160405260405161101d949392919093845260ff9290921660208401526040830152606082015260800190565b6020604051602081039080840390855afa15801561103f573d6000803e3d6000fd5b5050604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081015160ff808e1660009081526003602090815285822073ffffffffffffffffffffffffffffffffffffffff8516835281528582208587019096528554808416865293975090955092939284019161010090041660028111156110cb576110cb6116c4565b60028111156110dc576110dc6116c4565b90525090506001816020015160028111156110f9576110f96116c4565b14611130576040517fca31867a00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8051859060ff16601f811061114757611147611bf5565b602002015115611183576040517ff67bc7c400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600185826000015160ff16601f811061119e5761119e611bf5565b911515602090920201525050600101610f87565b60005b81518110156112455760ff8316600090815260036020526040812083519091908490849081106111e7576111e7611bf5565b60209081029190910181015173ffffffffffffffffffffffffffffffffffffffff16825281019190915260400160002080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00001690556001016111b5565b505050565b60005b82518160ff161015610234576000838260ff168151811061127057611270611bf5565b602002602001015190506000600281111561128d5761128d6116c4565b60ff808716600090815260036020908152604080832073ffffffffffffffffffffffffffffffffffffffff8716845290915290205461010090041660028111156112d9576112d96116c4565b146113135760046040517f367f56a20000000000000000000000000000000000000000000000000000000081526004016103ca9190611cba565b73ffffffffffffffffffffffffffffffffffffffff8116611360576040517fd6c62c9b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60405180604001604052808360ff168152602001846002811115611386576113866116c4565b905260ff808716600090815260036020908152604080832073ffffffffffffffffffffffffffffffffffffffff8716845282529091208351815493167fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00841681178255918401519092909183917fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000161761010083600281111561142b5761142b6116c4565b0217905550905050508061143e90611d8a565b905061124d565b60405160ff821681527f897ac1b2c12867721b284f3eb147bd4ab046d4eef1cf31c1d8988bfcfb962b539060200160405180910390a150565b8280548282559060005260206000209081019282156114f8579160200282015b828111156114f857825182547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff90911617825560209092019160019091019061149e565b50611504929150611527565b5090565b604051806103e00160405280601f906020820280368337509192915050565b5b808211156115045760008155600101611528565b60006020808352835180602085015260005b8181101561156a5785810183015185820160400152820161154e565b5060006040828601015260407fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8301168501019250505092915050565b80606081018310156102c457600080fd5b60008083601f8401126115cc57600080fd5b50813567ffffffffffffffff8111156115e457600080fd5b6020830191508360208285010111156115fc57600080fd5b9250929050565b60008060006080848603121561161857600080fd5b61162285856115a9565b9250606084013567ffffffffffffffff81111561163e57600080fd5b61164a868287016115ba565b9497909650939450505050565b803560ff8116811461166857600080fd5b919050565b803573ffffffffffffffffffffffffffffffffffffffff8116811461166857600080fd5b600080604083850312156116a457600080fd5b6116ad83611657565b91506116bb6020840161166d565b90509250929050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b815160ff1681526020820151604082019060038110611714576117146116c4565b8060208401525092915050565b60008083601f84011261173357600080fd5b50813567ffffffffffffffff81111561174b57600080fd5b6020830191508360208260051b85010111156115fc57600080fd5b60008060008060008060008060e0898b03121561178257600080fd5b61178c8a8a6115a9565b9750606089013567ffffffffffffffff808211156117a957600080fd5b6117b58c838d016115ba565b909950975060808b01359150808211156117ce57600080fd5b6117da8c838d01611721565b909750955060a08b01359150808211156117f357600080fd5b506118008b828c01611721565b999c989b50969995989497949560c00135949350505050565b60006020828403121561182b57600080fd5b61183482611657565b9392505050565b60008151808452602080850194506020840160005b8381101561188257815173ffffffffffffffffffffffffffffffffffffffff1687529582019590820190600101611850565b509495945050505050565b60208152600082518051602084015260ff602082015116604084015260ff604082015116606084015260608101511515608084015250602083015160c060a08401526118dc60e084018261183b565b905060408401517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08483030160c0850152611917828261183b565b95945050505050565b60006020828403121561193257600080fd5b6118348261166d565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b60405160c0810167ffffffffffffffff8111828210171561198d5761198d61193b565b60405290565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff811182821017156119da576119da61193b565b604052919050565b600067ffffffffffffffff8211156119fc576119fc61193b565b5060051b60200190565b8035801515811461166857600080fd5b600082601f830112611a2757600080fd5b81356020611a3c611a37836119e2565b611993565b8083825260208201915060208460051b870101935086841115611a5e57600080fd5b602086015b84811015611a8157611a748161166d565b8352918301918301611a63565b509695505050505050565b60006020808385031215611a9f57600080fd5b823567ffffffffffffffff80821115611ab757600080fd5b818501915085601f830112611acb57600080fd5b8135611ad9611a37826119e2565b81815260059190911b83018401908481019088831115611af857600080fd5b8585015b83811015611be857803585811115611b1357600080fd5b860160c0818c037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0011215611b485760008081fd5b611b5061196a565b8882013581526040611b63818401611657565b8a8301526060611b74818501611657565b8284015260809150611b87828501611a06565b9083015260a08381013589811115611b9f5760008081fd5b611bad8f8d83880101611a16565b838501525060c0840135915088821115611bc75760008081fd5b611bd58e8c84870101611a16565b9083015250845250918601918601611afc565b5098975050505050505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b808201808211156102c4576102c4611c24565b80820281158282048414176102c4576102c4611c24565b60ff81811683821601908111156102c4576102c4611c24565b8183823760009101908152919050565b828152606082602083013760800192915050565b6020810160058310611cce57611cce6116c4565b91905290565b60ff8181168382160290811690818114611cf057611cf0611c24565b5092915050565b600060a0820160ff88168352602087602085015260a0604085015281875480845260c086019150886000526020600020935060005b81811015611d5e57845473ffffffffffffffffffffffffffffffffffffffff1683526001948501949284019201611d2c565b50508481036060860152611d72818861183b565b935050505060ff831660808301529695505050505050565b600060ff821660ff8103611da057611da0611c24565b6001019291505056fea164736f6c6343000818000a", +} + +var MultiOCR3HelperABI = MultiOCR3HelperMetaData.ABI + +var MultiOCR3HelperBin = MultiOCR3HelperMetaData.Bin + +func DeployMultiOCR3Helper(auth *bind.TransactOpts, backend bind.ContractBackend) (common.Address, *types.Transaction, *MultiOCR3Helper, error) { + parsed, err := MultiOCR3HelperMetaData.GetAbi() + if err != nil { + return common.Address{}, nil, nil, err + } + if parsed == nil { + return common.Address{}, nil, nil, errors.New("GetABI returned nil") + } + + address, tx, contract, err := bind.DeployContract(auth, *parsed, common.FromHex(MultiOCR3HelperBin), backend) + if err != nil { + return common.Address{}, nil, nil, err + } + return address, tx, &MultiOCR3Helper{address: address, abi: *parsed, MultiOCR3HelperCaller: MultiOCR3HelperCaller{contract: contract}, MultiOCR3HelperTransactor: MultiOCR3HelperTransactor{contract: contract}, MultiOCR3HelperFilterer: MultiOCR3HelperFilterer{contract: contract}}, nil +} + +type MultiOCR3Helper struct { + address common.Address + abi abi.ABI + MultiOCR3HelperCaller + MultiOCR3HelperTransactor + MultiOCR3HelperFilterer +} + +type MultiOCR3HelperCaller struct { + contract *bind.BoundContract +} + +type MultiOCR3HelperTransactor struct { + contract *bind.BoundContract +} + +type MultiOCR3HelperFilterer struct { + contract *bind.BoundContract +} + +type MultiOCR3HelperSession struct { + Contract *MultiOCR3Helper + CallOpts bind.CallOpts + TransactOpts bind.TransactOpts +} + +type MultiOCR3HelperCallerSession struct { + Contract *MultiOCR3HelperCaller + CallOpts bind.CallOpts +} + +type MultiOCR3HelperTransactorSession struct { + Contract *MultiOCR3HelperTransactor + TransactOpts bind.TransactOpts +} + +type MultiOCR3HelperRaw struct { + Contract *MultiOCR3Helper +} + +type MultiOCR3HelperCallerRaw struct { + Contract *MultiOCR3HelperCaller +} + +type MultiOCR3HelperTransactorRaw struct { + Contract *MultiOCR3HelperTransactor +} + +func NewMultiOCR3Helper(address common.Address, backend bind.ContractBackend) (*MultiOCR3Helper, error) { + abi, err := abi.JSON(strings.NewReader(MultiOCR3HelperABI)) + if err != nil { + return nil, err + } + contract, err := bindMultiOCR3Helper(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &MultiOCR3Helper{address: address, abi: abi, MultiOCR3HelperCaller: MultiOCR3HelperCaller{contract: contract}, MultiOCR3HelperTransactor: MultiOCR3HelperTransactor{contract: contract}, MultiOCR3HelperFilterer: MultiOCR3HelperFilterer{contract: contract}}, nil +} + +func NewMultiOCR3HelperCaller(address common.Address, caller bind.ContractCaller) (*MultiOCR3HelperCaller, error) { + contract, err := bindMultiOCR3Helper(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &MultiOCR3HelperCaller{contract: contract}, nil +} + +func NewMultiOCR3HelperTransactor(address common.Address, transactor bind.ContractTransactor) (*MultiOCR3HelperTransactor, error) { + contract, err := bindMultiOCR3Helper(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &MultiOCR3HelperTransactor{contract: contract}, nil +} + +func NewMultiOCR3HelperFilterer(address common.Address, filterer bind.ContractFilterer) (*MultiOCR3HelperFilterer, error) { + contract, err := bindMultiOCR3Helper(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &MultiOCR3HelperFilterer{contract: contract}, nil +} + +func bindMultiOCR3Helper(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := MultiOCR3HelperMetaData.GetAbi() + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, *parsed, caller, transactor, filterer), nil +} + +func (_MultiOCR3Helper *MultiOCR3HelperRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _MultiOCR3Helper.Contract.MultiOCR3HelperCaller.contract.Call(opts, result, method, params...) +} + +func (_MultiOCR3Helper *MultiOCR3HelperRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _MultiOCR3Helper.Contract.MultiOCR3HelperTransactor.contract.Transfer(opts) +} + +func (_MultiOCR3Helper *MultiOCR3HelperRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _MultiOCR3Helper.Contract.MultiOCR3HelperTransactor.contract.Transact(opts, method, params...) +} + +func (_MultiOCR3Helper *MultiOCR3HelperCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _MultiOCR3Helper.Contract.contract.Call(opts, result, method, params...) +} + +func (_MultiOCR3Helper *MultiOCR3HelperTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _MultiOCR3Helper.Contract.contract.Transfer(opts) +} + +func (_MultiOCR3Helper *MultiOCR3HelperTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _MultiOCR3Helper.Contract.contract.Transact(opts, method, params...) +} + +func (_MultiOCR3Helper *MultiOCR3HelperCaller) GetOracle(opts *bind.CallOpts, ocrPluginType uint8, oracleAddress common.Address) (MultiOCR3BaseOracle, error) { + var out []interface{} + err := _MultiOCR3Helper.contract.Call(opts, &out, "getOracle", ocrPluginType, oracleAddress) + + if err != nil { + return *new(MultiOCR3BaseOracle), err + } + + out0 := *abi.ConvertType(out[0], new(MultiOCR3BaseOracle)).(*MultiOCR3BaseOracle) + + return out0, err + +} + +func (_MultiOCR3Helper *MultiOCR3HelperSession) GetOracle(ocrPluginType uint8, oracleAddress common.Address) (MultiOCR3BaseOracle, error) { + return _MultiOCR3Helper.Contract.GetOracle(&_MultiOCR3Helper.CallOpts, ocrPluginType, oracleAddress) +} + +func (_MultiOCR3Helper *MultiOCR3HelperCallerSession) GetOracle(ocrPluginType uint8, oracleAddress common.Address) (MultiOCR3BaseOracle, error) { + return _MultiOCR3Helper.Contract.GetOracle(&_MultiOCR3Helper.CallOpts, ocrPluginType, oracleAddress) +} + +func (_MultiOCR3Helper *MultiOCR3HelperCaller) LatestConfigDetails(opts *bind.CallOpts, ocrPluginType uint8) (MultiOCR3BaseOCRConfig, error) { + var out []interface{} + err := _MultiOCR3Helper.contract.Call(opts, &out, "latestConfigDetails", ocrPluginType) + + if err != nil { + return *new(MultiOCR3BaseOCRConfig), err + } + + out0 := *abi.ConvertType(out[0], new(MultiOCR3BaseOCRConfig)).(*MultiOCR3BaseOCRConfig) + + return out0, err + +} + +func (_MultiOCR3Helper *MultiOCR3HelperSession) LatestConfigDetails(ocrPluginType uint8) (MultiOCR3BaseOCRConfig, error) { + return _MultiOCR3Helper.Contract.LatestConfigDetails(&_MultiOCR3Helper.CallOpts, ocrPluginType) +} + +func (_MultiOCR3Helper *MultiOCR3HelperCallerSession) LatestConfigDetails(ocrPluginType uint8) (MultiOCR3BaseOCRConfig, error) { + return _MultiOCR3Helper.Contract.LatestConfigDetails(&_MultiOCR3Helper.CallOpts, ocrPluginType) +} + +func (_MultiOCR3Helper *MultiOCR3HelperCaller) Owner(opts *bind.CallOpts) (common.Address, error) { + var out []interface{} + err := _MultiOCR3Helper.contract.Call(opts, &out, "owner") + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + +} + +func (_MultiOCR3Helper *MultiOCR3HelperSession) Owner() (common.Address, error) { + return _MultiOCR3Helper.Contract.Owner(&_MultiOCR3Helper.CallOpts) +} + +func (_MultiOCR3Helper *MultiOCR3HelperCallerSession) Owner() (common.Address, error) { + return _MultiOCR3Helper.Contract.Owner(&_MultiOCR3Helper.CallOpts) +} + +func (_MultiOCR3Helper *MultiOCR3HelperCaller) TypeAndVersion(opts *bind.CallOpts) (string, error) { + var out []interface{} + err := _MultiOCR3Helper.contract.Call(opts, &out, "typeAndVersion") + + if err != nil { + return *new(string), err + } + + out0 := *abi.ConvertType(out[0], new(string)).(*string) + + return out0, err + +} + +func (_MultiOCR3Helper *MultiOCR3HelperSession) TypeAndVersion() (string, error) { + return _MultiOCR3Helper.Contract.TypeAndVersion(&_MultiOCR3Helper.CallOpts) +} + +func (_MultiOCR3Helper *MultiOCR3HelperCallerSession) TypeAndVersion() (string, error) { + return _MultiOCR3Helper.Contract.TypeAndVersion(&_MultiOCR3Helper.CallOpts) +} + +func (_MultiOCR3Helper *MultiOCR3HelperTransactor) AcceptOwnership(opts *bind.TransactOpts) (*types.Transaction, error) { + return _MultiOCR3Helper.contract.Transact(opts, "acceptOwnership") +} + +func (_MultiOCR3Helper *MultiOCR3HelperSession) AcceptOwnership() (*types.Transaction, error) { + return _MultiOCR3Helper.Contract.AcceptOwnership(&_MultiOCR3Helper.TransactOpts) +} + +func (_MultiOCR3Helper *MultiOCR3HelperTransactorSession) AcceptOwnership() (*types.Transaction, error) { + return _MultiOCR3Helper.Contract.AcceptOwnership(&_MultiOCR3Helper.TransactOpts) +} + +func (_MultiOCR3Helper *MultiOCR3HelperTransactor) SetOCR3Configs(opts *bind.TransactOpts, ocrConfigArgs []MultiOCR3BaseOCRConfigArgs) (*types.Transaction, error) { + return _MultiOCR3Helper.contract.Transact(opts, "setOCR3Configs", ocrConfigArgs) +} + +func (_MultiOCR3Helper *MultiOCR3HelperSession) SetOCR3Configs(ocrConfigArgs []MultiOCR3BaseOCRConfigArgs) (*types.Transaction, error) { + return _MultiOCR3Helper.Contract.SetOCR3Configs(&_MultiOCR3Helper.TransactOpts, ocrConfigArgs) +} + +func (_MultiOCR3Helper *MultiOCR3HelperTransactorSession) SetOCR3Configs(ocrConfigArgs []MultiOCR3BaseOCRConfigArgs) (*types.Transaction, error) { + return _MultiOCR3Helper.Contract.SetOCR3Configs(&_MultiOCR3Helper.TransactOpts, ocrConfigArgs) +} + +func (_MultiOCR3Helper *MultiOCR3HelperTransactor) SetTransmitOcrPluginType(opts *bind.TransactOpts, ocrPluginType uint8) (*types.Transaction, error) { + return _MultiOCR3Helper.contract.Transact(opts, "setTransmitOcrPluginType", ocrPluginType) +} + +func (_MultiOCR3Helper *MultiOCR3HelperSession) SetTransmitOcrPluginType(ocrPluginType uint8) (*types.Transaction, error) { + return _MultiOCR3Helper.Contract.SetTransmitOcrPluginType(&_MultiOCR3Helper.TransactOpts, ocrPluginType) +} + +func (_MultiOCR3Helper *MultiOCR3HelperTransactorSession) SetTransmitOcrPluginType(ocrPluginType uint8) (*types.Transaction, error) { + return _MultiOCR3Helper.Contract.SetTransmitOcrPluginType(&_MultiOCR3Helper.TransactOpts, ocrPluginType) +} + +func (_MultiOCR3Helper *MultiOCR3HelperTransactor) TransferOwnership(opts *bind.TransactOpts, to common.Address) (*types.Transaction, error) { + return _MultiOCR3Helper.contract.Transact(opts, "transferOwnership", to) +} + +func (_MultiOCR3Helper *MultiOCR3HelperSession) TransferOwnership(to common.Address) (*types.Transaction, error) { + return _MultiOCR3Helper.Contract.TransferOwnership(&_MultiOCR3Helper.TransactOpts, to) +} + +func (_MultiOCR3Helper *MultiOCR3HelperTransactorSession) TransferOwnership(to common.Address) (*types.Transaction, error) { + return _MultiOCR3Helper.Contract.TransferOwnership(&_MultiOCR3Helper.TransactOpts, to) +} + +func (_MultiOCR3Helper *MultiOCR3HelperTransactor) TransmitWithSignatures(opts *bind.TransactOpts, reportContext [3][32]byte, report []byte, rs [][32]byte, ss [][32]byte, rawVs [32]byte) (*types.Transaction, error) { + return _MultiOCR3Helper.contract.Transact(opts, "transmitWithSignatures", reportContext, report, rs, ss, rawVs) +} + +func (_MultiOCR3Helper *MultiOCR3HelperSession) TransmitWithSignatures(reportContext [3][32]byte, report []byte, rs [][32]byte, ss [][32]byte, rawVs [32]byte) (*types.Transaction, error) { + return _MultiOCR3Helper.Contract.TransmitWithSignatures(&_MultiOCR3Helper.TransactOpts, reportContext, report, rs, ss, rawVs) +} + +func (_MultiOCR3Helper *MultiOCR3HelperTransactorSession) TransmitWithSignatures(reportContext [3][32]byte, report []byte, rs [][32]byte, ss [][32]byte, rawVs [32]byte) (*types.Transaction, error) { + return _MultiOCR3Helper.Contract.TransmitWithSignatures(&_MultiOCR3Helper.TransactOpts, reportContext, report, rs, ss, rawVs) +} + +func (_MultiOCR3Helper *MultiOCR3HelperTransactor) TransmitWithoutSignatures(opts *bind.TransactOpts, reportContext [3][32]byte, report []byte) (*types.Transaction, error) { + return _MultiOCR3Helper.contract.Transact(opts, "transmitWithoutSignatures", reportContext, report) +} + +func (_MultiOCR3Helper *MultiOCR3HelperSession) TransmitWithoutSignatures(reportContext [3][32]byte, report []byte) (*types.Transaction, error) { + return _MultiOCR3Helper.Contract.TransmitWithoutSignatures(&_MultiOCR3Helper.TransactOpts, reportContext, report) +} + +func (_MultiOCR3Helper *MultiOCR3HelperTransactorSession) TransmitWithoutSignatures(reportContext [3][32]byte, report []byte) (*types.Transaction, error) { + return _MultiOCR3Helper.Contract.TransmitWithoutSignatures(&_MultiOCR3Helper.TransactOpts, reportContext, report) +} + +type MultiOCR3HelperAfterConfigSetIterator struct { + Event *MultiOCR3HelperAfterConfigSet + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *MultiOCR3HelperAfterConfigSetIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(MultiOCR3HelperAfterConfigSet) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + + select { + case log := <-it.logs: + it.Event = new(MultiOCR3HelperAfterConfigSet) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +func (it *MultiOCR3HelperAfterConfigSetIterator) Error() error { + return it.fail +} + +func (it *MultiOCR3HelperAfterConfigSetIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type MultiOCR3HelperAfterConfigSet struct { + OcrPluginType uint8 + Raw types.Log +} + +func (_MultiOCR3Helper *MultiOCR3HelperFilterer) FilterAfterConfigSet(opts *bind.FilterOpts) (*MultiOCR3HelperAfterConfigSetIterator, error) { + + logs, sub, err := _MultiOCR3Helper.contract.FilterLogs(opts, "AfterConfigSet") + if err != nil { + return nil, err + } + return &MultiOCR3HelperAfterConfigSetIterator{contract: _MultiOCR3Helper.contract, event: "AfterConfigSet", logs: logs, sub: sub}, nil +} + +func (_MultiOCR3Helper *MultiOCR3HelperFilterer) WatchAfterConfigSet(opts *bind.WatchOpts, sink chan<- *MultiOCR3HelperAfterConfigSet) (event.Subscription, error) { + + logs, sub, err := _MultiOCR3Helper.contract.WatchLogs(opts, "AfterConfigSet") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + + event := new(MultiOCR3HelperAfterConfigSet) + if err := _MultiOCR3Helper.contract.UnpackLog(event, "AfterConfigSet", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +func (_MultiOCR3Helper *MultiOCR3HelperFilterer) ParseAfterConfigSet(log types.Log) (*MultiOCR3HelperAfterConfigSet, error) { + event := new(MultiOCR3HelperAfterConfigSet) + if err := _MultiOCR3Helper.contract.UnpackLog(event, "AfterConfigSet", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +type MultiOCR3HelperConfigSetIterator struct { + Event *MultiOCR3HelperConfigSet + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *MultiOCR3HelperConfigSetIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(MultiOCR3HelperConfigSet) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + + select { + case log := <-it.logs: + it.Event = new(MultiOCR3HelperConfigSet) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +func (it *MultiOCR3HelperConfigSetIterator) Error() error { + return it.fail +} + +func (it *MultiOCR3HelperConfigSetIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type MultiOCR3HelperConfigSet struct { + OcrPluginType uint8 + ConfigDigest [32]byte + Signers []common.Address + Transmitters []common.Address + F uint8 + Raw types.Log +} + +func (_MultiOCR3Helper *MultiOCR3HelperFilterer) FilterConfigSet(opts *bind.FilterOpts) (*MultiOCR3HelperConfigSetIterator, error) { + + logs, sub, err := _MultiOCR3Helper.contract.FilterLogs(opts, "ConfigSet") + if err != nil { + return nil, err + } + return &MultiOCR3HelperConfigSetIterator{contract: _MultiOCR3Helper.contract, event: "ConfigSet", logs: logs, sub: sub}, nil +} + +func (_MultiOCR3Helper *MultiOCR3HelperFilterer) WatchConfigSet(opts *bind.WatchOpts, sink chan<- *MultiOCR3HelperConfigSet) (event.Subscription, error) { + + logs, sub, err := _MultiOCR3Helper.contract.WatchLogs(opts, "ConfigSet") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + + event := new(MultiOCR3HelperConfigSet) + if err := _MultiOCR3Helper.contract.UnpackLog(event, "ConfigSet", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +func (_MultiOCR3Helper *MultiOCR3HelperFilterer) ParseConfigSet(log types.Log) (*MultiOCR3HelperConfigSet, error) { + event := new(MultiOCR3HelperConfigSet) + if err := _MultiOCR3Helper.contract.UnpackLog(event, "ConfigSet", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +type MultiOCR3HelperOwnershipTransferRequestedIterator struct { + Event *MultiOCR3HelperOwnershipTransferRequested + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *MultiOCR3HelperOwnershipTransferRequestedIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(MultiOCR3HelperOwnershipTransferRequested) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + + select { + case log := <-it.logs: + it.Event = new(MultiOCR3HelperOwnershipTransferRequested) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +func (it *MultiOCR3HelperOwnershipTransferRequestedIterator) Error() error { + return it.fail +} + +func (it *MultiOCR3HelperOwnershipTransferRequestedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type MultiOCR3HelperOwnershipTransferRequested struct { + From common.Address + To common.Address + Raw types.Log +} + +func (_MultiOCR3Helper *MultiOCR3HelperFilterer) FilterOwnershipTransferRequested(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*MultiOCR3HelperOwnershipTransferRequestedIterator, error) { + + var fromRule []interface{} + for _, fromItem := range from { + fromRule = append(fromRule, fromItem) + } + var toRule []interface{} + for _, toItem := range to { + toRule = append(toRule, toItem) + } + + logs, sub, err := _MultiOCR3Helper.contract.FilterLogs(opts, "OwnershipTransferRequested", fromRule, toRule) + if err != nil { + return nil, err + } + return &MultiOCR3HelperOwnershipTransferRequestedIterator{contract: _MultiOCR3Helper.contract, event: "OwnershipTransferRequested", logs: logs, sub: sub}, nil +} + +func (_MultiOCR3Helper *MultiOCR3HelperFilterer) WatchOwnershipTransferRequested(opts *bind.WatchOpts, sink chan<- *MultiOCR3HelperOwnershipTransferRequested, from []common.Address, to []common.Address) (event.Subscription, error) { + + var fromRule []interface{} + for _, fromItem := range from { + fromRule = append(fromRule, fromItem) + } + var toRule []interface{} + for _, toItem := range to { + toRule = append(toRule, toItem) + } + + logs, sub, err := _MultiOCR3Helper.contract.WatchLogs(opts, "OwnershipTransferRequested", fromRule, toRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + + event := new(MultiOCR3HelperOwnershipTransferRequested) + if err := _MultiOCR3Helper.contract.UnpackLog(event, "OwnershipTransferRequested", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +func (_MultiOCR3Helper *MultiOCR3HelperFilterer) ParseOwnershipTransferRequested(log types.Log) (*MultiOCR3HelperOwnershipTransferRequested, error) { + event := new(MultiOCR3HelperOwnershipTransferRequested) + if err := _MultiOCR3Helper.contract.UnpackLog(event, "OwnershipTransferRequested", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +type MultiOCR3HelperOwnershipTransferredIterator struct { + Event *MultiOCR3HelperOwnershipTransferred + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *MultiOCR3HelperOwnershipTransferredIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(MultiOCR3HelperOwnershipTransferred) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + + select { + case log := <-it.logs: + it.Event = new(MultiOCR3HelperOwnershipTransferred) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +func (it *MultiOCR3HelperOwnershipTransferredIterator) Error() error { + return it.fail +} + +func (it *MultiOCR3HelperOwnershipTransferredIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type MultiOCR3HelperOwnershipTransferred struct { + From common.Address + To common.Address + Raw types.Log +} + +func (_MultiOCR3Helper *MultiOCR3HelperFilterer) FilterOwnershipTransferred(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*MultiOCR3HelperOwnershipTransferredIterator, error) { + + var fromRule []interface{} + for _, fromItem := range from { + fromRule = append(fromRule, fromItem) + } + var toRule []interface{} + for _, toItem := range to { + toRule = append(toRule, toItem) + } + + logs, sub, err := _MultiOCR3Helper.contract.FilterLogs(opts, "OwnershipTransferred", fromRule, toRule) + if err != nil { + return nil, err + } + return &MultiOCR3HelperOwnershipTransferredIterator{contract: _MultiOCR3Helper.contract, event: "OwnershipTransferred", logs: logs, sub: sub}, nil +} + +func (_MultiOCR3Helper *MultiOCR3HelperFilterer) WatchOwnershipTransferred(opts *bind.WatchOpts, sink chan<- *MultiOCR3HelperOwnershipTransferred, from []common.Address, to []common.Address) (event.Subscription, error) { + + var fromRule []interface{} + for _, fromItem := range from { + fromRule = append(fromRule, fromItem) + } + var toRule []interface{} + for _, toItem := range to { + toRule = append(toRule, toItem) + } + + logs, sub, err := _MultiOCR3Helper.contract.WatchLogs(opts, "OwnershipTransferred", fromRule, toRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + + event := new(MultiOCR3HelperOwnershipTransferred) + if err := _MultiOCR3Helper.contract.UnpackLog(event, "OwnershipTransferred", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +func (_MultiOCR3Helper *MultiOCR3HelperFilterer) ParseOwnershipTransferred(log types.Log) (*MultiOCR3HelperOwnershipTransferred, error) { + event := new(MultiOCR3HelperOwnershipTransferred) + if err := _MultiOCR3Helper.contract.UnpackLog(event, "OwnershipTransferred", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +type MultiOCR3HelperTransmittedIterator struct { + Event *MultiOCR3HelperTransmitted + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *MultiOCR3HelperTransmittedIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(MultiOCR3HelperTransmitted) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + + select { + case log := <-it.logs: + it.Event = new(MultiOCR3HelperTransmitted) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +func (it *MultiOCR3HelperTransmittedIterator) Error() error { + return it.fail +} + +func (it *MultiOCR3HelperTransmittedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type MultiOCR3HelperTransmitted struct { + OcrPluginType uint8 + ConfigDigest [32]byte + SequenceNumber uint64 + Raw types.Log +} + +func (_MultiOCR3Helper *MultiOCR3HelperFilterer) FilterTransmitted(opts *bind.FilterOpts, ocrPluginType []uint8) (*MultiOCR3HelperTransmittedIterator, error) { + + var ocrPluginTypeRule []interface{} + for _, ocrPluginTypeItem := range ocrPluginType { + ocrPluginTypeRule = append(ocrPluginTypeRule, ocrPluginTypeItem) + } + + logs, sub, err := _MultiOCR3Helper.contract.FilterLogs(opts, "Transmitted", ocrPluginTypeRule) + if err != nil { + return nil, err + } + return &MultiOCR3HelperTransmittedIterator{contract: _MultiOCR3Helper.contract, event: "Transmitted", logs: logs, sub: sub}, nil +} + +func (_MultiOCR3Helper *MultiOCR3HelperFilterer) WatchTransmitted(opts *bind.WatchOpts, sink chan<- *MultiOCR3HelperTransmitted, ocrPluginType []uint8) (event.Subscription, error) { + + var ocrPluginTypeRule []interface{} + for _, ocrPluginTypeItem := range ocrPluginType { + ocrPluginTypeRule = append(ocrPluginTypeRule, ocrPluginTypeItem) + } + + logs, sub, err := _MultiOCR3Helper.contract.WatchLogs(opts, "Transmitted", ocrPluginTypeRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + + event := new(MultiOCR3HelperTransmitted) + if err := _MultiOCR3Helper.contract.UnpackLog(event, "Transmitted", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +func (_MultiOCR3Helper *MultiOCR3HelperFilterer) ParseTransmitted(log types.Log) (*MultiOCR3HelperTransmitted, error) { + event := new(MultiOCR3HelperTransmitted) + if err := _MultiOCR3Helper.contract.UnpackLog(event, "Transmitted", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +func (_MultiOCR3Helper *MultiOCR3Helper) ParseLog(log types.Log) (generated.AbigenLog, error) { + switch log.Topics[0] { + case _MultiOCR3Helper.abi.Events["AfterConfigSet"].ID: + return _MultiOCR3Helper.ParseAfterConfigSet(log) + case _MultiOCR3Helper.abi.Events["ConfigSet"].ID: + return _MultiOCR3Helper.ParseConfigSet(log) + case _MultiOCR3Helper.abi.Events["OwnershipTransferRequested"].ID: + return _MultiOCR3Helper.ParseOwnershipTransferRequested(log) + case _MultiOCR3Helper.abi.Events["OwnershipTransferred"].ID: + return _MultiOCR3Helper.ParseOwnershipTransferred(log) + case _MultiOCR3Helper.abi.Events["Transmitted"].ID: + return _MultiOCR3Helper.ParseTransmitted(log) + + default: + return nil, fmt.Errorf("abigen wrapper received unknown log topic: %v", log.Topics[0]) + } +} + +func (MultiOCR3HelperAfterConfigSet) Topic() common.Hash { + return common.HexToHash("0x897ac1b2c12867721b284f3eb147bd4ab046d4eef1cf31c1d8988bfcfb962b53") +} + +func (MultiOCR3HelperConfigSet) Topic() common.Hash { + return common.HexToHash("0xab8b1b57514019638d7b5ce9c638fe71366fe8e2be1c40a7a80f1733d0e9f547") +} + +func (MultiOCR3HelperOwnershipTransferRequested) Topic() common.Hash { + return common.HexToHash("0xed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae1278") +} + +func (MultiOCR3HelperOwnershipTransferred) Topic() common.Hash { + return common.HexToHash("0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0") +} + +func (MultiOCR3HelperTransmitted) Topic() common.Hash { + return common.HexToHash("0x198d6990ef96613a9026203077e422916918b03ff47f0be6bee7b02d8e139ef0") +} + +func (_MultiOCR3Helper *MultiOCR3Helper) Address() common.Address { + return _MultiOCR3Helper.address +} + +type MultiOCR3HelperInterface interface { + GetOracle(opts *bind.CallOpts, ocrPluginType uint8, oracleAddress common.Address) (MultiOCR3BaseOracle, error) + + LatestConfigDetails(opts *bind.CallOpts, ocrPluginType uint8) (MultiOCR3BaseOCRConfig, error) + + Owner(opts *bind.CallOpts) (common.Address, error) + + TypeAndVersion(opts *bind.CallOpts) (string, error) + + AcceptOwnership(opts *bind.TransactOpts) (*types.Transaction, error) + + SetOCR3Configs(opts *bind.TransactOpts, ocrConfigArgs []MultiOCR3BaseOCRConfigArgs) (*types.Transaction, error) + + SetTransmitOcrPluginType(opts *bind.TransactOpts, ocrPluginType uint8) (*types.Transaction, error) + + TransferOwnership(opts *bind.TransactOpts, to common.Address) (*types.Transaction, error) + + TransmitWithSignatures(opts *bind.TransactOpts, reportContext [3][32]byte, report []byte, rs [][32]byte, ss [][32]byte, rawVs [32]byte) (*types.Transaction, error) + + TransmitWithoutSignatures(opts *bind.TransactOpts, reportContext [3][32]byte, report []byte) (*types.Transaction, error) + + FilterAfterConfigSet(opts *bind.FilterOpts) (*MultiOCR3HelperAfterConfigSetIterator, error) + + WatchAfterConfigSet(opts *bind.WatchOpts, sink chan<- *MultiOCR3HelperAfterConfigSet) (event.Subscription, error) + + ParseAfterConfigSet(log types.Log) (*MultiOCR3HelperAfterConfigSet, error) + + FilterConfigSet(opts *bind.FilterOpts) (*MultiOCR3HelperConfigSetIterator, error) + + WatchConfigSet(opts *bind.WatchOpts, sink chan<- *MultiOCR3HelperConfigSet) (event.Subscription, error) + + ParseConfigSet(log types.Log) (*MultiOCR3HelperConfigSet, error) + + FilterOwnershipTransferRequested(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*MultiOCR3HelperOwnershipTransferRequestedIterator, error) + + WatchOwnershipTransferRequested(opts *bind.WatchOpts, sink chan<- *MultiOCR3HelperOwnershipTransferRequested, from []common.Address, to []common.Address) (event.Subscription, error) + + ParseOwnershipTransferRequested(log types.Log) (*MultiOCR3HelperOwnershipTransferRequested, error) + + FilterOwnershipTransferred(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*MultiOCR3HelperOwnershipTransferredIterator, error) + + WatchOwnershipTransferred(opts *bind.WatchOpts, sink chan<- *MultiOCR3HelperOwnershipTransferred, from []common.Address, to []common.Address) (event.Subscription, error) + + ParseOwnershipTransferred(log types.Log) (*MultiOCR3HelperOwnershipTransferred, error) + + FilterTransmitted(opts *bind.FilterOpts, ocrPluginType []uint8) (*MultiOCR3HelperTransmittedIterator, error) + + WatchTransmitted(opts *bind.WatchOpts, sink chan<- *MultiOCR3HelperTransmitted, ocrPluginType []uint8) (event.Subscription, error) + + ParseTransmitted(log types.Log) (*MultiOCR3HelperTransmitted, error) + + ParseLog(log types.Log) (generated.AbigenLog, error) + + Address() common.Address +} diff --git a/core/gethwrappers/ccip/generation/generated-wrapper-dependency-versions-do-not-edit.txt b/core/gethwrappers/ccip/generation/generated-wrapper-dependency-versions-do-not-edit.txt index 496f7d1d95..e2485ce9e0 100644 --- a/core/gethwrappers/ccip/generation/generated-wrapper-dependency-versions-do-not-edit.txt +++ b/core/gethwrappers/ccip/generation/generated-wrapper-dependency-versions-do-not-edit.txt @@ -22,6 +22,7 @@ mock_usdc_token_messenger: ../../../contracts/solc/v0.8.24/MockE2EUSDCTokenMesse mock_usdc_token_transmitter: ../../../contracts/solc/v0.8.24/MockE2EUSDCTransmitter/MockE2EUSDCTransmitter.abi ../../../contracts/solc/v0.8.24/MockE2EUSDCTransmitter/MockE2EUSDCTransmitter.bin 33bdad70822e889de7c720ed20085cf9cd3f8eba8b68f26bd6535197749595fe mock_v3_aggregator_contract: ../../../contracts/solc/v0.8.24/MockV3Aggregator/MockV3Aggregator.abi ../../../contracts/solc/v0.8.24/MockV3Aggregator/MockV3Aggregator.bin 518e19efa2ff52b0fefd8e597b05765317ee7638189bfe34ca43de2f6599faf4 multi_aggregate_rate_limiter: ../../../contracts/solc/v0.8.24/MultiAggregateRateLimiter/MultiAggregateRateLimiter.abi ../../../contracts/solc/v0.8.24/MultiAggregateRateLimiter/MultiAggregateRateLimiter.bin abb0ecb1ed8621f26e43b39f5fa25f3d0b6d6c184fa37c404c4389605ecb74e7 +multi_ocr3_helper: ../../../contracts/solc/v0.8.24/MultiOCR3Helper/MultiOCR3Helper.abi ../../../contracts/solc/v0.8.24/MultiOCR3Helper/MultiOCR3Helper.bin aa299e0c2659d53aad4eace4d66be0e734b1366008593669cf30361ff529da6a nonce_manager: ../../../contracts/solc/v0.8.24/NonceManager/NonceManager.abi ../../../contracts/solc/v0.8.24/NonceManager/NonceManager.bin 78b58f4f192db7496e2b6de805d6a2c918b98d4fa62f3c7ed145ef3b5657a40d ocr3_config_encoder: ../../../contracts/solc/v0.8.24/IOCR3ConfigEncoder/IOCR3ConfigEncoder.abi ../../../contracts/solc/v0.8.24/IOCR3ConfigEncoder/IOCR3ConfigEncoder.bin e21180898e1ad54a045ee20add85a2793c681425ea06f66d1a9e5cab128b6487 ping_pong_demo: ../../../contracts/solc/v0.8.24/PingPongDemo/PingPongDemo.abi ../../../contracts/solc/v0.8.24/PingPongDemo/PingPongDemo.bin 1588313bb5e781d181a825247d30828f59007700f36b4b9b00391592b06ff4b4 diff --git a/core/gethwrappers/ccip/go_generate.go b/core/gethwrappers/ccip/go_generate.go index 50b212dfe4..b5d3b09e4a 100644 --- a/core/gethwrappers/ccip/go_generate.go +++ b/core/gethwrappers/ccip/go_generate.go @@ -36,6 +36,7 @@ package ccip //go:generate go run ../generation/generate/wrap.go ../../../contracts/solc/v0.8.24/PingPongDemo/PingPongDemo.abi ../../../contracts/solc/v0.8.24/PingPongDemo/PingPongDemo.bin PingPongDemo ping_pong_demo //go:generate go run ../generation/generate/wrap.go ../../../contracts/solc/v0.8.24/SelfFundedPingPong/SelfFundedPingPong.abi ../../../contracts/solc/v0.8.24/SelfFundedPingPong/SelfFundedPingPong.bin SelfFundedPingPong self_funded_ping_pong //go:generate go run ../generation/generate/wrap.go ../../../contracts/solc/v0.8.24/MessageHasher/MessageHasher.abi ../../../contracts/solc/v0.8.24/MessageHasher/MessageHasher.bin MessageHasher message_hasher +//go:generate go run ../generation/generate/wrap.go ../../../contracts/solc/v0.8.24/MultiOCR3Helper/MultiOCR3Helper.abi ../../../contracts/solc/v0.8.24/MultiOCR3Helper/MultiOCR3Helper.bin MultiOCR3Helper multi_ocr3_helper //go:generate go run ../generation/generate/wrap.go ../../../contracts/solc/v0.8.24/ReportCodec/ReportCodec.abi ../../../contracts/solc/v0.8.24/ReportCodec/ReportCodec.bin ReportCodec report_codec //go:generate go run ../generation/generate/wrap.go ../../../contracts/solc/v0.8.24/EtherSenderReceiver/EtherSenderReceiver.abi ../../../contracts/solc/v0.8.24/EtherSenderReceiver/EtherSenderReceiver.bin EtherSenderReceiver ether_sender_receiver //go:generate go run ../generation/generate/wrap.go ../../../contracts/solc/v0.8.24/WETH9/WETH9.abi ../../../contracts/solc/v0.8.24/WETH9/WETH9.bin WETH9 weth9 diff --git a/core/scripts/go.mod b/core/scripts/go.mod index ece51eb4ba..25c8589f61 100644 --- a/core/scripts/go.mod +++ b/core/scripts/go.mod @@ -278,6 +278,7 @@ require ( github.com/shirou/gopsutil v3.21.11+incompatible // indirect github.com/shirou/gopsutil/v3 v3.24.3 // indirect github.com/sirupsen/logrus v1.9.3 // indirect + github.com/smartcontractkit/chainlink-ccip v0.0.0-20240717202359-0bcfc8ee468d // indirect github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240621143432-85370a54b141 // indirect github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240702144807-761f63e7b527 // indirect github.com/smartcontractkit/chainlink-feeds v0.0.0-20240522213638-159fb2d99917 // indirect diff --git a/core/scripts/go.sum b/core/scripts/go.sum index 492054149f..6a4d410b04 100644 --- a/core/scripts/go.sum +++ b/core/scripts/go.sum @@ -1097,6 +1097,8 @@ github.com/smartcontractkit/chain-selectors v1.0.18 h1:ackCMDOlWuwULAyBNj9fQeQme github.com/smartcontractkit/chain-selectors v1.0.18/go.mod h1:d4Hi+E1zqjy9HqMkjBE5q1vcG9VGgxf5VxiRHfzi2kE= github.com/smartcontractkit/chainlink-automation v1.0.4 h1:iyW181JjKHLNMnDleI8umfIfVVlwC7+n5izbLSFgjw8= github.com/smartcontractkit/chainlink-automation v1.0.4/go.mod h1:u4NbPZKJ5XiayfKHD/v3z3iflQWqvtdhj13jVZXj/cM= +github.com/smartcontractkit/chainlink-ccip v0.0.0-20240717202359-0bcfc8ee468d h1:fOdEh9zHNT+QQBZWSRv0uxqyTMnw3rCPR4lr2DtYvhw= +github.com/smartcontractkit/chainlink-ccip v0.0.0-20240717202359-0bcfc8ee468d/go.mod h1:gyODeD1uMobe5VWRwVRiEXzL9wUzFTI80VvyCNLqWcw= github.com/smartcontractkit/chainlink-common v0.1.7-0.20240708180634-24440372521a h1:0HUP3qmHejg7FyFdY+R+8iFg0kNtrvnAxaQ//+fuZfc= github.com/smartcontractkit/chainlink-common v0.1.7-0.20240708180634-24440372521a/go.mod h1:fh9eBbrReCmv31bfz52ENCAMa7nTKQbdhb2B3+S2VGo= github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240621143432-85370a54b141 h1:TMOoYaeSDkkI3jkCH7lKHOZaLkeDuxFTNC+XblD6M0M= diff --git a/core/services/ccipcapability/common/common.go b/core/services/ccipcapability/common/common.go new file mode 100644 index 0000000000..6409345ed9 --- /dev/null +++ b/core/services/ccipcapability/common/common.go @@ -0,0 +1,23 @@ +package common + +import ( + "fmt" + + "github.com/ethereum/go-ethereum/crypto" + + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils" +) + +// HashedCapabilityID returns the hashed capability id in a manner equivalent to the capability registry. +func HashedCapabilityID(capabilityLabelledName, capabilityVersion string) (r [32]byte, err error) { + // TODO: investigate how to avoid parsing the ABI everytime. + tabi := `[{"type": "string"}, {"type": "string"}]` + abiEncoded, err := utils.ABIEncode(tabi, capabilityLabelledName, capabilityVersion) + if err != nil { + return r, fmt.Errorf("failed to ABI encode capability version and labelled name: %w", err) + } + + h := crypto.Keccak256(abiEncoded) + copy(r[:], h) + return r, nil +} diff --git a/core/services/ccipcapability/common/common_test.go b/core/services/ccipcapability/common/common_test.go new file mode 100644 index 0000000000..fc22f6ac58 --- /dev/null +++ b/core/services/ccipcapability/common/common_test.go @@ -0,0 +1,50 @@ +package common_test + +import ( + "testing" + + "github.com/ethereum/go-ethereum/accounts/abi/bind/backends" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" + kcr "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/capabilities_registry" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" + capcommon "github.com/smartcontractkit/chainlink/v2/core/services/ccipcapability/common" +) + +func Test_HashedCapabilityId(t *testing.T) { + transactor := testutils.MustNewSimTransactor(t) + sb := backends.NewSimulatedBackend(core.GenesisAlloc{ + transactor.From: {Balance: assets.Ether(1000).ToInt()}, + }, 30e6) + + crAddress, _, _, err := kcr.DeployCapabilitiesRegistry(transactor, sb) + require.NoError(t, err) + sb.Commit() + + cr, err := kcr.NewCapabilitiesRegistry(crAddress, sb) + require.NoError(t, err) + + // add a capability, ignore cap config for simplicity. + _, err = cr.AddCapabilities(transactor, []kcr.CapabilitiesRegistryCapability{ + { + LabelledName: "ccip", + Version: "v1.0.0", + CapabilityType: 0, + ResponseType: 0, + ConfigurationContract: common.Address{}, + }, + }) + require.NoError(t, err) + sb.Commit() + + hidExpected, err := cr.GetHashedCapabilityId(nil, "ccip", "v1.0.0") + require.NoError(t, err) + + hid, err := capcommon.HashedCapabilityID("ccip", "v1.0.0") + require.NoError(t, err) + + require.Equal(t, hidExpected, hid) +} diff --git a/core/services/ccipcapability/configs/evm/chain_writer.go b/core/services/ccipcapability/configs/evm/chain_writer.go new file mode 100644 index 0000000000..6b7696da1e --- /dev/null +++ b/core/services/ccipcapability/configs/evm/chain_writer.go @@ -0,0 +1,67 @@ +package evm + +import ( + "encoding/json" + "fmt" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + + "github.com/smartcontractkit/chainlink-ccip/pkg/consts" + "github.com/smartcontractkit/chainlink/v2/common/txmgr" + + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" + evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/evm_2_evm_multi_offramp" + evmrelaytypes "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/types" +) + +var ( + offrampABI = evmtypes.MustGetABI(evm_2_evm_multi_offramp.EVM2EVMMultiOffRampABI) +) + +func MustChainWriterConfig(fromAddress common.Address, maxGasPrice *assets.Wei) []byte { + rawConfig := ChainWriterConfigRaw(fromAddress, maxGasPrice) + encoded, err := json.Marshal(rawConfig) + if err != nil { + panic(fmt.Errorf("failed to marshal ChainWriterConfig: %w", err)) + } + + return encoded +} + +// ChainWriterConfigRaw returns a ChainWriterConfig that can be used to transmit commit and execute reports. +func ChainWriterConfigRaw(fromAddress common.Address, maxGasPrice *assets.Wei) evmrelaytypes.ChainWriterConfig { + return evmrelaytypes.ChainWriterConfig{ + Contracts: map[string]*evmrelaytypes.ContractConfig{ + consts.ContractNameOffRamp: { + ContractABI: evm_2_evm_multi_offramp.EVM2EVMMultiOffRampABI, + Configs: map[string]*evmrelaytypes.ChainWriterDefinition{ + consts.MethodCommit: { + ChainSpecificName: mustGetMethodName("commit", offrampABI), + FromAddress: fromAddress, + // TODO: inject this into the method, should be fetched from the OCR config. + GasLimit: 500_000, + }, + consts.MethodExecute: { + ChainSpecificName: mustGetMethodName("execute", offrampABI), + FromAddress: fromAddress, + // TODO: inject this into the method, should be fetched from the OCR config. + GasLimit: 6_500_000, + }, + }, + }, + }, + SendStrategy: txmgr.NewSendEveryStrategy(), + MaxGasPrice: maxGasPrice, + } +} + +// mustGetMethodName panics if the method name is not found in the provided ABI. +func mustGetMethodName(name string, tabi abi.ABI) (methodName string) { + m, ok := tabi.Methods[name] + if !ok { + panic(fmt.Sprintf("missing method %s in offrampABI", name)) + } + return m.Name +} diff --git a/core/services/ccipcapability/configs/evm/contract_reader.go b/core/services/ccipcapability/configs/evm/contract_reader.go new file mode 100644 index 0000000000..0cf6d21170 --- /dev/null +++ b/core/services/ccipcapability/configs/evm/contract_reader.go @@ -0,0 +1,190 @@ +package evm + +import ( + "encoding/json" + "fmt" + + "github.com/ethereum/go-ethereum/accounts/abi" + + "github.com/smartcontractkit/chainlink-ccip/pkg/consts" + + evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/ccip_config" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/evm_2_evm_multi_offramp" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/evm_2_evm_multi_onramp" + kcr "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/capabilities_registry" + evmrelaytypes "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/types" +) + +var ( + onrampABI = evmtypes.MustGetABI(evm_2_evm_multi_onramp.EVM2EVMMultiOnRampABI) + capabilitiesRegsitryABI = evmtypes.MustGetABI(kcr.CapabilitiesRegistryABI) + ccipConfigABI = evmtypes.MustGetABI(ccip_config.CCIPConfigABI) +) + +// MustSourceReaderConfig returns a ChainReaderConfig that can be used to read from the onramp. +// The configuration is marshaled into JSON so that it can be passed to the relayer NewContractReader() method. +func MustSourceReaderConfig() []byte { + rawConfig := SourceReaderConfig() + encoded, err := json.Marshal(rawConfig) + if err != nil { + panic(fmt.Errorf("failed to marshal ChainReaderConfig into JSON: %w", err)) + } + + return encoded +} + +// MustDestReaderConfig returns a ChainReaderConfig that can be used to read from the offramp. +// The configuration is marshaled into JSON so that it can be passed to the relayer NewContractReader() method. +func MustDestReaderConfig() []byte { + rawConfig := DestReaderConfig() + encoded, err := json.Marshal(rawConfig) + if err != nil { + panic(fmt.Errorf("failed to marshal ChainReaderConfig into JSON: %w", err)) + } + + return encoded +} + +// DestReaderConfig returns a ChainReaderConfig that can be used to read from the offramp. +func DestReaderConfig() evmrelaytypes.ChainReaderConfig { + return evmrelaytypes.ChainReaderConfig{ + Contracts: map[string]evmrelaytypes.ChainContractReader{ + consts.ContractNameOffRamp: { + ContractABI: evm_2_evm_multi_offramp.EVM2EVMMultiOffRampABI, + ContractPollingFilter: evmrelaytypes.ContractPollingFilter{ + GenericEventNames: []string{ + mustGetEventName(consts.EventNameExecutionStateChanged, offrampABI), + mustGetEventName(consts.EventNameCommitReportAccepted, offrampABI), + }, + }, + Configs: map[string]*evmrelaytypes.ChainReaderDefinition{ + consts.MethodNameGetExecutionState: { + ChainSpecificName: mustGetMethodName("getExecutionState", offrampABI), + ReadType: evmrelaytypes.Method, + }, + consts.MethodNameGetMerkleRoot: { + ChainSpecificName: mustGetMethodName("getMerkleRoot", offrampABI), + ReadType: evmrelaytypes.Method, + }, + consts.MethodNameIsBlessed: { + ChainSpecificName: mustGetMethodName("isBlessed", offrampABI), + ReadType: evmrelaytypes.Method, + }, + consts.MethodNameGetLatestPriceSequenceNumber: { + ChainSpecificName: mustGetMethodName("getLatestPriceSequenceNumber", offrampABI), + ReadType: evmrelaytypes.Method, + }, + consts.MethodNameOfframpGetStaticConfig: { + ChainSpecificName: mustGetMethodName("getStaticConfig", offrampABI), + ReadType: evmrelaytypes.Method, + }, + consts.MethodNameOfframpGetDynamicConfig: { + ChainSpecificName: mustGetMethodName("getDynamicConfig", offrampABI), + ReadType: evmrelaytypes.Method, + }, + consts.MethodNameGetSourceChainConfig: { + ChainSpecificName: mustGetMethodName("getSourceChainConfig", offrampABI), + ReadType: evmrelaytypes.Method, + }, + consts.EventNameCommitReportAccepted: { + ChainSpecificName: mustGetEventName(consts.EventNameCommitReportAccepted, offrampABI), + ReadType: evmrelaytypes.Event, + }, + consts.EventNameExecutionStateChanged: { + ChainSpecificName: mustGetEventName(consts.EventNameExecutionStateChanged, offrampABI), + ReadType: evmrelaytypes.Event, + }, + }, + }, + }, + } +} + +// SourceReaderConfig returns a ChainReaderConfig that can be used to read from the onramp. +func SourceReaderConfig() evmrelaytypes.ChainReaderConfig { + return evmrelaytypes.ChainReaderConfig{ + Contracts: map[string]evmrelaytypes.ChainContractReader{ + consts.ContractNameOnRamp: { + ContractABI: evm_2_evm_multi_onramp.EVM2EVMMultiOnRampABI, + ContractPollingFilter: evmrelaytypes.ContractPollingFilter{ + GenericEventNames: []string{ + mustGetEventName(consts.EventNameCCIPSendRequested, onrampABI), + }, + }, + Configs: map[string]*evmrelaytypes.ChainReaderDefinition{ + // all "{external|public} view" functions in the onramp except for getFee and getPoolBySourceToken are here. + // getFee is not expected to get called offchain and is only called by end-user contracts. + consts.MethodNameGetExpectedNextSequenceNumber: { + ChainSpecificName: mustGetMethodName("getExpectedNextSequenceNumber", onrampABI), + ReadType: evmrelaytypes.Method, + }, + consts.MethodNameOnrampGetStaticConfig: { + ChainSpecificName: mustGetMethodName("getStaticConfig", onrampABI), + ReadType: evmrelaytypes.Method, + }, + consts.MethodNameOnrampGetDynamicConfig: { + ChainSpecificName: mustGetMethodName("getDynamicConfig", onrampABI), + ReadType: evmrelaytypes.Method, + }, + consts.MethodNameGetDestChainConfig: { + ChainSpecificName: mustGetMethodName("getDestChainConfig", onrampABI), + ReadType: evmrelaytypes.Method, + }, + consts.MethodNameGetPremiumMultiplierWeiPerEth: { + ChainSpecificName: mustGetMethodName("getPremiumMultiplierWeiPerEth", onrampABI), + ReadType: evmrelaytypes.Method, + }, + consts.MethodNameGetTokenTransferFeeConfig: { + ChainSpecificName: mustGetMethodName("getTokenTransferFeeConfig", onrampABI), + ReadType: evmrelaytypes.Method, + }, + consts.EventNameCCIPSendRequested: { + ChainSpecificName: mustGetEventName(consts.EventNameCCIPSendRequested, onrampABI), + ReadType: evmrelaytypes.Event, + EventDefinitions: &evmrelaytypes.EventDefinitions{ + GenericDataWordNames: map[string]uint8{ + consts.EventAttributeSequenceNumber: 5, + }, + }, + }, + }, + }, + }, + } +} + +// HomeChainReaderConfigRaw returns a ChainReaderConfig that can be used to read from the home chain. +func HomeChainReaderConfigRaw() evmrelaytypes.ChainReaderConfig { + return evmrelaytypes.ChainReaderConfig{ + Contracts: map[string]evmrelaytypes.ChainContractReader{ + consts.ContractNameCapabilitiesRegistry: { + ContractABI: kcr.CapabilitiesRegistryABI, + Configs: map[string]*evmrelaytypes.ChainReaderDefinition{ + consts.MethodNameGetCapability: { + ChainSpecificName: mustGetMethodName("getCapability", capabilitiesRegsitryABI), + }, + }, + }, + consts.ContractNameCCIPConfig: { + ContractABI: ccip_config.CCIPConfigABI, + Configs: map[string]*evmrelaytypes.ChainReaderDefinition{ + consts.MethodNameGetAllChainConfigs: { + ChainSpecificName: mustGetMethodName("getAllChainConfigs", ccipConfigABI), + }, + consts.MethodNameGetOCRConfig: { + ChainSpecificName: mustGetMethodName("getOCRConfig", ccipConfigABI), + }, + }, + }, + }, + } +} + +func mustGetEventName(event string, tabi abi.ABI) string { + e, ok := tabi.Events[event] + if !ok { + panic(fmt.Sprintf("missing event %s in onrampABI", event)) + } + return e.Name +} diff --git a/core/services/ccipcapability/delegate.go b/core/services/ccipcapability/delegate.go new file mode 100644 index 0000000000..9b71d96ce0 --- /dev/null +++ b/core/services/ccipcapability/delegate.go @@ -0,0 +1,292 @@ +package ccipcapability + +import ( + "context" + "fmt" + "time" + + ragep2ptypes "github.com/smartcontractkit/libocr/ragep2p/types" + + "github.com/smartcontractkit/chainlink-ccip/pkg/consts" + ccipreaderpkg "github.com/smartcontractkit/chainlink-ccip/pkg/reader" + "github.com/smartcontractkit/chainlink-common/pkg/sqlutil" + "github.com/smartcontractkit/chainlink-common/pkg/types" + + "github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm" + "github.com/smartcontractkit/chainlink/v2/core/config" + kcr "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/capabilities_registry" + "github.com/smartcontractkit/chainlink/v2/core/logger" + "github.com/smartcontractkit/chainlink/v2/core/services/ccipcapability/common" + configsevm "github.com/smartcontractkit/chainlink/v2/core/services/ccipcapability/configs/evm" + "github.com/smartcontractkit/chainlink/v2/core/services/ccipcapability/launcher" + "github.com/smartcontractkit/chainlink/v2/core/services/ccipcapability/oraclecreator" + "github.com/smartcontractkit/chainlink/v2/core/services/job" + "github.com/smartcontractkit/chainlink/v2/core/services/keystore" + "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/ocr2key" + "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/p2pkey" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2" + "github.com/smartcontractkit/chainlink/v2/core/services/ocrcommon" + "github.com/smartcontractkit/chainlink/v2/core/services/pipeline" + "github.com/smartcontractkit/chainlink/v2/core/services/registrysyncer" + "github.com/smartcontractkit/chainlink/v2/core/services/relay" + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm" + "github.com/smartcontractkit/chainlink/v2/core/services/telemetry" + "github.com/smartcontractkit/chainlink/v2/plugins" +) + +type Delegate struct { + lggr logger.Logger + registrarConfig plugins.RegistrarConfig + pipelineRunner pipeline.Runner + chains legacyevm.LegacyChainContainer + keystore keystore.Master + ds sqlutil.DataSource + peerWrapper *ocrcommon.SingletonPeerWrapper + monitoringEndpointGen telemetry.MonitoringEndpointGenerator + registrySyncer registrysyncer.Syncer + capabilityConfig config.Capabilities + + isNewlyCreatedJob bool +} + +func NewDelegate( + lggr logger.Logger, + registrarConfig plugins.RegistrarConfig, + pipelineRunner pipeline.Runner, + chains legacyevm.LegacyChainContainer, + registrySyncer registrysyncer.Syncer, + keystore keystore.Master, + ds sqlutil.DataSource, + peerWrapper *ocrcommon.SingletonPeerWrapper, + monitoringEndpointGen telemetry.MonitoringEndpointGenerator, + capabilityConfig config.Capabilities, +) *Delegate { + return &Delegate{ + lggr: lggr, + registrarConfig: registrarConfig, + pipelineRunner: pipelineRunner, + chains: chains, + registrySyncer: registrySyncer, + ds: ds, + keystore: keystore, + peerWrapper: peerWrapper, + monitoringEndpointGen: monitoringEndpointGen, + capabilityConfig: capabilityConfig, + } +} + +func (d *Delegate) JobType() job.Type { + return job.CCIP +} + +func (d *Delegate) BeforeJobCreated(job.Job) { + // This is only called first time the job is created + d.isNewlyCreatedJob = true +} + +func (d *Delegate) ServicesForSpec(ctx context.Context, spec job.Job) (services []job.ServiceCtx, err error) { + // In general there should only be one P2P key but the node may have multiple. + // The job spec should specify the correct P2P key to use. + peerID, err := p2pkey.MakePeerID(spec.CCIPSpec.P2PKeyID) + if err != nil { + return nil, fmt.Errorf("failed to make peer ID from provided spec p2p id (%s): %w", spec.CCIPSpec.P2PKeyID, err) + } + + p2pID, err := d.keystore.P2P().Get(peerID) + if err != nil { + return nil, fmt.Errorf("failed to get all p2p keys: %w", err) + } + + ocrKeys, err := d.getOCRKeys(spec.CCIPSpec.OCRKeyBundleIDs) + if err != nil { + return nil, err + } + + transmitterKeys, err := d.getTransmitterKeys(ctx, d.chains) + if err != nil { + return nil, err + } + + bootstrapperLocators, err := ocrcommon.ParseBootstrapPeers(spec.CCIPSpec.P2PV2Bootstrappers) + if err != nil { + return nil, fmt.Errorf("failed to parse bootstrapper locators: %w", err) + } + + // NOTE: we can use the same DB for all plugin instances, + // since all queries are scoped by config digest. + ocrDB := ocr2.NewDB(d.ds, spec.ID, 0, d.lggr) + + homeChainContractReader, err := d.getHomeChainContractReader( + ctx, + d.chains, + spec.CCIPSpec.CapabilityLabelledName, + spec.CCIPSpec.CapabilityVersion) + if err != nil { + return nil, fmt.Errorf("failed to get home chain contract reader: %w", err) + } + + hcr := ccipreaderpkg.NewHomeChainReader( + homeChainContractReader, + d.lggr.Named("HomeChainReader"), + 100*time.Millisecond, + ) + + oracleCreator := oraclecreator.New( + ocrKeys, + transmitterKeys, + d.chains, + d.peerWrapper, + spec.ExternalJobID, + spec.ID, + d.isNewlyCreatedJob, + spec.CCIPSpec.PluginConfig, + ocrDB, + d.lggr, + d.monitoringEndpointGen, + bootstrapperLocators, + hcr, + ) + + capLauncher := launcher.New( + spec.CCIPSpec.CapabilityVersion, + spec.CCIPSpec.CapabilityLabelledName, + ragep2ptypes.PeerID(p2pID.PeerID()), + d.lggr, + hcr, + oracleCreator, + 12*time.Second, + ) + + // register the capability launcher with the registry syncer + d.registrySyncer.AddLauncher(capLauncher) + + return []job.ServiceCtx{ + hcr, + capLauncher, + }, nil +} + +func (d *Delegate) AfterJobCreated(spec job.Job) {} + +func (d *Delegate) BeforeJobDeleted(spec job.Job) {} + +func (d *Delegate) OnDeleteJob(ctx context.Context, spec job.Job) error { + // TODO: shut down needed services? + return nil +} + +func (d *Delegate) getOCRKeys(ocrKeyBundleIDs job.JSONConfig) (map[string]ocr2key.KeyBundle, error) { + ocrKeys := make(map[string]ocr2key.KeyBundle) + for networkType, bundleIDRaw := range ocrKeyBundleIDs { + if networkType != relay.NetworkEVM { + return nil, fmt.Errorf("unsupported chain type: %s", networkType) + } + + bundleID, ok := bundleIDRaw.(string) + if !ok { + return nil, fmt.Errorf("OCRKeyBundleIDs must be a map of chain types to OCR key bundle IDs, got: %T", bundleIDRaw) + } + + bundle, err2 := d.keystore.OCR2().Get(bundleID) + if err2 != nil { + return nil, fmt.Errorf("OCR key bundle with ID %s not found: %w", bundleID, err2) + } + + ocrKeys[networkType] = bundle + } + return ocrKeys, nil +} + +func (d *Delegate) getTransmitterKeys(ctx context.Context, chains legacyevm.LegacyChainContainer) (map[types.RelayID][]string, error) { + transmitterKeys := make(map[types.RelayID][]string) + for _, chain := range chains.Slice() { + relayID := types.NewRelayID(relay.NetworkEVM, chain.ID().String()) + ethKeys, err2 := d.keystore.Eth().EnabledAddressesForChain(ctx, chain.ID()) + if err2 != nil { + return nil, fmt.Errorf("error getting enabled addresses for chain: %s %w", chain.ID().String(), err2) + } + + transmitterKeys[relayID] = func() (r []string) { + for _, key := range ethKeys { + r = append(r, key.Hex()) + } + return + }() + } + return transmitterKeys, nil +} + +func (d *Delegate) getHomeChainContractReader( + ctx context.Context, + chains legacyevm.LegacyChainContainer, + capabilityLabelledName, + capabilityVersion string, +) (types.ContractReader, error) { + // home chain is where the capability registry is deployed, + // which should be set correctly in toml config. + homeChainRelayID := d.capabilityConfig.ExternalRegistry().RelayID() + homeChain, err := chains.Get(homeChainRelayID.ChainID) + if err != nil { + return nil, fmt.Errorf("home chain relayer not found, chain id: %s, err: %w", homeChainRelayID.String(), err) + } + + reader, err := evm.NewChainReaderService( + context.Background(), + d.lggr, + homeChain.LogPoller(), + homeChain.Client(), + configsevm.HomeChainReaderConfigRaw(), + ) + if err != nil { + return nil, fmt.Errorf("failed to create home chain contract reader: %w", err) + } + + reader, err = bindReader(ctx, reader, d.capabilityConfig.ExternalRegistry().Address(), capabilityLabelledName, capabilityVersion) + if err != nil { + return nil, fmt.Errorf("failed to bind home chain contract reader: %w", err) + } + + return reader, nil +} + +func bindReader(ctx context.Context, + reader types.ContractReader, + capRegAddress, + capabilityLabelledName, + capabilityVersion string) (types.ContractReader, error) { + err := reader.Bind(ctx, []types.BoundContract{ + { + Address: capRegAddress, + Name: consts.ContractNameCapabilitiesRegistry, + }, + }) + if err != nil { + return nil, fmt.Errorf("failed to bind home chain contract reader: %w", err) + } + + hid, err := common.HashedCapabilityID(capabilityLabelledName, capabilityVersion) + if err != nil { + return nil, fmt.Errorf("failed to hash capability id: %w", err) + } + + var ccipCapabilityInfo kcr.CapabilitiesRegistryCapabilityInfo + err = reader.GetLatestValue(ctx, consts.ContractNameCapabilitiesRegistry, consts.MethodNameGetCapability, map[string]any{ + "hashedId": hid, + }, &ccipCapabilityInfo) + if err != nil { + return nil, fmt.Errorf("failed to get CCIP capability info from chain reader: %w", err) + } + + // bind the ccip capability configuration contract + err = reader.Bind(ctx, []types.BoundContract{ + { + Address: ccipCapabilityInfo.ConfigurationContract.String(), + Name: consts.ContractNameCCIPConfig, + }, + }) + if err != nil { + return nil, fmt.Errorf("failed to bind CCIP capability configuration contract: %w", err) + } + + return reader, nil +} diff --git a/core/services/ccipcapability/delegate_test.go b/core/services/ccipcapability/delegate_test.go new file mode 100644 index 0000000000..b725c930ad --- /dev/null +++ b/core/services/ccipcapability/delegate_test.go @@ -0,0 +1 @@ +package ccipcapability diff --git a/core/services/ccipcapability/launcher/diff.go b/core/services/ccipcapability/launcher/diff.go index d56bf9dd12..bbc5684edf 100644 --- a/core/services/ccipcapability/launcher/diff.go +++ b/core/services/ccipcapability/launcher/diff.go @@ -3,12 +3,10 @@ package launcher import ( "fmt" - "github.com/ethereum/go-ethereum/crypto" - ragep2ptypes "github.com/smartcontractkit/libocr/ragep2p/types" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils" kcr "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/capabilities_registry" + "github.com/smartcontractkit/chainlink/v2/core/services/ccipcapability/common" "github.com/smartcontractkit/chainlink/v2/core/services/registrysyncer" ) @@ -98,7 +96,7 @@ func filterCCIPDONs( ccipDONs := make(map[registrysyncer.DonID]kcr.CapabilitiesRegistryDONInfo) for _, don := range state.IDsToDONs { for _, donCapabilities := range don.CapabilityConfigurations { - hid, err := hashedCapabilityId(ccipCapability.LabelledName, ccipCapability.Version) + hid, err := common.HashedCapabilityID(ccipCapability.LabelledName, ccipCapability.Version) if err != nil { return nil, fmt.Errorf("failed to hash capability id: %w", err) } @@ -119,7 +117,7 @@ func checkCapabilityPresence( state registrysyncer.State, ) (kcr.CapabilitiesRegistryCapabilityInfo, error) { // Sanity check to make sure the capability registry has the capability we are looking for. - hid, err := hashedCapabilityId(capabilityLabelledName, capabilityVersion) + hid, err := common.HashedCapabilityID(capabilityLabelledName, capabilityVersion) if err != nil { return kcr.CapabilitiesRegistryCapabilityInfo{}, fmt.Errorf("failed to hash capability id: %w", err) } @@ -133,29 +131,6 @@ func checkCapabilityPresence( return ccipCapability, nil } -// hashedCapabilityId returns the hashed capability id in a manner equivalent to the capability registry. -func hashedCapabilityId(capabilityLabelledName, capabilityVersion string) (r [32]byte, err error) { - tabi := `[{"type": "string"}, {"type": "string"}]` - abiEncoded, err := utils.ABIEncode(tabi, capabilityLabelledName, capabilityVersion) - if err != nil { - return r, fmt.Errorf("failed to ABI encode capability version and labelled name: %w", err) - } - - h := crypto.Keccak256(abiEncoded) - copy(r[:], h) - return r, nil -} - -// mustHashedCapabilityId is a helper function that panics if the hashedCapabilityId function returns an error. -// should only use in tests. -func mustHashedCapabilityId(capabilityLabelledName, capabilityVersion string) [32]byte { - r, err := hashedCapabilityId(capabilityLabelledName, capabilityVersion) - if err != nil { - panic(err) - } - return r -} - // isMemberOfDON returns true if and only if the given p2pID is a member of the given DON. func isMemberOfDON(don kcr.CapabilitiesRegistryDONInfo, p2pID ragep2ptypes.PeerID) bool { for _, node := range don.NodeP2PIds { diff --git a/core/services/ccipcapability/launcher/diff_test.go b/core/services/ccipcapability/launcher/diff_test.go index c7a54cad2f..5892933207 100644 --- a/core/services/ccipcapability/launcher/diff_test.go +++ b/core/services/ccipcapability/launcher/diff_test.go @@ -1,24 +1,27 @@ package launcher import ( + "fmt" "math/big" "reflect" "testing" - "github.com/ethereum/go-ethereum/accounts/abi/bind/backends" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core" ragep2ptypes "github.com/smartcontractkit/libocr/ragep2p/types" "github.com/stretchr/testify/require" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" kcr "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/capabilities_registry" - "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" + capcommon "github.com/smartcontractkit/chainlink/v2/core/services/ccipcapability/common" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/p2pkey" "github.com/smartcontractkit/chainlink/v2/core/services/p2p/types" "github.com/smartcontractkit/chainlink/v2/core/services/registrysyncer" ) +const ( + ccipCapVersion = "v1.0.0" + ccipCapNewVersion = "v1.1.0" + ccipCapName = "ccip" +) + func Test_diff(t *testing.T) { type args struct { capabilityVersion string @@ -35,13 +38,13 @@ func Test_diff(t *testing.T) { { "no diff", args{ - capabilityVersion: "v1.0.0", - capabilityLabelledName: "ccip", + capabilityVersion: ccipCapVersion, + capabilityLabelledName: ccipCapName, oldState: registrysyncer.State{ IDsToCapabilities: map[registrysyncer.HashedCapabilityID]kcr.CapabilitiesRegistryCapabilityInfo{ - mustHashedCapabilityId("ccip", "v1.0.0"): { - LabelledName: "ccip", - Version: "v1.0.0", + mustHashedCapabilityID(ccipCapName, ccipCapVersion): { + LabelledName: ccipCapName, + Version: ccipCapVersion, }, }, IDsToDONs: map[registrysyncer.DonID]kcr.CapabilitiesRegistryDONInfo{ @@ -49,7 +52,7 @@ func Test_diff(t *testing.T) { Id: 1, CapabilityConfigurations: []kcr.CapabilitiesRegistryCapabilityConfiguration{ { - CapabilityId: mustHashedCapabilityId("ccip", "v1.0.0"), + CapabilityId: mustHashedCapabilityID(ccipCapName, ccipCapVersion), }, }, }, @@ -58,9 +61,9 @@ func Test_diff(t *testing.T) { }, newState: registrysyncer.State{ IDsToCapabilities: map[registrysyncer.HashedCapabilityID]kcr.CapabilitiesRegistryCapabilityInfo{ - mustHashedCapabilityId("ccip", "v1.0.0"): { - LabelledName: "ccip", - Version: "v1.0.0", + mustHashedCapabilityID(ccipCapName, ccipCapVersion): { + LabelledName: ccipCapName, + Version: ccipCapVersion, }, }, IDsToDONs: map[registrysyncer.DonID]kcr.CapabilitiesRegistryDONInfo{ @@ -68,7 +71,7 @@ func Test_diff(t *testing.T) { Id: 1, CapabilityConfigurations: []kcr.CapabilitiesRegistryCapabilityConfiguration{ { - CapabilityId: mustHashedCapabilityId("ccip", "v1.0.0"), + CapabilityId: mustHashedCapabilityID(ccipCapName, ccipCapVersion), }, }, }, @@ -86,21 +89,21 @@ func Test_diff(t *testing.T) { { "capability not present", args{ - capabilityVersion: "v1.0.0", - capabilityLabelledName: "ccip", + capabilityVersion: ccipCapVersion, + capabilityLabelledName: ccipCapName, oldState: registrysyncer.State{ IDsToCapabilities: map[registrysyncer.HashedCapabilityID]kcr.CapabilitiesRegistryCapabilityInfo{ - mustHashedCapabilityId("ccip", "v1.1.0"): { - LabelledName: "ccip", - Version: "v1.1.0", + mustHashedCapabilityID(ccipCapName, ccipCapNewVersion): { + LabelledName: ccipCapName, + Version: ccipCapNewVersion, }, }, }, newState: registrysyncer.State{ IDsToCapabilities: map[registrysyncer.HashedCapabilityID]kcr.CapabilitiesRegistryCapabilityInfo{ - mustHashedCapabilityId("ccip", "v1.1.0"): { - LabelledName: "ccip", - Version: "v1.1.0", + mustHashedCapabilityID(ccipCapName, ccipCapNewVersion): { + LabelledName: ccipCapName, + Version: ccipCapNewVersion, }, }, }, @@ -111,22 +114,22 @@ func Test_diff(t *testing.T) { { "diff present, new don", args{ - capabilityVersion: "v1.0.0", - capabilityLabelledName: "ccip", + capabilityVersion: ccipCapVersion, + capabilityLabelledName: ccipCapName, oldState: registrysyncer.State{ IDsToCapabilities: map[registrysyncer.HashedCapabilityID]kcr.CapabilitiesRegistryCapabilityInfo{ - mustHashedCapabilityId("ccip", "v1.0.0"): { - LabelledName: "ccip", - Version: "v1.0.0", + mustHashedCapabilityID(ccipCapName, ccipCapVersion): { + LabelledName: ccipCapName, + Version: ccipCapVersion, }, }, IDsToDONs: map[registrysyncer.DonID]kcr.CapabilitiesRegistryDONInfo{}, }, newState: registrysyncer.State{ IDsToCapabilities: map[registrysyncer.HashedCapabilityID]kcr.CapabilitiesRegistryCapabilityInfo{ - mustHashedCapabilityId("ccip", "v1.0.0"): { - LabelledName: "ccip", - Version: "v1.0.0", + mustHashedCapabilityID(ccipCapName, ccipCapVersion): { + LabelledName: ccipCapName, + Version: ccipCapVersion, }, }, IDsToDONs: map[registrysyncer.DonID]kcr.CapabilitiesRegistryDONInfo{ @@ -134,7 +137,7 @@ func Test_diff(t *testing.T) { Id: 1, CapabilityConfigurations: []kcr.CapabilitiesRegistryCapabilityConfiguration{ { - CapabilityId: mustHashedCapabilityId("ccip", "v1.0.0"), + CapabilityId: mustHashedCapabilityID(ccipCapName, ccipCapVersion), }, }, }, @@ -147,7 +150,7 @@ func Test_diff(t *testing.T) { Id: 1, CapabilityConfigurations: []kcr.CapabilitiesRegistryCapabilityConfiguration{ { - CapabilityId: mustHashedCapabilityId("ccip", "v1.0.0"), + CapabilityId: mustHashedCapabilityID(ccipCapName, ccipCapVersion), }, }, }, @@ -279,8 +282,8 @@ func Test_filterCCIPDONs(t *testing.T) { "one ccip don", args{ ccipCapability: kcr.CapabilitiesRegistryCapabilityInfo{ - LabelledName: "ccip", - Version: "v1.0.0", + LabelledName: ccipCapName, + Version: ccipCapVersion, }, state: registrysyncer.State{ IDsToDONs: map[registrysyncer.DonID]kcr.CapabilitiesRegistryDONInfo{ @@ -288,7 +291,7 @@ func Test_filterCCIPDONs(t *testing.T) { Id: 1, CapabilityConfigurations: []kcr.CapabilitiesRegistryCapabilityConfiguration{ { - CapabilityId: mustHashedCapabilityId("ccip", "v1.0.0"), + CapabilityId: mustHashedCapabilityID(ccipCapName, ccipCapVersion), }, }, }, @@ -300,7 +303,7 @@ func Test_filterCCIPDONs(t *testing.T) { Id: 1, CapabilityConfigurations: []kcr.CapabilitiesRegistryCapabilityConfiguration{ { - CapabilityId: mustHashedCapabilityId("ccip", "v1.0.0"), + CapabilityId: mustHashedCapabilityID(ccipCapName, ccipCapVersion), }, }, }, @@ -311,8 +314,8 @@ func Test_filterCCIPDONs(t *testing.T) { "no ccip dons", args{ ccipCapability: kcr.CapabilitiesRegistryCapabilityInfo{ - LabelledName: "ccip", - Version: "v1.0.0", + LabelledName: ccipCapName, + Version: ccipCapVersion, }, state: registrysyncer.State{ IDsToDONs: map[registrysyncer.DonID]kcr.CapabilitiesRegistryDONInfo{ @@ -320,7 +323,7 @@ func Test_filterCCIPDONs(t *testing.T) { Id: 1, CapabilityConfigurations: []kcr.CapabilitiesRegistryCapabilityConfiguration{ { - CapabilityId: mustHashedCapabilityId("ccip", "v1.1.0"), + CapabilityId: mustHashedCapabilityID(ccipCapName, ccipCapNewVersion), }, }, }, @@ -334,8 +337,8 @@ func Test_filterCCIPDONs(t *testing.T) { "don with multiple capabilities, one of them ccip", args{ ccipCapability: kcr.CapabilitiesRegistryCapabilityInfo{ - LabelledName: "ccip", - Version: "v1.0.0", + LabelledName: ccipCapName, + Version: ccipCapVersion, }, state: registrysyncer.State{ IDsToDONs: map[registrysyncer.DonID]kcr.CapabilitiesRegistryDONInfo{ @@ -343,10 +346,10 @@ func Test_filterCCIPDONs(t *testing.T) { Id: 1, CapabilityConfigurations: []kcr.CapabilitiesRegistryCapabilityConfiguration{ { - CapabilityId: mustHashedCapabilityId("ccip", "v1.0.0"), + CapabilityId: mustHashedCapabilityID(ccipCapName, ccipCapVersion), }, { - CapabilityId: mustHashedCapabilityId("ccip", "v1.1.0"), + CapabilityId: mustHashedCapabilityID(ccipCapName, ccipCapNewVersion), }, }, }, @@ -358,10 +361,10 @@ func Test_filterCCIPDONs(t *testing.T) { Id: 1, CapabilityConfigurations: []kcr.CapabilitiesRegistryCapabilityConfiguration{ { - CapabilityId: mustHashedCapabilityId("ccip", "v1.0.0"), + CapabilityId: mustHashedCapabilityID(ccipCapName, ccipCapVersion), }, { - CapabilityId: mustHashedCapabilityId("ccip", "v1.1.0"), + CapabilityId: mustHashedCapabilityID(ccipCapName, ccipCapNewVersion), }, }, }, @@ -397,37 +400,37 @@ func Test_checkCapabilityPresence(t *testing.T) { { "in registry state", args{ - capabilityVersion: "v1.0.0", - capabilityLabelledName: "ccip", + capabilityVersion: ccipCapVersion, + capabilityLabelledName: ccipCapName, state: registrysyncer.State{ IDsToCapabilities: map[registrysyncer.HashedCapabilityID]kcr.CapabilitiesRegistryCapabilityInfo{ - mustHashedCapabilityId("ccip", "v1.0.0"): { - LabelledName: "ccip", - Version: "v1.0.0", + mustHashedCapabilityID(ccipCapName, ccipCapVersion): { + LabelledName: ccipCapName, + Version: ccipCapVersion, }, - mustHashedCapabilityId("ccip", "v1.1.0"): { - LabelledName: "ccip", - Version: "v1.1.0", + mustHashedCapabilityID(ccipCapName, ccipCapNewVersion): { + LabelledName: ccipCapName, + Version: ccipCapNewVersion, }, }, }, }, kcr.CapabilitiesRegistryCapabilityInfo{ - LabelledName: "ccip", - Version: "v1.0.0", + LabelledName: ccipCapName, + Version: ccipCapVersion, }, false, }, { "not in registry state", args{ - capabilityVersion: "v1.0.0", - capabilityLabelledName: "ccip", + capabilityVersion: ccipCapVersion, + capabilityLabelledName: ccipCapName, state: registrysyncer.State{ IDsToCapabilities: map[registrysyncer.HashedCapabilityID]kcr.CapabilitiesRegistryCapabilityInfo{ - mustHashedCapabilityId("ccip", "v1.1.0"): { - LabelledName: "ccip", - Version: "v1.1.0", + mustHashedCapabilityID(ccipCapName, ccipCapNewVersion): { + LabelledName: ccipCapName, + Version: ccipCapNewVersion, }, }, }, @@ -450,41 +453,6 @@ func Test_checkCapabilityPresence(t *testing.T) { } } -func Test_hashedCapabilityId(t *testing.T) { - transactor := testutils.MustNewSimTransactor(t) - sb := backends.NewSimulatedBackend(core.GenesisAlloc{ - transactor.From: {Balance: assets.Ether(1000).ToInt()}, - }, 30e6) - - crAddress, _, _, err := kcr.DeployCapabilitiesRegistry(transactor, sb) - require.NoError(t, err) - sb.Commit() - - cr, err := kcr.NewCapabilitiesRegistry(crAddress, sb) - require.NoError(t, err) - - // add a capability, ignore cap config for simplicity. - _, err = cr.AddCapabilities(transactor, []kcr.CapabilitiesRegistryCapability{ - { - LabelledName: "ccip", - Version: "v1.0.0", - CapabilityType: 0, - ResponseType: 0, - ConfigurationContract: common.Address{}, - }, - }) - require.NoError(t, err) - sb.Commit() - - hidExpected, err := cr.GetHashedCapabilityId(nil, "ccip", "v1.0.0") - require.NoError(t, err) - - hid, err := hashedCapabilityId("ccip", "v1.0.0") - require.NoError(t, err) - - require.Equal(t, hidExpected, hid) -} - func Test_isMemberOfDON(t *testing.T) { var p2pIDs [][32]byte for i := range [4]struct{}{} { @@ -506,3 +474,11 @@ func Test_isMemberOfBootstrapSubcommittee(t *testing.T) { require.True(t, isMemberOfBootstrapSubcommittee(bootstrapKeys, ragep2ptypes.PeerID(p2pkey.MustNewV2XXXTestingOnly(big.NewInt(1)).PeerID()))) require.False(t, isMemberOfBootstrapSubcommittee(bootstrapKeys, ragep2ptypes.PeerID(p2pkey.MustNewV2XXXTestingOnly(big.NewInt(5)).PeerID()))) } + +func mustHashedCapabilityID(capabilityLabelledName, capabilityVersion string) [32]byte { + r, err := capcommon.HashedCapabilityID(capabilityLabelledName, capabilityVersion) + if err != nil { + panic(fmt.Errorf("failed to hash capability id (labelled name: %s, version: %s): %w", capabilityLabelledName, capabilityVersion, err)) + } + return r +} diff --git a/core/services/ccipcapability/launcher/integration_test.go b/core/services/ccipcapability/launcher/integration_test.go index da04ef4cbc..29a2b92f0c 100644 --- a/core/services/ccipcapability/launcher/integration_test.go +++ b/core/services/ccipcapability/launcher/integration_test.go @@ -11,7 +11,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" "github.com/smartcontractkit/chainlink/v2/core/logger" cctypes "github.com/smartcontractkit/chainlink/v2/core/services/ccipcapability/types" - it "github.com/smartcontractkit/chainlink/v2/core/services/ocr3/plugins/ccip_integration_tests" + it "github.com/smartcontractkit/chainlink/v2/core/services/ocr3/plugins/ccip_integration_tests/integrationhelpers" "github.com/smartcontractkit/chainlink/v2/core/services/registrysyncer" ) diff --git a/core/services/ccipcapability/ocrimpls/config_digester.go b/core/services/ccipcapability/ocrimpls/config_digester.go new file mode 100644 index 0000000000..ef0c5e7ca3 --- /dev/null +++ b/core/services/ccipcapability/ocrimpls/config_digester.go @@ -0,0 +1,23 @@ +package ocrimpls + +import "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + +type configDigester struct { + d types.ConfigDigest +} + +func NewConfigDigester(d types.ConfigDigest) *configDigester { + return &configDigester{d: d} +} + +// ConfigDigest implements types.OffchainConfigDigester. +func (c *configDigester) ConfigDigest(types.ContractConfig) (types.ConfigDigest, error) { + return c.d, nil +} + +// ConfigDigestPrefix implements types.OffchainConfigDigester. +func (c *configDigester) ConfigDigestPrefix() (types.ConfigDigestPrefix, error) { + return types.ConfigDigestPrefixCCIPMultiRole, nil +} + +var _ types.OffchainConfigDigester = (*configDigester)(nil) diff --git a/core/services/ccipcapability/ocrimpls/config_tracker.go b/core/services/ccipcapability/ocrimpls/config_tracker.go new file mode 100644 index 0000000000..2bf329b55e --- /dev/null +++ b/core/services/ccipcapability/ocrimpls/config_tracker.go @@ -0,0 +1,66 @@ +package ocrimpls + +import ( + "context" + + gethcommon "github.com/ethereum/go-ethereum/common" + "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + + cctypes "github.com/smartcontractkit/chainlink/v2/core/services/ccipcapability/types" +) + +type configTracker struct { + cfg cctypes.OCR3ConfigWithMeta +} + +func NewConfigTracker(cfg cctypes.OCR3ConfigWithMeta) *configTracker { + return &configTracker{cfg: cfg} +} + +// LatestBlockHeight implements types.ContractConfigTracker. +func (c *configTracker) LatestBlockHeight(ctx context.Context) (blockHeight uint64, err error) { + return 0, nil +} + +// LatestConfig implements types.ContractConfigTracker. +func (c *configTracker) LatestConfig(ctx context.Context, changedInBlock uint64) (types.ContractConfig, error) { + return types.ContractConfig{ + ConfigDigest: c.cfg.ConfigDigest, + ConfigCount: c.cfg.ConfigCount, + Signers: toOnchainPublicKeys(c.cfg.Config.Signers), + Transmitters: toOCRAccounts(c.cfg.Config.Transmitters), + F: c.cfg.Config.F, + OnchainConfig: []byte{}, + OffchainConfigVersion: c.cfg.Config.OffchainConfigVersion, + OffchainConfig: c.cfg.Config.OffchainConfig, + }, nil +} + +// LatestConfigDetails implements types.ContractConfigTracker. +func (c *configTracker) LatestConfigDetails(ctx context.Context) (changedInBlock uint64, configDigest types.ConfigDigest, err error) { + return 0, c.cfg.ConfigDigest, nil +} + +// Notify implements types.ContractConfigTracker. +func (c *configTracker) Notify() <-chan struct{} { + return nil +} + +func toOnchainPublicKeys(signers [][]byte) []types.OnchainPublicKey { + keys := make([]types.OnchainPublicKey, len(signers)) + for i, signer := range signers { + keys[i] = types.OnchainPublicKey(signer) + } + return keys +} + +func toOCRAccounts(transmitters [][]byte) []types.Account { + accounts := make([]types.Account, len(transmitters)) + for i, transmitter := range transmitters { + // TODO: string-encode the transmitter appropriately to the dest chain family. + accounts[i] = types.Account(gethcommon.BytesToAddress(transmitter).Hex()) + } + return accounts +} + +var _ types.ContractConfigTracker = (*configTracker)(nil) diff --git a/core/services/ccipcapability/ocrimpls/contract_transmitter.go b/core/services/ccipcapability/ocrimpls/contract_transmitter.go new file mode 100644 index 0000000000..a74c9e0522 --- /dev/null +++ b/core/services/ccipcapability/ocrimpls/contract_transmitter.go @@ -0,0 +1,188 @@ +package ocrimpls + +import ( + "context" + "errors" + "fmt" + "math/big" + + "github.com/google/uuid" + "github.com/smartcontractkit/libocr/offchainreporting2/chains/evmutil" + "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3types" + ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + + "github.com/smartcontractkit/chainlink-ccip/pkg/consts" + commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" +) + +type ToCalldataFunc func(rawReportCtx [3][32]byte, report []byte, rs, ss [][32]byte, vs [32]byte) any + +func ToCommitCalldata(rawReportCtx [3][32]byte, report []byte, rs, ss [][32]byte, vs [32]byte) any { + // Note that the name of the struct field is very important, since the encoder used + // by the chainwriter uses mapstructure, which will use the struct field name to map + // to the argument name in the function call. + // If, for whatever reason, we want to change the field name, make sure to add a `mapstructure:""` tag + // for that field. + return struct { + ReportContext [3][32]byte + Report []byte + Rs [][32]byte + Ss [][32]byte + RawVs [32]byte + }{ + ReportContext: rawReportCtx, + Report: report, + Rs: rs, + Ss: ss, + RawVs: vs, + } +} + +func ToExecCalldata(rawReportCtx [3][32]byte, report []byte, _, _ [][32]byte, _ [32]byte) any { + // Note that the name of the struct field is very important, since the encoder used + // by the chainwriter uses mapstructure, which will use the struct field name to map + // to the argument name in the function call. + // If, for whatever reason, we want to change the field name, make sure to add a `mapstructure:""` tag + // for that field. + return struct { + ReportContext [3][32]byte + Report []byte + }{ + ReportContext: rawReportCtx, + Report: report, + } +} + +var _ ocr3types.ContractTransmitter[[]byte] = &commitTransmitter[[]byte]{} + +type commitTransmitter[RI any] struct { + cw commontypes.ChainWriter + fromAccount ocrtypes.Account + contractName string + method string + offrampAddress string + toCalldataFn ToCalldataFunc +} + +func XXXNewContractTransmitterTestsOnly[RI any]( + cw commontypes.ChainWriter, + fromAccount ocrtypes.Account, + contractName string, + method string, + offrampAddress string, + toCalldataFn ToCalldataFunc, +) ocr3types.ContractTransmitter[RI] { + return &commitTransmitter[RI]{ + cw: cw, + fromAccount: fromAccount, + contractName: contractName, + method: method, + offrampAddress: offrampAddress, + toCalldataFn: toCalldataFn, + } +} + +func NewCommitContractTransmitter[RI any]( + cw commontypes.ChainWriter, + fromAccount ocrtypes.Account, + offrampAddress string, +) ocr3types.ContractTransmitter[RI] { + return &commitTransmitter[RI]{ + cw: cw, + fromAccount: fromAccount, + contractName: consts.ContractNameOffRamp, + method: consts.MethodCommit, + offrampAddress: offrampAddress, + toCalldataFn: ToCommitCalldata, + } +} + +func NewExecContractTransmitter[RI any]( + cw commontypes.ChainWriter, + fromAccount ocrtypes.Account, + offrampAddress string, +) ocr3types.ContractTransmitter[RI] { + return &commitTransmitter[RI]{ + cw: cw, + fromAccount: fromAccount, + contractName: consts.ContractNameOffRamp, + method: consts.MethodExecute, + offrampAddress: offrampAddress, + toCalldataFn: ToExecCalldata, + } +} + +// FromAccount implements ocr3types.ContractTransmitter. +func (c *commitTransmitter[RI]) FromAccount() (ocrtypes.Account, error) { + return c.fromAccount, nil +} + +// Transmit implements ocr3types.ContractTransmitter. +func (c *commitTransmitter[RI]) Transmit( + ctx context.Context, + configDigest ocrtypes.ConfigDigest, + seqNr uint64, + reportWithInfo ocr3types.ReportWithInfo[RI], + sigs []ocrtypes.AttributedOnchainSignature, +) error { + var rs [][32]byte + var ss [][32]byte + var vs [32]byte + if len(sigs) > 32 { + return errors.New("too many signatures, maximum is 32") + } + for i, as := range sigs { + r, s, v, err := evmutil.SplitSignature(as.Signature) + if err != nil { + return fmt.Errorf("failed to split signature: %w", err) + } + rs = append(rs, r) + ss = append(ss, s) + vs[i] = v + } + + // report ctx for OCR3 consists of the following + // reportContext[0]: ConfigDigest + // reportContext[1]: 24 byte padding, 8 byte sequence number + // reportContext[2]: unused + // convert seqNum, which is a uint64, into a uint32 epoch and uint8 round + // while this does truncate the sequence number, it is not a problem because + // it still gives us 2^40 - 1 possible sequence numbers. + // assuming a sequence number is generated every second, this gives us + // 1099511627775 seconds, or approximately 34,865 years, before we run out + // of sequence numbers. + epoch, round := uint64ToUint32AndUint8(seqNr) + rawReportCtx := evmutil.RawReportContext(ocrtypes.ReportContext{ + ReportTimestamp: ocrtypes.ReportTimestamp{ + ConfigDigest: configDigest, + Epoch: epoch, + Round: round, + }, + // ExtraData not used in OCR3 + }) + + if c.toCalldataFn == nil { + return errors.New("toCalldataFn is nil") + } + + // chain writer takes in the raw calldata and packs it on its own. + args := c.toCalldataFn(rawReportCtx, reportWithInfo.Report, rs, ss, vs) + + // TODO: no meta fields yet, what should we add? + // probably whats in the info part of the report? + meta := commontypes.TxMeta{} + txID, err := uuid.NewRandom() // NOTE: CW expects us to generate an ID, rather than return one + if err != nil { + return fmt.Errorf("failed to generate UUID: %w", err) + } + zero := big.NewInt(0) + if err := c.cw.SubmitTransaction(ctx, c.contractName, c.method, args, txID.String(), c.offrampAddress, &meta, zero); err != nil { + return fmt.Errorf("failed to submit transaction thru chainwriter: %w", err) + } + + return nil +} + +func uint64ToUint32AndUint8(x uint64) (uint32, uint8) { + return uint32(x >> 32), uint8(x) +} diff --git a/core/services/ccipcapability/ocrimpls/contract_transmitter_test.go b/core/services/ccipcapability/ocrimpls/contract_transmitter_test.go new file mode 100644 index 0000000000..46d96f83d8 --- /dev/null +++ b/core/services/ccipcapability/ocrimpls/contract_transmitter_test.go @@ -0,0 +1,689 @@ +package ocrimpls_test + +import ( + "crypto/rand" + "math/big" + "net/url" + "testing" + "time" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/accounts/abi/bind/backends" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core" + "github.com/jmoiron/sqlx" + "github.com/onsi/gomega" + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/libocr/commontypes" + "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3types" + ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + + "github.com/smartcontractkit/chainlink-common/pkg/utils/mailbox" + txmgrcommon "github.com/smartcontractkit/chainlink/v2/common/txmgr" + + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" + evmconfig "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/chaintype" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/headtracker" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/keystore" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils" + "github.com/smartcontractkit/chainlink/v2/core/config" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/multi_ocr3_helper" + "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" + "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/ccipcapability/ocrimpls" + cctypes "github.com/smartcontractkit/chainlink/v2/core/services/ccipcapability/types" + kschaintype "github.com/smartcontractkit/chainlink/v2/core/services/keystore/chaintype" + "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/ocr2key" + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm" + evmrelaytypes "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/types" +) + +func Test_ContractTransmitter_TransmitWithoutSignatures(t *testing.T) { + type testCase struct { + name string + pluginType uint8 + withSigs bool + expectedSigsEnabled bool + report []byte + } + + testCases := []testCase{ + { + "empty report with sigs", + uint8(cctypes.PluginTypeCCIPCommit), + true, + true, + []byte{}, + }, + { + "empty report without sigs", + uint8(cctypes.PluginTypeCCIPExec), + false, + false, + []byte{}, + }, + { + "report with data with sigs", + uint8(cctypes.PluginTypeCCIPCommit), + true, + true, + randomReport(t, 96), + }, + { + "report with data without sigs", + uint8(cctypes.PluginTypeCCIPExec), + false, + false, + randomReport(t, 96), + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + tc := tc + t.Parallel() + testTransmitter(t, tc.pluginType, tc.withSigs, tc.expectedSigsEnabled, tc.report) + }) + } +} + +func testTransmitter( + t *testing.T, + pluginType uint8, + withSigs bool, + expectedSigsEnabled bool, + report []byte, +) { + uni := newTestUniverse[[]byte](t, nil) + + c, err := uni.wrapper.LatestConfigDetails(nil, pluginType) + require.NoError(t, err, "failed to get latest config details") + configDigest := c.ConfigInfo.ConfigDigest + require.Equal(t, expectedSigsEnabled, c.ConfigInfo.IsSignatureVerificationEnabled, "signature verification enabled setting not correct") + + // set the plugin type on the helper so it fetches the right config info. + // the important aspect is whether signatures should be enabled or not. + _, err = uni.wrapper.SetTransmitOcrPluginType(uni.deployer, pluginType) + require.NoError(t, err, "failed to set plugin type") + uni.backend.Commit() + + // create attributed sigs + // only need f+1 which is 2 in this case + rwi := ocr3types.ReportWithInfo[[]byte]{ + Report: report, + Info: []byte{}, + } + seqNr := uint64(1) + attributedSigs := uni.SignReport(t, configDigest, rwi, seqNr) + + account, err := uni.transmitterWithSigs.FromAccount() + require.NoError(t, err, "failed to get from account") + require.Equal(t, ocrtypes.Account(uni.transmitters[0].Hex()), account, "from account mismatch") + if withSigs { + err = uni.transmitterWithSigs.Transmit(testutils.Context(t), configDigest, seqNr, rwi, attributedSigs) + } else { + err = uni.transmitterWithoutSigs.Transmit(testutils.Context(t), configDigest, seqNr, rwi, attributedSigs) + } + require.NoError(t, err, "failed to transmit") + uni.backend.Commit() + + var txStatus uint64 + gomega.NewWithT(t).Eventually(func() bool { + uni.backend.Commit() + rows, err := uni.db.QueryContext(testutils.Context(t), `SELECT hash FROM evm.tx_attempts LIMIT 1`) + require.NoError(t, err, "failed to query txes") + defer rows.Close() + var txHash []byte + for rows.Next() { + require.NoError(t, rows.Scan(&txHash), "failed to scan") + } + t.Log("txHash:", txHash) + receipt, err := uni.simClient.TransactionReceipt(testutils.Context(t), common.BytesToHash(txHash)) + if err != nil { + t.Log("tx not found yet:", hexutil.Encode(txHash)) + return false + } + t.Log("tx found:", hexutil.Encode(txHash), "status:", receipt.Status) + txStatus = receipt.Status + return true + }, testutils.WaitTimeout(t), 1*time.Second).Should(gomega.BeTrue()) + + // wait for receipt to be written to the db + gomega.NewWithT(t).Eventually(func() bool { + rows, err := uni.db.QueryContext(testutils.Context(t), `SELECT count(*) as cnt FROM evm.receipts LIMIT 1`) + require.NoError(t, err, "failed to query receipts") + defer rows.Close() + var count int + for rows.Next() { + require.NoError(t, rows.Scan(&count), "failed to scan") + } + return count == 1 + }, testutils.WaitTimeout(t), 2*time.Second).Should(gomega.BeTrue()) + + require.Equal(t, uint64(1), txStatus, "tx status should be success") + + // check that the event was emitted + events := uni.TransmittedEvents(t) + require.Len(t, events, 1, "expected 1 event") + require.Equal(t, configDigest, events[0].ConfigDigest, "config digest mismatch") + require.Equal(t, seqNr, events[0].SequenceNumber, "seq num mismatch") +} + +type testUniverse[RI any] struct { + simClient *client.SimulatedBackendClient + backend *backends.SimulatedBackend + deployer *bind.TransactOpts + transmitters []common.Address + signers []common.Address + wrapper *multi_ocr3_helper.MultiOCR3Helper + transmitterWithSigs ocr3types.ContractTransmitter[RI] + transmitterWithoutSigs ocr3types.ContractTransmitter[RI] + keyrings []ocr3types.OnchainKeyring[RI] + f uint8 + db *sqlx.DB + txm txmgr.TxManager + gasEstimator gas.EvmFeeEstimator +} + +type keyringsAndSigners[RI any] struct { + keyrings []ocr3types.OnchainKeyring[RI] + signers []common.Address +} + +func newTestUniverse[RI any](t *testing.T, ks *keyringsAndSigners[RI]) *testUniverse[RI] { + t.Helper() + + db := pgtest.NewSqlxDB(t) + owner := testutils.MustNewSimTransactor(t) + + // create many transmitters but only need to fund one, rest are to get + // setOCR3Config to pass. + keyStore := cltest.NewKeyStore(t, db) + var transmitters []common.Address + for i := 0; i < 4; i++ { + key, err := keyStore.Eth().Create(testutils.Context(t), big.NewInt(1337)) + require.NoError(t, err, "failed to create key") + transmitters = append(transmitters, key.Address) + } + + backend := backends.NewSimulatedBackend(core.GenesisAlloc{ + owner.From: core.GenesisAccount{ + Balance: assets.Ether(1000).ToInt(), + }, + transmitters[0]: core.GenesisAccount{ + Balance: assets.Ether(1000).ToInt(), + }, + }, 30e6) + + ocr3HelperAddr, _, _, err := multi_ocr3_helper.DeployMultiOCR3Helper(owner, backend) + require.NoError(t, err) + backend.Commit() + wrapper, err := multi_ocr3_helper.NewMultiOCR3Helper(ocr3HelperAddr, backend) + require.NoError(t, err) + + // create the oracle identities for setConfig + // need to create at least 4 identities otherwise setConfig will fail + var ( + keyrings []ocr3types.OnchainKeyring[RI] + signers []common.Address + ) + if ks != nil { + keyrings = ks.keyrings + signers = ks.signers + } else { + for i := 0; i < 4; i++ { + kb, err2 := ocr2key.New(kschaintype.EVM) + require.NoError(t, err2, "failed to create key") + kr := ocrimpls.NewOnchainKeyring[RI](kb, logger.TestLogger(t)) + signers = append(signers, common.BytesToAddress(kr.PublicKey())) + keyrings = append(keyrings, kr) + } + } + f := uint8(1) + commitConfigDigest := testutils.Random32Byte() + execConfigDigest := testutils.Random32Byte() + _, err = wrapper.SetOCR3Configs( + owner, + []multi_ocr3_helper.MultiOCR3BaseOCRConfigArgs{ + { + ConfigDigest: commitConfigDigest, + OcrPluginType: uint8(cctypes.PluginTypeCCIPCommit), + F: f, + IsSignatureVerificationEnabled: true, + Signers: signers, + Transmitters: []common.Address{ + transmitters[0], + transmitters[1], + transmitters[2], + transmitters[3], + }, + }, + { + ConfigDigest: execConfigDigest, + OcrPluginType: uint8(cctypes.PluginTypeCCIPExec), + F: f, + IsSignatureVerificationEnabled: false, + Signers: signers, + Transmitters: []common.Address{ + transmitters[0], + transmitters[1], + transmitters[2], + transmitters[3], + }, + }, + }, + ) + require.NoError(t, err) + backend.Commit() + + commitConfig, err := wrapper.LatestConfigDetails(nil, uint8(cctypes.PluginTypeCCIPCommit)) + require.NoError(t, err, "failed to get latest commit config") + require.Equal(t, commitConfigDigest, commitConfig.ConfigInfo.ConfigDigest, "commit config digest mismatch") + execConfig, err := wrapper.LatestConfigDetails(nil, uint8(cctypes.PluginTypeCCIPExec)) + require.NoError(t, err, "failed to get latest exec config") + require.Equal(t, execConfigDigest, execConfig.ConfigInfo.ConfigDigest, "exec config digest mismatch") + + simClient := client.NewSimulatedBackendClient(t, backend, testutils.SimulatedChainID) + + // create the chain writer service + txm, gasEstimator := makeTestEvmTxm(t, db, simClient, keyStore.Eth()) + require.NoError(t, txm.Start(testutils.Context(t)), "failed to start tx manager") + t.Cleanup(func() { require.NoError(t, txm.Close()) }) + + chainWriter, err := evm.NewChainWriterService( + logger.TestLogger(t), + simClient, + txm, + gasEstimator, + chainWriterConfigRaw(transmitters[0], assets.GWei(1))) + require.NoError(t, err, "failed to create chain writer") + require.NoError(t, chainWriter.Start(testutils.Context(t)), "failed to start chain writer") + t.Cleanup(func() { require.NoError(t, chainWriter.Close()) }) + + transmitterWithSigs := ocrimpls.XXXNewContractTransmitterTestsOnly[RI]( + chainWriter, + ocrtypes.Account(transmitters[0].Hex()), + contractName, + methodTransmitWithSignatures, + ocr3HelperAddr.Hex(), + ocrimpls.ToCommitCalldata, + ) + transmitterWithoutSigs := ocrimpls.XXXNewContractTransmitterTestsOnly[RI]( + chainWriter, + ocrtypes.Account(transmitters[0].Hex()), + contractName, + methodTransmitWithoutSignatures, + ocr3HelperAddr.Hex(), + ocrimpls.ToExecCalldata, + ) + + return &testUniverse[RI]{ + simClient: simClient, + backend: backend, + deployer: owner, + transmitters: transmitters, + signers: signers, + wrapper: wrapper, + transmitterWithSigs: transmitterWithSigs, + transmitterWithoutSigs: transmitterWithoutSigs, + keyrings: keyrings, + f: f, + db: db, + txm: txm, + gasEstimator: gasEstimator, + } +} + +func (uni testUniverse[RI]) SignReport(t *testing.T, configDigest ocrtypes.ConfigDigest, rwi ocr3types.ReportWithInfo[RI], seqNum uint64) []ocrtypes.AttributedOnchainSignature { + var attributedSigs []ocrtypes.AttributedOnchainSignature + for i := uint8(0); i < uni.f+1; i++ { + t.Log("signing report with", hexutil.Encode(uni.keyrings[i].PublicKey())) + sig, err := uni.keyrings[i].Sign(configDigest, seqNum, rwi) + require.NoError(t, err, "failed to sign report") + attributedSigs = append(attributedSigs, ocrtypes.AttributedOnchainSignature{ + Signature: sig, + Signer: commontypes.OracleID(i), + }) + } + return attributedSigs +} + +func (uni testUniverse[RI]) TransmittedEvents(t *testing.T) []*multi_ocr3_helper.MultiOCR3HelperTransmitted { + iter, err := uni.wrapper.FilterTransmitted(&bind.FilterOpts{ + Start: 0, + }, nil) + require.NoError(t, err, "failed to create filter iterator") + var events []*multi_ocr3_helper.MultiOCR3HelperTransmitted + for iter.Next() { + event := iter.Event + events = append(events, event) + } + return events +} + +func randomReport(t *testing.T, len int) []byte { + report := make([]byte, len) + _, err := rand.Reader.Read(report) + require.NoError(t, err, "failed to read random bytes") + return report +} + +const ( + contractName = "MultiOCR3Helper" + methodTransmitWithSignatures = "TransmitWithSignatures" + methodTransmitWithoutSignatures = "TransmitWithoutSignatures" +) + +func chainWriterConfigRaw(fromAddress common.Address, maxGasPrice *assets.Wei) evmrelaytypes.ChainWriterConfig { + return evmrelaytypes.ChainWriterConfig{ + Contracts: map[string]*evmrelaytypes.ContractConfig{ + contractName: { + ContractABI: multi_ocr3_helper.MultiOCR3HelperABI, + Configs: map[string]*evmrelaytypes.ChainWriterDefinition{ + methodTransmitWithSignatures: { + ChainSpecificName: "transmitWithSignatures", + GasLimit: 1e6, + FromAddress: fromAddress, + }, + methodTransmitWithoutSignatures: { + ChainSpecificName: "transmitWithoutSignatures", + GasLimit: 1e6, + FromAddress: fromAddress, + }, + }, + }, + }, + SendStrategy: txmgrcommon.NewSendEveryStrategy(), + MaxGasPrice: maxGasPrice, + } +} + +func makeTestEvmTxm( + t *testing.T, + db *sqlx.DB, + ethClient client.Client, + keyStore keystore.Eth) (txmgr.TxManager, gas.EvmFeeEstimator) { + config, dbConfig, evmConfig := MakeTestConfigs(t) + + estimator, err := gas.NewEstimator(logger.TestLogger(t), ethClient, config, evmConfig.GasEstimator()) + require.NoError(t, err, "failed to create gas estimator") + + lggr := logger.TestLogger(t) + lpOpts := logpoller.Opts{ + PollPeriod: 100 * time.Millisecond, + FinalityDepth: 2, + BackfillBatchSize: 3, + RpcBatchSize: 2, + KeepFinalizedBlocksDepth: 1000, + } + + chainID := big.NewInt(1337) + headSaver := headtracker.NewHeadSaver( + logger.NullLogger, + headtracker.NewORM(*chainID, db), + evmConfig, + evmConfig.HeadTrackerConfig, + ) + + broadcaster := headtracker.NewHeadBroadcaster(logger.NullLogger) + require.NoError(t, broadcaster.Start(testutils.Context(t)), "failed to start head broadcaster") + t.Cleanup(func() { require.NoError(t, broadcaster.Close()) }) + + ht := headtracker.NewHeadTracker( + logger.NullLogger, + ethClient, + evmConfig, + evmConfig.HeadTrackerConfig, + broadcaster, + headSaver, + mailbox.NewMonitor("contract_transmitter_test", logger.NullLogger), + ) + require.NoError(t, ht.Start(testutils.Context(t)), "failed to start head tracker") + t.Cleanup(func() { require.NoError(t, ht.Close()) }) + + lp := logpoller.NewLogPoller(logpoller.NewORM(testutils.FixtureChainID, db, logger.NullLogger), + ethClient, logger.NullLogger, ht, lpOpts) + require.NoError(t, lp.Start(testutils.Context(t)), "failed to start log poller") + t.Cleanup(func() { require.NoError(t, lp.Close()) }) + + // logic for building components (from evm/evm_txm.go) ------- + lggr.Infow("Initializing EVM transaction manager", + "bumpTxDepth", evmConfig.GasEstimator().BumpTxDepth(), + "maxInFlightTransactions", config.EvmConfig.Transactions().MaxInFlight(), + "maxQueuedTransactions", config.EvmConfig.Transactions().MaxQueued(), + "nonceAutoSync", evmConfig.NonceAutoSync(), + "limitDefault", evmConfig.GasEstimator().LimitDefault(), + ) + + txm, err := txmgr.NewTxm( + db, + config, + config.EvmConfig.GasEstimator(), + config.EvmConfig.Transactions(), + nil, + dbConfig, + dbConfig.Listener(), + ethClient, + lggr, + lp, + keyStore, + estimator) + require.NoError(t, err, "can't create tx manager") + + _, unsub := broadcaster.Subscribe(txm) + t.Cleanup(unsub) + + return txm, estimator +} + +// Code below copied/pasted and slightly modified in order to work from core/chains/evm/txmgr/test_helpers.go. + +func ptr[T any](t T) *T { return &t } + +type TestDatabaseConfig struct { + config.Database + defaultQueryTimeout time.Duration +} + +func (d *TestDatabaseConfig) DefaultQueryTimeout() time.Duration { + return d.defaultQueryTimeout +} + +func (d *TestDatabaseConfig) LogSQL() bool { + return false +} + +type TestListenerConfig struct { + config.Listener +} + +func (l *TestListenerConfig) FallbackPollInterval() time.Duration { + return 1 * time.Minute +} + +func (d *TestDatabaseConfig) Listener() config.Listener { + return &TestListenerConfig{} +} + +type TestHeadTrackerConfig struct{} + +// FinalityTagBypass implements config.HeadTracker. +func (t *TestHeadTrackerConfig) FinalityTagBypass() bool { + return false +} + +// HistoryDepth implements config.HeadTracker. +func (t *TestHeadTrackerConfig) HistoryDepth() uint32 { + return 50 +} + +// MaxAllowedFinalityDepth implements config.HeadTracker. +func (t *TestHeadTrackerConfig) MaxAllowedFinalityDepth() uint32 { + return 100 +} + +// MaxBufferSize implements config.HeadTracker. +func (t *TestHeadTrackerConfig) MaxBufferSize() uint32 { + return 100 +} + +// SamplingInterval implements config.HeadTracker. +func (t *TestHeadTrackerConfig) SamplingInterval() time.Duration { + return 1 * time.Second +} + +var _ evmconfig.HeadTracker = (*TestHeadTrackerConfig)(nil) + +type TestEvmConfig struct { + evmconfig.EVM + HeadTrackerConfig evmconfig.HeadTracker + MaxInFlight uint32 + ReaperInterval time.Duration + ReaperThreshold time.Duration + ResendAfterThreshold time.Duration + BumpThreshold uint64 + MaxQueued uint64 + Enabled bool + Threshold uint32 + MinAttempts uint32 + DetectionApiUrl *url.URL +} + +func (e *TestEvmConfig) FinalityTagEnabled() bool { + return false +} + +func (e *TestEvmConfig) FinalityDepth() uint32 { + return 42 +} + +func (e *TestEvmConfig) FinalizedBlockOffset() uint32 { + return 42 +} + +func (e *TestEvmConfig) BlockEmissionIdleWarningThreshold() time.Duration { + return 10 * time.Second +} + +func (e *TestEvmConfig) Transactions() evmconfig.Transactions { + return &transactionsConfig{e: e, autoPurge: &autoPurgeConfig{}} +} + +func (e *TestEvmConfig) NonceAutoSync() bool { return true } + +func (e *TestEvmConfig) ChainType() chaintype.ChainType { return "" } + +type TestGasEstimatorConfig struct { + bumpThreshold uint64 +} + +func (g *TestGasEstimatorConfig) BlockHistory() evmconfig.BlockHistory { + return &TestBlockHistoryConfig{} +} + +func (g *TestGasEstimatorConfig) EIP1559DynamicFees() bool { return false } +func (g *TestGasEstimatorConfig) LimitDefault() uint64 { return 1e6 } +func (g *TestGasEstimatorConfig) BumpPercent() uint16 { return 2 } +func (g *TestGasEstimatorConfig) BumpThreshold() uint64 { return g.bumpThreshold } +func (g *TestGasEstimatorConfig) BumpMin() *assets.Wei { return assets.GWei(1) } +func (g *TestGasEstimatorConfig) FeeCapDefault() *assets.Wei { return assets.GWei(1) } +func (g *TestGasEstimatorConfig) PriceDefault() *assets.Wei { return assets.GWei(1) } +func (g *TestGasEstimatorConfig) TipCapDefault() *assets.Wei { return assets.GWei(1) } +func (g *TestGasEstimatorConfig) TipCapMin() *assets.Wei { return assets.GWei(1) } +func (g *TestGasEstimatorConfig) LimitMax() uint64 { return 0 } +func (g *TestGasEstimatorConfig) LimitMultiplier() float32 { return 1 } +func (g *TestGasEstimatorConfig) BumpTxDepth() uint32 { return 42 } +func (g *TestGasEstimatorConfig) LimitTransfer() uint64 { return 42 } +func (g *TestGasEstimatorConfig) PriceMax() *assets.Wei { return assets.GWei(1) } +func (g *TestGasEstimatorConfig) PriceMin() *assets.Wei { return assets.GWei(1) } +func (g *TestGasEstimatorConfig) Mode() string { return "FixedPrice" } +func (g *TestGasEstimatorConfig) LimitJobType() evmconfig.LimitJobType { + return &TestLimitJobTypeConfig{} +} +func (g *TestGasEstimatorConfig) PriceMaxKey(addr common.Address) *assets.Wei { + return assets.GWei(1) +} + +func (e *TestEvmConfig) GasEstimator() evmconfig.GasEstimator { + return &TestGasEstimatorConfig{bumpThreshold: e.BumpThreshold} +} + +type TestLimitJobTypeConfig struct { +} + +func (l *TestLimitJobTypeConfig) OCR() *uint32 { return ptr(uint32(0)) } +func (l *TestLimitJobTypeConfig) OCR2() *uint32 { return ptr(uint32(0)) } +func (l *TestLimitJobTypeConfig) DR() *uint32 { return ptr(uint32(0)) } +func (l *TestLimitJobTypeConfig) FM() *uint32 { return ptr(uint32(0)) } +func (l *TestLimitJobTypeConfig) Keeper() *uint32 { return ptr(uint32(0)) } +func (l *TestLimitJobTypeConfig) VRF() *uint32 { return ptr(uint32(0)) } + +type TestBlockHistoryConfig struct { + evmconfig.BlockHistory +} + +func (b *TestBlockHistoryConfig) BatchSize() uint32 { return 42 } +func (b *TestBlockHistoryConfig) BlockDelay() uint16 { return 42 } +func (b *TestBlockHistoryConfig) BlockHistorySize() uint16 { return 42 } +func (b *TestBlockHistoryConfig) EIP1559FeeCapBufferBlocks() uint16 { return 42 } +func (b *TestBlockHistoryConfig) TransactionPercentile() uint16 { return 42 } + +type transactionsConfig struct { + evmconfig.Transactions + e *TestEvmConfig + autoPurge evmconfig.AutoPurgeConfig +} + +func (*transactionsConfig) ForwardersEnabled() bool { return false } +func (t *transactionsConfig) MaxInFlight() uint32 { return t.e.MaxInFlight } +func (t *transactionsConfig) MaxQueued() uint64 { return t.e.MaxQueued } +func (t *transactionsConfig) ReaperInterval() time.Duration { return t.e.ReaperInterval } +func (t *transactionsConfig) ReaperThreshold() time.Duration { return t.e.ReaperThreshold } +func (t *transactionsConfig) ResendAfterThreshold() time.Duration { return t.e.ResendAfterThreshold } +func (t *transactionsConfig) AutoPurge() evmconfig.AutoPurgeConfig { return t.autoPurge } + +type autoPurgeConfig struct { + evmconfig.AutoPurgeConfig +} + +func (a *autoPurgeConfig) Enabled() bool { return false } + +type MockConfig struct { + EvmConfig *TestEvmConfig + RpcDefaultBatchSize uint32 + finalityDepth uint32 + finalityTagEnabled bool +} + +func (c *MockConfig) EVM() evmconfig.EVM { + return c.EvmConfig +} + +func (c *MockConfig) NonceAutoSync() bool { return true } +func (c *MockConfig) ChainType() chaintype.ChainType { return "" } +func (c *MockConfig) FinalityDepth() uint32 { return c.finalityDepth } +func (c *MockConfig) SetFinalityDepth(fd uint32) { c.finalityDepth = fd } +func (c *MockConfig) FinalityTagEnabled() bool { return c.finalityTagEnabled } +func (c *MockConfig) RPCDefaultBatchSize() uint32 { return c.RpcDefaultBatchSize } + +func MakeTestConfigs(t *testing.T) (*MockConfig, *TestDatabaseConfig, *TestEvmConfig) { + db := &TestDatabaseConfig{defaultQueryTimeout: utils.DefaultQueryTimeout} + ec := &TestEvmConfig{ + HeadTrackerConfig: &TestHeadTrackerConfig{}, + BumpThreshold: 42, + MaxInFlight: uint32(42), + MaxQueued: uint64(0), + ReaperInterval: time.Duration(0), + ReaperThreshold: time.Duration(0), + } + config := &MockConfig{EvmConfig: ec} + return config, db, ec +} diff --git a/core/services/ccipcapability/ocrimpls/keyring.go b/core/services/ccipcapability/ocrimpls/keyring.go new file mode 100644 index 0000000000..4b15c75b09 --- /dev/null +++ b/core/services/ccipcapability/ocrimpls/keyring.go @@ -0,0 +1,61 @@ +package ocrimpls + +import ( + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3types" + "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + + "github.com/smartcontractkit/chainlink/v2/core/logger" +) + +var _ ocr3types.OnchainKeyring[[]byte] = &ocr3Keyring[[]byte]{} + +type ocr3Keyring[RI any] struct { + core types.OnchainKeyring + lggr logger.Logger +} + +func NewOnchainKeyring[RI any](keyring types.OnchainKeyring, lggr logger.Logger) *ocr3Keyring[RI] { + return &ocr3Keyring[RI]{ + core: keyring, + lggr: lggr.Named("OCR3Keyring"), + } +} + +func (w *ocr3Keyring[RI]) PublicKey() types.OnchainPublicKey { + return w.core.PublicKey() +} + +func (w *ocr3Keyring[RI]) MaxSignatureLength() int { + return w.core.MaxSignatureLength() +} + +func (w *ocr3Keyring[RI]) Sign(configDigest types.ConfigDigest, seqNr uint64, r ocr3types.ReportWithInfo[RI]) (signature []byte, err error) { + epoch, round := uint64ToUint32AndUint8(seqNr) + rCtx := types.ReportContext{ + ReportTimestamp: types.ReportTimestamp{ + ConfigDigest: configDigest, + Epoch: epoch, + Round: round, + }, + } + + w.lggr.Debugw("signing report", "configDigest", configDigest.Hex(), "seqNr", seqNr, "report", hexutil.Encode(r.Report)) + + return w.core.Sign(rCtx, r.Report) +} + +func (w *ocr3Keyring[RI]) Verify(key types.OnchainPublicKey, configDigest types.ConfigDigest, seqNr uint64, r ocr3types.ReportWithInfo[RI], signature []byte) bool { + epoch, round := uint64ToUint32AndUint8(seqNr) + rCtx := types.ReportContext{ + ReportTimestamp: types.ReportTimestamp{ + ConfigDigest: configDigest, + Epoch: epoch, + Round: round, + }, + } + + w.lggr.Debugw("verifying report", "configDigest", configDigest.Hex(), "seqNr", seqNr, "report", hexutil.Encode(r.Report)) + + return w.core.Verify(key, rCtx, r.Report, signature) +} diff --git a/core/services/ccipcapability/oraclecreator/inprocess.go b/core/services/ccipcapability/oraclecreator/inprocess.go new file mode 100644 index 0000000000..36b4e0975d --- /dev/null +++ b/core/services/ccipcapability/oraclecreator/inprocess.go @@ -0,0 +1,339 @@ +package oraclecreator + +import ( + "context" + "fmt" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/google/uuid" + "github.com/prometheus/client_golang/prometheus" + + chainsel "github.com/smartcontractkit/chain-selectors" + + "github.com/smartcontractkit/chainlink-ccip/pkg/consts" + + "github.com/smartcontractkit/libocr/commontypes" + libocr3 "github.com/smartcontractkit/libocr/offchainreporting2plus" + "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3types" + ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + + commitocr3 "github.com/smartcontractkit/chainlink-ccip/commit" + execocr3 "github.com/smartcontractkit/chainlink-ccip/execute" + ccipreaderpkg "github.com/smartcontractkit/chainlink-ccip/pkg/reader" + cciptypes "github.com/smartcontractkit/chainlink-common/pkg/types/ccipocr3" + + "github.com/smartcontractkit/chainlink-common/pkg/types" + "github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm" + "github.com/smartcontractkit/chainlink/v2/core/logger" + evmconfigs "github.com/smartcontractkit/chainlink/v2/core/services/ccipcapability/configs/evm" + "github.com/smartcontractkit/chainlink/v2/core/services/ccipcapability/ocrimpls" + cctypes "github.com/smartcontractkit/chainlink/v2/core/services/ccipcapability/types" + "github.com/smartcontractkit/chainlink/v2/core/services/job" + "github.com/smartcontractkit/chainlink/v2/core/services/keystore/chaintype" + "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/ocr2key" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr3/plugins/ccipevm" + "github.com/smartcontractkit/chainlink/v2/core/services/ocrcommon" + "github.com/smartcontractkit/chainlink/v2/core/services/relay" + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm" + evmrelaytypes "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/types" + "github.com/smartcontractkit/chainlink/v2/core/services/synchronization" + "github.com/smartcontractkit/chainlink/v2/core/services/telemetry" +) + +var _ cctypes.OracleCreator = &inprocessOracleCreator{} + +// inprocessOracleCreator creates oracles that reference plugins running +// in the same process as the chainlink node, i.e not LOOPPs. +type inprocessOracleCreator struct { + ocrKeyBundles map[string]ocr2key.KeyBundle + transmitters map[types.RelayID][]string + chains legacyevm.LegacyChainContainer + peerWrapper *ocrcommon.SingletonPeerWrapper + externalJobID uuid.UUID + jobID int32 + isNewlyCreatedJob bool + pluginConfig job.JSONConfig + db ocr3types.Database + lggr logger.Logger + monitoringEndpointGen telemetry.MonitoringEndpointGenerator + bootstrapperLocators []commontypes.BootstrapperLocator + homeChainReader ccipreaderpkg.HomeChain +} + +func New( + ocrKeyBundles map[string]ocr2key.KeyBundle, + transmitters map[types.RelayID][]string, + chains legacyevm.LegacyChainContainer, + peerWrapper *ocrcommon.SingletonPeerWrapper, + externalJobID uuid.UUID, + jobID int32, + isNewlyCreatedJob bool, + pluginConfig job.JSONConfig, + db ocr3types.Database, + lggr logger.Logger, + monitoringEndpointGen telemetry.MonitoringEndpointGenerator, + bootstrapperLocators []commontypes.BootstrapperLocator, + homeChainReader ccipreaderpkg.HomeChain, +) cctypes.OracleCreator { + return &inprocessOracleCreator{ + ocrKeyBundles: ocrKeyBundles, + transmitters: transmitters, + chains: chains, + peerWrapper: peerWrapper, + externalJobID: externalJobID, + jobID: jobID, + isNewlyCreatedJob: isNewlyCreatedJob, + pluginConfig: pluginConfig, + db: db, + lggr: lggr, + monitoringEndpointGen: monitoringEndpointGen, + bootstrapperLocators: bootstrapperLocators, + homeChainReader: homeChainReader, + } +} + +// CreateBootstrapOracle implements types.OracleCreator. +func (i *inprocessOracleCreator) CreateBootstrapOracle(config cctypes.OCR3ConfigWithMeta) (cctypes.CCIPOracle, error) { + // Assuming that the chain selector is referring to an evm chain for now. + // TODO: add an api that returns chain family. + chainID, err := chainsel.ChainIdFromSelector(uint64(config.Config.ChainSelector)) + if err != nil { + return nil, fmt.Errorf("failed to get chain ID from selector: %w", err) + } + + destChainFamily := chaintype.EVM + destRelayID := types.NewRelayID(string(destChainFamily), fmt.Sprintf("%d", chainID)) + + bootstrapperArgs := libocr3.BootstrapperArgs{ + BootstrapperFactory: i.peerWrapper.Peer2, + V2Bootstrappers: i.bootstrapperLocators, + ContractConfigTracker: ocrimpls.NewConfigTracker(config), + Database: i.db, + LocalConfig: defaultLocalConfig(), + Logger: ocrcommon.NewOCRWrapper( + i.lggr. + Named("CCIPBootstrap"). + Named(destRelayID.String()). + Named(config.Config.ChainSelector.String()). + Named(hexutil.Encode(config.Config.OfframpAddress)), + false, /* traceLogging */ + func(ctx context.Context, msg string) {}), + MonitoringEndpoint: i.monitoringEndpointGen.GenMonitoringEndpoint( + string(destChainFamily), + destRelayID.ChainID, + hexutil.Encode(config.Config.OfframpAddress), + synchronization.OCR3CCIPBootstrap, + ), + OffchainConfigDigester: ocrimpls.NewConfigDigester(config.ConfigDigest), + } + bootstrapper, err := libocr3.NewBootstrapper(bootstrapperArgs) + if err != nil { + return nil, err + } + return bootstrapper, nil +} + +// CreatePluginOracle implements types.OracleCreator. +func (i *inprocessOracleCreator) CreatePluginOracle(pluginType cctypes.PluginType, config cctypes.OCR3ConfigWithMeta) (cctypes.CCIPOracle, error) { + // Assuming that the chain selector is referring to an evm chain for now. + // TODO: add an api that returns chain family. + destChainID, err := chainsel.ChainIdFromSelector(uint64(config.Config.ChainSelector)) + if err != nil { + return nil, fmt.Errorf("failed to get chain ID from selector %d: %w", config.Config.ChainSelector, err) + } + destChainFamily := relay.NetworkEVM + destRelayID := types.NewRelayID(destChainFamily, fmt.Sprintf("%d", destChainID)) + + // this is so that we can use the msg hasher and report encoder from that dest chain relayer's provider. + contractReaders := make(map[cciptypes.ChainSelector]types.ContractReader) + chainWriters := make(map[cciptypes.ChainSelector]types.ChainWriter) + for _, chain := range i.chains.Slice() { + var chainReaderConfig evmrelaytypes.ChainReaderConfig + if chain.ID().Uint64() == destChainID { + chainReaderConfig = evmconfigs.DestReaderConfig() + } else { + chainReaderConfig = evmconfigs.SourceReaderConfig() + } + cr, err2 := evm.NewChainReaderService( + context.Background(), + i.lggr. + Named("EVMChainReaderService"). + Named(chain.ID().String()). + Named(pluginType.String()), + chain.LogPoller(), + chain.Client(), + chainReaderConfig, + ) + if err2 != nil { + return nil, fmt.Errorf("failed to create contract reader for chain %s: %w", chain.ID(), err2) + } + + if chain.ID().Uint64() == destChainID { + // bind the chain reader to the dest chain's offramp. + offrampAddressHex := common.BytesToAddress(config.Config.OfframpAddress).Hex() + err3 := cr.Bind(context.Background(), []types.BoundContract{ + { + Address: offrampAddressHex, + Name: consts.ContractNameOffRamp, + }, + }) + if err3 != nil { + return nil, fmt.Errorf("failed to bind chain reader for dest chain %s's offramp at %s: %w", chain.ID(), offrampAddressHex, err3) + } + } + + // TODO: figure out shutdown. + // maybe from the plugin directly? + err2 = cr.Start(context.Background()) + if err2 != nil { + return nil, fmt.Errorf("failed to start contract reader for chain %s: %w", chain.ID(), err2) + } + + // Even though we only write to the dest chain, we need to create chain writers for all chains + // we know about in order to post gas prices on the dest. + var fromAddress common.Address + transmitter, ok := i.transmitters[types.NewRelayID(relay.NetworkEVM, chain.ID().String())] + if ok { + fromAddress = common.HexToAddress(transmitter[0]) + } + cw, err2 := evm.NewChainWriterService( + i.lggr.Named("EVMChainWriterService"). + Named(chain.ID().String()). + Named(pluginType.String()), + chain.Client(), + chain.TxManager(), + chain.GasEstimator(), + evmconfigs.ChainWriterConfigRaw(fromAddress, chain.Config().EVM().GasEstimator().PriceMaxKey(fromAddress)), + ) + if err2 != nil { + return nil, fmt.Errorf("failed to create chain writer for chain %s: %w", chain.ID(), err2) + } + + // TODO: figure out shutdown. + // maybe from the plugin directly? + err2 = cw.Start(context.Background()) + if err2 != nil { + return nil, fmt.Errorf("failed to start chain writer for chain %s: %w", chain.ID(), err2) + } + + chainSelector, ok := chainsel.EvmChainIdToChainSelector()[chain.ID().Uint64()] + if !ok { + return nil, fmt.Errorf("failed to get chain selector from chain ID %s", chain.ID()) + } + + contractReaders[cciptypes.ChainSelector(chainSelector)] = cr + chainWriters[cciptypes.ChainSelector(chainSelector)] = cw + } + + // build the onchain keyring. it will be the signing key for the destination chain family. + keybundle, ok := i.ocrKeyBundles[destChainFamily] + if !ok { + return nil, fmt.Errorf("no OCR key bundle found for chain family %s, forgot to create one?", destChainFamily) + } + onchainKeyring := ocrimpls.NewOnchainKeyring[[]byte](keybundle, i.lggr) + + // build the contract transmitter + // assume that we are using the first account in the keybundle as the from account + // and that we are able to transmit to the dest chain. + // TODO: revisit this in the future, since not all oracles will be able to transmit to the dest chain. + destChainWriter, ok := chainWriters[config.Config.ChainSelector] + if !ok { + return nil, fmt.Errorf("no chain writer found for dest chain selector %d, can't create contract transmitter", + config.Config.ChainSelector) + } + destFromAccounts, ok := i.transmitters[destRelayID] + if !ok { + return nil, fmt.Errorf("no transmitter found for dest relay ID %s, can't create contract transmitter", destRelayID) + } + + //TODO: Extract the correct transmitter address from the destsFromAccount + var factory ocr3types.ReportingPluginFactory[[]byte] + var transmitter ocr3types.ContractTransmitter[[]byte] + if config.Config.PluginType == uint8(cctypes.PluginTypeCCIPCommit) { + factory = commitocr3.NewPluginFactory( + i.lggr. + Named("CCIPCommitPlugin"). + Named(destRelayID.String()). + Named(hexutil.Encode(config.Config.OfframpAddress)), + ccipreaderpkg.OCR3ConfigWithMeta(config), + ccipevm.NewCommitPluginCodecV1(), + ccipevm.NewMessageHasherV1(), + i.homeChainReader, + contractReaders, + chainWriters, + ) + transmitter = ocrimpls.NewCommitContractTransmitter[[]byte](destChainWriter, + ocrtypes.Account(destFromAccounts[0]), + hexutil.Encode(config.Config.OfframpAddress), // TODO: this works for evm only, how about non-evm? + ) + } else if config.Config.PluginType == uint8(cctypes.PluginTypeCCIPExec) { + factory = execocr3.NewPluginFactory( + i.lggr. + Named("CCIPExecPlugin"). + Named(destRelayID.String()). + Named(hexutil.Encode(config.Config.OfframpAddress)), + ccipreaderpkg.OCR3ConfigWithMeta(config), + ccipevm.NewExecutePluginCodecV1(), + ccipevm.NewMessageHasherV1(), + i.homeChainReader, + contractReaders, + chainWriters, + ) + transmitter = ocrimpls.NewExecContractTransmitter[[]byte](destChainWriter, + ocrtypes.Account(destFromAccounts[0]), + hexutil.Encode(config.Config.OfframpAddress), // TODO: this works for evm only, how about non-evm? + ) + } else { + return nil, fmt.Errorf("unsupported plugin type %d", config.Config.PluginType) + } + + oracleArgs := libocr3.OCR3OracleArgs[[]byte]{ + BinaryNetworkEndpointFactory: i.peerWrapper.Peer2, + Database: i.db, + V2Bootstrappers: i.bootstrapperLocators, + ContractConfigTracker: ocrimpls.NewConfigTracker(config), + ContractTransmitter: transmitter, + LocalConfig: defaultLocalConfig(), + Logger: ocrcommon.NewOCRWrapper( + i.lggr. + Named(fmt.Sprintf("CCIP%sOCR3", pluginType.String())). + Named(destRelayID.String()). + Named(hexutil.Encode(config.Config.OfframpAddress)), + false, + func(ctx context.Context, msg string) {}), + MetricsRegisterer: prometheus.WrapRegistererWith(map[string]string{"name": fmt.Sprintf("commit-%d", config.Config.ChainSelector)}, prometheus.DefaultRegisterer), + MonitoringEndpoint: i.monitoringEndpointGen.GenMonitoringEndpoint( + destChainFamily, + destRelayID.ChainID, + string(config.Config.OfframpAddress), + synchronization.OCR3CCIPCommit, + ), + OffchainConfigDigester: ocrimpls.NewConfigDigester(config.ConfigDigest), + OffchainKeyring: keybundle, + OnchainKeyring: onchainKeyring, + ReportingPluginFactory: factory, + } + oracle, err := libocr3.NewOracle(oracleArgs) + if err != nil { + return nil, err + } + return oracle, nil +} + +func defaultLocalConfig() ocrtypes.LocalConfig { + return ocrtypes.LocalConfig{ + BlockchainTimeout: 10 * time.Second, + // Config tracking is handled by the launcher, since we're doing blue-green + // deployments we're not going to be using OCR's built-in config switching, + // which always shuts down the previous instance. + ContractConfigConfirmations: 1, + SkipContractConfigConfirmations: true, + ContractConfigTrackerPollInterval: 10 * time.Second, + ContractTransmitterTransmitTimeout: 10 * time.Second, + DatabaseTimeout: 10 * time.Second, + MinOCR2MaxDurationQuery: 1 * time.Second, + DevelopmentMode: "false", + } +} diff --git a/core/services/ccipcapability/oraclecreator/inprocess_test.go b/core/services/ccipcapability/oraclecreator/inprocess_test.go new file mode 100644 index 0000000000..dfc5aac10e --- /dev/null +++ b/core/services/ccipcapability/oraclecreator/inprocess_test.go @@ -0,0 +1,238 @@ +package oraclecreator_test + +import ( + "fmt" + "testing" + "time" + + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/google/uuid" + "github.com/hashicorp/consul/sdk/freeport" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "gopkg.in/guregu/null.v4" + + chainsel "github.com/smartcontractkit/chain-selectors" + "github.com/smartcontractkit/libocr/offchainreporting2/types" + confighelper2 "github.com/smartcontractkit/libocr/offchainreporting2plus/confighelper" + "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3confighelper" + + "github.com/smartcontractkit/chainlink-ccip/pkg/reader" + "github.com/smartcontractkit/chainlink-common/pkg/types/ccipocr3" + "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" + + "github.com/smartcontractkit/libocr/commontypes" + + "github.com/smartcontractkit/chainlink/v2/core/bridges" + "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" + "github.com/smartcontractkit/chainlink/v2/core/logger" + "github.com/smartcontractkit/chainlink/v2/core/services/ccipcapability/oraclecreator" + cctypes "github.com/smartcontractkit/chainlink/v2/core/services/ccipcapability/types" + "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" + "github.com/smartcontractkit/chainlink/v2/core/services/job" + "github.com/smartcontractkit/chainlink/v2/core/services/keystore" + "github.com/smartcontractkit/chainlink/v2/core/services/keystore/chaintype" + "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/p2pkey" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2" + ocr2validate "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/validate" + "github.com/smartcontractkit/chainlink/v2/core/services/ocrcommon" + "github.com/smartcontractkit/chainlink/v2/core/services/pipeline" + "github.com/smartcontractkit/chainlink/v2/core/services/synchronization" + "github.com/smartcontractkit/chainlink/v2/core/services/telemetry" + "github.com/smartcontractkit/chainlink/v2/core/testdata/testspecs" + "github.com/smartcontractkit/chainlink/v2/core/utils" +) + +func TestOracleCreator_CreateBootstrap(t *testing.T) { + db := pgtest.NewSqlxDB(t) + + keyStore := keystore.New(db, utils.DefaultScryptParams, logger.NullLogger) + require.NoError(t, keyStore.Unlock(testutils.Context(t), cltest.Password), "unable to unlock keystore") + p2pKey, err := keyStore.P2P().Create(testutils.Context(t)) + require.NoError(t, err) + peerID := p2pKey.PeerID() + listenPort := freeport.GetOne(t) + generalConfig := configtest.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { + c.P2P.PeerID = ptr(peerID) + c.P2P.TraceLogging = ptr(false) + c.P2P.V2.Enabled = ptr(true) + c.P2P.V2.ListenAddresses = ptr([]string{fmt.Sprintf("127.0.0.1:%d", listenPort)}) + + c.OCR2.Enabled = ptr(true) + }) + peerWrapper := ocrcommon.NewSingletonPeerWrapper(keyStore, generalConfig.P2P(), generalConfig.OCR(), db, logger.NullLogger) + require.NoError(t, peerWrapper.Start(testutils.Context(t))) + t.Cleanup(func() { assert.NoError(t, peerWrapper.Close()) }) + + // NOTE: this is a bit of a hack to get the OCR2 job created in order to use the ocr db + // the ocr2_contract_configs table has a foreign key constraint on ocr2_oracle_spec_id + // which is passed into ocr2.NewDB. + pipelineORM := pipeline.NewORM(db, + logger.NullLogger, generalConfig.JobPipeline().MaxSuccessfulRuns()) + bridgesORM := bridges.NewORM(db) + + jobORM := job.NewORM(db, pipelineORM, bridgesORM, keyStore, logger.TestLogger(t)) + t.Cleanup(func() { assert.NoError(t, jobORM.Close()) }) + + jb, err := ocr2validate.ValidatedOracleSpecToml(testutils.Context(t), generalConfig.OCR2(), generalConfig.Insecure(), testspecs.GetOCR2EVMSpecMinimal(), nil) + require.NoError(t, err) + const juelsPerFeeCoinSource = ` + ds [type=http method=GET url="https://chain.link/ETH-USD"]; + ds_parse [type=jsonparse path="data.price" separator="."]; + ds_multiply [type=multiply times=100]; + ds -> ds_parse -> ds_multiply;` + + _, address := cltest.MustInsertRandomKey(t, keyStore.Eth()) + jb.Name = null.StringFrom("Job 1") + jb.OCR2OracleSpec.TransmitterID = null.StringFrom(address.String()) + jb.OCR2OracleSpec.PluginConfig["juelsPerFeeCoinSource"] = juelsPerFeeCoinSource + + err = jobORM.CreateJob(testutils.Context(t), &jb) + require.NoError(t, err) + + cltest.AssertCount(t, db, "ocr2_oracle_specs", 1) + cltest.AssertCount(t, db, "jobs", 1) + + var oracleSpecID int32 + err = db.Get(&oracleSpecID, "SELECT id FROM ocr2_oracle_specs LIMIT 1") + require.NoError(t, err) + + ocrdb := ocr2.NewDB(db, oracleSpecID, 0, logger.NullLogger) + + oc := oraclecreator.New( + nil, + nil, + nil, + peerWrapper, + uuid.Max, + 0, + false, + nil, + ocrdb, + logger.TestLogger(t), + &mockEndpointGen{}, + []commontypes.BootstrapperLocator{}, + nil, + ) + + chainSelector := chainsel.GETH_TESTNET.Selector + oracles, offchainConfig := ocrOffchainConfig(t, keyStore) + bootstrapP2PID, err := p2pkey.MakePeerID(oracles[0].PeerID) + require.NoError(t, err) + transmitters := func() [][]byte { + var transmitters [][]byte + for _, o := range oracles { + transmitters = append(transmitters, hexutil.MustDecode(string(o.TransmitAccount))) + } + return transmitters + }() + configDigest := ccipConfigDigest() + bootstrap, err := oc.CreateBootstrapOracle(cctypes.OCR3ConfigWithMeta{ + ConfigDigest: configDigest, + ConfigCount: 1, + Config: reader.OCR3Config{ + ChainSelector: ccipocr3.ChainSelector(chainSelector), + OfframpAddress: testutils.NewAddress().Bytes(), + PluginType: uint8(cctypes.PluginTypeCCIPCommit), + F: 1, + OffchainConfigVersion: 30, + BootstrapP2PIds: [][32]byte{bootstrapP2PID}, + P2PIds: func() [][32]byte { + var ids [][32]byte + for _, o := range oracles { + id, err2 := p2pkey.MakePeerID(o.PeerID) + require.NoError(t, err2) + ids = append(ids, id) + } + return ids + }(), + Signers: func() [][]byte { + var signers [][]byte + for _, o := range oracles { + signers = append(signers, o.OnchainPublicKey) + } + return signers + }(), + Transmitters: transmitters, + OffchainConfig: offchainConfig, + }, + }) + require.NoError(t, err) + require.NoError(t, bootstrap.Start()) + t.Cleanup(func() { assert.NoError(t, bootstrap.Close()) }) + + tests.AssertEventually(t, func() bool { + c, err := ocrdb.ReadConfig(testutils.Context(t)) + require.NoError(t, err) + return c.ConfigDigest == configDigest + }) +} + +func ccipConfigDigest() [32]byte { + rand32Bytes := testutils.Random32Byte() + // overwrite first four bytes to be 0x000a, to match the prefix in libocr. + rand32Bytes[0] = 0x00 + rand32Bytes[1] = 0x0a + return rand32Bytes +} + +type mockEndpointGen struct{} + +func (m *mockEndpointGen) GenMonitoringEndpoint(network string, chainID string, contractID string, telemType synchronization.TelemetryType) commontypes.MonitoringEndpoint { + return &telemetry.NoopAgent{} +} + +func ptr[T any](b T) *T { + return &b +} + +func ocrOffchainConfig(t *testing.T, ks keystore.Master) (oracles []confighelper2.OracleIdentityExtra, offchainConfig []byte) { + for i := 0; i < 4; i++ { + kb, err := ks.OCR2().Create(testutils.Context(t), chaintype.EVM) + require.NoError(t, err) + p2pKey, err := ks.P2P().Create(testutils.Context(t)) + require.NoError(t, err) + ethKey, err := ks.Eth().Create(testutils.Context(t)) + require.NoError(t, err) + oracles = append(oracles, confighelper2.OracleIdentityExtra{ + OracleIdentity: confighelper2.OracleIdentity{ + OffchainPublicKey: kb.OffchainPublicKey(), + OnchainPublicKey: types.OnchainPublicKey(kb.OnChainPublicKey()), + PeerID: p2pKey.ID(), + TransmitAccount: types.Account(ethKey.Address.Hex()), + }, + ConfigEncryptionPublicKey: kb.ConfigEncryptionPublicKey(), + }) + } + var schedule []int + for range oracles { + schedule = append(schedule, 1) + } + offchainConfig, onchainConfig := []byte{}, []byte{} + f := uint8(1) + + _, _, _, _, _, offchainConfig, err := ocr3confighelper.ContractSetConfigArgsForTests( + 30*time.Second, // deltaProgress + 10*time.Second, // deltaResend + 20*time.Second, // deltaInitial + 2*time.Second, // deltaRound + 20*time.Second, // deltaGrace + 10*time.Second, // deltaCertifiedCommitRequest + 10*time.Second, // deltaStage + 3, // rmax + schedule, + oracles, + offchainConfig, + 50*time.Millisecond, // maxDurationQuery + 5*time.Second, // maxDurationObservation + 10*time.Second, // maxDurationShouldAcceptAttestedReport + 10*time.Second, // maxDurationShouldTransmitAcceptedReport + int(f), + onchainConfig) + require.NoError(t, err, "failed to create contract config") + + return oracles, offchainConfig +} diff --git a/core/services/ccipcapability/validate/validate.go b/core/services/ccipcapability/validate/validate.go new file mode 100644 index 0000000000..04f4f4a495 --- /dev/null +++ b/core/services/ccipcapability/validate/validate.go @@ -0,0 +1,94 @@ +package validate + +import ( + "fmt" + + "github.com/google/uuid" + "github.com/pelletier/go-toml" + + "github.com/smartcontractkit/chainlink/v2/core/services/job" + "github.com/smartcontractkit/chainlink/v2/core/services/ocrcommon" +) + +// ValidatedCCIPSpec validates the given toml string as a CCIP spec. +func ValidatedCCIPSpec(tomlString string) (jb job.Job, err error) { + var spec job.CCIPSpec + tree, err := toml.Load(tomlString) + if err != nil { + return job.Job{}, fmt.Errorf("toml error on load: %w", err) + } + // Note this validates all the fields which implement an UnmarshalText + err = tree.Unmarshal(&spec) + if err != nil { + return job.Job{}, fmt.Errorf("toml unmarshal error on spec: %w", err) + } + err = tree.Unmarshal(&jb) + if err != nil { + return job.Job{}, fmt.Errorf("toml unmarshal error on job: %w", err) + } + jb.CCIPSpec = &spec + + if jb.Type != job.CCIP { + return job.Job{}, fmt.Errorf("the only supported type is currently 'ccip', got %s", jb.Type) + } + if jb.CCIPSpec.CapabilityLabelledName == "" { + return job.Job{}, fmt.Errorf("capabilityLabelledName must be set") + } + if jb.CCIPSpec.CapabilityVersion == "" { + return job.Job{}, fmt.Errorf("capabilityVersion must be set") + } + if jb.CCIPSpec.P2PKeyID == "" { + return job.Job{}, fmt.Errorf("p2pKeyID must be set") + } + if len(jb.CCIPSpec.P2PV2Bootstrappers) == 0 { + return job.Job{}, fmt.Errorf("p2pV2Bootstrappers must be set") + } + + // ensure that the P2PV2Bootstrappers is in the right format. + for _, bootstrapperLocator := range jb.CCIPSpec.P2PV2Bootstrappers { + // needs to be of the form @: + _, err := ocrcommon.ParseBootstrapPeers([]string{bootstrapperLocator}) + if err != nil { + return job.Job{}, fmt.Errorf("p2p v2 bootstrapper locator %s is not in the correct format: %w", bootstrapperLocator, err) + } + } + + return jb, nil +} + +type SpecArgs struct { + P2PV2Bootstrappers []string `toml:"p2pV2Bootstrappers"` + CapabilityVersion string `toml:"capabilityVersion"` + CapabilityLabelledName string `toml:"capabilityLabelledName"` + OCRKeyBundleIDs map[string]string `toml:"ocrKeyBundleIDs"` + P2PKeyID string `toml:"p2pKeyID"` + RelayConfigs map[string]any `toml:"relayConfigs"` + PluginConfig map[string]any `toml:"pluginConfig"` +} + +// NewCCIPSpecToml creates a new CCIP spec in toml format from the given spec args. +func NewCCIPSpecToml(spec SpecArgs) (string, error) { + type fullSpec struct { + SpecArgs + Type string `toml:"type"` + SchemaVersion uint64 `toml:"schemaVersion"` + Name string `toml:"name"` + ExternalJobID string `toml:"externalJobID"` + } + extJobID, err := uuid.NewRandom() + if err != nil { + return "", fmt.Errorf("failed to generate external job id: %w", err) + } + marshaled, err := toml.Marshal(fullSpec{ + SpecArgs: spec, + Type: "ccip", + SchemaVersion: 1, + Name: fmt.Sprintf("%s-%s", "ccip", extJobID.String()), + ExternalJobID: extJobID.String(), + }) + if err != nil { + return "", fmt.Errorf("failed to marshal spec into toml: %w", err) + } + + return string(marshaled), nil +} diff --git a/core/services/ccipcapability/validate/validate_test.go b/core/services/ccipcapability/validate/validate_test.go new file mode 100644 index 0000000000..e56da69336 --- /dev/null +++ b/core/services/ccipcapability/validate/validate_test.go @@ -0,0 +1,57 @@ +package validate_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink/v2/core/services/ccipcapability/validate" + "github.com/smartcontractkit/chainlink/v2/core/services/job" +) + +func TestNewCCIPSpecToml(t *testing.T) { + tests := []struct { + name string + specArgs validate.SpecArgs + want string + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := validate.NewCCIPSpecToml(tt.specArgs) + if tt.wantErr { + require.Error(t, err) + } else { + require.NoError(t, err) + require.Equal(t, tt.want, got) + } + }) + } +} + +func TestValidatedCCIPSpec(t *testing.T) { + type args struct { + tomlString string + } + tests := []struct { + name string + args args + wantJb job.Job + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotJb, err := validate.ValidatedCCIPSpec(tt.args.tomlString) + if tt.wantErr { + require.Error(t, err) + } else { + require.NoError(t, err) + require.Equal(t, tt.wantJb, gotJb) + } + }) + } +} diff --git a/core/services/chainlink/application.go b/core/services/chainlink/application.go index 7a928c46b8..da315f3791 100644 --- a/core/services/chainlink/application.go +++ b/core/services/chainlink/application.go @@ -27,6 +27,7 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/utils/mailbox" "github.com/smartcontractkit/chainlink/v2/core/capabilities" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" + "github.com/smartcontractkit/chainlink/v2/core/services/ccipcapability" "github.com/smartcontractkit/chainlink/v2/core/services/standardcapabilities" "github.com/smartcontractkit/chainlink/v2/core/static" @@ -208,22 +209,15 @@ func NewApplication(opts ApplicationOpts) (Application, error) { var externalPeerWrapper p2ptypes.PeerWrapper var getLocalNode func(ctx context.Context) (pkgcapabilities.Node, error) - if cfg.Capabilities().Peering().Enabled() { - externalPeer := externalp2p.NewExternalPeerWrapper(keyStore.P2P(), cfg.Capabilities().Peering(), opts.DS, globalLogger) - signer := externalPeer - externalPeerWrapper = externalPeer - - srvcs = append(srvcs, externalPeerWrapper) - - dispatcher := remote.NewDispatcher(externalPeerWrapper, signer, opts.CapabilitiesRegistry, globalLogger) + var capabilityRegistrySyncer registrysyncer.Syncer + if cfg.Capabilities().ExternalRegistry().Address() != "" { rid := cfg.Capabilities().ExternalRegistry().RelayID() registryAddress := cfg.Capabilities().ExternalRegistry().Address() relayer, err := relayerChainInterops.Get(rid) if err != nil { return nil, fmt.Errorf("could not fetch relayer %s configured for capabilities registry: %w", rid, err) } - registrySyncer, err := registrysyncer.New( globalLogger, relayer, @@ -233,16 +227,32 @@ func NewApplication(opts ApplicationOpts) (Application, error) { return nil, fmt.Errorf("could not configure syncer: %w", err) } + capabilityRegistrySyncer = registrySyncer + srvcs = append(srvcs, capabilityRegistrySyncer) + } + + if cfg.Capabilities().Peering().Enabled() { + if capabilityRegistrySyncer == nil { + return nil, errors.Errorf("peering enabled but no capability registry found") + } + externalPeer := externalp2p.NewExternalPeerWrapper(keyStore.P2P(), cfg.Capabilities().Peering(), opts.DS, globalLogger) + signer := externalPeer + externalPeerWrapper = externalPeer + + srvcs = append(srvcs, externalPeerWrapper) + + dispatcher := remote.NewDispatcher(externalPeerWrapper, signer, opts.CapabilitiesRegistry, globalLogger) + wfLauncher := capabilities.NewLauncher( globalLogger, externalPeerWrapper, dispatcher, opts.CapabilitiesRegistry, ) - registrySyncer.AddLauncher(wfLauncher) + capabilityRegistrySyncer.AddLauncher(wfLauncher) getLocalNode = wfLauncher.LocalNode - srvcs = append(srvcs, dispatcher, wfLauncher, registrySyncer) + srvcs = append(srvcs, dispatcher, wfLauncher) } // LOOPs can be created as options, in the case of LOOP relayers, or @@ -516,6 +526,18 @@ func NewApplication(opts ApplicationOpts) (Application, error) { cfg.Insecure(), opts.RelayerChainInteroperators, ) + delegates[job.CCIP] = ccipcapability.NewDelegate( + globalLogger, + loopRegistrarConfig, + pipelineRunner, + opts.RelayerChainInteroperators.LegacyEVMChains(), + capabilityRegistrySyncer, + opts.KeyStore, + opts.DS, + peerWrapper, + telemetryManager, + cfg.Capabilities(), + ) } else { globalLogger.Debug("Off-chain reporting v2 disabled") } diff --git a/core/services/job/job_orm_test.go b/core/services/job/job_orm_test.go index d9a4ea16ab..36ce025630 100644 --- a/core/services/job/job_orm_test.go +++ b/core/services/job/job_orm_test.go @@ -34,6 +34,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/blockhashstore" "github.com/smartcontractkit/chainlink/v2/core/services/blockheaderfeeder" + "github.com/smartcontractkit/chainlink/v2/core/services/ccipcapability/validate" "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" "github.com/smartcontractkit/chainlink/v2/core/services/directrequest" "github.com/smartcontractkit/chainlink/v2/core/services/job" @@ -431,6 +432,39 @@ func TestORM_DeleteJob_DeletesAssociatedRecords(t *testing.T) { cltest.AssertCount(t, db, "jobs", 0) }) + t.Run("it creates and deletes records for ccip jobs", func(t *testing.T) { + ctx := testutils.Context(t) + p2pKey, err := keyStore.P2P().Create(ctx) + require.NoError(t, err) + specArgs := validate.SpecArgs{ + P2PV2Bootstrappers: []string{ + fmt.Sprintf("%s@somechainlinknode.com:%d", p2pKey.ID(), 8080), + }, + CapabilityVersion: "v1.0.0", + CapabilityLabelledName: "ccip", + OCRKeyBundleIDs: map[string]string{ + relay.NetworkEVM: cltest.DefaultOCRKey.ID(), + }, + P2PKeyID: p2pKey.ID(), + PluginConfig: map[string]any{ + "pricesPipeline": ".... the pipeline ....", + }, + } + specToml, err := validate.NewCCIPSpecToml(specArgs) + require.NoError(t, err) + jb, err := validate.ValidatedCCIPSpec(specToml) + require.NoError(t, err) + + err = jobORM.CreateJob(ctx, &jb) + require.NoError(t, err) + cltest.AssertCount(t, db, "ccip_specs", 1) + cltest.AssertCount(t, db, "jobs", 1) + err = jobORM.DeleteJob(ctx, jb.ID) + require.NoError(t, err) + cltest.AssertCount(t, db, "ccip_specs", 0) + cltest.AssertCount(t, db, "jobs", 0) + }) + t.Run("it deletes records for webhook jobs", func(t *testing.T) { ctx := testutils.Context(t) ei := cltest.MustInsertExternalInitiator(t, bridges.NewORM(db)) @@ -631,6 +665,89 @@ func TestORM_CreateJob_VRFV2Plus(t *testing.T) { cltest.AssertCount(t, db, "jobs", 0) } +func TestORM_CreateJob_CCIP(t *testing.T) { + ctx := testutils.Context(t) + config := configtest.NewTestGeneralConfig(t) + db := pgtest.NewSqlxDB(t) + keyStore := cltest.NewKeyStore(t, db) + require.NoError(t, keyStore.OCR().Add(ctx, cltest.DefaultOCRKey)) + + p2pKey, err := keyStore.P2P().Create(ctx) + require.NoError(t, err) + + lggr := logger.TestLogger(t) + pipelineORM := pipeline.NewORM(db, lggr, config.JobPipeline().MaxSuccessfulRuns()) + bridgesORM := bridges.NewORM(db) + jobORM := NewTestORM(t, db, pipelineORM, bridgesORM, keyStore) + + specArgs := validate.SpecArgs{ + P2PV2Bootstrappers: []string{ + fmt.Sprintf("%s@somechainlinknode.com:%d", p2pKey.ID(), 8080), + }, + CapabilityVersion: "v1.0.0", + CapabilityLabelledName: "ccip", + OCRKeyBundleIDs: map[string]string{ + relay.NetworkEVM: cltest.DefaultOCRKey.ID(), + }, + P2PKeyID: p2pKey.ID(), + RelayConfigs: map[string]any{ + "hello": "world", + }, + PluginConfig: map[string]any{ + "pricesPipeline": ".... the pipeline ....", + }, + } + specToml, err := validate.NewCCIPSpecToml(specArgs) + require.NoError(t, err) + jb, err := validate.ValidatedCCIPSpec(specToml) + require.NoError(t, err) + + require.NoError(t, jobORM.CreateJob(ctx, &jb)) + cltest.AssertCount(t, db, "ccip_specs", 1) + cltest.AssertCount(t, db, "jobs", 1) + + var capabilityVersion string + require.NoError(t, db.Get(&capabilityVersion, `SELECT capability_version FROM ccip_specs LIMIT 1`)) + require.Equal(t, specArgs.CapabilityVersion, capabilityVersion) + + var capabilityLabelledName string + require.NoError(t, db.Get(&capabilityLabelledName, `SELECT capability_labelled_name FROM ccip_specs LIMIT 1`)) + require.Equal(t, specArgs.CapabilityLabelledName, capabilityLabelledName) + + var ocrKeyBundleIDs job.JSONConfig + require.NoError(t, db.Get(&ocrKeyBundleIDs, `SELECT ocr_key_bundle_ids FROM ccip_specs LIMIT 1`)) + actual, ok := ocrKeyBundleIDs[relay.NetworkEVM] + require.True(t, ok) + actualStr, ok := actual.(string) + require.True(t, ok) + require.Equal(t, specArgs.OCRKeyBundleIDs[relay.NetworkEVM], actualStr) + + var p2pV2Bootstrappers pq.StringArray + require.NoError(t, db.Get(&p2pV2Bootstrappers, `SELECT p2pv2_bootstrappers FROM ccip_specs LIMIT 1`)) + require.Len(t, p2pV2Bootstrappers, len(specArgs.P2PV2Bootstrappers)) + require.Equal(t, specArgs.P2PV2Bootstrappers[0], p2pV2Bootstrappers[0]) + + var p2pKeyID string + require.NoError(t, db.Get(&p2pKeyID, `SELECT p2p_key_id FROM ccip_specs LIMIT 1`)) + require.Equal(t, p2pKey.ID(), p2pKeyID) + + var relayConfigs job.JSONConfig + require.NoError(t, db.Get(&relayConfigs, `SELECT relay_configs FROM ccip_specs LIMIT 1`)) + actual, ok = relayConfigs["hello"] + require.True(t, ok) + actualStr, ok = actual.(string) + require.True(t, ok) + require.Equal(t, specArgs.RelayConfigs["hello"], actualStr) + + var pluginConfig job.JSONConfig + require.NoError(t, db.Get(&pluginConfig, `SELECT plugin_config FROM ccip_specs LIMIT 1`)) + actual, ok = pluginConfig["pricesPipeline"] + require.True(t, ok) + actualStr, ok = actual.(string) + require.True(t, ok) + require.Equal(t, specArgs.PluginConfig["pricesPipeline"], actualStr) +} + func TestORM_CreateJob_OCRBootstrap(t *testing.T) { ctx := testutils.Context(t) config := configtest.NewTestGeneralConfig(t) diff --git a/core/services/job/models.go b/core/services/job/models.go index a8c12cbece..d009585105 100644 --- a/core/services/job/models.go +++ b/core/services/job/models.go @@ -38,6 +38,7 @@ const ( BlockhashStore Type = (Type)(pipeline.BlockhashStoreJobType) Bootstrap Type = (Type)(pipeline.BootstrapJobType) Cron Type = (Type)(pipeline.CronJobType) + CCIP Type = (Type)(pipeline.CCIPJobType) DirectRequest Type = (Type)(pipeline.DirectRequestJobType) FluxMonitor Type = (Type)(pipeline.FluxMonitorJobType) Gateway Type = (Type)(pipeline.GatewayJobType) @@ -78,6 +79,7 @@ var ( BlockhashStore: false, Bootstrap: false, Cron: true, + CCIP: false, DirectRequest: true, FluxMonitor: true, Gateway: false, @@ -97,6 +99,7 @@ var ( BlockhashStore: false, Bootstrap: false, Cron: true, + CCIP: false, DirectRequest: true, FluxMonitor: false, Gateway: false, @@ -116,6 +119,7 @@ var ( BlockhashStore: 1, Bootstrap: 1, Cron: 1, + CCIP: 1, DirectRequest: 1, FluxMonitor: 1, Gateway: 1, @@ -175,6 +179,7 @@ type Job struct { StandardCapabilitiesSpecID *int32 StandardCapabilitiesSpec *StandardCapabilitiesSpec CCIPSpecID *int32 + CCIPSpec *CCIPSpec CCIPBootstrapSpecID *int32 JobSpecErrors []SpecError Type Type `toml:"type"` @@ -909,3 +914,48 @@ func (w *StandardCapabilitiesSpec) SetID(value string) error { w.ID = int32(ID) return nil } + +type CCIPSpec struct { + ID int32 + CreatedAt time.Time `toml:"-"` + UpdatedAt time.Time `toml:"-"` + + // P2PV2Bootstrappers is a list of "peer_id@ip_address:port" strings that are used to + // identify the bootstrap nodes of the P2P network. + // These bootstrappers will be used to bootstrap all CCIP DONs. + P2PV2Bootstrappers pq.StringArray `toml:"p2pV2Bootstrappers" db:"p2pv2_bootstrappers"` + + // CapabilityVersion is the semantic version of the CCIP capability. + // This capability version must exist in the onchain capability registry. + CapabilityVersion string `toml:"capabilityVersion" db:"capability_version"` + + // CapabilityLabelledName is the labelled name of the CCIP capability. + // Corresponds to the labelled name of the capability in the onchain capability registry. + CapabilityLabelledName string `toml:"capabilityLabelledName" db:"capability_labelled_name"` + + // OCRKeyBundleIDs is a mapping from chain type to OCR key bundle ID. + // These are explicitly specified here so that we don't run into strange errors auto-detecting + // the valid bundle, since nops can create as many bundles as they want. + // This will most likely never change for a particular CCIP capability version, + // since new chain families will likely require a new capability version. + // {"evm": "evm_key_bundle_id", "solana": "solana_key_bundle_id", ... } + OCRKeyBundleIDs JSONConfig `toml:"ocrKeyBundleIDs" db:"ocr_key_bundle_ids"` + + // RelayConfigs consists of relay specific configuration. + // Chain reader configurations are stored here, and are defined on a chain family basis, e.g + // we will have one chain reader config for EVM, one for solana, starknet, etc. + // Chain writer configurations are also stored here, and are also defined on a chain family basis, + // e.g we will have one chain writer config for EVM, one for solana, starknet, etc. + // See tests for examples of relay configs in TOML. + // { "evm": {"chainReader": {...}, "chainWriter": {...}}, "solana": {...}, ... } + // see core/services/relay/evm/types/types.go for EVM configs. + RelayConfigs JSONConfig `toml:"relayConfigs" db:"relay_configs"` + + // P2PKeyID is the ID of the P2P key of the node. + // This must be present in the capability registry otherwise the job will not start correctly. + P2PKeyID string `toml:"p2pKeyID" db:"p2p_key_id"` + + // PluginConfig contains plugin-specific config, like token price pipelines + // and RMN network info for offchain blessing. + PluginConfig JSONConfig `toml:"pluginConfig"` +} diff --git a/core/services/job/orm.go b/core/services/job/orm.go index 3f2d5c237d..3a29a0a567 100644 --- a/core/services/job/orm.go +++ b/core/services/job/orm.go @@ -427,7 +427,34 @@ func (o *orm) CreateJob(ctx context.Context, jb *Job) error { return errors.Wrap(err, "failed to create StandardCapabilities for jobSpec") } jb.StandardCapabilitiesSpecID = &specID - + case CCIP: + sql := `INSERT INTO ccip_specs ( + capability_version, + capability_labelled_name, + ocr_key_bundle_ids, + p2p_key_id, + p2pv2_bootstrappers, + relay_configs, + plugin_config, + created_at, + updated_at + ) VALUES ( + :capability_version, + :capability_labelled_name, + :ocr_key_bundle_ids, + :p2p_key_id, + :p2pv2_bootstrappers, + :relay_configs, + :plugin_config, + NOW(), + NOW() + ) + RETURNING id;` + specID, err := tx.prepareQuerySpecID(ctx, sql, jb.CCIPSpec) + if err != nil { + return errors.Wrap(err, "failed to create CCIPSpec for jobSpec") + } + jb.CCIPSpecID = &specID default: o.lggr.Panicf("Unsupported jb.Type: %v", jb.Type) } @@ -645,19 +672,19 @@ func (o *orm) InsertJob(ctx context.Context, job *Job) error { // if job has id, emplace otherwise insert with a new id. if job.ID == 0 { query = `INSERT INTO jobs (name, stream_id, schema_version, type, max_task_duration, ocr_oracle_spec_id, ocr2_oracle_spec_id, direct_request_spec_id, flux_monitor_spec_id, - keeper_spec_id, cron_spec_id, vrf_spec_id, webhook_spec_id, blockhash_store_spec_id, bootstrap_spec_id, block_header_feeder_spec_id, gateway_spec_id, - legacy_gas_station_server_spec_id, legacy_gas_station_sidecar_spec_id, workflow_spec_id, standard_capabilities_spec_id, external_job_id, gas_limit, forwarding_allowed, created_at) + keeper_spec_id, cron_spec_id, vrf_spec_id, webhook_spec_id, blockhash_store_spec_id, bootstrap_spec_id, block_header_feeder_spec_id, gateway_spec_id, + legacy_gas_station_server_spec_id, legacy_gas_station_sidecar_spec_id, workflow_spec_id, standard_capabilities_spec_id, ccip_spec_id, external_job_id, gas_limit, forwarding_allowed, created_at) VALUES (:name, :stream_id, :schema_version, :type, :max_task_duration, :ocr_oracle_spec_id, :ocr2_oracle_spec_id, :direct_request_spec_id, :flux_monitor_spec_id, - :keeper_spec_id, :cron_spec_id, :vrf_spec_id, :webhook_spec_id, :blockhash_store_spec_id, :bootstrap_spec_id, :block_header_feeder_spec_id, :gateway_spec_id, - :legacy_gas_station_server_spec_id, :legacy_gas_station_sidecar_spec_id, :workflow_spec_id, :standard_capabilities_spec_id, :external_job_id, :gas_limit, :forwarding_allowed, NOW()) + :keeper_spec_id, :cron_spec_id, :vrf_spec_id, :webhook_spec_id, :blockhash_store_spec_id, :bootstrap_spec_id, :block_header_feeder_spec_id, :gateway_spec_id, + :legacy_gas_station_server_spec_id, :legacy_gas_station_sidecar_spec_id, :workflow_spec_id, :standard_capabilities_spec_id, :ccip_spec_id, :external_job_id, :gas_limit, :forwarding_allowed, NOW()) RETURNING *;` } else { query = `INSERT INTO jobs (id, name, stream_id, schema_version, type, max_task_duration, ocr_oracle_spec_id, ocr2_oracle_spec_id, direct_request_spec_id, flux_monitor_spec_id, - keeper_spec_id, cron_spec_id, vrf_spec_id, webhook_spec_id, blockhash_store_spec_id, bootstrap_spec_id, block_header_feeder_spec_id, gateway_spec_id, - legacy_gas_station_server_spec_id, legacy_gas_station_sidecar_spec_id, workflow_spec_id, standard_capabilities_spec_id, external_job_id, gas_limit, forwarding_allowed, created_at) + keeper_spec_id, cron_spec_id, vrf_spec_id, webhook_spec_id, blockhash_store_spec_id, bootstrap_spec_id, block_header_feeder_spec_id, gateway_spec_id, + legacy_gas_station_server_spec_id, legacy_gas_station_sidecar_spec_id, workflow_spec_id, standard_capabilities_spec_id, ccip_spec_id, external_job_id, gas_limit, forwarding_allowed, created_at) VALUES (:id, :name, :stream_id, :schema_version, :type, :max_task_duration, :ocr_oracle_spec_id, :ocr2_oracle_spec_id, :direct_request_spec_id, :flux_monitor_spec_id, - :keeper_spec_id, :cron_spec_id, :vrf_spec_id, :webhook_spec_id, :blockhash_store_spec_id, :bootstrap_spec_id, :block_header_feeder_spec_id, :gateway_spec_id, - :legacy_gas_station_server_spec_id, :legacy_gas_station_sidecar_spec_id, :workflow_spec_id, :standard_capabilities_spec_id, :external_job_id, :gas_limit, :forwarding_allowed, NOW()) + :keeper_spec_id, :cron_spec_id, :vrf_spec_id, :webhook_spec_id, :blockhash_store_spec_id, :bootstrap_spec_id, :block_header_feeder_spec_id, :gateway_spec_id, + :legacy_gas_station_server_spec_id, :legacy_gas_station_sidecar_spec_id, :workflow_spec_id, :standard_capabilities_spec_id, :ccip_spec_id, :external_job_id, :gas_limit, :forwarding_allowed, NOW()) RETURNING *;` } query, args, err := tx.ds.BindNamed(query, job) @@ -701,7 +728,8 @@ func (o *orm) DeleteJob(ctx context.Context, id int32) error { block_header_feeder_spec_id, gateway_spec_id, workflow_spec_id, - standard_capabilities_spec_id + standard_capabilities_spec_id, + ccip_spec_id ), deleted_oracle_specs AS ( DELETE FROM ocr_oracle_specs WHERE id IN (SELECT ocr_oracle_spec_id FROM deleted_jobs) @@ -744,7 +772,10 @@ func (o *orm) DeleteJob(ctx context.Context, id int32) error { ), deleted_standardcapabilities_specs AS ( DELETE FROM standardcapabilities_specs WHERE id in (SELECT standard_capabilities_spec_id FROM deleted_jobs) - ), + ), + deleted_ccip_specs AS ( + DELETE FROM ccip_specs WHERE id in (SELECT ccip_spec_id FROM deleted_jobs) + ), deleted_job_pipeline_specs AS ( DELETE FROM job_pipeline_specs WHERE job_id IN (SELECT id FROM deleted_jobs) RETURNING pipeline_spec_id ) @@ -818,7 +849,7 @@ func (o *orm) FindJobs(ctx context.Context, offset, limit int) (jobs []Job, coun return fmt.Errorf("failed to query jobs count: %w", err) } - sql = `SELECT jobs.*, job_pipeline_specs.pipeline_spec_id as pipeline_spec_id + sql = `SELECT jobs.*, job_pipeline_specs.pipeline_spec_id as pipeline_spec_id FROM jobs JOIN job_pipeline_specs ON (jobs.id = job_pipeline_specs.job_id) ORDER BY jobs.created_at DESC, jobs.id DESC OFFSET $1 LIMIT $2;` @@ -1032,8 +1063,8 @@ func (o *orm) findJob(ctx context.Context, jb *Job, col string, arg interface{}) } func (o *orm) FindJobIDsWithBridge(ctx context.Context, name string) (jids []int32, err error) { - query := `SELECT - jobs.id, pipeline_specs.dot_dag_source + query := `SELECT + jobs.id, pipeline_specs.dot_dag_source FROM jobs JOIN job_pipeline_specs ON job_pipeline_specs.job_id = jobs.id JOIN pipeline_specs ON pipeline_specs.id = job_pipeline_specs.pipeline_spec_id @@ -1080,7 +1111,7 @@ func (o *orm) FindJobIDsWithBridge(ctx context.Context, name string) (jids []int func (o *orm) FindJobIDByWorkflow(ctx context.Context, spec WorkflowSpec) (jobID int32, err error) { stmt := ` SELECT jobs.id FROM jobs -INNER JOIN workflow_specs ws on jobs.workflow_spec_id = ws.id AND ws.workflow_owner = $1 AND ws.workflow_name = $2 +INNER JOIN workflow_specs ws on jobs.workflow_spec_id = ws.id AND ws.workflow_owner = $1 AND ws.workflow_name = $2 ` err = o.ds.GetContext(ctx, &jobID, stmt, spec.WorkflowOwner, spec.WorkflowName) if err != nil { @@ -1393,6 +1424,7 @@ func (o *orm) loadAllJobTypes(ctx context.Context, job *Job) error { o.loadJobType(ctx, job, "GatewaySpec", "gateway_specs", job.GatewaySpecID), o.loadJobType(ctx, job, "WorkflowSpec", "workflow_specs", job.WorkflowSpecID), o.loadJobType(ctx, job, "StandardCapabilitiesSpec", "standardcapabilities_specs", job.StandardCapabilitiesSpecID), + o.loadJobType(ctx, job, "CCIPSpec", "ccip_specs", job.CCIPSpecID), ) } @@ -1430,7 +1462,7 @@ func (o *orm) loadJobPipelineSpec(ctx context.Context, job *Job, id *int32) erro ctx, pipelineSpecRow, `SELECT pipeline_specs.*, job_pipeline_specs.job_id as job_id - FROM pipeline_specs + FROM pipeline_specs JOIN job_pipeline_specs ON(pipeline_specs.id = job_pipeline_specs.pipeline_spec_id) WHERE job_pipeline_specs.job_id = $1 AND job_pipeline_specs.pipeline_spec_id = $2`, job.ID, *id, diff --git a/core/services/ocr3/plugins/ccip_integration_tests/helpers.go b/core/services/ocr3/plugins/ccip_integration_tests/helpers.go index 18bcf6469c..51ef640b5e 100644 --- a/core/services/ocr3/plugins/ccip_integration_tests/helpers.go +++ b/core/services/ocr3/plugins/ccip_integration_tests/helpers.go @@ -30,6 +30,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/shared/generated/link_token" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" cctypes "github.com/smartcontractkit/chainlink/v2/core/services/ccipcapability/types" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr3/plugins/ccip_integration_tests/integrationhelpers" confighelper2 "github.com/smartcontractkit/libocr/offchainreporting2plus/confighelper" "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3confighelper" @@ -126,7 +127,7 @@ func createUniverses( backend, evm_2_evm_multi_onramp.EVM2EVMMultiOnRampStaticConfig{ LinkToken: linkToken.Address(), - ChainSelector: chainID, + ChainSelector: getSelector(chainID), RmnProxy: rmnProxy.Address(), MaxFeeJuelsPerMsg: big.NewInt(1e18), NonceManager: nonceManager.Address(), @@ -167,7 +168,7 @@ func createUniverses( owner, backend, evm_2_evm_multi_offramp.EVM2EVMMultiOffRampStaticConfig{ - ChainSelector: chainID, + ChainSelector: getSelector(chainID), RmnProxy: rmnProxy.Address(), TokenAdminRegistry: tokenAdminRegistry.Address(), NonceManager: nonceManager.Address(), @@ -208,6 +209,24 @@ func createUniverses( // Once we have all chains created and contracts deployed, we can set up the initial configurations and wire chains together connectUniverses(t, universes) + // print out all contract addresses for debugging purposes + for chainID, uni := range universes { + t.Logf("Chain ID: %d\n Chain Selector: %d\n LinkToken: %s\n WETH: %s\n Router: %s\n RMNProxy: %s\n RMN: %s\n OnRamp: %s\n OffRamp: %s\n PriceRegistry: %s\n TokenAdminRegistry: %s\n NonceManager: %s\n", + chainID, + getSelector(chainID), + uni.linkToken.Address().Hex(), + uni.weth.Address().Hex(), + uni.router.Address().Hex(), + uni.rmnProxy.Address().Hex(), + uni.rmn.Address().Hex(), + uni.onramp.Address().Hex(), + uni.offramp.Address().Hex(), + uni.priceRegistry.Address().Hex(), + uni.tokenAdminRegistry.Address().Hex(), + uni.nonceManager.Address().Hex(), + ) + } + return homeChainUniverse, universes } @@ -316,27 +335,36 @@ func (h *homeChain) AddNodes( h.backend.Commit() } -func (h *homeChain) AddDON( +func AddChainConfig( t *testing.T, - ccipCapabilityID [32]byte, + h homeChain, chainSelector uint64, - OfframpAddress []byte, - f uint8, - bootstrapP2PID [32]byte, p2pIDs [][32]byte, - oracles []confighelper2.OracleIdentityExtra, -) { + f uint8, +) ccip_config.CCIPConfigTypesChainConfigInfo { // Need to sort, otherwise _checkIsValidUniqueSubset onChain will fail sortP2PIDS(p2pIDs) // First Add ChainConfig that includes all p2pIDs as readers - chainConfig := SetupConfigInfo(chainSelector, p2pIDs, FChainA, []byte(strconv.FormatUint(chainSelector, 10))) + chainConfig := integrationhelpers.SetupConfigInfo(chainSelector, p2pIDs, f, []byte(strconv.FormatUint(chainSelector, 10))) inputConfig := []ccip_config.CCIPConfigTypesChainConfigInfo{ chainConfig, } _, err := h.ccipConfig.ApplyChainConfigUpdates(h.owner, nil, inputConfig) require.NoError(t, err) h.backend.Commit() + return chainConfig +} +func (h *homeChain) AddDON( + t *testing.T, + ccipCapabilityID [32]byte, + chainSelector uint64, + uni onchainUniverse, + f uint8, + bootstrapP2PID [32]byte, + p2pIDs [][32]byte, + oracles []confighelper2.OracleIdentityExtra, +) { // Get OCR3 Config from helper var schedule []int for range oracles { @@ -386,7 +414,7 @@ func (h *homeChain) AddDON( ChainSelector: chainSelector, F: f, OffchainConfigVersion: offchainConfigVersion, - OfframpAddress: OfframpAddress, + OfframpAddress: uni.offramp.Address().Bytes(), BootstrapP2PIds: [][32]byte{bootstrapP2PID}, P2pIds: p2pIDs, Signers: signersBytes, @@ -401,6 +429,9 @@ func (h *homeChain) AddDON( // Trim first four bytes to remove function selector. encodedConfigs := encodedCall[4:] + // commit so that we have an empty block to filter events from + h.backend.Commit() + _, err = h.capabilityRegistry.AddDON(h.owner, p2pIDs, []kcr.CapabilitiesRegistryCapabilityConfiguration{ { CapabilityId: ccipCapabilityID, @@ -409,6 +440,70 @@ func (h *homeChain) AddDON( }, false, false, f) require.NoError(t, err) h.backend.Commit() + + endBlock := h.backend.Blockchain().CurrentBlock().Number.Uint64() + iter, err := h.capabilityRegistry.FilterConfigSet(&bind.FilterOpts{ + Start: h.backend.Blockchain().CurrentBlock().Number.Uint64() - 1, + End: &endBlock, + }) + require.NoError(t, err, "failed to filter config set events") + var donID uint32 + for iter.Next() { + donID = iter.Event.DonId + break + } + require.NotZero(t, donID, "failed to get donID from config set event") + + var signerAddresses []common.Address + for _, signer := range signers { + signerAddresses = append(signerAddresses, common.BytesToAddress(signer)) + } + + var transmitterAddresses []common.Address + for _, transmitter := range transmitters { + transmitterAddresses = append(transmitterAddresses, common.HexToAddress(string(transmitter))) + } + + // get the config digest from the ccip config contract and set config on the offramp. + var offrampOCR3Configs []evm_2_evm_multi_offramp.MultiOCR3BaseOCRConfigArgs + for _, pluginType := range []cctypes.PluginType{cctypes.PluginTypeCCIPCommit, cctypes.PluginTypeCCIPExec} { + ocrConfig, err1 := h.ccipConfig.GetOCRConfig(&bind.CallOpts{ + Context: testutils.Context(t), + }, donID, uint8(pluginType)) + require.NoError(t, err1, "failed to get OCR3 config from ccip config contract") + require.Len(t, ocrConfig, 1, "expected exactly one OCR3 config") + offrampOCR3Configs = append(offrampOCR3Configs, evm_2_evm_multi_offramp.MultiOCR3BaseOCRConfigArgs{ + ConfigDigest: ocrConfig[0].ConfigDigest, + OcrPluginType: uint8(pluginType), + F: f, + IsSignatureVerificationEnabled: pluginType == cctypes.PluginTypeCCIPCommit, + Signers: signerAddresses, + Transmitters: transmitterAddresses, + }) + } + + uni.backend.Commit() + + _, err = uni.offramp.SetOCR3Configs(uni.owner, offrampOCR3Configs) + require.NoError(t, err, "failed to set ocr3 configs on offramp") + uni.backend.Commit() + + for _, pluginType := range []cctypes.PluginType{cctypes.PluginTypeCCIPCommit, cctypes.PluginTypeCCIPExec} { + ocrConfig, err := uni.offramp.LatestConfigDetails(&bind.CallOpts{ + Context: testutils.Context(t), + }, uint8(pluginType)) + require.NoError(t, err, "failed to get latest commit OCR3 config") + require.Equalf(t, offrampOCR3Configs[pluginType].ConfigDigest, ocrConfig.ConfigInfo.ConfigDigest, "%s OCR3 config digest mismatch", pluginType.String()) + require.Equalf(t, offrampOCR3Configs[pluginType].F, ocrConfig.ConfigInfo.F, "%s OCR3 config F mismatch", pluginType.String()) + require.Equalf(t, offrampOCR3Configs[pluginType].IsSignatureVerificationEnabled, ocrConfig.ConfigInfo.IsSignatureVerificationEnabled, "%s OCR3 config signature verification mismatch", pluginType.String()) + if pluginType == cctypes.PluginTypeCCIPCommit { + // only commit will set signers, exec doesn't need them. + require.Equalf(t, offrampOCR3Configs[pluginType].Signers, ocrConfig.Signers, "%s OCR3 config signers mismatch", pluginType.String()) + } + require.Equalf(t, offrampOCR3Configs[pluginType].Transmitters, ocrConfig.Transmitters, "%s OCR3 config transmitters mismatch", pluginType.String()) + } + + t.Logf("set ocr3 config on the offramp, signers: %+v, transmitters: %+v", signerAddresses, transmitterAddresses) } func connectUniverses( @@ -463,6 +558,14 @@ func setupUniverseBasics(t *testing.T, uni onchainUniverse) { require.NoErrorf(t, err, "failed to apply price registry updates on chain id %d", uni.chainID) uni.backend.Commit() + _, err = uni.priceRegistry.ApplyAuthorizedCallerUpdates(owner, price_registry.AuthorizedCallersAuthorizedCallerArgs{ + AddedCallers: []common.Address{ + uni.offramp.Address(), + }, + }) + require.NoError(t, err, "failed to authorize offramp on price registry") + uni.backend.Commit() + //============================================================================= // Authorize OnRamp & OffRamp on NonceManager // Otherwise the onramp will not be able to call the nonceManager to get next Nonce @@ -478,17 +581,6 @@ func setupUniverseBasics(t *testing.T, uni onchainUniverse) { uni.backend.Commit() } -func SetupConfigInfo(chainSelector uint64, readers [][32]byte, fChain uint8, cfg []byte) ccip_config.CCIPConfigTypesChainConfigInfo { - return ccip_config.CCIPConfigTypesChainConfigInfo{ - ChainSelector: chainSelector, - ChainConfig: ccip_config.CCIPConfigTypesChainConfig{ - Readers: readers, - FChain: fChain, - Config: cfg, - }, - } -} - // As we can't change router contract. The contract was expecting onRamp and offRamp per lane and not per chain // In the new architecture we have only one onRamp and one offRamp per chain. // hence we add the mapping for all remote chains to the onRamp/offRamp contract of the local chain @@ -503,11 +595,11 @@ func wireRouter(t *testing.T, uni onchainUniverse, universes map[uint64]onchainU continue } routerOnrampUpdates = append(routerOnrampUpdates, router.RouterOnRamp{ - DestChainSelector: remoteChainID, + DestChainSelector: getSelector(remoteChainID), OnRamp: uni.onramp.Address(), }) routerOfframpUpdates = append(routerOfframpUpdates, router.RouterOffRamp{ - SourceChainSelector: remoteChainID, + SourceChainSelector: getSelector(remoteChainID), OffRamp: uni.offramp.Address(), }) } @@ -525,7 +617,7 @@ func wireOnRamp(t *testing.T, uni onchainUniverse, universes map[uint64]onchainU continue } onrampDestChainConfigArgs = append(onrampDestChainConfigArgs, evm_2_evm_multi_onramp.EVM2EVMMultiOnRampDestChainConfigArgs{ - DestChainSelector: remoteChainID, + DestChainSelector: getSelector(remoteChainID), DynamicConfig: defaultOnRampDynamicConfig(t), }) } @@ -543,7 +635,7 @@ func wireOffRamp(t *testing.T, uni onchainUniverse, universes map[uint64]onchain continue } offrampSourceChainConfigArgs = append(offrampSourceChainConfigArgs, evm_2_evm_multi_offramp.EVM2EVMMultiOffRampSourceChainConfigArgs{ - SourceChainSelector: remoteChainID, // for each destination chain, add a source chain config + SourceChainSelector: getSelector(remoteChainID), // for each destination chain, add a source chain config IsEnabled: true, OnRamp: remoteUniverse.onramp.Address().Bytes(), }) @@ -553,6 +645,14 @@ func wireOffRamp(t *testing.T, uni onchainUniverse, universes map[uint64]onchain uni.backend.Commit() } +func getSelector(chainID uint64) uint64 { + selector, err := chainsel.SelectorFromChainId(chainID) + if err != nil { + panic(err) + } + return selector +} + // initRemoteChainsGasPrices sets the gas prices for all chains except the local chain in the local price registry func initRemoteChainsGasPrices(t *testing.T, uni onchainUniverse, universes map[uint64]onchainUniverse) { var gasPriceUpdates []price_registry.InternalGasPriceUpdate @@ -562,7 +662,7 @@ func initRemoteChainsGasPrices(t *testing.T, uni onchainUniverse, universes map[ } gasPriceUpdates = append(gasPriceUpdates, price_registry.InternalGasPriceUpdate{ - DestChainSelector: remoteChainID, + DestChainSelector: getSelector(remoteChainID), UsdPerUnitGas: big.NewInt(2e12), }, ) diff --git a/core/services/ocr3/plugins/ccip_integration_tests/home_chain_test.go b/core/services/ocr3/plugins/ccip_integration_tests/home_chain_test.go index ab710ff152..8e4c70d7fb 100644 --- a/core/services/ocr3/plugins/ccip_integration_tests/home_chain_test.go +++ b/core/services/ocr3/plugins/ccip_integration_tests/home_chain_test.go @@ -16,6 +16,7 @@ import ( capcfg "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/ccip_config" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" "github.com/smartcontractkit/chainlink/v2/core/logger" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr3/plugins/ccip_integration_tests/integrationhelpers" "github.com/stretchr/testify/require" ) @@ -23,19 +24,19 @@ import ( func TestHomeChainReader(t *testing.T) { ctx := testutils.Context(t) lggr := logger.TestLogger(t) - uni := NewTestUniverse(ctx, t, lggr) + uni := integrationhelpers.NewTestUniverse(ctx, t, lggr) // We need 3*f + 1 p2pIDs to have enough nodes to bootstrap var arr []int64 - n := int(FChainA*3 + 1) + n := int(integrationhelpers.FChainA*3 + 1) for i := 0; i <= n; i++ { arr = append(arr, int64(i)) } - p2pIDs := P2pIDsFromInts(arr) + p2pIDs := integrationhelpers.P2pIDsFromInts(arr) uni.AddCapability(p2pIDs) //==============================Apply configs to Capability Contract================================= - chainAConf := SetupConfigInfo(ChainA, p2pIDs, FChainA, []byte("ChainA")) - chainBConf := SetupConfigInfo(ChainB, p2pIDs[1:], FChainB, []byte("ChainB")) - chainCConf := SetupConfigInfo(ChainC, p2pIDs[2:], FChainC, []byte("ChainC")) + chainAConf := integrationhelpers.SetupConfigInfo(integrationhelpers.ChainA, p2pIDs, integrationhelpers.FChainA, []byte("ChainA")) + chainBConf := integrationhelpers.SetupConfigInfo(integrationhelpers.ChainB, p2pIDs[1:], integrationhelpers.FChainB, []byte("ChainB")) + chainCConf := integrationhelpers.SetupConfigInfo(integrationhelpers.ChainC, p2pIDs[2:], integrationhelpers.FChainC, []byte("ChainC")) inputConfig := []capcfg.CCIPConfigTypesChainConfigInfo{ chainAConf, chainBConf, @@ -67,18 +68,14 @@ func TestHomeChainReader(t *testing.T) { require.NoError(t, err) require.Equal(t, expectedChainConfigs, configs) //=================================Remove ChainC from OnChainConfig========================================= - _, err = uni.CcipCfg.ApplyChainConfigUpdates(uni.Transactor, []uint64{ChainC}, nil) + _, err = uni.CcipCfg.ApplyChainConfigUpdates(uni.Transactor, []uint64{integrationhelpers.ChainC}, nil) require.NoError(t, err) uni.Backend.Commit() time.Sleep(pollDuration * 5) // Wait for the chain reader to update configs, err = homeChain.GetAllChainConfigs() require.NoError(t, err) - delete(expectedChainConfigs, cciptypes.ChainSelector(ChainC)) + delete(expectedChainConfigs, cciptypes.ChainSelector(integrationhelpers.ChainC)) require.Equal(t, expectedChainConfigs, configs) - //================================Close HomeChain Reader=============================== - //require.NoError(t, homeChain.Close()) - //require.NoError(t, uni.LogPoller.Close()) - t.Logf("homchain reader successfully closed") } func toPeerIDs(readers [][32]byte) mapset.Set[libocrtypes.PeerID] { diff --git a/core/services/ocr3/plugins/ccip_integration_tests/integration_helpers.go b/core/services/ocr3/plugins/ccip_integration_tests/integrationhelpers/integration_helpers.go similarity index 91% rename from core/services/ocr3/plugins/ccip_integration_tests/integration_helpers.go rename to core/services/ocr3/plugins/ccip_integration_tests/integrationhelpers/integration_helpers.go index 93942dbc18..2e5773b401 100644 --- a/core/services/ocr3/plugins/ccip_integration_tests/integration_helpers.go +++ b/core/services/ocr3/plugins/ccip_integration_tests/integrationhelpers/integration_helpers.go @@ -1,4 +1,4 @@ -package ccip_integration_tests +package integrationhelpers import ( "context" @@ -14,6 +14,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" + "github.com/smartcontractkit/chainlink-ccip/pkg/consts" ccipreader "github.com/smartcontractkit/chainlink-ccip/pkg/reader" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/headtracker" @@ -21,6 +22,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/ocr3_config_encoder" kcr "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/capabilities_registry" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" + configsevm "github.com/smartcontractkit/chainlink/v2/core/services/ccipcapability/configs/evm" cctypes "github.com/smartcontractkit/chainlink/v2/core/services/ccipcapability/types" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/p2pkey" @@ -38,13 +40,19 @@ import ( const chainID = 1337 -func NewReader(t *testing.T, logPoller logpoller.LogPoller, client client.Client, address common.Address, chainReaderConfig evmrelaytypes.ChainReaderConfig, contractName string) types.ContractReader { +func NewReader( + t *testing.T, + logPoller logpoller.LogPoller, + client client.Client, + address common.Address, + chainReaderConfig evmrelaytypes.ChainReaderConfig, +) types.ContractReader { cr, err := evm.NewChainReaderService(testutils.Context(t), logger.TestLogger(t), logPoller, client, chainReaderConfig) require.NoError(t, err) err = cr.Bind(testutils.Context(t), []types.BoundContract{ { Address: address.String(), - Name: contractName, + Name: consts.ContractNameCCIPConfig, Pending: false, }, }) @@ -219,23 +227,7 @@ func (t *TestUniverse) AddCapability(p2pIDs [][32]byte) { } func NewHomeChainReader(t *testing.T, logPoller logpoller.LogPoller, client client.Client, ccAddress common.Address) ccipreader.HomeChain { - cfg := evmrelaytypes.ChainReaderConfig{ - Contracts: map[string]evmrelaytypes.ChainContractReader{ - "CCIPConfig": { - ContractABI: ccip_config.CCIPConfigMetaData.ABI, - Configs: map[string]*evmrelaytypes.ChainReaderDefinition{ - "getAllChainConfigs": { - ChainSpecificName: "getAllChainConfigs", - }, - "getOCRConfig": { - ChainSpecificName: "getOCRConfig", - }, - }, - }, - }, - } - - cr := NewReader(t, logPoller, client, ccAddress, cfg, "CCIPConfig") + cr := NewReader(t, logPoller, client, ccAddress, configsevm.HomeChainReaderConfigRaw()) hcr := ccipreader.NewHomeChainReader(cr, logger.TestLogger(t), 500*time.Millisecond) require.NoError(t, hcr.Start(testutils.Context(t))) @@ -294,3 +286,14 @@ func (t *TestUniverse) AddDONToRegistry( require.NoError(t.TestingT, err) t.Backend.Commit() } + +func SetupConfigInfo(chainSelector uint64, readers [][32]byte, fChain uint8, cfg []byte) ccip_config.CCIPConfigTypesChainConfigInfo { + return ccip_config.CCIPConfigTypesChainConfigInfo{ + ChainSelector: chainSelector, + ChainConfig: ccip_config.CCIPConfigTypesChainConfig{ + Readers: readers, + FChain: fChain, + Config: cfg, + }, + } +} diff --git a/core/services/ocr3/plugins/ccip_integration_tests/ocr3_node_test.go b/core/services/ocr3/plugins/ccip_integration_tests/ocr3_node_test.go index bd5c12903d..2a7507d7a7 100644 --- a/core/services/ocr3/plugins/ccip_integration_tests/ocr3_node_test.go +++ b/core/services/ocr3/plugins/ccip_integration_tests/ocr3_node_test.go @@ -2,6 +2,7 @@ package ccip_integration_tests import ( "fmt" + "math/big" "testing" "time" @@ -9,6 +10,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/hashicorp/consul/sdk/freeport" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/evm_2_evm_multi_offramp" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/p2pkey" @@ -21,16 +23,17 @@ import ( ) func TestIntegration_OCR3Nodes(t *testing.T) { + t.Skip("Currently failing, will fix in follow-ups") + numChains := 3 homeChainUni, universes := createUniverses(t, numChains) numNodes := 4 t.Log("creating ocr3 nodes") var ( - oracles = make(map[uint64][]confighelper2.OracleIdentityExtra) - transmitters = make(map[uint64][]common.Address) - apps []chainlink.Application - nodes []*ocr3Node - p2pIDs [][32]byte + oracles = make(map[uint64][]confighelper2.OracleIdentityExtra) + apps []chainlink.Application + nodes []*ocr3Node + p2pIDs [][32]byte // The bootstrap node will be the first node (index 0) bootstrapPort int @@ -39,21 +42,19 @@ func TestIntegration_OCR3Nodes(t *testing.T) { ) ports := freeport.GetN(t, numNodes) - capabilitiesPorts := freeport.GetN(t, numNodes) for i := 0; i < numNodes; i++ { - node := setupNodeOCR3(t, ports[i], capabilitiesPorts[i], bootstrappers, universes, homeChainUni) + node := setupNodeOCR3(t, ports[i], bootstrappers, universes, homeChainUni) apps = append(apps, node.app) for chainID, transmitter := range node.transmitters { - transmitters[chainID] = append(transmitters[chainID], transmitter) identity := confighelper2.OracleIdentityExtra{ OracleIdentity: confighelper2.OracleIdentity{ - OnchainPublicKey: node.keybundle.PublicKey(), + OnchainPublicKey: node.keybundle.PublicKey(), // Different for each chain TransmitAccount: ocrtypes.Account(transmitter.Hex()), - OffchainPublicKey: node.keybundle.OffchainPublicKey(), + OffchainPublicKey: node.keybundle.OffchainPublicKey(), // Same for each family PeerID: node.peerID, }, - ConfigEncryptionPublicKey: node.keybundle.ConfigEncryptionPublicKey(), + ConfigEncryptionPublicKey: node.keybundle.ConfigEncryptionPublicKey(), // Different for each chain } oracles[chainID] = append(oracles[chainID], identity) } @@ -75,21 +76,11 @@ func TestIntegration_OCR3Nodes(t *testing.T) { } // Start committing periodically in the background for all the chains - tick := time.NewTicker(100 * time.Millisecond) + tick := time.NewTicker(900 * time.Millisecond) defer tick.Stop() commitBlocksBackground(t, universes, tick) ctx := testutils.Context(t) - t.Log("creating ocr3 jobs") - for i := 0; i < len(nodes); i++ { - err := nodes[i].app.Start(ctx) - require.NoError(t, err) - tApp := apps[i] - t.Cleanup(func() { - require.NoError(t, tApp.Stop()) - }) - //TODO: Create Jobs and add them to the app - } ccipCapabilityID, err := homeChainUni.capabilityRegistry.GetHashedCapabilityId(&bind.CallOpts{ Context: ctx, @@ -99,18 +90,146 @@ func TestIntegration_OCR3Nodes(t *testing.T) { // Need to Add nodes and assign capabilities to them before creating DONS homeChainUni.AddNodes(t, p2pIDs, [][32]byte{ccipCapabilityID}) + + // Add homechain configs + for _, uni := range universes { + AddChainConfig(t, homeChainUni, getSelector(uni.chainID), p2pIDs, 1) + } + + cfgs, err3 := homeChainUni.ccipConfig.GetAllChainConfigs(&bind.CallOpts{}) + require.NoError(t, err3) + t.Logf("homechain_configs %+v", cfgs) + require.Len(t, cfgs, numChains) + // Create a DON for each chain for _, uni := range universes { // Add nodes and give them the capability t.Log("AddingDON for universe: ", uni.chainID) - homeChainUni.AddDON(t, + chainSelector := getSelector(uni.chainID) + homeChainUni.AddDON( + t, ccipCapabilityID, - uni.chainID, - uni.offramp.Address().Bytes(), + chainSelector, + uni, 1, // f bootstrapP2PID, p2pIDs, oracles[uni.chainID], ) } + + t.Log("creating ocr3 jobs") + for i := 0; i < len(nodes); i++ { + err1 := nodes[i].app.Start(ctx) + require.NoError(t, err1) + tApp := apps[i] + t.Cleanup(func() { + require.NoError(t, tApp.Stop()) + }) + + jb := mustGetJobSpec(t, bootstrapP2PID, bootstrapPort, nodes[i].peerID, nodes[i].keybundle.ID()) + require.NoErrorf(t, tApp.AddJobV2(ctx, &jb), "Wasn't able to create ccip job for node %d", i) + } + + // sourceChain map[uint64], destChain [32]byte + var messageIDs = make(map[uint64]map[uint64][32]byte) + // map[uint64] chainID, blocks + var replayBlocks = make(map[uint64]uint64) + pingPongs := initializePingPongContracts(t, universes) + for chainID, uni := range universes { + var replayBlock uint64 + for otherChain, pingPong := range pingPongs[chainID] { + t.Log("PingPong From: ", chainID, " To: ", otherChain) + + dcc, err1 := uni.onramp.GetDestChainConfig(&bind.CallOpts{}, getSelector(otherChain)) + require.NoError(t, err1) + prevSeqNr := dcc.SequenceNumber + + uni.backend.Commit() + + _, err2 := pingPong.StartPingPong(uni.owner) + require.NoError(t, err2) + uni.backend.Commit() + + endBlock := uni.backend.Blockchain().CurrentBlock().Number.Uint64() + logIter, err3 := uni.onramp.FilterCCIPSendRequested(&bind.FilterOpts{ + Start: endBlock - 1, + End: &endBlock, + }, []uint64{getSelector(otherChain)}) + require.NoError(t, err3) + // Iterate until latest event + var count int + for logIter.Next() { + count++ + } + require.Equal(t, 1, count, "expected 1 CCIPSendRequested log only") + + log := logIter.Event + require.Equal(t, getSelector(otherChain), log.DestChainSelector) + require.Equal(t, pingPong.Address(), log.Message.Sender) + chainPingPongAddr := pingPongs[otherChain][chainID].Address().Bytes() + + // Receiver address is abi-encoded if destination is EVM. + paddedAddr := common.LeftPadBytes(chainPingPongAddr, len(log.Message.Receiver)) + require.Equal(t, paddedAddr, log.Message.Receiver) + + // check that sequence number is bumped in the onramp. + dcc, err = uni.onramp.GetDestChainConfig(&bind.CallOpts{}, log.DestChainSelector) + require.NoError(t, err) + require.Equalf(t, dcc.SequenceNumber, prevSeqNr+1, "sequence number not bumped for dest chain selector %d", log.DestChainSelector) + + _, ok := messageIDs[chainID] + if !ok { + messageIDs[chainID] = make(map[uint64][32]byte) + } + messageIDs[chainID][otherChain] = log.Message.Header.MessageId + + // replay block should be the earliest block that has a ccip message. + if replayBlock == 0 { + replayBlock = endBlock + } + } + replayBlocks[chainID] = replayBlock + } + + // HACK: wait for the oracles to come up. + // Need some data driven way to do this. + time.Sleep(30 * time.Second) + + // replay the log poller on all the chains so that the logs are in the db. + // otherwise the plugins won't pick them up. + // TODO: this is happening too early, we need to wait for the chain readers to get their config + // and register the LP filters before this has any effect. + for _, node := range nodes { + for chainID, replayBlock := range replayBlocks { + t.Logf("Replaying logs for chain %d from block %d", chainID, replayBlock) + require.NoError(t, node.app.ReplayFromBlock(big.NewInt(int64(chainID)), replayBlock, false), "failed to replay logs") + } + } + + for _, uni := range universes { + waitForCommit(t, uni) + } +} + +func waitForCommit(t *testing.T, uni onchainUniverse) { + sink := make(chan *evm_2_evm_multi_offramp.EVM2EVMMultiOffRampCommitReportAccepted) + subscipriton, err := uni.offramp.WatchCommitReportAccepted(&bind.WatchOpts{}, sink) + require.NoError(t, err) + + for { + select { + case <-time.After(5 * time.Second): + t.Logf("Waiting for commit report on chain id %d (selector %d)", uni.chainID, getSelector(uni.chainID)) + case subErr := <-subscipriton.Err(): + t.Fatalf("Subscription error: %+v", subErr) + case report := <-sink: + if len(report.Report.MerkleRoots) > 0 { + t.Logf("Received commit report with merkle roots: %+v", report) + } else { + t.Logf("Received commit report without merkle roots: %+v", report) + } + return + } + } } diff --git a/core/services/ocr3/plugins/ccip_integration_tests/ocr_node_helper.go b/core/services/ocr3/plugins/ccip_integration_tests/ocr_node_helper.go index 0163388183..ece3a22f02 100644 --- a/core/services/ocr3/plugins/ccip_integration_tests/ocr_node_helper.go +++ b/core/services/ocr3/plugins/ccip_integration_tests/ocr_node_helper.go @@ -15,7 +15,10 @@ import ( "github.com/ethereum/go-ethereum/common" gethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/jmoiron/sqlx" - "go.uber.org/zap/zapcore" + + "github.com/smartcontractkit/chainlink/v2/core/services/ccipcapability/validate" + "github.com/smartcontractkit/chainlink/v2/core/services/job" + "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/p2pkey" "github.com/smartcontractkit/chainlink-common/pkg/config" "github.com/smartcontractkit/chainlink-common/pkg/loop" @@ -27,6 +30,7 @@ import ( v2toml "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/toml" evmutils "github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils/big" "github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm" + configv2 "github.com/smartcontractkit/chainlink/v2/core/config/toml" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest/heavyweight" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" "github.com/smartcontractkit/chainlink/v2/core/logger" @@ -39,7 +43,9 @@ import ( "github.com/smartcontractkit/chainlink/v2/plugins" "github.com/smartcontractkit/libocr/commontypes" + "github.com/stretchr/testify/require" + "go.uber.org/zap/zapcore" ) type ocr3Node struct { @@ -55,7 +61,6 @@ type ocr3Node struct { func setupNodeOCR3( t *testing.T, port int, - capabilitiesPort int, p2pV2Bootstrappers []commontypes.BootstrapperLocator, universes map[uint64]onchainUniverse, homeChainUniverse homeChain, @@ -76,9 +81,6 @@ func setupNodeOCR3( } // Enable Capabilities, This is a pre-requisite for registrySyncer to work. - // Same values as P2P.V2 except for the listen address. - c.Capabilities.Peering.V2 = c.P2P.V2 - c.Capabilities.Peering.V2.ListenAddresses = &[]string{fmt.Sprintf("127.0.0.1:%d", capabilitiesPort)} c.Capabilities.ExternalRegistry.NetworkID = ptr(relay.NetworkEVM) c.Capabilities.ExternalRegistry.ChainID = ptr(strconv.FormatUint(homeChainUniverse.chainID, 10)) c.Capabilities.ExternalRegistry.Address = ptr(homeChainUniverse.capabilityRegistry.Address().String()) @@ -89,6 +91,8 @@ func setupNodeOCR3( c.OCR2.Enabled = ptr(true) c.OCR2.ContractPollInterval = config.MustNewDuration(5 * time.Second) + c.Log.Level = ptr(configv2.LogLevel(zapcore.InfoLevel)) + var chains v2toml.EVMConfigs for chainID := range universes { chains = append(chains, createConfigV2Chain(uBigInt(chainID))) @@ -292,3 +296,25 @@ func commitBlocksBackground(t *testing.T, universes map[uint64]onchainUniverse, wg.Wait() }) } + +// p2pKeyID: nodes p2p id +// ocrKeyBundleID: nodes ocr key bundle id +func mustGetJobSpec(t *testing.T, bootstrapP2PID p2pkey.PeerID, bootstrapPort int, p2pKeyID string, ocrKeyBundleID string) job.Job { + specArgs := validate.SpecArgs{ + P2PV2Bootstrappers: []string{ + fmt.Sprintf("%s@127.0.0.1:%d", bootstrapP2PID.Raw(), bootstrapPort), + }, + CapabilityVersion: CapabilityVersion, + CapabilityLabelledName: CapabilityLabelledName, + OCRKeyBundleIDs: map[string]string{ + relay.NetworkEVM: ocrKeyBundleID, + }, + P2PKeyID: p2pKeyID, + PluginConfig: map[string]any{}, + } + specToml, err := validate.NewCCIPSpecToml(specArgs) + require.NoError(t, err) + jb, err := validate.ValidatedCCIPSpec(specToml) + require.NoError(t, err) + return jb +} diff --git a/core/services/ocr3/plugins/ccip_integration_tests/ping_pong_test.go b/core/services/ocr3/plugins/ccip_integration_tests/ping_pong_test.go index 1b1e96693a..8a65ff5167 100644 --- a/core/services/ocr3/plugins/ccip_integration_tests/ping_pong_test.go +++ b/core/services/ocr3/plugins/ccip_integration_tests/ping_pong_test.go @@ -37,7 +37,7 @@ func TestPingPong(t *testing.T) { for logIter.Next() { } log := logIter.Event - require.Equal(t, otherChain, log.DestChainSelector) + require.Equal(t, getSelector(otherChain), log.DestChainSelector) require.Equal(t, pingPong.Address(), log.Message.Sender) chainPingPongAddr := pingPongs[otherChain][chainID].Address().Bytes() // With chain agnostic addresses we need to pad the address to the correct length if the receiver is zero prefixed @@ -83,7 +83,7 @@ func initializePingPongContracts( for chainToConnect, pingPong := range pingPongs[chainID] { _, err := pingPong.SetCounterpart( universe.owner, - chainUniverses[chainToConnect].chainID, + getSelector(chainUniverses[chainToConnect].chainID), // This is the address of the ping pong contract on the other chain pingPongs[chainToConnect][chainID].Address(), ) diff --git a/core/services/pipeline/common.go b/core/services/pipeline/common.go index 1e2c52dad6..7a301e21e0 100644 --- a/core/services/pipeline/common.go +++ b/core/services/pipeline/common.go @@ -28,6 +28,7 @@ const ( BlockhashStoreJobType string = "blockhashstore" BootstrapJobType string = "bootstrap" CronJobType string = "cron" + CCIPJobType string = "ccip" DirectRequestJobType string = "directrequest" FluxMonitorJobType string = "fluxmonitor" GatewayJobType string = "gateway" diff --git a/core/services/relay/evm/types/codec_entry.go b/core/services/relay/evm/types/codec_entry.go index 38242c43a2..e5c15f8148 100644 --- a/core/services/relay/evm/types/codec_entry.go +++ b/core/services/relay/evm/types/codec_entry.go @@ -199,8 +199,14 @@ func getNativeAndCheckedTypesForArg(arg *abi.Argument) (reflect.Type, reflect.Ty if arg.Type.Elem.GetType() == u8.native { return reflect.TypeOf(common.Hash{}), reflect.TypeOf(common.Hash{}), nil } + return nil, nil, fmt.Errorf("%w: unsupported array type: %v", commontypes.ErrInvalidConfig, arg.Type) + case abi.FixedBytesTy: + typ, ok := GetAbiEncodingType(fmt.Sprintf("bytes%d", arg.Type.Size)) + if ok { + return typ.native, typ.checked, nil + } fallthrough - case abi.SliceTy, abi.TupleTy, abi.FixedBytesTy, abi.FixedPointTy, abi.FunctionTy: + case abi.SliceTy, abi.TupleTy, abi.FixedPointTy, abi.FunctionTy: // https://github.com/ethereum/go-ethereum/blob/release/1.12/accounts/abi/topics.go#L78 return nil, nil, fmt.Errorf("%w: unsupported indexed type: %v", commontypes.ErrInvalidConfig, arg.Type) default: diff --git a/core/services/synchronization/common.go b/core/services/synchronization/common.go index 405e90d463..e6a9c4c618 100644 --- a/core/services/synchronization/common.go +++ b/core/services/synchronization/common.go @@ -26,6 +26,9 @@ const ( AutomationCustom TelemetryType = "automation-custom" OCR3Automation TelemetryType = "ocr3-automation" OCR3Rebalancer TelemetryType = "ocr3-rebalancer" + OCR3CCIPCommit TelemetryType = "ocr3-ccip-commit" + OCR3CCIPExec TelemetryType = "ocr3-ccip-exec" + OCR3CCIPBootstrap TelemetryType = "ocr3-bootstrap" ) type TelemPayload struct { diff --git a/core/web/presenters/job.go b/core/web/presenters/job.go index ad6bf617a8..bb51865051 100644 --- a/core/web/presenters/job.go +++ b/core/web/presenters/job.go @@ -468,6 +468,26 @@ func NewStandardCapabilitiesSpec(spec *job.StandardCapabilitiesSpec) *StandardCa } } +type CCIPSpec struct { + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` + CapabilityVersion string `json:"capabilityVersion"` + CapabilityLabelledName string `json:"capabilityLabelledName"` + OCRKeyBundleIDs map[string]interface{} `json:"ocrKeyBundleIDs"` + P2PKeyID string `json:"p2pKeyID"` +} + +func NewCCIPSpec(spec *job.CCIPSpec) *CCIPSpec { + return &CCIPSpec{ + CreatedAt: spec.CreatedAt, + UpdatedAt: spec.UpdatedAt, + CapabilityVersion: spec.CapabilityVersion, + CapabilityLabelledName: spec.CapabilityLabelledName, + OCRKeyBundleIDs: spec.OCRKeyBundleIDs, + P2PKeyID: spec.P2PKeyID, + } +} + // JobError represents errors on the job type JobError struct { ID int64 `json:"id"` @@ -512,6 +532,7 @@ type JobResource struct { GatewaySpec *GatewaySpec `json:"gatewaySpec"` WorkflowSpec *WorkflowSpec `json:"workflowSpec"` StandardCapabilitiesSpec *StandardCapabilitiesSpec `json:"standardCapabilitiesSpec"` + CCIPSpec *CCIPSpec `json:"ccipSpec"` PipelineSpec PipelineSpec `json:"pipelineSpec"` Errors []JobError `json:"errors"` } @@ -562,6 +583,8 @@ func NewJobResource(j job.Job) *JobResource { resource.WorkflowSpec = NewWorkflowSpec(j.WorkflowSpec) case job.StandardCapabilities: resource.StandardCapabilitiesSpec = NewStandardCapabilitiesSpec(j.StandardCapabilitiesSpec) + case job.CCIP: + resource.CCIPSpec = NewCCIPSpec(j.CCIPSpec) case job.LegacyGasStationServer, job.LegacyGasStationSidecar: // unsupported } diff --git a/core/web/presenters/job_test.go b/core/web/presenters/job_test.go index 5de71f918e..75697c6e06 100644 --- a/core/web/presenters/job_test.go +++ b/core/web/presenters/job_test.go @@ -130,6 +130,7 @@ func TestJob(t *testing.T) { "bootstrapSpec": null, "gatewaySpec": null, "standardCapabilitiesSpec": null, + "ccipSpec": null, "errors": [] } } @@ -208,6 +209,7 @@ func TestJob(t *testing.T) { "bootstrapSpec": null, "gatewaySpec": null, "standardCapabilitiesSpec": null, + "ccipSpec": null, "errors": [] } } @@ -296,6 +298,7 @@ func TestJob(t *testing.T) { "bootstrapSpec": null, "gatewaySpec": null, "standardCapabilitiesSpec": null, + "ccipSpec": null, "errors": [] } } @@ -361,6 +364,7 @@ func TestJob(t *testing.T) { "bootstrapSpec": null, "gatewaySpec": null, "standardCapabilitiesSpec": null, + "ccipSpec": null, "errors": [] } } @@ -423,6 +427,7 @@ func TestJob(t *testing.T) { "bootstrapSpec": null, "gatewaySpec": null, "standardCapabilitiesSpec": null, + "ccipSpec": null, "errors": [] } } @@ -481,6 +486,7 @@ func TestJob(t *testing.T) { "bootstrapSpec": null, "gatewaySpec": null, "standardCapabilitiesSpec": null, + "ccipSpec": null, "errors": [] } } @@ -566,7 +572,9 @@ func TestJob(t *testing.T) { "dotDagSource": "" }, "gatewaySpec": null, - "standardCapabilitiesSpec": null, + "standardCapabilitiesSpec": null, + "standardCapabilitiesSpec": null, + "ccipSpec": null, "errors": [] } } @@ -649,6 +657,7 @@ func TestJob(t *testing.T) { }, "gatewaySpec": null, "standardCapabilitiesSpec": null, + "ccipSpec": null, "errors": [] } } @@ -731,6 +740,7 @@ func TestJob(t *testing.T) { }, "gatewaySpec": null, "standardCapabilitiesSpec": null, + "ccipSpec": null, "errors": [] } } @@ -780,14 +790,14 @@ func TestJob(t *testing.T) { "blockhashStoreSpec": null, "blockHeaderFeederSpec": null, "bootstrapSpec": { - "blockchainTimeout":"0s", - "contractConfigConfirmations":0, - "contractConfigTrackerPollInterval":"0s", - "contractConfigTrackerSubscribeInterval":"0s", - "contractID":"0x16988483b46e695f6c8D58e6e1461DC703e008e1", - "createdAt":"0001-01-01T00:00:00Z", - "relay":"evm", - "relayConfig":{"chainID":1337}, + "blockchainTimeout":"0s", + "contractConfigConfirmations":0, + "contractConfigTrackerPollInterval":"0s", + "contractConfigTrackerSubscribeInterval":"0s", + "contractID":"0x16988483b46e695f6c8D58e6e1461DC703e008e1", + "createdAt":"0001-01-01T00:00:00Z", + "relay":"evm", + "relayConfig":{"chainID":1337}, "updatedAt":"0001-01-01T00:00:00Z" }, "pipelineSpec": { @@ -797,6 +807,7 @@ func TestJob(t *testing.T) { }, "gatewaySpec": null, "standardCapabilitiesSpec": null, + "ccipSpec": null, "errors": [] } } @@ -855,6 +866,7 @@ func TestJob(t *testing.T) { "updatedAt":"0001-01-01T00:00:00Z" }, "standardCapabilitiesSpec": null, + "ccipSpec": null, "pipelineSpec": { "id": 1, "jobID": 0, @@ -919,6 +931,7 @@ func TestJob(t *testing.T) { "bootstrapSpec": null, "gatewaySpec": null, "standardCapabilitiesSpec": null, + "ccipSpec": null, "pipelineSpec": { "id": 1, "jobID": 0, @@ -979,6 +992,72 @@ func TestJob(t *testing.T) { "createdAt":"0001-01-01T00:00:00Z", "updatedAt":"0001-01-01T00:00:00Z" }, + "ccipSpec": null, + "pipelineSpec": { + "id": 1, + "jobID": 0, + "dotDagSource": "" + }, + "errors": [] + } + } + }`, + }, + { + name: "ccip spec", + job: job.Job{ + ID: 1, + CCIPSpec: &job.CCIPSpec{ + ID: 3, + CreatedAt: timestamp, + UpdatedAt: timestamp, + CapabilityVersion: "4.5.9", + CapabilityLabelledName: "ccip", + }, + PipelineSpec: &pipeline.Spec{ + ID: 1, + DotDagSource: "", + }, + ExternalJobID: uuid.MustParse("0eec7e1d-d0d2-476c-a1a8-72dfb6633f46"), + Type: job.CCIP, + SchemaVersion: 1, + Name: null.StringFrom("ccip test"), + }, + want: ` + { + "data": { + "type": "jobs", + "id": "1", + "attributes": { + "name": "ccip test", + "type": "ccip", + "schemaVersion": 1, + "maxTaskDuration": "0s", + "externalJobID": "0eec7e1d-d0d2-476c-a1a8-72dfb6633f46", + "directRequestSpec": null, + "fluxMonitorSpec": null, + "gasLimit": null, + "forwardingAllowed": false, + "cronSpec": null, + "offChainReportingOracleSpec": null, + "offChainReporting2OracleSpec": null, + "keeperSpec": null, + "vrfSpec": null, + "webhookSpec": null, + "workflowSpec": null, + "blockhashStoreSpec": null, + "blockHeaderFeederSpec": null, + "bootstrapSpec": null, + "gatewaySpec": null, + "standardCapabilitiesSpec": null, + "ccipSpec": { + "capabilityVersion":"4.5.9", + "capabilityLabelledName":"ccip", + "ocrKeyBundleIDs": null, + "p2pKeyID": "", + "createdAt":"2000-01-01T00:00:00Z", + "updatedAt":"2000-01-01T00:00:00Z" + }, "pipelineSpec": { "id": 1, "jobID": 0, @@ -1058,6 +1137,7 @@ func TestJob(t *testing.T) { "bootstrapSpec": null, "gatewaySpec": null, "standardCapabilitiesSpec": null, + "ccipSpec": null, "errors": [{ "id": 200, "description": "some error", diff --git a/go.mod b/go.mod index 9fdd5421f5..5fb65003a7 100644 --- a/go.mod +++ b/go.mod @@ -74,7 +74,7 @@ require ( github.com/shopspring/decimal v1.3.1 github.com/smartcontractkit/chain-selectors v1.0.18 github.com/smartcontractkit/chainlink-automation v1.0.4 - github.com/smartcontractkit/chainlink-ccip v0.0.0-20240708100035-6d7cad39cc74 + github.com/smartcontractkit/chainlink-ccip v0.0.0-20240717202359-0bcfc8ee468d github.com/smartcontractkit/chainlink-common v0.1.7-0.20240708180634-24440372521a github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240621143432-85370a54b141 github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240702144807-761f63e7b527 @@ -358,5 +358,4 @@ replace ( // until merged upstream: https://github.com/mwitkow/grpc-proxy/pull/69 github.com/mwitkow/grpc-proxy => github.com/smartcontractkit/grpc-proxy v0.0.0-20230731113816-f1be6620749f - ) diff --git a/go.sum b/go.sum index 77adda7ce6..a1286545d8 100644 --- a/go.sum +++ b/go.sum @@ -1059,8 +1059,8 @@ github.com/smartcontractkit/chain-selectors v1.0.18 h1:ackCMDOlWuwULAyBNj9fQeQme github.com/smartcontractkit/chain-selectors v1.0.18/go.mod h1:d4Hi+E1zqjy9HqMkjBE5q1vcG9VGgxf5VxiRHfzi2kE= github.com/smartcontractkit/chainlink-automation v1.0.4 h1:iyW181JjKHLNMnDleI8umfIfVVlwC7+n5izbLSFgjw8= github.com/smartcontractkit/chainlink-automation v1.0.4/go.mod h1:u4NbPZKJ5XiayfKHD/v3z3iflQWqvtdhj13jVZXj/cM= -github.com/smartcontractkit/chainlink-ccip v0.0.0-20240708100035-6d7cad39cc74 h1:dsRsqoCRF+SrIvARgJXs6RQcJ6oGfh3XigIfrrEWO2E= -github.com/smartcontractkit/chainlink-ccip v0.0.0-20240708100035-6d7cad39cc74/go.mod h1:gyODeD1uMobe5VWRwVRiEXzL9wUzFTI80VvyCNLqWcw= +github.com/smartcontractkit/chainlink-ccip v0.0.0-20240717202359-0bcfc8ee468d h1:fOdEh9zHNT+QQBZWSRv0uxqyTMnw3rCPR4lr2DtYvhw= +github.com/smartcontractkit/chainlink-ccip v0.0.0-20240717202359-0bcfc8ee468d/go.mod h1:gyODeD1uMobe5VWRwVRiEXzL9wUzFTI80VvyCNLqWcw= github.com/smartcontractkit/chainlink-common v0.1.7-0.20240708180634-24440372521a h1:0HUP3qmHejg7FyFdY+R+8iFg0kNtrvnAxaQ//+fuZfc= github.com/smartcontractkit/chainlink-common v0.1.7-0.20240708180634-24440372521a/go.mod h1:fh9eBbrReCmv31bfz52ENCAMa7nTKQbdhb2B3+S2VGo= github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240621143432-85370a54b141 h1:TMOoYaeSDkkI3jkCH7lKHOZaLkeDuxFTNC+XblD6M0M= diff --git a/integration-tests/go.mod b/integration-tests/go.mod index bf264296a7..63b841b426 100644 --- a/integration-tests/go.mod +++ b/integration-tests/go.mod @@ -386,6 +386,7 @@ require ( github.com/shirou/gopsutil/v3 v3.24.3 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect github.com/sirupsen/logrus v1.9.3 // indirect + github.com/smartcontractkit/chainlink-ccip v0.0.0-20240717202359-0bcfc8ee468d // indirect github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240621143432-85370a54b141 // indirect github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240702144807-761f63e7b527 // indirect github.com/smartcontractkit/chainlink-feeds v0.0.0-20240522213638-159fb2d99917 // indirect diff --git a/integration-tests/go.sum b/integration-tests/go.sum index 49fb3fb2fc..4aaefc5163 100644 --- a/integration-tests/go.sum +++ b/integration-tests/go.sum @@ -1423,6 +1423,8 @@ github.com/smartcontractkit/chain-selectors v1.0.18 h1:ackCMDOlWuwULAyBNj9fQeQme github.com/smartcontractkit/chain-selectors v1.0.18/go.mod h1:d4Hi+E1zqjy9HqMkjBE5q1vcG9VGgxf5VxiRHfzi2kE= github.com/smartcontractkit/chainlink-automation v1.0.4 h1:iyW181JjKHLNMnDleI8umfIfVVlwC7+n5izbLSFgjw8= github.com/smartcontractkit/chainlink-automation v1.0.4/go.mod h1:u4NbPZKJ5XiayfKHD/v3z3iflQWqvtdhj13jVZXj/cM= +github.com/smartcontractkit/chainlink-ccip v0.0.0-20240717202359-0bcfc8ee468d h1:fOdEh9zHNT+QQBZWSRv0uxqyTMnw3rCPR4lr2DtYvhw= +github.com/smartcontractkit/chainlink-ccip v0.0.0-20240717202359-0bcfc8ee468d/go.mod h1:gyODeD1uMobe5VWRwVRiEXzL9wUzFTI80VvyCNLqWcw= github.com/smartcontractkit/chainlink-common v0.1.7-0.20240708180634-24440372521a h1:0HUP3qmHejg7FyFdY+R+8iFg0kNtrvnAxaQ//+fuZfc= github.com/smartcontractkit/chainlink-common v0.1.7-0.20240708180634-24440372521a/go.mod h1:fh9eBbrReCmv31bfz52ENCAMa7nTKQbdhb2B3+S2VGo= github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240621143432-85370a54b141 h1:TMOoYaeSDkkI3jkCH7lKHOZaLkeDuxFTNC+XblD6M0M= diff --git a/integration-tests/load/go.mod b/integration-tests/load/go.mod index 9ff38e7784..549fbe4126 100644 --- a/integration-tests/load/go.mod +++ b/integration-tests/load/go.mod @@ -368,6 +368,7 @@ require ( github.com/shopspring/decimal v1.3.1 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/smartcontractkit/chain-selectors v1.0.18 // indirect + github.com/smartcontractkit/chainlink-ccip v0.0.0-20240717202359-0bcfc8ee468d // indirect github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240621143432-85370a54b141 // indirect github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240702144807-761f63e7b527 // indirect github.com/smartcontractkit/chainlink-feeds v0.0.0-20240522213638-159fb2d99917 // indirect diff --git a/integration-tests/load/go.sum b/integration-tests/load/go.sum index fabfd7c7ee..5494e4a48c 100644 --- a/integration-tests/load/go.sum +++ b/integration-tests/load/go.sum @@ -1413,6 +1413,8 @@ github.com/smartcontractkit/chain-selectors v1.0.18 h1:ackCMDOlWuwULAyBNj9fQeQme github.com/smartcontractkit/chain-selectors v1.0.18/go.mod h1:d4Hi+E1zqjy9HqMkjBE5q1vcG9VGgxf5VxiRHfzi2kE= github.com/smartcontractkit/chainlink-automation v1.0.4 h1:iyW181JjKHLNMnDleI8umfIfVVlwC7+n5izbLSFgjw8= github.com/smartcontractkit/chainlink-automation v1.0.4/go.mod h1:u4NbPZKJ5XiayfKHD/v3z3iflQWqvtdhj13jVZXj/cM= +github.com/smartcontractkit/chainlink-ccip v0.0.0-20240717202359-0bcfc8ee468d h1:fOdEh9zHNT+QQBZWSRv0uxqyTMnw3rCPR4lr2DtYvhw= +github.com/smartcontractkit/chainlink-ccip v0.0.0-20240717202359-0bcfc8ee468d/go.mod h1:gyODeD1uMobe5VWRwVRiEXzL9wUzFTI80VvyCNLqWcw= github.com/smartcontractkit/chainlink-common v0.1.7-0.20240708180634-24440372521a h1:0HUP3qmHejg7FyFdY+R+8iFg0kNtrvnAxaQ//+fuZfc= github.com/smartcontractkit/chainlink-common v0.1.7-0.20240708180634-24440372521a/go.mod h1:fh9eBbrReCmv31bfz52ENCAMa7nTKQbdhb2B3+S2VGo= github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240621143432-85370a54b141 h1:TMOoYaeSDkkI3jkCH7lKHOZaLkeDuxFTNC+XblD6M0M=