From 6ec5ab83158172ac0c8e591bfa8dd9ffb35880ea Mon Sep 17 00:00:00 2001 From: Sam Date: Fri, 18 Oct 2024 12:13:03 -0400 Subject: [PATCH] Blue/green deployments for LLO; code cleanup (#14628) * Blue/green deployments for LLO; code cleanup * Go mod tidy --- .changeset/real-doors-visit.md | 6 + ccip/config/evm/Simulated.toml | 2 +- contracts/.changeset/cool-laws-drum.md | 7 + .../gas-snapshots/llo-feeds.gas-snapshot | 26 +- .../scripts/native_solc_compile_all_llo-feeds | 1 + .../llo-feeds/interfaces/IConfigurator.sol | 28 - .../v0.5.0/configuration/Configurator.sol | 227 +++- .../interfaces/IConfigurator.sol | 61 + .../test/ChannelConfigStore.t.sol | 7 + .../configurator/BaseConfiguratorTest.t.sol | 91 ++ ...ConfiguratorPromoteStagingConfigTest.t.sol | 109 ++ .../ConfiguratorSetProductionConfigTest.t.sol | 338 +++++ .../ConfiguratorSetStagingConfigTest.t.sol | 290 +++++ .../test/configurator/ConfiguratorTest.t.sol | 20 + .../test/mocks/ExposedConfigurator.sol | 39 +- core/cmd/shell.go | 17 +- .../generated/configurator/configurator.go | 539 ++++++-- .../exposed_configurator.go | 1158 +++++++++++++++++ ...rapper-dependency-versions-do-not-edit.txt | 3 +- core/gethwrappers/llo-feeds/go_generate.go | 2 + core/internal/cltest/cltest.go | 16 +- core/scripts/go.mod | 4 +- core/scripts/go.sum | 8 +- core/services/chainlink/application.go | 38 +- core/services/chainlink/relayer_factory.go | 21 +- core/services/job/spawner_test.go | 3 +- .../llo/attested_retirement_report.pb.go | 213 +++ .../llo/attested_retirement_report.proto | 17 + core/services/llo/cleanup.go | 2 +- core/services/llo/cleanup_test.go | 2 +- core/services/llo/codecs.go | 2 +- core/services/llo/codecs_test.go | 4 +- core/services/llo/data_source.go | 4 +- core/services/llo/delegate.go | 132 +- core/services/llo/generate.go | 3 + .../services/llo/mercurytransmitter/server.go | 7 +- .../llo/mercurytransmitter/transmitter.go | 2 +- core/services/llo/never_retire_cache.go | 17 + .../llo/null_retirement_report_cache.go | 25 + .../llo/onchain_channel_definition_cache.go | 11 +- .../onchain_channel_definition_cache_test.go | 12 +- core/services/llo/orm.go | 16 +- core/services/llo/orm_test.go | 2 +- .../plugin_scoped_retirement_report_cache.go | 84 ++ ...gin_scoped_retirement_report_cache_test.go | 164 +++ core/services/llo/retirement_report_cache.go | 151 +++ .../llo/retirement_report_cache_test.go | 170 +++ core/services/llo/retirement_report_orm.go | 110 ++ .../llo/retirement_report_orm_test.go | 62 + core/services/llo/transmitter.go | 20 +- core/services/ocr2/delegate.go | 90 +- .../ocr2/plugins/llo/integration_test.go | 560 ++++++-- ...annel_definition_cache_integration_test.go | 2 +- core/services/relay/dummy/llo_provider.go | 11 +- core/services/relay/dummy/relayer.go | 3 +- core/services/relay/evm/evm.go | 67 +- core/services/relay/evm/llo/abi.go | 29 + core/services/relay/evm/llo/config_poller.go | 276 ++++ .../relay/evm/llo/config_poller_test.go | 400 ++++++ .../relay/evm/llo/offchain_config_digester.go | 129 ++ .../evm/llo/offchain_config_digester_test.go | 65 + .../relay/evm/llo/should_retire_cache.go | 120 ++ .../relay/evm/llo/should_retire_cache_test.go | 50 + .../services/relay/evm/llo_config_provider.go | 94 +- core/services/relay/evm/llo_provider.go | 213 ++- core/services/relay/evm/types/types.go | 3 +- .../0257_add_llo_retirement_report_cache.sql | 24 + core/web/testdata/body/health.html | 3 + core/web/testdata/body/health.json | 9 + core/web/testdata/body/health.txt | 1 + go.mod | 4 +- go.sum | 8 +- integration-tests/go.mod | 4 +- integration-tests/go.sum | 8 +- integration-tests/load/go.mod | 4 +- integration-tests/load/go.sum | 8 +- testdata/scripts/health/default.txtar | 12 +- testdata/scripts/health/multi-chain.txtar | 10 + 78 files changed, 5997 insertions(+), 503 deletions(-) create mode 100644 .changeset/real-doors-visit.md create mode 100644 contracts/.changeset/cool-laws-drum.md delete mode 100644 contracts/src/v0.8/llo-feeds/interfaces/IConfigurator.sol create mode 100644 contracts/src/v0.8/llo-feeds/v0.5.0/configuration/interfaces/IConfigurator.sol create mode 100644 contracts/src/v0.8/llo-feeds/v0.5.0/configuration/test/configurator/BaseConfiguratorTest.t.sol create mode 100644 contracts/src/v0.8/llo-feeds/v0.5.0/configuration/test/configurator/ConfiguratorPromoteStagingConfigTest.t.sol create mode 100644 contracts/src/v0.8/llo-feeds/v0.5.0/configuration/test/configurator/ConfiguratorSetProductionConfigTest.t.sol create mode 100644 contracts/src/v0.8/llo-feeds/v0.5.0/configuration/test/configurator/ConfiguratorSetStagingConfigTest.t.sol create mode 100644 contracts/src/v0.8/llo-feeds/v0.5.0/configuration/test/configurator/ConfiguratorTest.t.sol create mode 100644 core/gethwrappers/llo-feeds/generated/exposed_configurator/exposed_configurator.go create mode 100644 core/services/llo/attested_retirement_report.pb.go create mode 100644 core/services/llo/attested_retirement_report.proto create mode 100644 core/services/llo/generate.go create mode 100644 core/services/llo/never_retire_cache.go create mode 100644 core/services/llo/null_retirement_report_cache.go create mode 100644 core/services/llo/plugin_scoped_retirement_report_cache.go create mode 100644 core/services/llo/plugin_scoped_retirement_report_cache_test.go create mode 100644 core/services/llo/retirement_report_cache.go create mode 100644 core/services/llo/retirement_report_cache_test.go create mode 100644 core/services/llo/retirement_report_orm.go create mode 100644 core/services/llo/retirement_report_orm_test.go create mode 100644 core/services/relay/evm/llo/abi.go create mode 100644 core/services/relay/evm/llo/config_poller.go create mode 100644 core/services/relay/evm/llo/config_poller_test.go create mode 100644 core/services/relay/evm/llo/offchain_config_digester.go create mode 100644 core/services/relay/evm/llo/offchain_config_digester_test.go create mode 100644 core/services/relay/evm/llo/should_retire_cache.go create mode 100644 core/services/relay/evm/llo/should_retire_cache_test.go create mode 100644 core/store/migrate/migrations/0257_add_llo_retirement_report_cache.sql diff --git a/.changeset/real-doors-visit.md b/.changeset/real-doors-visit.md new file mode 100644 index 00000000000..39fa064c3a8 --- /dev/null +++ b/.changeset/real-doors-visit.md @@ -0,0 +1,6 @@ +--- +"chainlink": patch +--- + +Implement blue-green Configurator contract and retirement report handover for LLO +#added diff --git a/ccip/config/evm/Simulated.toml b/ccip/config/evm/Simulated.toml index b3392a93363..e21dc0990f0 100644 --- a/ccip/config/evm/Simulated.toml +++ b/ccip/config/evm/Simulated.toml @@ -1,5 +1,5 @@ ChainID = '1337' -FinalityDepth = 10 +FinalityDepth = 1 MinIncomingConfirmations = 1 MinContractPayment = '100' NoNewHeadsThreshold = '0s' diff --git a/contracts/.changeset/cool-laws-drum.md b/contracts/.changeset/cool-laws-drum.md new file mode 100644 index 00000000000..f7851c3d822 --- /dev/null +++ b/contracts/.changeset/cool-laws-drum.md @@ -0,0 +1,7 @@ +--- +'@chainlink/contracts': patch +--- + +Add new Configurator contract for blue-green LLO deployments + +PR issue: MERC-5954 diff --git a/contracts/gas-snapshots/llo-feeds.gas-snapshot b/contracts/gas-snapshots/llo-feeds.gas-snapshot index daab80b0032..99f2fcc3430 100644 --- a/contracts/gas-snapshots/llo-feeds.gas-snapshot +++ b/contracts/gas-snapshots/llo-feeds.gas-snapshot @@ -18,9 +18,31 @@ ByteUtilTest:test_readUint32MultiWord() (gas: 3371) ByteUtilTest:test_readUint32WithEmptyArray() (gas: 3231) ByteUtilTest:test_readUint32WithNotEnoughBytes() (gas: 3272) ByteUtilTest:test_readZeroAddress() (gas: 3343) -ChannelConfigStoreTest:testSetChannelDefinitions() (gas: 46927) +ChannelConfigStoreTest:testSetChannelDefinitions() (gas: 46905) ChannelConfigStoreTest:testSupportsInterface() (gas: 8367) -ChannelConfigStoreTest:testTypeAndVersion() (gas: 9621) +ChannelConfigStoreTest:testTypeAndVersion() (gas: 9644) +ChannelConfigStoreTest:test_revertsIfCalledByNonOwner() (gas: 11288) +ConfiguratorPromoteStagingConfigTest:test_promotesStagingConfig() (gas: 99047) +ConfiguratorPromoteStagingConfigTest:test_revertsIfCalledByNonOwner() (gas: 10962) +ConfiguratorPromoteStagingConfigTest:test_revertsIfIsGreenProductionDoesNotMatchContractState() (gas: 13423) +ConfiguratorPromoteStagingConfigTest:test_revertsIfNoConfigHasEverBeenSetWithThisConfigId() (gas: 13419) +ConfiguratorPromoteStagingConfigTest:test_revertsIfProductionConfigDigestIsZero() (gas: 68521) +ConfiguratorPromoteStagingConfigTest:test_revertsIfStagingConfigDigestIsZero() (gas: 48258) +ConfiguratorSetProductionConfigTest:test_correctlyUpdatesTheConfig() (gas: 259776) +ConfiguratorSetProductionConfigTest:test_revertsIfCalledByNonOwner() (gas: 266559) +ConfiguratorSetProductionConfigTest:test_revertsIfFaultToleranceIsZero() (gas: 264173) +ConfiguratorSetProductionConfigTest:test_revertsIfNotEnoughSigners() (gas: 95951) +ConfiguratorSetProductionConfigTest:test_revertsIfOnchainConfigIsInvalid() (gas: 60885) +ConfiguratorSetProductionConfigTest:test_revertsIfSetWithTooManySigners() (gas: 107412) +ConfiguratorSetProductionConfigTest:test_supportsHigherVersionsIgnoringExcessOnchainConfig() (gas: 125099) +ConfiguratorSetStagingConfigTest:test_correctlyUpdatesTheConfig() (gas: 265921) +ConfiguratorSetStagingConfigTest:test_revertsIfCalledByNonOwner() (gas: 266528) +ConfiguratorSetStagingConfigTest:test_revertsIfFaultToleranceIsZero() (gas: 264142) +ConfiguratorSetStagingConfigTest:test_revertsIfNotEnoughSigners() (gas: 95920) +ConfiguratorSetStagingConfigTest:test_revertsIfOnchainConfigIsInvalid() (gas: 67763) +ConfiguratorSetStagingConfigTest:test_revertsIfSetWithTooManySigners() (gas: 107392) +ConfiguratorTest:testSupportsInterface() (gas: 8367) +ConfiguratorTest:testTypeAndVersion() (gas: 9683) DestinationFeeManagerProcessFeeTest:test_DiscountIsAppliedForNative() (gas: 52717) DestinationFeeManagerProcessFeeTest:test_DiscountIsReturnedForNative() (gas: 52645) DestinationFeeManagerProcessFeeTest:test_DiscountIsReturnedForNativeWithSurcharge() (gas: 78879) diff --git a/contracts/scripts/native_solc_compile_all_llo-feeds b/contracts/scripts/native_solc_compile_all_llo-feeds index ba5e6a4c8b9..bbcd53df900 100755 --- a/contracts/scripts/native_solc_compile_all_llo-feeds +++ b/contracts/scripts/native_solc_compile_all_llo-feeds @@ -42,3 +42,4 @@ compileContract llo-feeds/v0.5.0/configuration/Configurator.sol # Test | Mocks compileContract llo-feeds/v0.3.0/test/mocks/ErroredVerifier.sol compileContract llo-feeds/v0.3.0/test/mocks/ExposedVerifier.sol +compileContract llo-feeds/v0.5.0/configuration/test/mocks/ExposedConfigurator.sol diff --git a/contracts/src/v0.8/llo-feeds/interfaces/IConfigurator.sol b/contracts/src/v0.8/llo-feeds/interfaces/IConfigurator.sol deleted file mode 100644 index 478afa918cd..00000000000 --- a/contracts/src/v0.8/llo-feeds/interfaces/IConfigurator.sol +++ /dev/null @@ -1,28 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.19; - -interface IConfigurator { - /// @notice This event is emitted whenever a new configuration is set for a feed. It triggers a new run of the offchain reporting protocol. - event ConfigSet( - bytes32 indexed configId, - uint32 previousConfigBlockNumber, - bytes32 configDigest, - uint64 configCount, - address[] signers, - bytes32[] offchainTransmitters, - uint8 f, - bytes onchainConfig, - uint64 offchainConfigVersion, - bytes offchainConfig - ); - - function setConfig( - bytes32 configId, - address[] memory signers, - bytes32[] memory offchainTransmitters, - uint8 f, - bytes memory onchainConfig, - uint64 offchainConfigVersion, - bytes memory offchainConfig - ) external; -} diff --git a/contracts/src/v0.8/llo-feeds/v0.5.0/configuration/Configurator.sol b/contracts/src/v0.8/llo-feeds/v0.5.0/configuration/Configurator.sol index 5945f7ab730..96be15fd6be 100644 --- a/contracts/src/v0.8/llo-feeds/v0.5.0/configuration/Configurator.sol +++ b/contracts/src/v0.8/llo-feeds/v0.5.0/configuration/Configurator.sol @@ -4,11 +4,14 @@ pragma solidity 0.8.19; import {ConfirmedOwner} from "../../../shared/access/ConfirmedOwner.sol"; import {TypeAndVersionInterface} from "../../../interfaces/TypeAndVersionInterface.sol"; import {IERC165} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC165.sol"; -import {IConfigurator} from "../../interfaces/IConfigurator.sol"; +import {IConfigurator} from "./interfaces/IConfigurator.sol"; // OCR2 standard uint256 constant MAX_NUM_ORACLES = 31; +// Subsequent versions of the onchain config must be backwards compatible and only append fields +uint256 constant MIN_SUPPORTED_ONCHAIN_CONFIG_VERSION = 1; + /** * @title Configurator * @author samsondav @@ -32,32 +35,96 @@ contract Configurator is IConfigurator, ConfirmedOwner, TypeAndVersionInterface, /// @param minSigners The minimum number of signers that need to sign a report error InsufficientSigners(uint256 numSigners, uint256 minSigners); + /// @notice This error is thrown whenever the onchainConfig length is invalid + /// (must be at least 64 bytes) + /// @param onchainConfigLength The (wrong) length of the onchainConfig + error InvalidOnchainLength(uint256 onchainConfigLength); + + /// @notice This error is thrown if the onchainConfig version is too old. + /// @param version The version of the onchainConfig + error UnsupportedOnchainConfigVersion(uint256 version); + + /// @notice This event is emitted when a production config is set with a non-zero predecessor config digest in the on-chain config. + /// @param predecessorConfigDigest The predecessor config digest + error NonZeroPredecessorConfigDigest(bytes32 predecessorConfigDigest); + + /// @notice This event is emitted when a staging config is set with a predecessor config digest that does not match the current production config digest. + /// @param predecessorConfigDigest The predecessor config digest + error InvalidPredecessorConfigDigest(bytes32 predecessorConfigDigest); + + /// @notice This event is emitted during promoteStagingConfig if the isGreenProduction flag does not match the contract state + /// @param configId The configId + /// @param isGreenProductionContractState The current (correct) isGreenProduction state according to the contract + error IsGreenProductionMustMatchContractState(bytes32 configId, bool isGreenProductionContractState); + + /// @notice This event is emitted during promoteStagingConfig if the configId has never been set + /// @param configId The configId that has never been set + error ConfigUnset(bytes32 configId); + + /// @notice This event is emitted during promoteStagingConfig if the configId has never been set as a staging config + /// @param configId The configId that has never been set as a staging config + /// @param isGreenProduction The isGreenProduction flag + error ConfigUnsetStaging(bytes32 configId, bool isGreenProduction); + + /// @notice This event is emitted during promoteStagingConfig if the configId has never been set as a production config + /// @param configId The configId that has never been set as a production config + /// @param isGreenProduction The isGreenProduction flag + error ConfigUnsetProduction(bytes32 configId, bool isGreenProduction); + struct ConfigurationState { - // The number of times a new configuration - // has been set + // The number of times a configuration (either staging or production) has + // been set for this configId uint64 configCount; // The block number of the block the last time - /// the configuration was updated. + // the configuration was updated. uint32 latestConfigBlockNumber; + // isGreenProduction is a bit flip that indicates whether blue is production + // exactly one of blue/green must be production at all times. + // 0 -> blue is production + // 1 -> green is production + // + // So, to clarify, if isGreenProduction is false (initial state) then: + // [0](blue) is production and [1](green) is staging/retired + // + // and if isGreenProduction is true then: + // [0](blue) is staging/retired and [1](green) is production + // State is swapped every time a staging config is promoted to production. + bool isGreenProduction; + // The digest of the current configurations (0 is always blue, 1 is always green) + bytes32[2] configDigest; } constructor() ConfirmedOwner(msg.sender) {} - /// @notice Configuration states keyed on DON ID + /// @notice Configuration states keyed on config ID + /// @dev The first element is the blue configuration state + /// and the second element is the green configuration state mapping(bytes32 => ConfigurationState) internal s_configurationStates; /// @inheritdoc IConfigurator - function setConfig( - bytes32 donId, - address[] memory signers, + function setProductionConfig( + bytes32 configId, + bytes[] memory signers, bytes32[] memory offchainTransmitters, uint8 f, bytes memory onchainConfig, uint64 offchainConfigVersion, bytes memory offchainConfig ) external override checkConfigValid(signers.length, f) onlyOwner { + if (onchainConfig.length < 64) revert InvalidOnchainLength(onchainConfig.length); + + // Ensure that predecessorConfigDigest is unset and version is correct + uint256 version; + bytes32 predecessorConfigDigest; + assembly { + version := mload(add(onchainConfig, 32)) + predecessorConfigDigest := mload(add(onchainConfig, 64)) + } + if (version < MIN_SUPPORTED_ONCHAIN_CONFIG_VERSION) revert UnsupportedOnchainConfigVersion(version); + if (predecessorConfigDigest != 0) revert NonZeroPredecessorConfigDigest(predecessorConfigDigest); + _setConfig( - donId, + configId, block.chainid, address(this), signers, @@ -65,12 +132,84 @@ contract Configurator is IConfigurator, ConfirmedOwner, TypeAndVersionInterface, f, onchainConfig, offchainConfigVersion, - offchainConfig + offchainConfig, + true + ); + } + + /// @inheritdoc IConfigurator + function setStagingConfig( + bytes32 configId, + bytes[] memory signers, + bytes32[] memory offchainTransmitters, + uint8 f, + bytes memory onchainConfig, + uint64 offchainConfigVersion, + bytes memory offchainConfig + ) external override checkConfigValid(signers.length, f) onlyOwner { + if (onchainConfig.length < 64) revert InvalidOnchainLength(onchainConfig.length); + + // Ensure that predecessorConfigDigest is set and corresponds to an + // existing production instance + uint256 version; + bytes32 predecessorConfigDigest; + assembly { + version := mload(add(onchainConfig, 32)) + predecessorConfigDigest := mload(add(onchainConfig, 64)) + } + if (version < MIN_SUPPORTED_ONCHAIN_CONFIG_VERSION) revert UnsupportedOnchainConfigVersion(version); + + ConfigurationState memory configurationState = s_configurationStates[configId]; + if ( + predecessorConfigDigest != + s_configurationStates[configId].configDigest[configurationState.isGreenProduction ? 1 : 0] + ) revert InvalidPredecessorConfigDigest(predecessorConfigDigest); + + _setConfig( + configId, + block.chainid, + address(this), + signers, + offchainTransmitters, + f, + onchainConfig, + offchainConfigVersion, + offchainConfig, + false ); } + /// @inheritdoc IConfigurator + // This will trigger the following: + // - Offchain ShouldRetireCache will start returning true for the old (production) + // protocol instance + // - Once the old production instance retires it will generate a handover + // retirement report + // - The staging instance will become the new production instance once + // any honest oracle that is on both instances forward the retirement + // report from the old instance to the new instance via the + // PredecessorRetirementReportCache + // + // Note: the promotion flow only works if the previous production instance + // is working correctly & generating reports. If that's not the case, the + // owner is expected to "setProductionConfig" directly instead. This will + // cause "gaps" to be created, but that seems unavoidable in such a scenario. + function promoteStagingConfig(bytes32 configId, bool isGreenProduction) external onlyOwner { + ConfigurationState storage configurationState = s_configurationStates[configId]; + if (isGreenProduction != configurationState.isGreenProduction) + revert IsGreenProductionMustMatchContractState(configId, !isGreenProduction); + if (configurationState.configCount == 0) revert ConfigUnset(configId); + if (configurationState.configDigest[isGreenProduction ? 0 : 1] == bytes32(0)) + revert ConfigUnsetStaging(configId, isGreenProduction); + bytes32 retiredConfigDigest = configurationState.configDigest[isGreenProduction ? 1 : 0]; + if (retiredConfigDigest == bytes32(0)) revert ConfigUnsetProduction(configId, isGreenProduction); + + configurationState.isGreenProduction = !isGreenProduction; // flip blue<->green + emit PromoteStagingConfig(configId, retiredConfigDigest, !isGreenProduction); + } + /// @notice Sets config based on the given arguments - /// @param donId DON ID to set config for + /// @param configId config ID to set config for /// @param sourceChainId Chain ID of source config /// @param sourceAddress Address of source config Verifier /// @param signers addresses with which oracles sign the reports @@ -80,22 +219,23 @@ contract Configurator is IConfigurator, ConfirmedOwner, TypeAndVersionInterface, /// @param offchainConfigVersion version number for offchainEncoding schema /// @param offchainConfig serialized configuration used by the oracles exclusively and only passed through the contract function _setConfig( - bytes32 donId, + bytes32 configId, uint256 sourceChainId, address sourceAddress, - address[] memory signers, + bytes[] memory signers, bytes32[] memory offchainTransmitters, uint8 f, bytes memory onchainConfig, uint64 offchainConfigVersion, - bytes memory offchainConfig + bytes memory offchainConfig, + bool isProduction ) internal { - ConfigurationState storage configurationState = s_configurationStates[donId]; + ConfigurationState storage configurationState = s_configurationStates[configId]; uint64 newConfigCount = ++configurationState.configCount; bytes32 configDigest = _configDigestFromConfigData( - donId, + configId, sourceChainId, sourceAddress, newConfigCount, @@ -107,24 +247,43 @@ contract Configurator is IConfigurator, ConfirmedOwner, TypeAndVersionInterface, offchainConfig ); - emit ConfigSet( - donId, - configurationState.latestConfigBlockNumber, - configDigest, - newConfigCount, - signers, - offchainTransmitters, - f, - onchainConfig, - offchainConfigVersion, - offchainConfig - ); + if (isProduction) { + emit ProductionConfigSet( + configId, + configurationState.latestConfigBlockNumber, + configDigest, + newConfigCount, + signers, + offchainTransmitters, + f, + onchainConfig, + offchainConfigVersion, + offchainConfig, + configurationState.isGreenProduction + ); + s_configurationStates[configId].configDigest[configurationState.isGreenProduction ? 1 : 0] = configDigest; + } else { + emit StagingConfigSet( + configId, + configurationState.latestConfigBlockNumber, + configDigest, + newConfigCount, + signers, + offchainTransmitters, + f, + onchainConfig, + offchainConfigVersion, + offchainConfig, + configurationState.isGreenProduction + ); + s_configurationStates[configId].configDigest[configurationState.isGreenProduction ? 0 : 1] = configDigest; + } configurationState.latestConfigBlockNumber = uint32(block.number); } /// @notice Generates the config digest from config data - /// @param donId DON ID to set config for + /// @param configId config ID to set config for /// @param sourceChainId Chain ID of configurator contract /// @param sourceAddress Address of configurator contract /// @param configCount ordinal number of this config setting among all config settings over the life of this contract @@ -136,11 +295,11 @@ contract Configurator is IConfigurator, ConfirmedOwner, TypeAndVersionInterface, /// @param offchainConfig serialized configuration used by the oracles exclusively and only passed through the contract /// @dev This function is a modified version of the method from OCR2Abstract function _configDigestFromConfigData( - bytes32 donId, + bytes32 configId, uint256 sourceChainId, address sourceAddress, uint64 configCount, - address[] memory signers, + bytes[] memory signers, bytes32[] memory offchainTransmitters, uint8 f, bytes memory onchainConfig, @@ -150,7 +309,7 @@ contract Configurator is IConfigurator, ConfirmedOwner, TypeAndVersionInterface, uint256 h = uint256( keccak256( abi.encode( - donId, + configId, sourceChainId, sourceAddress, configCount, @@ -164,7 +323,7 @@ contract Configurator is IConfigurator, ConfirmedOwner, TypeAndVersionInterface, ) ); uint256 prefixMask = type(uint256).max << (256 - 16); // 0xFFFF00..00 - // 0x0006 corresponds to ConfigDigestPrefixLLO in libocr + // 0x0009 corresponds to ConfigDigestPrefixLLO in libocr uint256 prefix = 0x0009 << (256 - 16); // 0x000900..00 return bytes32((prefix & prefixMask) | (h & ~prefixMask)); } @@ -176,7 +335,7 @@ contract Configurator is IConfigurator, ConfirmedOwner, TypeAndVersionInterface, /// @inheritdoc TypeAndVersionInterface function typeAndVersion() external pure override returns (string memory) { - return "Configurator 0.4.0"; + return "Configurator 0.5.0"; } modifier checkConfigValid(uint256 numSigners, uint256 f) { diff --git a/contracts/src/v0.8/llo-feeds/v0.5.0/configuration/interfaces/IConfigurator.sol b/contracts/src/v0.8/llo-feeds/v0.5.0/configuration/interfaces/IConfigurator.sol new file mode 100644 index 00000000000..5f9b14b29c5 --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.5.0/configuration/interfaces/IConfigurator.sol @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.19; + +interface IConfigurator { + /// @notice This event is emitted whenever a new production configuration is set for a feed. It triggers a new run of the offchain reporting protocol. + event ProductionConfigSet( + bytes32 indexed configId, + uint32 previousConfigBlockNumber, + bytes32 configDigest, + uint64 configCount, + bytes[] signers, + bytes32[] offchainTransmitters, + uint8 f, + bytes onchainConfig, + uint64 offchainConfigVersion, + bytes offchainConfig, + bool isGreenProduction + ); + + /// @notice This event is emitted whenever a new staging configuration is set for a feed. It triggers a new run of the offchain reporting protocol. + event StagingConfigSet( + bytes32 indexed configId, + uint32 previousConfigBlockNumber, + bytes32 configDigest, + uint64 configCount, + bytes[] signers, + bytes32[] offchainTransmitters, + uint8 f, + bytes onchainConfig, + uint64 offchainConfigVersion, + bytes offchainConfig, + bool isGreenProduction + ); + + event PromoteStagingConfig(bytes32 indexed configId, bytes32 indexed retiredConfigDigest, bool isGreenProduction); + + /// @notice Promotes the staging configuration to production + // currentState must match the current state for the given configId (prevents + // accidentally double-flipping if same transaction is sent twice) + function promoteStagingConfig(bytes32 configId, bool currentState) external; + + function setProductionConfig( + bytes32 configId, + bytes[] memory signers, + bytes32[] memory offchainTransmitters, + uint8 f, + bytes memory onchainConfig, + uint64 offchainConfigVersion, + bytes memory offchainConfig + ) external; + + function setStagingConfig( + bytes32 configId, + bytes[] memory signers, + bytes32[] memory offchainTransmitters, + uint8 f, + bytes memory onchainConfig, + uint64 offchainConfigVersion, + bytes memory offchainConfig + ) external; +} diff --git a/contracts/src/v0.8/llo-feeds/v0.5.0/configuration/test/ChannelConfigStore.t.sol b/contracts/src/v0.8/llo-feeds/v0.5.0/configuration/test/ChannelConfigStore.t.sol index 70b033e66b1..afea37f48a3 100644 --- a/contracts/src/v0.8/llo-feeds/v0.5.0/configuration/test/ChannelConfigStore.t.sol +++ b/contracts/src/v0.8/llo-feeds/v0.5.0/configuration/test/ChannelConfigStore.t.sol @@ -27,6 +27,13 @@ contract ChannelConfigStoreTest is Test { assertTrue(channelConfigStore.supportsInterface(type(IChannelConfigStore).interfaceId)); } + function test_revertsIfCalledByNonOwner() public { + vm.expectRevert("Only callable by owner"); + + vm.startPrank(address(2)); + channelConfigStore.setChannelDefinitions(42, "url", keccak256("sha")); + } + function testSetChannelDefinitions() public { vm.expectEmit(); emit NewChannelDefinition(42, 1, "url", keccak256("sha")); diff --git a/contracts/src/v0.8/llo-feeds/v0.5.0/configuration/test/configurator/BaseConfiguratorTest.t.sol b/contracts/src/v0.8/llo-feeds/v0.5.0/configuration/test/configurator/BaseConfiguratorTest.t.sol new file mode 100644 index 00000000000..fb44c1f1981 --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.5.0/configuration/test/configurator/BaseConfiguratorTest.t.sol @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.19; + +import {IConfigurator} from "../../interfaces/IConfigurator.sol"; +import {Test} from "forge-std/Test.sol"; +import {Configurator} from "../../Configurator.sol"; +import {ExposedConfigurator} from "../mocks/ExposedConfigurator.sol"; +import {ExposedChannelConfigStore} from "../mocks/ExposedChannelConfigStore.sol"; + +/** + * @title ConfiguratorTest + * @author samsondav + * @notice Base class for Configurator tests + */ +contract BaseTest is Test { + uint256 internal constant MAX_ORACLES = 31; + address internal constant USER = address(2); + bytes32 internal constant CONFIG_ID_1 = (keccak256("CONFIG_ID_1")); + uint8 internal constant FAULT_TOLERANCE = 10; + uint64 internal constant OFFCHAIN_CONFIG_VERSION = 1; + + bytes32[] internal s_offchaintransmitters; + bool private s_baseTestInitialized; + + Configurator internal s_configurator; + ExposedConfigurator internal s_exposedConfigurator; + + event ProductionConfigSet( + bytes32 indexed configId, + uint32 previousConfigBlockNumber, + bytes32 configDigest, + uint64 configCount, + bytes[] signers, + bytes32[] offchainTransmitters, + uint8 f, + bytes onchainConfig, + uint64 offchainConfigVersion, + bytes offchainConfig, + bool isGreenProduction + ); + event StagingConfigSet( + bytes32 indexed configId, + uint32 previousConfigBlockNumber, + bytes32 configDigest, + uint64 configCount, + bytes[] signers, + bytes32[] offchainTransmitters, + uint8 f, + bytes onchainConfig, + uint64 offchainConfigVersion, + bytes offchainConfig, + bool isGreenProduction + ); + event PromoteStagingConfig(bytes32 indexed configId, bytes32 indexed retiredConfigDigest, bool isGreenProduction); + + bytes[MAX_ORACLES] internal s_signers; + + function setUp() public virtual { + // BaseTest.setUp may be called multiple times from tests' setUp due to inheritance. + if (s_baseTestInitialized) return; + s_baseTestInitialized = true; + + s_configurator = new Configurator(); + s_exposedConfigurator = new ExposedConfigurator(); + + for (uint256 i; i < MAX_ORACLES; i++) { + bytes memory mockSigner = abi.encodePacked(i + 1); + s_signers[i] = mockSigner; + } + + for (uint256 i; i < MAX_ORACLES; i++) { + s_offchaintransmitters.push(bytes32(i + 1)); + } + } + + function _getSigners(uint256 numSigners) internal view returns (bytes[] memory) { + bytes[] memory signers = new bytes[](numSigners); + for (uint256 i; i < numSigners; i++) { + signers[i] = s_signers[i]; + } + return signers; + } + + function _getOffchainTransmitters(uint256 numTransmitters) internal pure returns (bytes32[] memory) { + bytes32[] memory transmitters = new bytes32[](numTransmitters); + for (uint256 i; i < numTransmitters; i++) { + transmitters[i] = bytes32(101 + i); + } + return transmitters; + } +} diff --git a/contracts/src/v0.8/llo-feeds/v0.5.0/configuration/test/configurator/ConfiguratorPromoteStagingConfigTest.t.sol b/contracts/src/v0.8/llo-feeds/v0.5.0/configuration/test/configurator/ConfiguratorPromoteStagingConfigTest.t.sol new file mode 100644 index 00000000000..604c9d266eb --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.5.0/configuration/test/configurator/ConfiguratorPromoteStagingConfigTest.t.sol @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.19; + +import {BaseTest} from "./BaseConfiguratorTest.t.sol"; +import {Configurator} from "../../Configurator.sol"; + +contract ConfiguratorPromoteStagingConfigTest is BaseTest { + function setUp() public virtual override { + BaseTest.setUp(); + } + + function test_revertsIfCalledByNonOwner() public { + vm.startPrank(USER); + + vm.expectRevert("Only callable by owner"); + + s_configurator.promoteStagingConfig(CONFIG_ID_1, false); + } + + function test_revertsIfIsGreenProductionDoesNotMatchContractState() public { + vm.expectRevert( + abi.encodeWithSelector(Configurator.IsGreenProductionMustMatchContractState.selector, CONFIG_ID_1, false) + ); + s_configurator.promoteStagingConfig(CONFIG_ID_1, true); + } + + function test_revertsIfNoConfigHasEverBeenSetWithThisConfigId() public { + vm.expectRevert(abi.encodeWithSelector(Configurator.ConfigUnset.selector, keccak256("nonExistentConfigId"))); + s_configurator.promoteStagingConfig(keccak256("nonExistentConfigId"), false); + } + + function test_revertsIfStagingConfigDigestIsZero() public { + // isGreenProduction = false + s_exposedConfigurator.exposedSetConfigurationState( + CONFIG_ID_1, + Configurator.ConfigurationState(1, uint32(block.number), false, [bytes32(0), bytes32(0)]) + ); + + vm.expectRevert(abi.encodeWithSelector(Configurator.ConfigUnsetStaging.selector, CONFIG_ID_1, false)); + s_exposedConfigurator.promoteStagingConfig(CONFIG_ID_1, false); + + // isGreenProduction = true + s_exposedConfigurator.exposedSetConfigurationState( + CONFIG_ID_1, + Configurator.ConfigurationState(1, uint32(block.number), true, [bytes32(0), bytes32(0)]) + ); + + vm.expectRevert(abi.encodeWithSelector(Configurator.ConfigUnsetStaging.selector, CONFIG_ID_1, true)); + s_exposedConfigurator.promoteStagingConfig(CONFIG_ID_1, true); + } + + function test_revertsIfProductionConfigDigestIsZero() public { + // isGreenProduction = false + s_exposedConfigurator.exposedSetConfigurationState( + CONFIG_ID_1, + Configurator.ConfigurationState(1, uint32(block.number), false, [bytes32(0), keccak256("stagingConfigDigest")]) + ); + + vm.expectRevert(abi.encodeWithSelector(Configurator.ConfigUnsetProduction.selector, CONFIG_ID_1, false)); + s_exposedConfigurator.promoteStagingConfig(CONFIG_ID_1, false); + + // isGreenProduction = true + + s_exposedConfigurator.exposedSetConfigurationState( + CONFIG_ID_1, + Configurator.ConfigurationState(1, uint32(block.number), true, [keccak256("stagingConfigDigest"), bytes32(0)]) + ); + + vm.expectRevert(abi.encodeWithSelector(Configurator.ConfigUnsetProduction.selector, CONFIG_ID_1, true)); + s_exposedConfigurator.promoteStagingConfig(CONFIG_ID_1, true); + } + + function test_promotesStagingConfig() public { + // isGreenProduction = false + s_exposedConfigurator.exposedSetConfigurationState( + CONFIG_ID_1, + Configurator.ConfigurationState( + 1, + uint32(block.number), + false, + [keccak256("productionConfigDigest"), keccak256("stagingConfigDigest")] + ) + ); + + vm.expectEmit(); + emit PromoteStagingConfig(CONFIG_ID_1, keccak256("productionConfigDigest"), true); + + s_exposedConfigurator.promoteStagingConfig(CONFIG_ID_1, false); + assertEq(s_exposedConfigurator.exposedReadConfigurationStates(CONFIG_ID_1).isGreenProduction, true); + + // isGreenProduction = true + + s_exposedConfigurator.exposedSetConfigurationState( + CONFIG_ID_1, + Configurator.ConfigurationState( + 1, + uint32(block.number), + true, + [keccak256("stagingConfigDigest"), keccak256("productionConfigDigest")] + ) + ); + + vm.expectEmit(); + emit PromoteStagingConfig(CONFIG_ID_1, keccak256("productionConfigDigest"), false); + + s_exposedConfigurator.promoteStagingConfig(CONFIG_ID_1, true); + assertEq(s_exposedConfigurator.exposedReadConfigurationStates(CONFIG_ID_1).isGreenProduction, false); + } +} diff --git a/contracts/src/v0.8/llo-feeds/v0.5.0/configuration/test/configurator/ConfiguratorSetProductionConfigTest.t.sol b/contracts/src/v0.8/llo-feeds/v0.5.0/configuration/test/configurator/ConfiguratorSetProductionConfigTest.t.sol new file mode 100644 index 00000000000..c4c177ef4c6 --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.5.0/configuration/test/configurator/ConfiguratorSetProductionConfigTest.t.sol @@ -0,0 +1,338 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.19; + +import {BaseTest} from "./BaseConfiguratorTest.t.sol"; +import {Configurator} from "../../Configurator.sol"; + +contract ConfiguratorSetProductionConfigTest is BaseTest { + function setUp() public virtual override { + BaseTest.setUp(); + } + + function test_revertsIfCalledByNonOwner() public { + vm.expectRevert("Only callable by owner"); + bytes[] memory signers = _getSigners(MAX_ORACLES); + + vm.startPrank(USER); + s_configurator.setProductionConfig( + CONFIG_ID_1, + signers, + s_offchaintransmitters, + FAULT_TOLERANCE, + bytes(""), + OFFCHAIN_CONFIG_VERSION, + bytes("") + ); + } + + function test_revertsIfSetWithTooManySigners() public { + bytes[] memory signers = new bytes[](MAX_ORACLES + 1); + vm.expectRevert(abi.encodeWithSelector(Configurator.ExcessSigners.selector, signers.length, MAX_ORACLES)); + s_configurator.setProductionConfig( + CONFIG_ID_1, + signers, + s_offchaintransmitters, + FAULT_TOLERANCE, + bytes(""), + OFFCHAIN_CONFIG_VERSION, + bytes("") + ); + } + + function test_revertsIfFaultToleranceIsZero() public { + vm.expectRevert(abi.encodeWithSelector(Configurator.FaultToleranceMustBePositive.selector)); + bytes[] memory signers = _getSigners(MAX_ORACLES); + s_configurator.setProductionConfig( + CONFIG_ID_1, + signers, + s_offchaintransmitters, + 0, + bytes(""), + OFFCHAIN_CONFIG_VERSION, + bytes("") + ); + } + + function test_revertsIfNotEnoughSigners() public { + bytes[] memory signers = _getSigners(2); + + vm.expectRevert( + abi.encodeWithSelector(Configurator.InsufficientSigners.selector, signers.length, FAULT_TOLERANCE * 3 + 1) + ); + s_configurator.setProductionConfig( + CONFIG_ID_1, + signers, + s_offchaintransmitters, + FAULT_TOLERANCE, + bytes(""), + OFFCHAIN_CONFIG_VERSION, + bytes("") + ); + } + + function test_revertsIfOnchainConfigIsInvalid() public { + bytes[] memory signers = _getSigners(4); + bytes32[] memory offchainTransmitters = _getOffchainTransmitters(4); + bytes memory onchainConfig = bytes(""); + uint8 f = 1; + bytes memory offchainConfig = abi.encodePacked(keccak256("offchainConfig")); + + vm.expectRevert(abi.encodeWithSelector(Configurator.InvalidOnchainLength.selector, onchainConfig.length)); + s_configurator.setProductionConfig( + CONFIG_ID_1, + signers, + offchainTransmitters, + f, + onchainConfig, + OFFCHAIN_CONFIG_VERSION, + offchainConfig + ); + + onchainConfig = abi.encode(uint256(0), bytes32(0)); + + vm.expectRevert(abi.encodeWithSelector(Configurator.UnsupportedOnchainConfigVersion.selector, uint256(0))); + s_configurator.setProductionConfig( + CONFIG_ID_1, + signers, + offchainTransmitters, + f, + onchainConfig, + OFFCHAIN_CONFIG_VERSION, + offchainConfig + ); + + onchainConfig = abi.encode( + uint256(1), + keccak256("any non-zero predecessor config digest is invalid for production") + ); + + vm.expectRevert( + abi.encodeWithSelector( + Configurator.NonZeroPredecessorConfigDigest.selector, + keccak256("any non-zero predecessor config digest is invalid for production") + ) + ); + s_configurator.setProductionConfig( + CONFIG_ID_1, + signers, + offchainTransmitters, + f, + onchainConfig, + OFFCHAIN_CONFIG_VERSION, + offchainConfig + ); + } + + function test_supportsHigherVersionsIgnoringExcessOnchainConfig() public { + bytes[] memory signers = _getSigners(4); + bytes32[] memory offchainTransmitters = _getOffchainTransmitters(4); + uint8 f = 1; + bytes memory onchainConfig = abi.encodePacked(uint256(2), bytes32(0), keccak256("some rubbish")); + bytes memory offchainConfig = abi.encodePacked(keccak256("offchainConfig")); + + // initial block number + vm.roll(5); + + bytes32 cd1 = s_exposedConfigurator.exposedConfigDigestFromConfigData( + CONFIG_ID_1, + block.chainid, + address(s_exposedConfigurator), + 1, + signers, + offchainTransmitters, + f, + onchainConfig, + OFFCHAIN_CONFIG_VERSION, + offchainConfig + ); + + // when isGreenProduction=false + + vm.expectEmit(); + emit ProductionConfigSet( + CONFIG_ID_1, + 0, + cd1, + 1, + signers, + offchainTransmitters, + f, + onchainConfig, + OFFCHAIN_CONFIG_VERSION, + offchainConfig, + false + ); + + s_exposedConfigurator.setProductionConfig( + CONFIG_ID_1, + signers, + offchainTransmitters, + f, + onchainConfig, + OFFCHAIN_CONFIG_VERSION, + offchainConfig + ); + } + + function test_correctlyUpdatesTheConfig() public { + bytes[] memory signers = _getSigners(4); + bytes32[] memory offchainTransmitters = _getOffchainTransmitters(4); + uint8 f = 1; + bytes memory onchainConfig = abi.encodePacked(uint256(1), bytes32(0)); + bytes memory offchainConfig = abi.encodePacked(keccak256("offchainConfig")); + + // initial block number + vm.roll(5); + + bytes32 cd1 = s_exposedConfigurator.exposedConfigDigestFromConfigData( + CONFIG_ID_1, + block.chainid, + address(s_exposedConfigurator), + 1, + signers, + offchainTransmitters, + f, + onchainConfig, + OFFCHAIN_CONFIG_VERSION, + offchainConfig + ); + + // when isGreenProduction=false + + vm.expectEmit(); + emit ProductionConfigSet( + CONFIG_ID_1, + 0, + cd1, + 1, + signers, + offchainTransmitters, + f, + onchainConfig, + OFFCHAIN_CONFIG_VERSION, + offchainConfig, + false + ); + + s_exposedConfigurator.setProductionConfig( + CONFIG_ID_1, + signers, + offchainTransmitters, + f, + onchainConfig, + OFFCHAIN_CONFIG_VERSION, + offchainConfig + ); + + Configurator.ConfigurationState memory configurationState = s_exposedConfigurator.exposedReadConfigurationStates( + CONFIG_ID_1 + ); + assertEq(configurationState.configDigest[0], cd1); + assertEq(configurationState.configDigest[1], 0); // no staging config yet + assertEq(configurationState.configCount, 1); + assertEq(configurationState.isGreenProduction, false); + assertEq(configurationState.latestConfigBlockNumber, block.number); + + // go to new block + vm.roll(10); + + // set it again, configCount=2 + + bytes32 cd2 = s_exposedConfigurator.exposedConfigDigestFromConfigData( + CONFIG_ID_1, + block.chainid, + address(s_exposedConfigurator), + 2, + signers, + offchainTransmitters, + f, + onchainConfig, + OFFCHAIN_CONFIG_VERSION, + offchainConfig + ); + + vm.expectEmit(); + emit ProductionConfigSet( + CONFIG_ID_1, + 5, + cd2, + 2, + signers, + offchainTransmitters, + f, + onchainConfig, + OFFCHAIN_CONFIG_VERSION, + offchainConfig, + false + ); + + s_exposedConfigurator.setProductionConfig( + CONFIG_ID_1, + signers, + offchainTransmitters, + f, + onchainConfig, + OFFCHAIN_CONFIG_VERSION, + offchainConfig + ); + + configurationState = s_exposedConfigurator.exposedReadConfigurationStates(CONFIG_ID_1); + assertEq(configurationState.configDigest[0], cd2); + assertEq(configurationState.configDigest[1], 0); // no staging config yet + assertEq(configurationState.configCount, 2); + assertEq(configurationState.isGreenProduction, false); + assertEq(configurationState.latestConfigBlockNumber, block.number); + + // when isGreenProduction=true + s_exposedConfigurator.exposedSetIsGreenProduction(CONFIG_ID_1, true); + + // go to new block + vm.roll(15); + + // set it again, configCount=3 + bytes32 cd3 = s_exposedConfigurator.exposedConfigDigestFromConfigData( + CONFIG_ID_1, + block.chainid, + address(s_exposedConfigurator), + 3, + signers, + offchainTransmitters, + f, + onchainConfig, + OFFCHAIN_CONFIG_VERSION, + offchainConfig + ); + + vm.expectEmit(); + emit ProductionConfigSet( + CONFIG_ID_1, + 10, + cd3, + 3, + signers, + offchainTransmitters, + f, + onchainConfig, + OFFCHAIN_CONFIG_VERSION, + offchainConfig, + true + ); + + s_exposedConfigurator.setProductionConfig( + CONFIG_ID_1, + signers, + offchainTransmitters, + f, + onchainConfig, + OFFCHAIN_CONFIG_VERSION, + offchainConfig + ); + + configurationState = s_exposedConfigurator.exposedReadConfigurationStates(CONFIG_ID_1); + assertEq(configurationState.configDigest[0], cd2); // the previous config left unchanged + assertEq(configurationState.configDigest[1], cd3); // new config is on green now because green is production due to isGreenProduction=true + assertEq(configurationState.configCount, 3); + assertEq(configurationState.isGreenProduction, true); + assertEq(configurationState.latestConfigBlockNumber, block.number); + } +} diff --git a/contracts/src/v0.8/llo-feeds/v0.5.0/configuration/test/configurator/ConfiguratorSetStagingConfigTest.t.sol b/contracts/src/v0.8/llo-feeds/v0.5.0/configuration/test/configurator/ConfiguratorSetStagingConfigTest.t.sol new file mode 100644 index 00000000000..4487ece16e1 --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.5.0/configuration/test/configurator/ConfiguratorSetStagingConfigTest.t.sol @@ -0,0 +1,290 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.19; + +import {BaseTest} from "./BaseConfiguratorTest.t.sol"; +import {Configurator} from "../../Configurator.sol"; + +contract ConfiguratorSetStagingConfigTest is BaseTest { + function setUp() public virtual override { + BaseTest.setUp(); + } + + function test_revertsIfCalledByNonOwner() public { + vm.expectRevert("Only callable by owner"); + bytes[] memory signers = _getSigners(MAX_ORACLES); + + vm.startPrank(USER); + s_configurator.setStagingConfig( + CONFIG_ID_1, + signers, + s_offchaintransmitters, + FAULT_TOLERANCE, + bytes(""), + OFFCHAIN_CONFIG_VERSION, + bytes("") + ); + } + + function test_revertsIfSetWithTooManySigners() public { + bytes[] memory signers = new bytes[](MAX_ORACLES + 1); + vm.expectRevert(abi.encodeWithSelector(Configurator.ExcessSigners.selector, signers.length, MAX_ORACLES)); + s_configurator.setStagingConfig( + CONFIG_ID_1, + signers, + s_offchaintransmitters, + FAULT_TOLERANCE, + bytes(""), + OFFCHAIN_CONFIG_VERSION, + bytes("") + ); + } + + function test_revertsIfFaultToleranceIsZero() public { + vm.expectRevert(abi.encodeWithSelector(Configurator.FaultToleranceMustBePositive.selector)); + bytes[] memory signers = _getSigners(MAX_ORACLES); + s_configurator.setStagingConfig( + CONFIG_ID_1, + signers, + s_offchaintransmitters, + 0, + bytes(""), + OFFCHAIN_CONFIG_VERSION, + bytes("") + ); + } + + function test_revertsIfNotEnoughSigners() public { + bytes[] memory signers = _getSigners(2); + + vm.expectRevert( + abi.encodeWithSelector(Configurator.InsufficientSigners.selector, signers.length, FAULT_TOLERANCE * 3 + 1) + ); + s_configurator.setStagingConfig( + CONFIG_ID_1, + signers, + s_offchaintransmitters, + FAULT_TOLERANCE, + bytes(""), + OFFCHAIN_CONFIG_VERSION, + bytes("") + ); + } + + function test_revertsIfOnchainConfigIsInvalid() public { + bytes[] memory signers = _getSigners(4); + bytes32[] memory offchainTransmitters = _getOffchainTransmitters(4); + bytes memory onchainConfig = bytes(""); + uint8 f = 1; + bytes memory offchainConfig = abi.encodePacked(keccak256("offchainConfig")); + + vm.expectRevert(abi.encodeWithSelector(Configurator.InvalidOnchainLength.selector, onchainConfig.length)); + s_configurator.setStagingConfig( + CONFIG_ID_1, + signers, + offchainTransmitters, + f, + onchainConfig, + OFFCHAIN_CONFIG_VERSION, + offchainConfig + ); + + onchainConfig = abi.encode(uint256(0), keccak256("previousConfigDigest")); + + vm.expectRevert(abi.encodeWithSelector(Configurator.UnsupportedOnchainConfigVersion.selector, uint256(0))); + s_configurator.setStagingConfig( + CONFIG_ID_1, + signers, + offchainTransmitters, + f, + onchainConfig, + OFFCHAIN_CONFIG_VERSION, + offchainConfig + ); + + onchainConfig = abi.encode(uint256(1), keccak256("previousConfigDigest")); + + vm.expectRevert( + abi.encodeWithSelector(Configurator.InvalidPredecessorConfigDigest.selector, keccak256("previousConfigDigest")) + ); + s_configurator.setStagingConfig( + CONFIG_ID_1, + signers, + offchainTransmitters, + f, + onchainConfig, + OFFCHAIN_CONFIG_VERSION, + offchainConfig + ); + } + + function test_correctlyUpdatesTheConfig() public { + bytes[] memory signers = _getSigners(4); + bytes32[] memory offchainTransmitters = _getOffchainTransmitters(4); + uint8 f = 1; + + // initial block number + vm.roll(2); + + bytes32 productionConfigDigest = keccak256("productionConfigDigest"); + s_exposedConfigurator.exposedSetConfigurationState( + CONFIG_ID_1, + Configurator.ConfigurationState(1, uint32(block.number), false, [productionConfigDigest, bytes32(0)]) + ); + bytes memory onchainConfig = abi.encodePacked(uint256(1), productionConfigDigest); + bytes memory offchainConfig = abi.encodePacked(keccak256("offchainConfig")); + + vm.roll(5); + + bytes32 cd1 = s_exposedConfigurator.exposedConfigDigestFromConfigData( + CONFIG_ID_1, + block.chainid, + address(s_exposedConfigurator), + 2, + signers, + offchainTransmitters, + f, + onchainConfig, + OFFCHAIN_CONFIG_VERSION, + offchainConfig + ); + + // when isGreenProduction=false + + vm.expectEmit(); + emit StagingConfigSet( + CONFIG_ID_1, + 2, + cd1, + 2, + signers, + offchainTransmitters, + f, + onchainConfig, + OFFCHAIN_CONFIG_VERSION, + offchainConfig, + false + ); + + s_exposedConfigurator.setStagingConfig( + CONFIG_ID_1, + signers, + offchainTransmitters, + f, + onchainConfig, + OFFCHAIN_CONFIG_VERSION, + offchainConfig + ); + + Configurator.ConfigurationState memory configurationState = s_exposedConfigurator.exposedReadConfigurationStates( + CONFIG_ID_1 + ); + assertEq(configurationState.configDigest[0], productionConfigDigest); + assertEq(configurationState.configDigest[1], cd1); + assertEq(configurationState.configCount, 2); + assertEq(configurationState.isGreenProduction, false); + assertEq(configurationState.latestConfigBlockNumber, block.number); + + // go to new block + vm.roll(10); + + // set it again, configCount=2 + + bytes32 cd2 = s_exposedConfigurator.exposedConfigDigestFromConfigData( + CONFIG_ID_1, + block.chainid, + address(s_exposedConfigurator), + 3, + signers, + offchainTransmitters, + f, + onchainConfig, + OFFCHAIN_CONFIG_VERSION, + offchainConfig + ); + + vm.expectEmit(); + emit StagingConfigSet( + CONFIG_ID_1, + 5, + cd2, + 3, + signers, + offchainTransmitters, + f, + onchainConfig, + OFFCHAIN_CONFIG_VERSION, + offchainConfig, + false + ); + + s_exposedConfigurator.setStagingConfig( + CONFIG_ID_1, + signers, + offchainTransmitters, + f, + onchainConfig, + OFFCHAIN_CONFIG_VERSION, + offchainConfig + ); + + configurationState = s_exposedConfigurator.exposedReadConfigurationStates(CONFIG_ID_1); + assertEq(configurationState.configDigest[0], productionConfigDigest); + assertEq(configurationState.configDigest[1], cd2); + assertEq(configurationState.configCount, 3); + assertEq(configurationState.isGreenProduction, false); + assertEq(configurationState.latestConfigBlockNumber, block.number); + + // when isGreenProduction=true + s_exposedConfigurator.exposedSetIsGreenProduction(CONFIG_ID_1, true); + onchainConfig = abi.encodePacked(uint256(1), cd2); // predecessorConfigDigest the production digest is now the green digest + + // go to new block + vm.roll(15); + + // set it again, configCount=3 + bytes32 cd3 = s_exposedConfigurator.exposedConfigDigestFromConfigData( + CONFIG_ID_1, + block.chainid, + address(s_exposedConfigurator), + 4, + signers, + offchainTransmitters, + f, + onchainConfig, + OFFCHAIN_CONFIG_VERSION, + offchainConfig + ); + + vm.expectEmit(); + emit StagingConfigSet( + CONFIG_ID_1, + 10, + cd3, + 4, + signers, + offchainTransmitters, + f, + onchainConfig, + OFFCHAIN_CONFIG_VERSION, + offchainConfig, + true + ); + + s_exposedConfigurator.setStagingConfig( + CONFIG_ID_1, + signers, + offchainTransmitters, + f, + onchainConfig, + OFFCHAIN_CONFIG_VERSION, + offchainConfig + ); + + configurationState = s_exposedConfigurator.exposedReadConfigurationStates(CONFIG_ID_1); + assertEq(configurationState.configDigest[0], cd3); // new config is on blue now because blue is staging due to isGreenProduction=true + assertEq(configurationState.configDigest[1], cd2); // the previous config left unchanged + assertEq(configurationState.configCount, 4); + assertEq(configurationState.isGreenProduction, true); + assertEq(configurationState.latestConfigBlockNumber, block.number); + } +} diff --git a/contracts/src/v0.8/llo-feeds/v0.5.0/configuration/test/configurator/ConfiguratorTest.t.sol b/contracts/src/v0.8/llo-feeds/v0.5.0/configuration/test/configurator/ConfiguratorTest.t.sol new file mode 100644 index 00000000000..272cbb2e0d6 --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.5.0/configuration/test/configurator/ConfiguratorTest.t.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.19; + +import {BaseTest} from "./BaseConfiguratorTest.t.sol"; +import {Configurator} from "../../Configurator.sol"; +import {IConfigurator} from "../../interfaces/IConfigurator.sol"; + +contract ConfiguratorTest is BaseTest { + function setUp() public virtual override { + BaseTest.setUp(); + } + + function testTypeAndVersion() public view { + assertEq(s_configurator.typeAndVersion(), "Configurator 0.5.0"); + } + + function testSupportsInterface() public view { + assertTrue(s_configurator.supportsInterface(type(IConfigurator).interfaceId)); + } +} diff --git a/contracts/src/v0.8/llo-feeds/v0.5.0/configuration/test/mocks/ExposedConfigurator.sol b/contracts/src/v0.8/llo-feeds/v0.5.0/configuration/test/mocks/ExposedConfigurator.sol index 7b1370a8467..29a2bde54c7 100644 --- a/contracts/src/v0.8/llo-feeds/v0.5.0/configuration/test/mocks/ExposedConfigurator.sol +++ b/contracts/src/v0.8/llo-feeds/v0.5.0/configuration/test/mocks/ExposedConfigurator.sol @@ -9,7 +9,42 @@ import {Configurator} from "../../Configurator.sol"; contract ExposedConfigurator is Configurator { constructor() {} - function exposedReadConfigurationStates(bytes32 donId) public view returns (ConfigurationState memory) { - return s_configurationStates[donId]; + function exposedReadConfigurationStates(bytes32 configId) public view returns (ConfigurationState memory) { + return s_configurationStates[configId]; + } + + function exposedSetIsGreenProduction(bytes32 configId, bool isGreenProduction) public { + s_configurationStates[configId].isGreenProduction = isGreenProduction; + } + + function exposedSetConfigurationState(bytes32 configId, ConfigurationState memory state) public { + s_configurationStates[configId] = state; + } + + function exposedConfigDigestFromConfigData( + bytes32 _configId, + uint256 _chainId, + address _contractAddress, + uint64 _configCount, + bytes[] memory _signers, + bytes32[] memory _offchainTransmitters, + uint8 _f, + bytes calldata _onchainConfig, + uint64 _encodedConfigVersion, + bytes memory _encodedConfig + ) public pure returns (bytes32) { + return + _configDigestFromConfigData( + _configId, + _chainId, + _contractAddress, + _configCount, + _signers, + _offchainTransmitters, + _f, + _onchainConfig, + _encodedConfigVersion, + _encodedConfig + ); } } diff --git a/core/cmd/shell.go b/core/cmd/shell.go index c862b936140..59514d3a1fc 100644 --- a/core/cmd/shell.go +++ b/core/cmd/shell.go @@ -45,6 +45,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/services" "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" "github.com/smartcontractkit/chainlink/v2/core/services/keystore" + "github.com/smartcontractkit/chainlink/v2/core/services/llo" "github.com/smartcontractkit/chainlink/v2/core/services/periodicbackup" "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/wsrpc" "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/wsrpc/cache" @@ -210,15 +211,18 @@ func (n ChainlinkAppFactory) NewApplication(ctx context.Context, cfg chainlink.G capabilitiesRegistry := capabilities.NewRegistry(appLggr) + retirementReportCache := llo.NewRetirementReportCache(appLggr, ds) + unrestrictedClient := clhttp.NewUnrestrictedHTTPClient() // create the relayer-chain interoperators from application configuration relayerFactory := chainlink.RelayerFactory{ - Logger: appLggr, - LoopRegistry: loopRegistry, - GRPCOpts: grpcOpts, - MercuryPool: mercuryPool, - CapabilitiesRegistry: capabilitiesRegistry, - HTTPClient: unrestrictedClient, + Logger: appLggr, + LoopRegistry: loopRegistry, + GRPCOpts: grpcOpts, + MercuryPool: mercuryPool, + CapabilitiesRegistry: capabilitiesRegistry, + HTTPClient: unrestrictedClient, + RetirementReportCache: retirementReportCache, } evmFactoryCfg := chainlink.EVMFactoryConfig{ @@ -289,6 +293,7 @@ func (n ChainlinkAppFactory) NewApplication(ctx context.Context, cfg chainlink.G LoopRegistry: loopRegistry, GRPCOpts: grpcOpts, MercuryPool: mercuryPool, + RetirementReportCache: retirementReportCache, CapabilitiesRegistry: capabilitiesRegistry, }) } diff --git a/core/gethwrappers/llo-feeds/generated/configurator/configurator.go b/core/gethwrappers/llo-feeds/generated/configurator/configurator.go index 3e6f1c7e2d6..d9a1581938f 100644 --- a/core/gethwrappers/llo-feeds/generated/configurator/configurator.go +++ b/core/gethwrappers/llo-feeds/generated/configurator/configurator.go @@ -31,8 +31,8 @@ var ( ) var ConfiguratorMetaData = &bind.MetaData{ - ABI: "[{\"inputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"numSigners\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"maxSigners\",\"type\":\"uint256\"}],\"name\":\"ExcessSigners\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"FaultToleranceMustBePositive\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"numSigners\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"minSigners\",\"type\":\"uint256\"}],\"name\":\"InsufficientSigners\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"configId\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"uint32\",\"name\":\"previousConfigBlockNumber\",\"type\":\"uint32\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"configDigest\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"configCount\",\"type\":\"uint64\"},{\"indexed\":false,\"internalType\":\"address[]\",\"name\":\"signers\",\"type\":\"address[]\"},{\"indexed\":false,\"internalType\":\"bytes32[]\",\"name\":\"offchainTransmitters\",\"type\":\"bytes32[]\"},{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"f\",\"type\":\"uint8\"},{\"indexed\":false,\"internalType\":\"bytes\",\"name\":\"onchainConfig\",\"type\":\"bytes\"},{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"offchainConfigVersion\",\"type\":\"uint64\"},{\"indexed\":false,\"internalType\":\"bytes\",\"name\":\"offchainConfig\",\"type\":\"bytes\"}],\"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\"},{\"inputs\":[],\"name\":\"acceptOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"donId\",\"type\":\"bytes32\"},{\"internalType\":\"address[]\",\"name\":\"signers\",\"type\":\"address[]\"},{\"internalType\":\"bytes32[]\",\"name\":\"offchainTransmitters\",\"type\":\"bytes32[]\"},{\"internalType\":\"uint8\",\"name\":\"f\",\"type\":\"uint8\"},{\"internalType\":\"bytes\",\"name\":\"onchainConfig\",\"type\":\"bytes\"},{\"internalType\":\"uint64\",\"name\":\"offchainConfigVersion\",\"type\":\"uint64\"},{\"internalType\":\"bytes\",\"name\":\"offchainConfig\",\"type\":\"bytes\"}],\"name\":\"setConfig\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes4\",\"name\":\"interfaceId\",\"type\":\"bytes4\"}],\"name\":\"supportsInterface\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"isVerifier\",\"type\":\"bool\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"typeAndVersion\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"pure\",\"type\":\"function\"}]", - Bin: "0x608060405234801561001057600080fd5b5033806000816100675760405162461bcd60e51b815260206004820152601860248201527f43616e6e6f7420736574206f776e657220746f207a65726f000000000000000060448201526064015b60405180910390fd5b600080546001600160a01b0319166001600160a01b0384811691909117909155811615610097576100978161009f565b505050610148565b336001600160a01b038216036100f75760405162461bcd60e51b815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c66000000000000000000604482015260640161005e565b600180546001600160a01b0319166001600160a01b0383811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b610d21806101576000396000f3fe608060405234801561001057600080fd5b50600436106100725760003560e01c806379ba50971161005057806379ba5097146101355780638da5cb5b1461013d578063f2fde38b1461016557600080fd5b806301ffc9a714610077578063181f5a77146100e157806344a0b2ad14610120575b600080fd5b6100cc6100853660046106c9565b7fffffffff00000000000000000000000000000000000000000000000000000000167f44a0b2ad000000000000000000000000000000000000000000000000000000001490565b60405190151581526020015b60405180910390f35b604080518082018252601281527f436f6e666967757261746f7220302e342e300000000000000000000000000000602082015290516100d89190610776565b61013361012e3660046109d8565b610178565b005b610133610289565b60005460405173ffffffffffffffffffffffffffffffffffffffff90911681526020016100d8565b610133610173366004610ab0565b610386565b85518460ff16806000036101b8576040517f0743bae600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b601f821115610202576040517f61750f4000000000000000000000000000000000000000000000000000000000815260048101839052601f60248201526044015b60405180910390fd5b61020d816003610afa565b8211610265578161021f826003610afa565b61022a906001610b17565b6040517f9dd9e6d8000000000000000000000000000000000000000000000000000000008152600481019290925260248201526044016101f9565b61026d61039a565b61027e8946308b8b8b8b8b8b61041d565b505050505050505050565b60015473ffffffffffffffffffffffffffffffffffffffff16331461030a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4d7573742062652070726f706f736564206f776e65720000000000000000000060448201526064016101f9565b60008054337fffffffffffffffffffffffff00000000000000000000000000000000000000008083168217845560018054909116905560405173ffffffffffffffffffffffffffffffffffffffff90921692909183917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a350565b61038e61039a565b61039781610526565b50565b60005473ffffffffffffffffffffffffffffffffffffffff16331461041b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4f6e6c792063616c6c61626c65206279206f776e65720000000000000000000060448201526064016101f9565b565b60008981526002602052604081208054909190829082906104479067ffffffffffffffff16610b2a565b91906101000a81548167ffffffffffffffff021916908367ffffffffffffffff1602179055905060006104828c8c8c858d8d8d8d8d8d61061b565b90508b7fa23a88453230b183877098801ff5a8f771a120e2573eea559ce6c4c2e305a4da8460000160089054906101000a900463ffffffff1683858d8d8d8d8d8d6040516104d899989796959493929190610bd2565b60405180910390a2505080547fffffffffffffffffffffffffffffffffffffffff00000000ffffffffffffffff16680100000000000000004363ffffffff1602179055505050505050505050565b3373ffffffffffffffffffffffffffffffffffffffff8216036105a5576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c6600000000000000000060448201526064016101f9565b600180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b6000808b8b8b8b8b8b8b8b8b8b6040516020016106419a99989796959493929190610c67565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081840301815291905280516020909101207dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff167e09000000000000000000000000000000000000000000000000000000000000179150509a9950505050505050505050565b6000602082840312156106db57600080fd5b81357fffffffff000000000000000000000000000000000000000000000000000000008116811461070b57600080fd5b9392505050565b6000815180845260005b818110156107385760208185018101518683018201520161071c565b5060006020828601015260207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f83011685010191505092915050565b60208152600061070b6020830184610712565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff811182821017156107ff576107ff610789565b604052919050565b600067ffffffffffffffff82111561082157610821610789565b5060051b60200190565b803573ffffffffffffffffffffffffffffffffffffffff8116811461084f57600080fd5b919050565b600082601f83011261086557600080fd5b8135602061087a61087583610807565b6107b8565b82815260059290921b8401810191818101908684111561089957600080fd5b8286015b848110156108bb576108ae8161082b565b835291830191830161089d565b509695505050505050565b600082601f8301126108d757600080fd5b813560206108e761087583610807565b82815260059290921b8401810191818101908684111561090657600080fd5b8286015b848110156108bb578035835291830191830161090a565b803560ff8116811461084f57600080fd5b600082601f83011261094357600080fd5b813567ffffffffffffffff81111561095d5761095d610789565b61098e60207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f840116016107b8565b8181528460208386010111156109a357600080fd5b816020850160208301376000918101602001919091529392505050565b803567ffffffffffffffff8116811461084f57600080fd5b600080600080600080600060e0888a0312156109f357600080fd5b87359650602088013567ffffffffffffffff80821115610a1257600080fd5b610a1e8b838c01610854565b975060408a0135915080821115610a3457600080fd5b610a408b838c016108c6565b9650610a4e60608b01610921565b955060808a0135915080821115610a6457600080fd5b610a708b838c01610932565b9450610a7e60a08b016109c0565b935060c08a0135915080821115610a9457600080fd5b50610aa18a828b01610932565b91505092959891949750929550565b600060208284031215610ac257600080fd5b61070b8261082b565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b8082028115828204841417610b1157610b11610acb565b92915050565b80820180821115610b1157610b11610acb565b600067ffffffffffffffff808316818103610b4757610b47610acb565b6001019392505050565b600081518084526020808501945080840160005b83811015610b9757815173ffffffffffffffffffffffffffffffffffffffff1687529582019590820190600101610b65565b509495945050505050565b600081518084526020808501945080840160005b83811015610b9757815187529582019590820190600101610bb6565b600061012063ffffffff8c1683528a602084015267ffffffffffffffff808b166040850152816060850152610c098285018b610b51565b91508382036080850152610c1d828a610ba2565b915060ff881660a085015283820360c0850152610c3a8288610712565b90861660e08501528381036101008501529050610c578185610712565b9c9b505050505050505050505050565b60006101408c83528b602084015273ffffffffffffffffffffffffffffffffffffffff8b16604084015267ffffffffffffffff808b166060850152816080850152610cb48285018b610b51565b915083820360a0850152610cc8828a610ba2565b915060ff881660c085015283820360e0850152610ce58288610712565b9086166101008501528381036101208501529050610d038185610712565b9d9c5050505050505050505050505056fea164736f6c6343000813000a", + ABI: "[{\"inputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"configId\",\"type\":\"bytes32\"}],\"name\":\"ConfigUnset\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"configId\",\"type\":\"bytes32\"},{\"internalType\":\"bool\",\"name\":\"isGreenProduction\",\"type\":\"bool\"}],\"name\":\"ConfigUnsetProduction\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"configId\",\"type\":\"bytes32\"},{\"internalType\":\"bool\",\"name\":\"isGreenProduction\",\"type\":\"bool\"}],\"name\":\"ConfigUnsetStaging\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"numSigners\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"maxSigners\",\"type\":\"uint256\"}],\"name\":\"ExcessSigners\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"FaultToleranceMustBePositive\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"numSigners\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"minSigners\",\"type\":\"uint256\"}],\"name\":\"InsufficientSigners\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"onchainConfigLength\",\"type\":\"uint256\"}],\"name\":\"InvalidOnchainLength\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"predecessorConfigDigest\",\"type\":\"bytes32\"}],\"name\":\"InvalidPredecessorConfigDigest\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"configId\",\"type\":\"bytes32\"},{\"internalType\":\"bool\",\"name\":\"isGreenProductionContractState\",\"type\":\"bool\"}],\"name\":\"IsGreenProductionMustMatchContractState\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"predecessorConfigDigest\",\"type\":\"bytes32\"}],\"name\":\"NonZeroPredecessorConfigDigest\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"version\",\"type\":\"uint256\"}],\"name\":\"UnsupportedOnchainConfigVersion\",\"type\":\"error\"},{\"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\":\"bytes32\",\"name\":\"configId\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"uint32\",\"name\":\"previousConfigBlockNumber\",\"type\":\"uint32\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"configDigest\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"configCount\",\"type\":\"uint64\"},{\"indexed\":false,\"internalType\":\"bytes[]\",\"name\":\"signers\",\"type\":\"bytes[]\"},{\"indexed\":false,\"internalType\":\"bytes32[]\",\"name\":\"offchainTransmitters\",\"type\":\"bytes32[]\"},{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"f\",\"type\":\"uint8\"},{\"indexed\":false,\"internalType\":\"bytes\",\"name\":\"onchainConfig\",\"type\":\"bytes\"},{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"offchainConfigVersion\",\"type\":\"uint64\"},{\"indexed\":false,\"internalType\":\"bytes\",\"name\":\"offchainConfig\",\"type\":\"bytes\"},{\"indexed\":false,\"internalType\":\"bool\",\"name\":\"isGreenProduction\",\"type\":\"bool\"}],\"name\":\"ProductionConfigSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"configId\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"retiredConfigDigest\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"bool\",\"name\":\"isGreenProduction\",\"type\":\"bool\"}],\"name\":\"PromoteStagingConfig\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"configId\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"uint32\",\"name\":\"previousConfigBlockNumber\",\"type\":\"uint32\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"configDigest\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"configCount\",\"type\":\"uint64\"},{\"indexed\":false,\"internalType\":\"bytes[]\",\"name\":\"signers\",\"type\":\"bytes[]\"},{\"indexed\":false,\"internalType\":\"bytes32[]\",\"name\":\"offchainTransmitters\",\"type\":\"bytes32[]\"},{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"f\",\"type\":\"uint8\"},{\"indexed\":false,\"internalType\":\"bytes\",\"name\":\"onchainConfig\",\"type\":\"bytes\"},{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"offchainConfigVersion\",\"type\":\"uint64\"},{\"indexed\":false,\"internalType\":\"bytes\",\"name\":\"offchainConfig\",\"type\":\"bytes\"},{\"indexed\":false,\"internalType\":\"bool\",\"name\":\"isGreenProduction\",\"type\":\"bool\"}],\"name\":\"StagingConfigSet\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"acceptOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"configId\",\"type\":\"bytes32\"},{\"internalType\":\"bool\",\"name\":\"isGreenProduction\",\"type\":\"bool\"}],\"name\":\"promoteStagingConfig\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"configId\",\"type\":\"bytes32\"},{\"internalType\":\"bytes[]\",\"name\":\"signers\",\"type\":\"bytes[]\"},{\"internalType\":\"bytes32[]\",\"name\":\"offchainTransmitters\",\"type\":\"bytes32[]\"},{\"internalType\":\"uint8\",\"name\":\"f\",\"type\":\"uint8\"},{\"internalType\":\"bytes\",\"name\":\"onchainConfig\",\"type\":\"bytes\"},{\"internalType\":\"uint64\",\"name\":\"offchainConfigVersion\",\"type\":\"uint64\"},{\"internalType\":\"bytes\",\"name\":\"offchainConfig\",\"type\":\"bytes\"}],\"name\":\"setProductionConfig\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"configId\",\"type\":\"bytes32\"},{\"internalType\":\"bytes[]\",\"name\":\"signers\",\"type\":\"bytes[]\"},{\"internalType\":\"bytes32[]\",\"name\":\"offchainTransmitters\",\"type\":\"bytes32[]\"},{\"internalType\":\"uint8\",\"name\":\"f\",\"type\":\"uint8\"},{\"internalType\":\"bytes\",\"name\":\"onchainConfig\",\"type\":\"bytes\"},{\"internalType\":\"uint64\",\"name\":\"offchainConfigVersion\",\"type\":\"uint64\"},{\"internalType\":\"bytes\",\"name\":\"offchainConfig\",\"type\":\"bytes\"}],\"name\":\"setStagingConfig\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes4\",\"name\":\"interfaceId\",\"type\":\"bytes4\"}],\"name\":\"supportsInterface\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"isVerifier\",\"type\":\"bool\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"typeAndVersion\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"pure\",\"type\":\"function\"}]", + Bin: "0x608060405234801561001057600080fd5b5033806000816100675760405162461bcd60e51b815260206004820152601860248201527f43616e6e6f7420736574206f776e657220746f207a65726f000000000000000060448201526064015b60405180910390fd5b600080546001600160a01b0319166001600160a01b0384811691909117909155811615610097576100978161009f565b505050610148565b336001600160a01b038216036100f75760405162461bcd60e51b815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c66000000000000000000604482015260640161005e565b600180546001600160a01b0319166001600160a01b0383811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b61144a806101576000396000f3fe608060405234801561001057600080fd5b50600436106100885760003560e01c80638da5cb5b1161005b5780638da5cb5b14610153578063dfb533d01461017b578063e6e7c5a41461018e578063f2fde38b146101a157600080fd5b806301ffc9a71461008d578063181f5a77146100f7578063790464e01461013657806379ba50971461014b575b600080fd5b6100e261009b366004610d62565b7fffffffff00000000000000000000000000000000000000000000000000000000167f40569294000000000000000000000000000000000000000000000000000000001490565b60405190151581526020015b60405180910390f35b604080518082018252601281527f436f6e666967757261746f7220302e352e300000000000000000000000000000602082015290516100ee9190610e0f565b61014961014436600461106b565b6101b4565b005b61014961038d565b60005460405173ffffffffffffffffffffffffffffffffffffffff90911681526020016100ee565b61014961018936600461106b565b61048a565b61014961019c366004611143565b6106ec565b6101496101af366004611178565b6108f8565b85518460ff16806000036101f4576040517f0743bae600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b601f82111561023e576040517f61750f4000000000000000000000000000000000000000000000000000000000815260048101839052601f60248201526044015b60405180910390fd5b6102498160036111dd565b82116102a1578161025b8260036111dd565b6102669060016111fa565b6040517f9dd9e6d800000000000000000000000000000000000000000000000000000000815260048101929092526024820152604401610235565b6102a961090c565b6040855110156102ea5784516040517f3e936ca800000000000000000000000000000000000000000000000000000000815260040161023591815260200190565b602085015160408601516001821015610332576040517f8f01e0d700000000000000000000000000000000000000000000000000000000815260048101839052602401610235565b801561036d576040517fb96bb76000000000000000000000000000000000000000000000000000000000815260048101829052602401610235565b6103808b46308d8d8d8d8d8d600161098f565b5050505050505050505050565b60015473ffffffffffffffffffffffffffffffffffffffff16331461040e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4d7573742062652070726f706f736564206f776e6572000000000000000000006044820152606401610235565b60008054337fffffffffffffffffffffffff00000000000000000000000000000000000000008083168217845560018054909116905560405173ffffffffffffffffffffffffffffffffffffffff90921692909183917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a350565b85518460ff16806000036104ca576040517f0743bae600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b601f82111561050f576040517f61750f4000000000000000000000000000000000000000000000000000000000815260048101839052601f6024820152604401610235565b61051a8160036111dd565b821161052c578161025b8260036111dd565b61053461090c565b6040855110156105755784516040517f3e936ca800000000000000000000000000000000000000000000000000000000815260040161023591815260200190565b6020850151604086015160018210156105bd576040517f8f01e0d700000000000000000000000000000000000000000000000000000000815260048101839052602401610235565b60008b81526002602081815260408084208151608081018352815467ffffffffffffffff8116825268010000000000000000810463ffffffff16948201949094526c0100000000000000000000000090930460ff161515838301528151808301928390529293909260608501929091600185019182845b815481526020019060010190808311610634575050505050815250509050600260008d8152602001908152602001600020600101816040015161067857600061067b565b60015b60ff166002811061068e5761068e61120d565b015482146106cb576040517f7d78c2a100000000000000000000000000000000000000000000000000000000815260048101839052602401610235565b6106de8c46308e8e8e8e8e8e600061098f565b505050505050505050505050565b6106f461090c565b600082815260026020526040902080546c01000000000000000000000000900460ff1615158215151461075d576040517f85fa3a370000000000000000000000000000000000000000000000000000000081526004810184905282156024820152604401610235565b805467ffffffffffffffff166000036107a5576040517f90e6f6dc00000000000000000000000000000000000000000000000000000000815260048101849052602401610235565b600060018201836107b75760016107ba565b60005b60ff16600281106107cd576107cd61120d565b015403610811576040517f5b7f6357000000000000000000000000000000000000000000000000000000008152600481018490528215156024820152604401610235565b60008160010183610823576000610826565b60015b60ff16600281106108395761083961120d565b015490508061087f576040517fcaf1e773000000000000000000000000000000000000000000000000000000008152600481018590528315156024820152604401610235565b81547fffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffff1683156c010000000000000000000000008102919091178355604051908152819085907f1062aa08ac6046a0e69e3eafdf12d1eba63a67b71a874623e86eb06348a1d84f9060200160405180910390a350505050565b61090061090c565b61090981610bbf565b50565b60005473ffffffffffffffffffffffffffffffffffffffff16331461098d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4f6e6c792063616c6c61626c65206279206f776e6572000000000000000000006044820152606401610235565b565b60008a81526002602052604081208054909190829082906109b99067ffffffffffffffff1661123c565b91906101000a81548167ffffffffffffffff021916908367ffffffffffffffff1602179055905060006109f48d8d8d858e8e8e8e8e8e610cb4565b90508315610abc578c7f261b20c2ecd99d86d6e936279e4f78db34603a3de3a4a84d6f3d4e0dd55e24788460000160089054906101000a900463ffffffff1683858e8e8e8e8e8e8d600001600c9054906101000a900460ff16604051610a639a999897969594939291906112f0565b60405180910390a260008d815260026020526040902083548291600101906c01000000000000000000000000900460ff16610a9f576000610aa2565b60015b60ff1660028110610ab557610ab561120d565b0155610b78565b8c7fef1b5f9d1b927b0fe871b12c7e7846457602d67b2bc36b0bc95feaf480e890568460000160089054906101000a900463ffffffff1683858e8e8e8e8e8e8d600001600c9054906101000a900460ff16604051610b239a999897969594939291906112f0565b60405180910390a260008d815260026020526040902083548291600101906c01000000000000000000000000900460ff16610b5f576001610b62565b60005b60ff1660028110610b7557610b7561120d565b01555b505080547fffffffffffffffffffffffffffffffffffffffff00000000ffffffffffffffff16680100000000000000004363ffffffff160217905550505050505050505050565b3373ffffffffffffffffffffffffffffffffffffffff821603610c3e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c660000000000000000006044820152606401610235565b600180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b6000808b8b8b8b8b8b8b8b8b8b604051602001610cda9a99989796959493929190611390565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081840301815291905280516020909101207dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff167e09000000000000000000000000000000000000000000000000000000000000179150509a9950505050505050505050565b600060208284031215610d7457600080fd5b81357fffffffff0000000000000000000000000000000000000000000000000000000081168114610da457600080fd5b9392505050565b6000815180845260005b81811015610dd157602081850181015186830182015201610db5565b5060006020828601015260207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f83011685010191505092915050565b602081526000610da46020830184610dab565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff81118282101715610e9857610e98610e22565b604052919050565b600067ffffffffffffffff821115610eba57610eba610e22565b5060051b60200190565b600082601f830112610ed557600080fd5b813567ffffffffffffffff811115610eef57610eef610e22565b610f2060207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f84011601610e51565b818152846020838601011115610f3557600080fd5b816020850160208301376000918101602001919091529392505050565b600082601f830112610f6357600080fd5b81356020610f78610f7383610ea0565b610e51565b82815260059290921b84018101918181019086841115610f9757600080fd5b8286015b84811015610fd757803567ffffffffffffffff811115610fbb5760008081fd5b610fc98986838b0101610ec4565b845250918301918301610f9b565b509695505050505050565b600082601f830112610ff357600080fd5b81356020611003610f7383610ea0565b82815260059290921b8401810191818101908684111561102257600080fd5b8286015b84811015610fd75780358352918301918301611026565b803560ff8116811461104e57600080fd5b919050565b803567ffffffffffffffff8116811461104e57600080fd5b600080600080600080600060e0888a03121561108657600080fd5b87359650602088013567ffffffffffffffff808211156110a557600080fd5b6110b18b838c01610f52565b975060408a01359150808211156110c757600080fd5b6110d38b838c01610fe2565b96506110e160608b0161103d565b955060808a01359150808211156110f757600080fd5b6111038b838c01610ec4565b945061111160a08b01611053565b935060c08a013591508082111561112757600080fd5b506111348a828b01610ec4565b91505092959891949750929550565b6000806040838503121561115657600080fd5b823591506020830135801515811461116d57600080fd5b809150509250929050565b60006020828403121561118a57600080fd5b813573ffffffffffffffffffffffffffffffffffffffff81168114610da457600080fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b80820281158282048414176111f4576111f46111ae565b92915050565b808201808211156111f4576111f46111ae565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b600067ffffffffffffffff808316818103611259576112596111ae565b6001019392505050565b6000815180845260208085019450848260051b860182860160005b858110156112a8578383038952611296838351610dab565b9885019892509084019060010161127e565b5090979650505050505050565b600081518084526020808501945080840160005b838110156112e5578151875295820195908201906001016112c9565b509495945050505050565b600061014063ffffffff8d1683528b602084015267ffffffffffffffff808c1660408501528160608501526113278285018c611263565b9150838203608085015261133b828b6112b5565b915060ff891660a085015283820360c08501526113588289610dab565b90871660e085015283810361010085015290506113758186610dab565b9150508215156101208301529b9a5050505050505050505050565b60006101408c83528b602084015273ffffffffffffffffffffffffffffffffffffffff8b16604084015267ffffffffffffffff808b1660608501528160808501526113dd8285018b611263565b915083820360a08501526113f1828a6112b5565b915060ff881660c085015283820360e085015261140e8288610dab565b908616610100850152838103610120850152905061142c8185610dab565b9d9c5050505050505050505050505056fea164736f6c6343000813000a", } var ConfiguratorABI = ConfiguratorMetaData.ABI @@ -249,16 +249,40 @@ func (_Configurator *ConfiguratorTransactorSession) AcceptOwnership() (*types.Tr return _Configurator.Contract.AcceptOwnership(&_Configurator.TransactOpts) } -func (_Configurator *ConfiguratorTransactor) SetConfig(opts *bind.TransactOpts, donId [32]byte, signers []common.Address, offchainTransmitters [][32]byte, f uint8, onchainConfig []byte, offchainConfigVersion uint64, offchainConfig []byte) (*types.Transaction, error) { - return _Configurator.contract.Transact(opts, "setConfig", donId, signers, offchainTransmitters, f, onchainConfig, offchainConfigVersion, offchainConfig) +func (_Configurator *ConfiguratorTransactor) PromoteStagingConfig(opts *bind.TransactOpts, configId [32]byte, isGreenProduction bool) (*types.Transaction, error) { + return _Configurator.contract.Transact(opts, "promoteStagingConfig", configId, isGreenProduction) } -func (_Configurator *ConfiguratorSession) SetConfig(donId [32]byte, signers []common.Address, offchainTransmitters [][32]byte, f uint8, onchainConfig []byte, offchainConfigVersion uint64, offchainConfig []byte) (*types.Transaction, error) { - return _Configurator.Contract.SetConfig(&_Configurator.TransactOpts, donId, signers, offchainTransmitters, f, onchainConfig, offchainConfigVersion, offchainConfig) +func (_Configurator *ConfiguratorSession) PromoteStagingConfig(configId [32]byte, isGreenProduction bool) (*types.Transaction, error) { + return _Configurator.Contract.PromoteStagingConfig(&_Configurator.TransactOpts, configId, isGreenProduction) } -func (_Configurator *ConfiguratorTransactorSession) SetConfig(donId [32]byte, signers []common.Address, offchainTransmitters [][32]byte, f uint8, onchainConfig []byte, offchainConfigVersion uint64, offchainConfig []byte) (*types.Transaction, error) { - return _Configurator.Contract.SetConfig(&_Configurator.TransactOpts, donId, signers, offchainTransmitters, f, onchainConfig, offchainConfigVersion, offchainConfig) +func (_Configurator *ConfiguratorTransactorSession) PromoteStagingConfig(configId [32]byte, isGreenProduction bool) (*types.Transaction, error) { + return _Configurator.Contract.PromoteStagingConfig(&_Configurator.TransactOpts, configId, isGreenProduction) +} + +func (_Configurator *ConfiguratorTransactor) SetProductionConfig(opts *bind.TransactOpts, configId [32]byte, signers [][]byte, offchainTransmitters [][32]byte, f uint8, onchainConfig []byte, offchainConfigVersion uint64, offchainConfig []byte) (*types.Transaction, error) { + return _Configurator.contract.Transact(opts, "setProductionConfig", configId, signers, offchainTransmitters, f, onchainConfig, offchainConfigVersion, offchainConfig) +} + +func (_Configurator *ConfiguratorSession) SetProductionConfig(configId [32]byte, signers [][]byte, offchainTransmitters [][32]byte, f uint8, onchainConfig []byte, offchainConfigVersion uint64, offchainConfig []byte) (*types.Transaction, error) { + return _Configurator.Contract.SetProductionConfig(&_Configurator.TransactOpts, configId, signers, offchainTransmitters, f, onchainConfig, offchainConfigVersion, offchainConfig) +} + +func (_Configurator *ConfiguratorTransactorSession) SetProductionConfig(configId [32]byte, signers [][]byte, offchainTransmitters [][32]byte, f uint8, onchainConfig []byte, offchainConfigVersion uint64, offchainConfig []byte) (*types.Transaction, error) { + return _Configurator.Contract.SetProductionConfig(&_Configurator.TransactOpts, configId, signers, offchainTransmitters, f, onchainConfig, offchainConfigVersion, offchainConfig) +} + +func (_Configurator *ConfiguratorTransactor) SetStagingConfig(opts *bind.TransactOpts, configId [32]byte, signers [][]byte, offchainTransmitters [][32]byte, f uint8, onchainConfig []byte, offchainConfigVersion uint64, offchainConfig []byte) (*types.Transaction, error) { + return _Configurator.contract.Transact(opts, "setStagingConfig", configId, signers, offchainTransmitters, f, onchainConfig, offchainConfigVersion, offchainConfig) +} + +func (_Configurator *ConfiguratorSession) SetStagingConfig(configId [32]byte, signers [][]byte, offchainTransmitters [][32]byte, f uint8, onchainConfig []byte, offchainConfigVersion uint64, offchainConfig []byte) (*types.Transaction, error) { + return _Configurator.Contract.SetStagingConfig(&_Configurator.TransactOpts, configId, signers, offchainTransmitters, f, onchainConfig, offchainConfigVersion, offchainConfig) +} + +func (_Configurator *ConfiguratorTransactorSession) SetStagingConfig(configId [32]byte, signers [][]byte, offchainTransmitters [][32]byte, f uint8, onchainConfig []byte, offchainConfigVersion uint64, offchainConfig []byte) (*types.Transaction, error) { + return _Configurator.Contract.SetStagingConfig(&_Configurator.TransactOpts, configId, signers, offchainTransmitters, f, onchainConfig, offchainConfigVersion, offchainConfig) } func (_Configurator *ConfiguratorTransactor) TransferOwnership(opts *bind.TransactOpts, to common.Address) (*types.Transaction, error) { @@ -273,8 +297,8 @@ func (_Configurator *ConfiguratorTransactorSession) TransferOwnership(to common. return _Configurator.Contract.TransferOwnership(&_Configurator.TransactOpts, to) } -type ConfiguratorConfigSetIterator struct { - Event *ConfiguratorConfigSet +type ConfiguratorOwnershipTransferRequestedIterator struct { + Event *ConfiguratorOwnershipTransferRequested contract *bind.BoundContract event string @@ -285,7 +309,7 @@ type ConfiguratorConfigSetIterator struct { fail error } -func (it *ConfiguratorConfigSetIterator) Next() bool { +func (it *ConfiguratorOwnershipTransferRequestedIterator) Next() bool { if it.fail != nil { return false @@ -294,7 +318,7 @@ func (it *ConfiguratorConfigSetIterator) Next() bool { if it.done { select { case log := <-it.logs: - it.Event = new(ConfiguratorConfigSet) + it.Event = new(ConfiguratorOwnershipTransferRequested) if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { it.fail = err return false @@ -309,7 +333,7 @@ func (it *ConfiguratorConfigSetIterator) Next() bool { select { case log := <-it.logs: - it.Event = new(ConfiguratorConfigSet) + it.Event = new(ConfiguratorOwnershipTransferRequested) if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { it.fail = err return false @@ -324,51 +348,51 @@ func (it *ConfiguratorConfigSetIterator) Next() bool { } } -func (it *ConfiguratorConfigSetIterator) Error() error { +func (it *ConfiguratorOwnershipTransferRequestedIterator) Error() error { return it.fail } -func (it *ConfiguratorConfigSetIterator) Close() error { +func (it *ConfiguratorOwnershipTransferRequestedIterator) Close() error { it.sub.Unsubscribe() return nil } -type ConfiguratorConfigSet struct { - ConfigId [32]byte - PreviousConfigBlockNumber uint32 - ConfigDigest [32]byte - ConfigCount uint64 - Signers []common.Address - OffchainTransmitters [][32]byte - F uint8 - OnchainConfig []byte - OffchainConfigVersion uint64 - OffchainConfig []byte - Raw types.Log +type ConfiguratorOwnershipTransferRequested struct { + From common.Address + To common.Address + Raw types.Log } -func (_Configurator *ConfiguratorFilterer) FilterConfigSet(opts *bind.FilterOpts, configId [][32]byte) (*ConfiguratorConfigSetIterator, error) { +func (_Configurator *ConfiguratorFilterer) FilterOwnershipTransferRequested(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*ConfiguratorOwnershipTransferRequestedIterator, error) { - var configIdRule []interface{} - for _, configIdItem := range configId { - configIdRule = append(configIdRule, configIdItem) + 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 := _Configurator.contract.FilterLogs(opts, "ConfigSet", configIdRule) + logs, sub, err := _Configurator.contract.FilterLogs(opts, "OwnershipTransferRequested", fromRule, toRule) if err != nil { return nil, err } - return &ConfiguratorConfigSetIterator{contract: _Configurator.contract, event: "ConfigSet", logs: logs, sub: sub}, nil + return &ConfiguratorOwnershipTransferRequestedIterator{contract: _Configurator.contract, event: "OwnershipTransferRequested", logs: logs, sub: sub}, nil } -func (_Configurator *ConfiguratorFilterer) WatchConfigSet(opts *bind.WatchOpts, sink chan<- *ConfiguratorConfigSet, configId [][32]byte) (event.Subscription, error) { +func (_Configurator *ConfiguratorFilterer) WatchOwnershipTransferRequested(opts *bind.WatchOpts, sink chan<- *ConfiguratorOwnershipTransferRequested, from []common.Address, to []common.Address) (event.Subscription, error) { - var configIdRule []interface{} - for _, configIdItem := range configId { - configIdRule = append(configIdRule, configIdItem) + 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 := _Configurator.contract.WatchLogs(opts, "ConfigSet", configIdRule) + logs, sub, err := _Configurator.contract.WatchLogs(opts, "OwnershipTransferRequested", fromRule, toRule) if err != nil { return nil, err } @@ -378,8 +402,8 @@ func (_Configurator *ConfiguratorFilterer) WatchConfigSet(opts *bind.WatchOpts, select { case log := <-logs: - event := new(ConfiguratorConfigSet) - if err := _Configurator.contract.UnpackLog(event, "ConfigSet", log); err != nil { + event := new(ConfiguratorOwnershipTransferRequested) + if err := _Configurator.contract.UnpackLog(event, "OwnershipTransferRequested", log); err != nil { return err } event.Raw = log @@ -400,17 +424,17 @@ func (_Configurator *ConfiguratorFilterer) WatchConfigSet(opts *bind.WatchOpts, }), nil } -func (_Configurator *ConfiguratorFilterer) ParseConfigSet(log types.Log) (*ConfiguratorConfigSet, error) { - event := new(ConfiguratorConfigSet) - if err := _Configurator.contract.UnpackLog(event, "ConfigSet", log); err != nil { +func (_Configurator *ConfiguratorFilterer) ParseOwnershipTransferRequested(log types.Log) (*ConfiguratorOwnershipTransferRequested, error) { + event := new(ConfiguratorOwnershipTransferRequested) + if err := _Configurator.contract.UnpackLog(event, "OwnershipTransferRequested", log); err != nil { return nil, err } event.Raw = log return event, nil } -type ConfiguratorOwnershipTransferRequestedIterator struct { - Event *ConfiguratorOwnershipTransferRequested +type ConfiguratorOwnershipTransferredIterator struct { + Event *ConfiguratorOwnershipTransferred contract *bind.BoundContract event string @@ -421,7 +445,7 @@ type ConfiguratorOwnershipTransferRequestedIterator struct { fail error } -func (it *ConfiguratorOwnershipTransferRequestedIterator) Next() bool { +func (it *ConfiguratorOwnershipTransferredIterator) Next() bool { if it.fail != nil { return false @@ -430,7 +454,7 @@ func (it *ConfiguratorOwnershipTransferRequestedIterator) Next() bool { if it.done { select { case log := <-it.logs: - it.Event = new(ConfiguratorOwnershipTransferRequested) + it.Event = new(ConfiguratorOwnershipTransferred) if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { it.fail = err return false @@ -445,7 +469,7 @@ func (it *ConfiguratorOwnershipTransferRequestedIterator) Next() bool { select { case log := <-it.logs: - it.Event = new(ConfiguratorOwnershipTransferRequested) + it.Event = new(ConfiguratorOwnershipTransferred) if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { it.fail = err return false @@ -460,22 +484,22 @@ func (it *ConfiguratorOwnershipTransferRequestedIterator) Next() bool { } } -func (it *ConfiguratorOwnershipTransferRequestedIterator) Error() error { +func (it *ConfiguratorOwnershipTransferredIterator) Error() error { return it.fail } -func (it *ConfiguratorOwnershipTransferRequestedIterator) Close() error { +func (it *ConfiguratorOwnershipTransferredIterator) Close() error { it.sub.Unsubscribe() return nil } -type ConfiguratorOwnershipTransferRequested struct { +type ConfiguratorOwnershipTransferred struct { From common.Address To common.Address Raw types.Log } -func (_Configurator *ConfiguratorFilterer) FilterOwnershipTransferRequested(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*ConfiguratorOwnershipTransferRequestedIterator, error) { +func (_Configurator *ConfiguratorFilterer) FilterOwnershipTransferred(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*ConfiguratorOwnershipTransferredIterator, error) { var fromRule []interface{} for _, fromItem := range from { @@ -486,14 +510,14 @@ func (_Configurator *ConfiguratorFilterer) FilterOwnershipTransferRequested(opts toRule = append(toRule, toItem) } - logs, sub, err := _Configurator.contract.FilterLogs(opts, "OwnershipTransferRequested", fromRule, toRule) + logs, sub, err := _Configurator.contract.FilterLogs(opts, "OwnershipTransferred", fromRule, toRule) if err != nil { return nil, err } - return &ConfiguratorOwnershipTransferRequestedIterator{contract: _Configurator.contract, event: "OwnershipTransferRequested", logs: logs, sub: sub}, nil + return &ConfiguratorOwnershipTransferredIterator{contract: _Configurator.contract, event: "OwnershipTransferred", logs: logs, sub: sub}, nil } -func (_Configurator *ConfiguratorFilterer) WatchOwnershipTransferRequested(opts *bind.WatchOpts, sink chan<- *ConfiguratorOwnershipTransferRequested, from []common.Address, to []common.Address) (event.Subscription, error) { +func (_Configurator *ConfiguratorFilterer) WatchOwnershipTransferred(opts *bind.WatchOpts, sink chan<- *ConfiguratorOwnershipTransferred, from []common.Address, to []common.Address) (event.Subscription, error) { var fromRule []interface{} for _, fromItem := range from { @@ -504,7 +528,7 @@ func (_Configurator *ConfiguratorFilterer) WatchOwnershipTransferRequested(opts toRule = append(toRule, toItem) } - logs, sub, err := _Configurator.contract.WatchLogs(opts, "OwnershipTransferRequested", fromRule, toRule) + logs, sub, err := _Configurator.contract.WatchLogs(opts, "OwnershipTransferred", fromRule, toRule) if err != nil { return nil, err } @@ -514,8 +538,8 @@ func (_Configurator *ConfiguratorFilterer) WatchOwnershipTransferRequested(opts select { case log := <-logs: - event := new(ConfiguratorOwnershipTransferRequested) - if err := _Configurator.contract.UnpackLog(event, "OwnershipTransferRequested", log); err != nil { + event := new(ConfiguratorOwnershipTransferred) + if err := _Configurator.contract.UnpackLog(event, "OwnershipTransferred", log); err != nil { return err } event.Raw = log @@ -536,17 +560,17 @@ func (_Configurator *ConfiguratorFilterer) WatchOwnershipTransferRequested(opts }), nil } -func (_Configurator *ConfiguratorFilterer) ParseOwnershipTransferRequested(log types.Log) (*ConfiguratorOwnershipTransferRequested, error) { - event := new(ConfiguratorOwnershipTransferRequested) - if err := _Configurator.contract.UnpackLog(event, "OwnershipTransferRequested", log); err != nil { +func (_Configurator *ConfiguratorFilterer) ParseOwnershipTransferred(log types.Log) (*ConfiguratorOwnershipTransferred, error) { + event := new(ConfiguratorOwnershipTransferred) + if err := _Configurator.contract.UnpackLog(event, "OwnershipTransferred", log); err != nil { return nil, err } event.Raw = log return event, nil } -type ConfiguratorOwnershipTransferredIterator struct { - Event *ConfiguratorOwnershipTransferred +type ConfiguratorProductionConfigSetIterator struct { + Event *ConfiguratorProductionConfigSet contract *bind.BoundContract event string @@ -557,7 +581,7 @@ type ConfiguratorOwnershipTransferredIterator struct { fail error } -func (it *ConfiguratorOwnershipTransferredIterator) Next() bool { +func (it *ConfiguratorProductionConfigSetIterator) Next() bool { if it.fail != nil { return false @@ -566,7 +590,7 @@ func (it *ConfiguratorOwnershipTransferredIterator) Next() bool { if it.done { select { case log := <-it.logs: - it.Event = new(ConfiguratorOwnershipTransferred) + it.Event = new(ConfiguratorProductionConfigSet) if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { it.fail = err return false @@ -581,7 +605,7 @@ func (it *ConfiguratorOwnershipTransferredIterator) Next() bool { select { case log := <-it.logs: - it.Event = new(ConfiguratorOwnershipTransferred) + it.Event = new(ConfiguratorProductionConfigSet) if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { it.fail = err return false @@ -596,51 +620,189 @@ func (it *ConfiguratorOwnershipTransferredIterator) Next() bool { } } -func (it *ConfiguratorOwnershipTransferredIterator) Error() error { +func (it *ConfiguratorProductionConfigSetIterator) Error() error { return it.fail } -func (it *ConfiguratorOwnershipTransferredIterator) Close() error { +func (it *ConfiguratorProductionConfigSetIterator) Close() error { it.sub.Unsubscribe() return nil } -type ConfiguratorOwnershipTransferred struct { - From common.Address - To common.Address - Raw types.Log +type ConfiguratorProductionConfigSet struct { + ConfigId [32]byte + PreviousConfigBlockNumber uint32 + ConfigDigest [32]byte + ConfigCount uint64 + Signers [][]byte + OffchainTransmitters [][32]byte + F uint8 + OnchainConfig []byte + OffchainConfigVersion uint64 + OffchainConfig []byte + IsGreenProduction bool + Raw types.Log } -func (_Configurator *ConfiguratorFilterer) FilterOwnershipTransferred(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*ConfiguratorOwnershipTransferredIterator, error) { +func (_Configurator *ConfiguratorFilterer) FilterProductionConfigSet(opts *bind.FilterOpts, configId [][32]byte) (*ConfiguratorProductionConfigSetIterator, error) { - var fromRule []interface{} - for _, fromItem := range from { - fromRule = append(fromRule, fromItem) + var configIdRule []interface{} + for _, configIdItem := range configId { + configIdRule = append(configIdRule, configIdItem) } - var toRule []interface{} - for _, toItem := range to { - toRule = append(toRule, toItem) + + logs, sub, err := _Configurator.contract.FilterLogs(opts, "ProductionConfigSet", configIdRule) + if err != nil { + return nil, err } + return &ConfiguratorProductionConfigSetIterator{contract: _Configurator.contract, event: "ProductionConfigSet", logs: logs, sub: sub}, nil +} - logs, sub, err := _Configurator.contract.FilterLogs(opts, "OwnershipTransferred", fromRule, toRule) +func (_Configurator *ConfiguratorFilterer) WatchProductionConfigSet(opts *bind.WatchOpts, sink chan<- *ConfiguratorProductionConfigSet, configId [][32]byte) (event.Subscription, error) { + + var configIdRule []interface{} + for _, configIdItem := range configId { + configIdRule = append(configIdRule, configIdItem) + } + + logs, sub, err := _Configurator.contract.WatchLogs(opts, "ProductionConfigSet", configIdRule) if err != nil { return nil, err } - return &ConfiguratorOwnershipTransferredIterator{contract: _Configurator.contract, event: "OwnershipTransferred", logs: logs, sub: sub}, nil + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + + event := new(ConfiguratorProductionConfigSet) + if err := _Configurator.contract.UnpackLog(event, "ProductionConfigSet", 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 (_Configurator *ConfiguratorFilterer) WatchOwnershipTransferred(opts *bind.WatchOpts, sink chan<- *ConfiguratorOwnershipTransferred, from []common.Address, to []common.Address) (event.Subscription, error) { +func (_Configurator *ConfiguratorFilterer) ParseProductionConfigSet(log types.Log) (*ConfiguratorProductionConfigSet, error) { + event := new(ConfiguratorProductionConfigSet) + if err := _Configurator.contract.UnpackLog(event, "ProductionConfigSet", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} - var fromRule []interface{} - for _, fromItem := range from { - fromRule = append(fromRule, fromItem) +type ConfiguratorPromoteStagingConfigIterator struct { + Event *ConfiguratorPromoteStagingConfig + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *ConfiguratorPromoteStagingConfigIterator) Next() bool { + + if it.fail != nil { + return false } - var toRule []interface{} - for _, toItem := range to { - toRule = append(toRule, toItem) + + if it.done { + select { + case log := <-it.logs: + it.Event = new(ConfiguratorPromoteStagingConfig) + 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 + } } - logs, sub, err := _Configurator.contract.WatchLogs(opts, "OwnershipTransferred", fromRule, toRule) + select { + case log := <-it.logs: + it.Event = new(ConfiguratorPromoteStagingConfig) + 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 *ConfiguratorPromoteStagingConfigIterator) Error() error { + return it.fail +} + +func (it *ConfiguratorPromoteStagingConfigIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type ConfiguratorPromoteStagingConfig struct { + ConfigId [32]byte + RetiredConfigDigest [32]byte + IsGreenProduction bool + Raw types.Log +} + +func (_Configurator *ConfiguratorFilterer) FilterPromoteStagingConfig(opts *bind.FilterOpts, configId [][32]byte, retiredConfigDigest [][32]byte) (*ConfiguratorPromoteStagingConfigIterator, error) { + + var configIdRule []interface{} + for _, configIdItem := range configId { + configIdRule = append(configIdRule, configIdItem) + } + var retiredConfigDigestRule []interface{} + for _, retiredConfigDigestItem := range retiredConfigDigest { + retiredConfigDigestRule = append(retiredConfigDigestRule, retiredConfigDigestItem) + } + + logs, sub, err := _Configurator.contract.FilterLogs(opts, "PromoteStagingConfig", configIdRule, retiredConfigDigestRule) + if err != nil { + return nil, err + } + return &ConfiguratorPromoteStagingConfigIterator{contract: _Configurator.contract, event: "PromoteStagingConfig", logs: logs, sub: sub}, nil +} + +func (_Configurator *ConfiguratorFilterer) WatchPromoteStagingConfig(opts *bind.WatchOpts, sink chan<- *ConfiguratorPromoteStagingConfig, configId [][32]byte, retiredConfigDigest [][32]byte) (event.Subscription, error) { + + var configIdRule []interface{} + for _, configIdItem := range configId { + configIdRule = append(configIdRule, configIdItem) + } + var retiredConfigDigestRule []interface{} + for _, retiredConfigDigestItem := range retiredConfigDigest { + retiredConfigDigestRule = append(retiredConfigDigestRule, retiredConfigDigestItem) + } + + logs, sub, err := _Configurator.contract.WatchLogs(opts, "PromoteStagingConfig", configIdRule, retiredConfigDigestRule) if err != nil { return nil, err } @@ -650,8 +812,8 @@ func (_Configurator *ConfiguratorFilterer) WatchOwnershipTransferred(opts *bind. select { case log := <-logs: - event := new(ConfiguratorOwnershipTransferred) - if err := _Configurator.contract.UnpackLog(event, "OwnershipTransferred", log); err != nil { + event := new(ConfiguratorPromoteStagingConfig) + if err := _Configurator.contract.UnpackLog(event, "PromoteStagingConfig", log); err != nil { return err } event.Raw = log @@ -672,9 +834,146 @@ func (_Configurator *ConfiguratorFilterer) WatchOwnershipTransferred(opts *bind. }), nil } -func (_Configurator *ConfiguratorFilterer) ParseOwnershipTransferred(log types.Log) (*ConfiguratorOwnershipTransferred, error) { - event := new(ConfiguratorOwnershipTransferred) - if err := _Configurator.contract.UnpackLog(event, "OwnershipTransferred", log); err != nil { +func (_Configurator *ConfiguratorFilterer) ParsePromoteStagingConfig(log types.Log) (*ConfiguratorPromoteStagingConfig, error) { + event := new(ConfiguratorPromoteStagingConfig) + if err := _Configurator.contract.UnpackLog(event, "PromoteStagingConfig", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +type ConfiguratorStagingConfigSetIterator struct { + Event *ConfiguratorStagingConfigSet + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *ConfiguratorStagingConfigSetIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(ConfiguratorStagingConfigSet) + 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(ConfiguratorStagingConfigSet) + 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 *ConfiguratorStagingConfigSetIterator) Error() error { + return it.fail +} + +func (it *ConfiguratorStagingConfigSetIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type ConfiguratorStagingConfigSet struct { + ConfigId [32]byte + PreviousConfigBlockNumber uint32 + ConfigDigest [32]byte + ConfigCount uint64 + Signers [][]byte + OffchainTransmitters [][32]byte + F uint8 + OnchainConfig []byte + OffchainConfigVersion uint64 + OffchainConfig []byte + IsGreenProduction bool + Raw types.Log +} + +func (_Configurator *ConfiguratorFilterer) FilterStagingConfigSet(opts *bind.FilterOpts, configId [][32]byte) (*ConfiguratorStagingConfigSetIterator, error) { + + var configIdRule []interface{} + for _, configIdItem := range configId { + configIdRule = append(configIdRule, configIdItem) + } + + logs, sub, err := _Configurator.contract.FilterLogs(opts, "StagingConfigSet", configIdRule) + if err != nil { + return nil, err + } + return &ConfiguratorStagingConfigSetIterator{contract: _Configurator.contract, event: "StagingConfigSet", logs: logs, sub: sub}, nil +} + +func (_Configurator *ConfiguratorFilterer) WatchStagingConfigSet(opts *bind.WatchOpts, sink chan<- *ConfiguratorStagingConfigSet, configId [][32]byte) (event.Subscription, error) { + + var configIdRule []interface{} + for _, configIdItem := range configId { + configIdRule = append(configIdRule, configIdItem) + } + + logs, sub, err := _Configurator.contract.WatchLogs(opts, "StagingConfigSet", configIdRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + + event := new(ConfiguratorStagingConfigSet) + if err := _Configurator.contract.UnpackLog(event, "StagingConfigSet", 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 (_Configurator *ConfiguratorFilterer) ParseStagingConfigSet(log types.Log) (*ConfiguratorStagingConfigSet, error) { + event := new(ConfiguratorStagingConfigSet) + if err := _Configurator.contract.UnpackLog(event, "StagingConfigSet", log); err != nil { return nil, err } event.Raw = log @@ -683,22 +982,22 @@ func (_Configurator *ConfiguratorFilterer) ParseOwnershipTransferred(log types.L func (_Configurator *Configurator) ParseLog(log types.Log) (generated.AbigenLog, error) { switch log.Topics[0] { - case _Configurator.abi.Events["ConfigSet"].ID: - return _Configurator.ParseConfigSet(log) case _Configurator.abi.Events["OwnershipTransferRequested"].ID: return _Configurator.ParseOwnershipTransferRequested(log) case _Configurator.abi.Events["OwnershipTransferred"].ID: return _Configurator.ParseOwnershipTransferred(log) + case _Configurator.abi.Events["ProductionConfigSet"].ID: + return _Configurator.ParseProductionConfigSet(log) + case _Configurator.abi.Events["PromoteStagingConfig"].ID: + return _Configurator.ParsePromoteStagingConfig(log) + case _Configurator.abi.Events["StagingConfigSet"].ID: + return _Configurator.ParseStagingConfigSet(log) default: return nil, fmt.Errorf("abigen wrapper received unknown log topic: %v", log.Topics[0]) } } -func (ConfiguratorConfigSet) Topic() common.Hash { - return common.HexToHash("0xa23a88453230b183877098801ff5a8f771a120e2573eea559ce6c4c2e305a4da") -} - func (ConfiguratorOwnershipTransferRequested) Topic() common.Hash { return common.HexToHash("0xed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae1278") } @@ -707,6 +1006,18 @@ func (ConfiguratorOwnershipTransferred) Topic() common.Hash { return common.HexToHash("0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0") } +func (ConfiguratorProductionConfigSet) Topic() common.Hash { + return common.HexToHash("0x261b20c2ecd99d86d6e936279e4f78db34603a3de3a4a84d6f3d4e0dd55e2478") +} + +func (ConfiguratorPromoteStagingConfig) Topic() common.Hash { + return common.HexToHash("0x1062aa08ac6046a0e69e3eafdf12d1eba63a67b71a874623e86eb06348a1d84f") +} + +func (ConfiguratorStagingConfigSet) Topic() common.Hash { + return common.HexToHash("0xef1b5f9d1b927b0fe871b12c7e7846457602d67b2bc36b0bc95feaf480e89056") +} + func (_Configurator *Configurator) Address() common.Address { return _Configurator.address } @@ -720,15 +1031,13 @@ type ConfiguratorInterface interface { AcceptOwnership(opts *bind.TransactOpts) (*types.Transaction, error) - SetConfig(opts *bind.TransactOpts, donId [32]byte, signers []common.Address, offchainTransmitters [][32]byte, f uint8, onchainConfig []byte, offchainConfigVersion uint64, offchainConfig []byte) (*types.Transaction, error) + PromoteStagingConfig(opts *bind.TransactOpts, configId [32]byte, isGreenProduction bool) (*types.Transaction, error) - TransferOwnership(opts *bind.TransactOpts, to common.Address) (*types.Transaction, error) - - FilterConfigSet(opts *bind.FilterOpts, configId [][32]byte) (*ConfiguratorConfigSetIterator, error) + SetProductionConfig(opts *bind.TransactOpts, configId [32]byte, signers [][]byte, offchainTransmitters [][32]byte, f uint8, onchainConfig []byte, offchainConfigVersion uint64, offchainConfig []byte) (*types.Transaction, error) - WatchConfigSet(opts *bind.WatchOpts, sink chan<- *ConfiguratorConfigSet, configId [][32]byte) (event.Subscription, error) + SetStagingConfig(opts *bind.TransactOpts, configId [32]byte, signers [][]byte, offchainTransmitters [][32]byte, f uint8, onchainConfig []byte, offchainConfigVersion uint64, offchainConfig []byte) (*types.Transaction, error) - ParseConfigSet(log types.Log) (*ConfiguratorConfigSet, error) + TransferOwnership(opts *bind.TransactOpts, to common.Address) (*types.Transaction, error) FilterOwnershipTransferRequested(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*ConfiguratorOwnershipTransferRequestedIterator, error) @@ -742,6 +1051,24 @@ type ConfiguratorInterface interface { ParseOwnershipTransferred(log types.Log) (*ConfiguratorOwnershipTransferred, error) + FilterProductionConfigSet(opts *bind.FilterOpts, configId [][32]byte) (*ConfiguratorProductionConfigSetIterator, error) + + WatchProductionConfigSet(opts *bind.WatchOpts, sink chan<- *ConfiguratorProductionConfigSet, configId [][32]byte) (event.Subscription, error) + + ParseProductionConfigSet(log types.Log) (*ConfiguratorProductionConfigSet, error) + + FilterPromoteStagingConfig(opts *bind.FilterOpts, configId [][32]byte, retiredConfigDigest [][32]byte) (*ConfiguratorPromoteStagingConfigIterator, error) + + WatchPromoteStagingConfig(opts *bind.WatchOpts, sink chan<- *ConfiguratorPromoteStagingConfig, configId [][32]byte, retiredConfigDigest [][32]byte) (event.Subscription, error) + + ParsePromoteStagingConfig(log types.Log) (*ConfiguratorPromoteStagingConfig, error) + + FilterStagingConfigSet(opts *bind.FilterOpts, configId [][32]byte) (*ConfiguratorStagingConfigSetIterator, error) + + WatchStagingConfigSet(opts *bind.WatchOpts, sink chan<- *ConfiguratorStagingConfigSet, configId [][32]byte) (event.Subscription, error) + + ParseStagingConfigSet(log types.Log) (*ConfiguratorStagingConfigSet, error) + ParseLog(log types.Log) (generated.AbigenLog, error) Address() common.Address diff --git a/core/gethwrappers/llo-feeds/generated/exposed_configurator/exposed_configurator.go b/core/gethwrappers/llo-feeds/generated/exposed_configurator/exposed_configurator.go new file mode 100644 index 00000000000..756a9fa8432 --- /dev/null +++ b/core/gethwrappers/llo-feeds/generated/exposed_configurator/exposed_configurator.go @@ -0,0 +1,1158 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package exposed_configurator + +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 ConfiguratorConfigurationState struct { + ConfigCount uint64 + LatestConfigBlockNumber uint32 + IsGreenProduction bool + ConfigDigest [2][32]byte +} + +var ExposedConfiguratorMetaData = &bind.MetaData{ + ABI: "[{\"inputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"configId\",\"type\":\"bytes32\"}],\"name\":\"ConfigUnset\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"configId\",\"type\":\"bytes32\"},{\"internalType\":\"bool\",\"name\":\"isGreenProduction\",\"type\":\"bool\"}],\"name\":\"ConfigUnsetProduction\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"configId\",\"type\":\"bytes32\"},{\"internalType\":\"bool\",\"name\":\"isGreenProduction\",\"type\":\"bool\"}],\"name\":\"ConfigUnsetStaging\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"numSigners\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"maxSigners\",\"type\":\"uint256\"}],\"name\":\"ExcessSigners\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"FaultToleranceMustBePositive\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"numSigners\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"minSigners\",\"type\":\"uint256\"}],\"name\":\"InsufficientSigners\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"onchainConfigLength\",\"type\":\"uint256\"}],\"name\":\"InvalidOnchainLength\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"predecessorConfigDigest\",\"type\":\"bytes32\"}],\"name\":\"InvalidPredecessorConfigDigest\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"configId\",\"type\":\"bytes32\"},{\"internalType\":\"bool\",\"name\":\"isGreenProductionContractState\",\"type\":\"bool\"}],\"name\":\"IsGreenProductionMustMatchContractState\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"predecessorConfigDigest\",\"type\":\"bytes32\"}],\"name\":\"NonZeroPredecessorConfigDigest\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"version\",\"type\":\"uint256\"}],\"name\":\"UnsupportedOnchainConfigVersion\",\"type\":\"error\"},{\"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\":\"bytes32\",\"name\":\"configId\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"uint32\",\"name\":\"previousConfigBlockNumber\",\"type\":\"uint32\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"configDigest\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"configCount\",\"type\":\"uint64\"},{\"indexed\":false,\"internalType\":\"bytes[]\",\"name\":\"signers\",\"type\":\"bytes[]\"},{\"indexed\":false,\"internalType\":\"bytes32[]\",\"name\":\"offchainTransmitters\",\"type\":\"bytes32[]\"},{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"f\",\"type\":\"uint8\"},{\"indexed\":false,\"internalType\":\"bytes\",\"name\":\"onchainConfig\",\"type\":\"bytes\"},{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"offchainConfigVersion\",\"type\":\"uint64\"},{\"indexed\":false,\"internalType\":\"bytes\",\"name\":\"offchainConfig\",\"type\":\"bytes\"},{\"indexed\":false,\"internalType\":\"bool\",\"name\":\"isGreenProduction\",\"type\":\"bool\"}],\"name\":\"ProductionConfigSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"configId\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"retiredConfigDigest\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"bool\",\"name\":\"isGreenProduction\",\"type\":\"bool\"}],\"name\":\"PromoteStagingConfig\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"configId\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"uint32\",\"name\":\"previousConfigBlockNumber\",\"type\":\"uint32\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"configDigest\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"configCount\",\"type\":\"uint64\"},{\"indexed\":false,\"internalType\":\"bytes[]\",\"name\":\"signers\",\"type\":\"bytes[]\"},{\"indexed\":false,\"internalType\":\"bytes32[]\",\"name\":\"offchainTransmitters\",\"type\":\"bytes32[]\"},{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"f\",\"type\":\"uint8\"},{\"indexed\":false,\"internalType\":\"bytes\",\"name\":\"onchainConfig\",\"type\":\"bytes\"},{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"offchainConfigVersion\",\"type\":\"uint64\"},{\"indexed\":false,\"internalType\":\"bytes\",\"name\":\"offchainConfig\",\"type\":\"bytes\"},{\"indexed\":false,\"internalType\":\"bool\",\"name\":\"isGreenProduction\",\"type\":\"bool\"}],\"name\":\"StagingConfigSet\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"acceptOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"_configId\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"_chainId\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"_contractAddress\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"_configCount\",\"type\":\"uint64\"},{\"internalType\":\"bytes[]\",\"name\":\"_signers\",\"type\":\"bytes[]\"},{\"internalType\":\"bytes32[]\",\"name\":\"_offchainTransmitters\",\"type\":\"bytes32[]\"},{\"internalType\":\"uint8\",\"name\":\"_f\",\"type\":\"uint8\"},{\"internalType\":\"bytes\",\"name\":\"_onchainConfig\",\"type\":\"bytes\"},{\"internalType\":\"uint64\",\"name\":\"_encodedConfigVersion\",\"type\":\"uint64\"},{\"internalType\":\"bytes\",\"name\":\"_encodedConfig\",\"type\":\"bytes\"}],\"name\":\"exposedConfigDigestFromConfigData\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"configId\",\"type\":\"bytes32\"}],\"name\":\"exposedReadConfigurationStates\",\"outputs\":[{\"components\":[{\"internalType\":\"uint64\",\"name\":\"configCount\",\"type\":\"uint64\"},{\"internalType\":\"uint32\",\"name\":\"latestConfigBlockNumber\",\"type\":\"uint32\"},{\"internalType\":\"bool\",\"name\":\"isGreenProduction\",\"type\":\"bool\"},{\"internalType\":\"bytes32[2]\",\"name\":\"configDigest\",\"type\":\"bytes32[2]\"}],\"internalType\":\"structConfigurator.ConfigurationState\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"configId\",\"type\":\"bytes32\"},{\"components\":[{\"internalType\":\"uint64\",\"name\":\"configCount\",\"type\":\"uint64\"},{\"internalType\":\"uint32\",\"name\":\"latestConfigBlockNumber\",\"type\":\"uint32\"},{\"internalType\":\"bool\",\"name\":\"isGreenProduction\",\"type\":\"bool\"},{\"internalType\":\"bytes32[2]\",\"name\":\"configDigest\",\"type\":\"bytes32[2]\"}],\"internalType\":\"structConfigurator.ConfigurationState\",\"name\":\"state\",\"type\":\"tuple\"}],\"name\":\"exposedSetConfigurationState\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"configId\",\"type\":\"bytes32\"},{\"internalType\":\"bool\",\"name\":\"isGreenProduction\",\"type\":\"bool\"}],\"name\":\"exposedSetIsGreenProduction\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"configId\",\"type\":\"bytes32\"},{\"internalType\":\"bool\",\"name\":\"isGreenProduction\",\"type\":\"bool\"}],\"name\":\"promoteStagingConfig\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"configId\",\"type\":\"bytes32\"},{\"internalType\":\"bytes[]\",\"name\":\"signers\",\"type\":\"bytes[]\"},{\"internalType\":\"bytes32[]\",\"name\":\"offchainTransmitters\",\"type\":\"bytes32[]\"},{\"internalType\":\"uint8\",\"name\":\"f\",\"type\":\"uint8\"},{\"internalType\":\"bytes\",\"name\":\"onchainConfig\",\"type\":\"bytes\"},{\"internalType\":\"uint64\",\"name\":\"offchainConfigVersion\",\"type\":\"uint64\"},{\"internalType\":\"bytes\",\"name\":\"offchainConfig\",\"type\":\"bytes\"}],\"name\":\"setProductionConfig\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"configId\",\"type\":\"bytes32\"},{\"internalType\":\"bytes[]\",\"name\":\"signers\",\"type\":\"bytes[]\"},{\"internalType\":\"bytes32[]\",\"name\":\"offchainTransmitters\",\"type\":\"bytes32[]\"},{\"internalType\":\"uint8\",\"name\":\"f\",\"type\":\"uint8\"},{\"internalType\":\"bytes\",\"name\":\"onchainConfig\",\"type\":\"bytes\"},{\"internalType\":\"uint64\",\"name\":\"offchainConfigVersion\",\"type\":\"uint64\"},{\"internalType\":\"bytes\",\"name\":\"offchainConfig\",\"type\":\"bytes\"}],\"name\":\"setStagingConfig\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes4\",\"name\":\"interfaceId\",\"type\":\"bytes4\"}],\"name\":\"supportsInterface\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"isVerifier\",\"type\":\"bool\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"typeAndVersion\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"pure\",\"type\":\"function\"}]", + Bin: "0x608060405234801561001057600080fd5b5033806000816100675760405162461bcd60e51b815260206004820152601860248201527f43616e6e6f7420736574206f776e657220746f207a65726f000000000000000060448201526064015b60405180910390fd5b600080546001600160a01b0319166001600160a01b0384811691909117909155811615610097576100978161009f565b505050610148565b336001600160a01b038216036100f75760405162461bcd60e51b815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c66000000000000000000604482015260640161005e565b600180546001600160a01b0319166001600160a01b0383811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b611ac080620001586000396000f3fe608060405234801561001057600080fd5b50600436106100d45760003560e01c806379ba509711610081578063dfb533d01161005b578063dfb533d014610278578063e6e7c5a41461028b578063f2fde38b1461029e57600080fd5b806379ba5097146102285780638da5cb5b1461023057806399a073401461025857600080fd5b8063639fec28116100b2578063639fec28146101a357806369a120eb146101b8578063790464e01461021557600080fd5b806301ffc9a7146100d9578063181f5a771461014357806360e72ec914610182575b600080fd5b61012e6100e73660046110bc565b7fffffffff00000000000000000000000000000000000000000000000000000000167f40569294000000000000000000000000000000000000000000000000000000001490565b60405190151581526020015b60405180910390f35b604080518082018252601281527f436f6e666967757261746f7220302e352e3000000000000000000000000000006020820152905161013a9190611169565b61019561019036600461147e565b6102b1565b60405190815260200161013a565b6101b66101b136600461159f565b61030d565b005b6101b66101c6366004611684565b60009182526002602052604090912080549115156c01000000000000000000000000027fffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffff909216919091179055565b6101b66102233660046116b0565b6103cc565b6101b66105a5565b60005460405173ffffffffffffffffffffffffffffffffffffffff909116815260200161013a565b61026b610266366004611788565b6106a2565b60405161013a91906117a1565b6101b66102863660046116b0565b610745565b6101b6610299366004611684565b6109a7565b6101b66102ac366004611806565b610bb3565b60006102fd8c8c8c8c8c8c8c8c8c8080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152508e92508d9150610bc79050565b9c9b505050505050505050505050565b60008281526002602081815260409283902084518154928601519486015115156c01000000000000000000000000027fffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffff63ffffffff90961668010000000000000000027fffffffffffffffffffffffffffffffffffffffff00000000000000000000000090941667ffffffffffffffff90921691909117929092179390931617825560608301518392916103c591600184019161101d565b5050505050565b85518460ff168060000361040c576040517f0743bae600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b601f821115610456576040517f61750f4000000000000000000000000000000000000000000000000000000000815260048101839052601f60248201526044015b60405180910390fd5b610461816003611850565b82116104b95781610473826003611850565b61047e90600161186d565b6040517f9dd9e6d80000000000000000000000000000000000000000000000000000000081526004810192909252602482015260440161044d565b6104c1610c75565b6040855110156105025784516040517f3e936ca800000000000000000000000000000000000000000000000000000000815260040161044d91815260200190565b60208501516040860151600182101561054a576040517f8f01e0d70000000000000000000000000000000000000000000000000000000081526004810183905260240161044d565b8015610585576040517fb96bb7600000000000000000000000000000000000000000000000000000000081526004810182905260240161044d565b6105988b46308d8d8d8d8d8d6001610cf8565b5050505050505050505050565b60015473ffffffffffffffffffffffffffffffffffffffff163314610626576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4d7573742062652070726f706f736564206f776e657200000000000000000000604482015260640161044d565b60008054337fffffffffffffffffffffffff00000000000000000000000000000000000000008083168217845560018054909116905560405173ffffffffffffffffffffffffffffffffffffffff90921692909183917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a350565b6106aa61105b565b6000828152600260208181526040928390208351608081018552815467ffffffffffffffff8116825268010000000000000000810463ffffffff16938201939093526c0100000000000000000000000090920460ff161515828501528351808501948590529193909260608501929160018501919082845b815481526020019060010190808311610722575050505050815250509050919050565b85518460ff1680600003610785576040517f0743bae600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b601f8211156107ca576040517f61750f4000000000000000000000000000000000000000000000000000000000815260048101839052601f602482015260440161044d565b6107d5816003611850565b82116107e75781610473826003611850565b6107ef610c75565b6040855110156108305784516040517f3e936ca800000000000000000000000000000000000000000000000000000000815260040161044d91815260200190565b602085015160408601516001821015610878576040517f8f01e0d70000000000000000000000000000000000000000000000000000000081526004810183905260240161044d565b60008b81526002602081815260408084208151608081018352815467ffffffffffffffff8116825268010000000000000000810463ffffffff16948201949094526c0100000000000000000000000090930460ff161515838301528151808301928390529293909260608501929091600185019182845b8154815260200190600101908083116108ef575050505050815250509050600260008d81526020019081526020016000206001018160400151610933576000610936565b60015b60ff166002811061094957610949611880565b01548214610986576040517f7d78c2a10000000000000000000000000000000000000000000000000000000081526004810183905260240161044d565b6109998c46308e8e8e8e8e8e6000610cf8565b505050505050505050505050565b6109af610c75565b600082815260026020526040902080546c01000000000000000000000000900460ff16151582151514610a18576040517f85fa3a37000000000000000000000000000000000000000000000000000000008152600481018490528215602482015260440161044d565b805467ffffffffffffffff16600003610a60576040517f90e6f6dc0000000000000000000000000000000000000000000000000000000081526004810184905260240161044d565b60006001820183610a72576001610a75565b60005b60ff1660028110610a8857610a88611880565b015403610acc576040517f5b7f635700000000000000000000000000000000000000000000000000000000815260048101849052821515602482015260440161044d565b60008160010183610ade576000610ae1565b60015b60ff1660028110610af457610af4611880565b0154905080610b3a576040517fcaf1e77300000000000000000000000000000000000000000000000000000000815260048101859052831515602482015260440161044d565b81547fffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffff1683156c010000000000000000000000008102919091178355604051908152819085907f1062aa08ac6046a0e69e3eafdf12d1eba63a67b71a874623e86eb06348a1d84f9060200160405180910390a350505050565b610bbb610c75565b610bc481610f28565b50565b6000808b8b8b8b8b8b8b8b8b8b604051602001610bed9a9998979695949392919061193f565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081840301815291905280516020909101207dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff167e09000000000000000000000000000000000000000000000000000000000000179150509a9950505050505050505050565b60005473ffffffffffffffffffffffffffffffffffffffff163314610cf6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4f6e6c792063616c6c61626c65206279206f776e657200000000000000000000604482015260640161044d565b565b60008a8152600260205260408120805490919082908290610d229067ffffffffffffffff166119ec565b91906101000a81548167ffffffffffffffff021916908367ffffffffffffffff160217905590506000610d5d8d8d8d858e8e8e8e8e8e610bc7565b90508315610e25578c7f261b20c2ecd99d86d6e936279e4f78db34603a3de3a4a84d6f3d4e0dd55e24788460000160089054906101000a900463ffffffff1683858e8e8e8e8e8e8d600001600c9054906101000a900460ff16604051610dcc9a99989796959493929190611a13565b60405180910390a260008d815260026020526040902083548291600101906c01000000000000000000000000900460ff16610e08576000610e0b565b60015b60ff1660028110610e1e57610e1e611880565b0155610ee1565b8c7fef1b5f9d1b927b0fe871b12c7e7846457602d67b2bc36b0bc95feaf480e890568460000160089054906101000a900463ffffffff1683858e8e8e8e8e8e8d600001600c9054906101000a900460ff16604051610e8c9a99989796959493929190611a13565b60405180910390a260008d815260026020526040902083548291600101906c01000000000000000000000000900460ff16610ec8576001610ecb565b60005b60ff1660028110610ede57610ede611880565b01555b505080547fffffffffffffffffffffffffffffffffffffffff00000000ffffffffffffffff16680100000000000000004363ffffffff160217905550505050505050505050565b3373ffffffffffffffffffffffffffffffffffffffff821603610fa7576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c66000000000000000000604482015260640161044d565b600180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b826002810192821561104b579160200282015b8281111561104b578251825591602001919060010190611030565b50611057929150611089565b5090565b60408051608081018252600080825260208201819052918101919091526060810161108461109e565b905290565b5b80821115611057576000815560010161108a565b60405180604001604052806002906020820280368337509192915050565b6000602082840312156110ce57600080fd5b81357fffffffff00000000000000000000000000000000000000000000000000000000811681146110fe57600080fd5b9392505050565b6000815180845260005b8181101561112b5760208185018101518683018201520161110f565b5060006020828601015260207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f83011685010191505092915050565b6020815260006110fe6020830184611105565b803573ffffffffffffffffffffffffffffffffffffffff811681146111a057600080fd5b919050565b803567ffffffffffffffff811681146111a057600080fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6040516080810167ffffffffffffffff8111828210171561120f5761120f6111bd565b60405290565b6040805190810167ffffffffffffffff8111828210171561120f5761120f6111bd565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff8111828210171561127f5761127f6111bd565b604052919050565b600067ffffffffffffffff8211156112a1576112a16111bd565b5060051b60200190565b600082601f8301126112bc57600080fd5b813567ffffffffffffffff8111156112d6576112d66111bd565b61130760207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f84011601611238565b81815284602083860101111561131c57600080fd5b816020850160208301376000918101602001919091529392505050565b600082601f83011261134a57600080fd5b8135602061135f61135a83611287565b611238565b82815260059290921b8401810191818101908684111561137e57600080fd5b8286015b848110156113be57803567ffffffffffffffff8111156113a25760008081fd5b6113b08986838b01016112ab565b845250918301918301611382565b509695505050505050565b600082601f8301126113da57600080fd5b813560206113ea61135a83611287565b82815260059290921b8401810191818101908684111561140957600080fd5b8286015b848110156113be578035835291830191830161140d565b803560ff811681146111a057600080fd5b60008083601f84011261144757600080fd5b50813567ffffffffffffffff81111561145f57600080fd5b60208301915083602082850101111561147757600080fd5b9250929050565b60008060008060008060008060008060006101408c8e0312156114a057600080fd5b8b359a5060208c013599506114b760408d0161117c565b98506114c560608d016111a5565b975067ffffffffffffffff8060808e013511156114e157600080fd5b6114f18e60808f01358f01611339565b97508060a08e0135111561150457600080fd5b6115148e60a08f01358f016113c9565b965061152260c08e01611424565b95508060e08e0135111561153557600080fd5b6115458e60e08f01358f01611435565b90955093506115576101008e016111a5565b9250806101208e0135111561156b57600080fd5b5061157d8d6101208e01358e016112ab565b90509295989b509295989b9093969950565b803580151581146111a057600080fd5b60008082840360c08112156115b357600080fd5b83359250602060a07fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0830112156115e957600080fd5b6115f16111ec565b91506115fe8186016111a5565b8252604085013563ffffffff8116811461161757600080fd5b828201526116276060860161158f565b604083015285609f86011261163b57600080fd5b611643611215565b8060c087018881111561165557600080fd5b608088015b81811015611671578035845292840192840161165a565b5050606084015250929590945092505050565b6000806040838503121561169757600080fd5b823591506116a76020840161158f565b90509250929050565b600080600080600080600060e0888a0312156116cb57600080fd5b87359650602088013567ffffffffffffffff808211156116ea57600080fd5b6116f68b838c01611339565b975060408a013591508082111561170c57600080fd5b6117188b838c016113c9565b965061172660608b01611424565b955060808a013591508082111561173c57600080fd5b6117488b838c016112ab565b945061175660a08b016111a5565b935060c08a013591508082111561176c57600080fd5b506117798a828b016112ab565b91505092959891949750929550565b60006020828403121561179a57600080fd5b5035919050565b600060a08201905067ffffffffffffffff8351168252602063ffffffff81850151168184015260408401511515604084015260608401516060840160005b60028110156117fc578251825291830191908301906001016117df565b5050505092915050565b60006020828403121561181857600080fd5b6110fe8261117c565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b808202811582820484141761186757611867611821565b92915050565b8082018082111561186757611867611821565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b600081518084526020808501808196508360051b8101915082860160005b858110156118f75782840389526118e5848351611105565b988501989350908401906001016118cd565b5091979650505050505050565b600081518084526020808501945080840160005b8381101561193457815187529582019590820190600101611918565b509495945050505050565b60006101408c83528b602084015273ffffffffffffffffffffffffffffffffffffffff8b16604084015267ffffffffffffffff808b16606085015281608085015261198c8285018b6118af565b915083820360a08501526119a0828a611904565b915060ff881660c085015283820360e08501526119bd8288611105565b90861661010085015283810361012085015290506119db8185611105565b9d9c50505050505050505050505050565b600067ffffffffffffffff808316818103611a0957611a09611821565b6001019392505050565b600061014063ffffffff8d1683528b602084015267ffffffffffffffff808c166040850152816060850152611a4a8285018c6118af565b91508382036080850152611a5e828b611904565b915060ff891660a085015283820360c0850152611a7b8289611105565b90871660e08501528381036101008501529050611a988186611105565b9150508215156101208301529b9a505050505050505050505056fea164736f6c6343000813000a", +} + +var ExposedConfiguratorABI = ExposedConfiguratorMetaData.ABI + +var ExposedConfiguratorBin = ExposedConfiguratorMetaData.Bin + +func DeployExposedConfigurator(auth *bind.TransactOpts, backend bind.ContractBackend) (common.Address, *types.Transaction, *ExposedConfigurator, error) { + parsed, err := ExposedConfiguratorMetaData.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(ExposedConfiguratorBin), backend) + if err != nil { + return common.Address{}, nil, nil, err + } + return address, tx, &ExposedConfigurator{address: address, abi: *parsed, ExposedConfiguratorCaller: ExposedConfiguratorCaller{contract: contract}, ExposedConfiguratorTransactor: ExposedConfiguratorTransactor{contract: contract}, ExposedConfiguratorFilterer: ExposedConfiguratorFilterer{contract: contract}}, nil +} + +type ExposedConfigurator struct { + address common.Address + abi abi.ABI + ExposedConfiguratorCaller + ExposedConfiguratorTransactor + ExposedConfiguratorFilterer +} + +type ExposedConfiguratorCaller struct { + contract *bind.BoundContract +} + +type ExposedConfiguratorTransactor struct { + contract *bind.BoundContract +} + +type ExposedConfiguratorFilterer struct { + contract *bind.BoundContract +} + +type ExposedConfiguratorSession struct { + Contract *ExposedConfigurator + CallOpts bind.CallOpts + TransactOpts bind.TransactOpts +} + +type ExposedConfiguratorCallerSession struct { + Contract *ExposedConfiguratorCaller + CallOpts bind.CallOpts +} + +type ExposedConfiguratorTransactorSession struct { + Contract *ExposedConfiguratorTransactor + TransactOpts bind.TransactOpts +} + +type ExposedConfiguratorRaw struct { + Contract *ExposedConfigurator +} + +type ExposedConfiguratorCallerRaw struct { + Contract *ExposedConfiguratorCaller +} + +type ExposedConfiguratorTransactorRaw struct { + Contract *ExposedConfiguratorTransactor +} + +func NewExposedConfigurator(address common.Address, backend bind.ContractBackend) (*ExposedConfigurator, error) { + abi, err := abi.JSON(strings.NewReader(ExposedConfiguratorABI)) + if err != nil { + return nil, err + } + contract, err := bindExposedConfigurator(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &ExposedConfigurator{address: address, abi: abi, ExposedConfiguratorCaller: ExposedConfiguratorCaller{contract: contract}, ExposedConfiguratorTransactor: ExposedConfiguratorTransactor{contract: contract}, ExposedConfiguratorFilterer: ExposedConfiguratorFilterer{contract: contract}}, nil +} + +func NewExposedConfiguratorCaller(address common.Address, caller bind.ContractCaller) (*ExposedConfiguratorCaller, error) { + contract, err := bindExposedConfigurator(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &ExposedConfiguratorCaller{contract: contract}, nil +} + +func NewExposedConfiguratorTransactor(address common.Address, transactor bind.ContractTransactor) (*ExposedConfiguratorTransactor, error) { + contract, err := bindExposedConfigurator(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &ExposedConfiguratorTransactor{contract: contract}, nil +} + +func NewExposedConfiguratorFilterer(address common.Address, filterer bind.ContractFilterer) (*ExposedConfiguratorFilterer, error) { + contract, err := bindExposedConfigurator(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &ExposedConfiguratorFilterer{contract: contract}, nil +} + +func bindExposedConfigurator(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := ExposedConfiguratorMetaData.GetAbi() + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, *parsed, caller, transactor, filterer), nil +} + +func (_ExposedConfigurator *ExposedConfiguratorRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _ExposedConfigurator.Contract.ExposedConfiguratorCaller.contract.Call(opts, result, method, params...) +} + +func (_ExposedConfigurator *ExposedConfiguratorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _ExposedConfigurator.Contract.ExposedConfiguratorTransactor.contract.Transfer(opts) +} + +func (_ExposedConfigurator *ExposedConfiguratorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _ExposedConfigurator.Contract.ExposedConfiguratorTransactor.contract.Transact(opts, method, params...) +} + +func (_ExposedConfigurator *ExposedConfiguratorCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _ExposedConfigurator.Contract.contract.Call(opts, result, method, params...) +} + +func (_ExposedConfigurator *ExposedConfiguratorTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _ExposedConfigurator.Contract.contract.Transfer(opts) +} + +func (_ExposedConfigurator *ExposedConfiguratorTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _ExposedConfigurator.Contract.contract.Transact(opts, method, params...) +} + +func (_ExposedConfigurator *ExposedConfiguratorCaller) ExposedConfigDigestFromConfigData(opts *bind.CallOpts, _configId [32]byte, _chainId *big.Int, _contractAddress common.Address, _configCount uint64, _signers [][]byte, _offchainTransmitters [][32]byte, _f uint8, _onchainConfig []byte, _encodedConfigVersion uint64, _encodedConfig []byte) ([32]byte, error) { + var out []interface{} + err := _ExposedConfigurator.contract.Call(opts, &out, "exposedConfigDigestFromConfigData", _configId, _chainId, _contractAddress, _configCount, _signers, _offchainTransmitters, _f, _onchainConfig, _encodedConfigVersion, _encodedConfig) + + if err != nil { + return *new([32]byte), err + } + + out0 := *abi.ConvertType(out[0], new([32]byte)).(*[32]byte) + + return out0, err + +} + +func (_ExposedConfigurator *ExposedConfiguratorSession) ExposedConfigDigestFromConfigData(_configId [32]byte, _chainId *big.Int, _contractAddress common.Address, _configCount uint64, _signers [][]byte, _offchainTransmitters [][32]byte, _f uint8, _onchainConfig []byte, _encodedConfigVersion uint64, _encodedConfig []byte) ([32]byte, error) { + return _ExposedConfigurator.Contract.ExposedConfigDigestFromConfigData(&_ExposedConfigurator.CallOpts, _configId, _chainId, _contractAddress, _configCount, _signers, _offchainTransmitters, _f, _onchainConfig, _encodedConfigVersion, _encodedConfig) +} + +func (_ExposedConfigurator *ExposedConfiguratorCallerSession) ExposedConfigDigestFromConfigData(_configId [32]byte, _chainId *big.Int, _contractAddress common.Address, _configCount uint64, _signers [][]byte, _offchainTransmitters [][32]byte, _f uint8, _onchainConfig []byte, _encodedConfigVersion uint64, _encodedConfig []byte) ([32]byte, error) { + return _ExposedConfigurator.Contract.ExposedConfigDigestFromConfigData(&_ExposedConfigurator.CallOpts, _configId, _chainId, _contractAddress, _configCount, _signers, _offchainTransmitters, _f, _onchainConfig, _encodedConfigVersion, _encodedConfig) +} + +func (_ExposedConfigurator *ExposedConfiguratorCaller) ExposedReadConfigurationStates(opts *bind.CallOpts, configId [32]byte) (ConfiguratorConfigurationState, error) { + var out []interface{} + err := _ExposedConfigurator.contract.Call(opts, &out, "exposedReadConfigurationStates", configId) + + if err != nil { + return *new(ConfiguratorConfigurationState), err + } + + out0 := *abi.ConvertType(out[0], new(ConfiguratorConfigurationState)).(*ConfiguratorConfigurationState) + + return out0, err + +} + +func (_ExposedConfigurator *ExposedConfiguratorSession) ExposedReadConfigurationStates(configId [32]byte) (ConfiguratorConfigurationState, error) { + return _ExposedConfigurator.Contract.ExposedReadConfigurationStates(&_ExposedConfigurator.CallOpts, configId) +} + +func (_ExposedConfigurator *ExposedConfiguratorCallerSession) ExposedReadConfigurationStates(configId [32]byte) (ConfiguratorConfigurationState, error) { + return _ExposedConfigurator.Contract.ExposedReadConfigurationStates(&_ExposedConfigurator.CallOpts, configId) +} + +func (_ExposedConfigurator *ExposedConfiguratorCaller) Owner(opts *bind.CallOpts) (common.Address, error) { + var out []interface{} + err := _ExposedConfigurator.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 (_ExposedConfigurator *ExposedConfiguratorSession) Owner() (common.Address, error) { + return _ExposedConfigurator.Contract.Owner(&_ExposedConfigurator.CallOpts) +} + +func (_ExposedConfigurator *ExposedConfiguratorCallerSession) Owner() (common.Address, error) { + return _ExposedConfigurator.Contract.Owner(&_ExposedConfigurator.CallOpts) +} + +func (_ExposedConfigurator *ExposedConfiguratorCaller) SupportsInterface(opts *bind.CallOpts, interfaceId [4]byte) (bool, error) { + var out []interface{} + err := _ExposedConfigurator.contract.Call(opts, &out, "supportsInterface", interfaceId) + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + +} + +func (_ExposedConfigurator *ExposedConfiguratorSession) SupportsInterface(interfaceId [4]byte) (bool, error) { + return _ExposedConfigurator.Contract.SupportsInterface(&_ExposedConfigurator.CallOpts, interfaceId) +} + +func (_ExposedConfigurator *ExposedConfiguratorCallerSession) SupportsInterface(interfaceId [4]byte) (bool, error) { + return _ExposedConfigurator.Contract.SupportsInterface(&_ExposedConfigurator.CallOpts, interfaceId) +} + +func (_ExposedConfigurator *ExposedConfiguratorCaller) TypeAndVersion(opts *bind.CallOpts) (string, error) { + var out []interface{} + err := _ExposedConfigurator.contract.Call(opts, &out, "typeAndVersion") + + if err != nil { + return *new(string), err + } + + out0 := *abi.ConvertType(out[0], new(string)).(*string) + + return out0, err + +} + +func (_ExposedConfigurator *ExposedConfiguratorSession) TypeAndVersion() (string, error) { + return _ExposedConfigurator.Contract.TypeAndVersion(&_ExposedConfigurator.CallOpts) +} + +func (_ExposedConfigurator *ExposedConfiguratorCallerSession) TypeAndVersion() (string, error) { + return _ExposedConfigurator.Contract.TypeAndVersion(&_ExposedConfigurator.CallOpts) +} + +func (_ExposedConfigurator *ExposedConfiguratorTransactor) AcceptOwnership(opts *bind.TransactOpts) (*types.Transaction, error) { + return _ExposedConfigurator.contract.Transact(opts, "acceptOwnership") +} + +func (_ExposedConfigurator *ExposedConfiguratorSession) AcceptOwnership() (*types.Transaction, error) { + return _ExposedConfigurator.Contract.AcceptOwnership(&_ExposedConfigurator.TransactOpts) +} + +func (_ExposedConfigurator *ExposedConfiguratorTransactorSession) AcceptOwnership() (*types.Transaction, error) { + return _ExposedConfigurator.Contract.AcceptOwnership(&_ExposedConfigurator.TransactOpts) +} + +func (_ExposedConfigurator *ExposedConfiguratorTransactor) ExposedSetConfigurationState(opts *bind.TransactOpts, configId [32]byte, state ConfiguratorConfigurationState) (*types.Transaction, error) { + return _ExposedConfigurator.contract.Transact(opts, "exposedSetConfigurationState", configId, state) +} + +func (_ExposedConfigurator *ExposedConfiguratorSession) ExposedSetConfigurationState(configId [32]byte, state ConfiguratorConfigurationState) (*types.Transaction, error) { + return _ExposedConfigurator.Contract.ExposedSetConfigurationState(&_ExposedConfigurator.TransactOpts, configId, state) +} + +func (_ExposedConfigurator *ExposedConfiguratorTransactorSession) ExposedSetConfigurationState(configId [32]byte, state ConfiguratorConfigurationState) (*types.Transaction, error) { + return _ExposedConfigurator.Contract.ExposedSetConfigurationState(&_ExposedConfigurator.TransactOpts, configId, state) +} + +func (_ExposedConfigurator *ExposedConfiguratorTransactor) ExposedSetIsGreenProduction(opts *bind.TransactOpts, configId [32]byte, isGreenProduction bool) (*types.Transaction, error) { + return _ExposedConfigurator.contract.Transact(opts, "exposedSetIsGreenProduction", configId, isGreenProduction) +} + +func (_ExposedConfigurator *ExposedConfiguratorSession) ExposedSetIsGreenProduction(configId [32]byte, isGreenProduction bool) (*types.Transaction, error) { + return _ExposedConfigurator.Contract.ExposedSetIsGreenProduction(&_ExposedConfigurator.TransactOpts, configId, isGreenProduction) +} + +func (_ExposedConfigurator *ExposedConfiguratorTransactorSession) ExposedSetIsGreenProduction(configId [32]byte, isGreenProduction bool) (*types.Transaction, error) { + return _ExposedConfigurator.Contract.ExposedSetIsGreenProduction(&_ExposedConfigurator.TransactOpts, configId, isGreenProduction) +} + +func (_ExposedConfigurator *ExposedConfiguratorTransactor) PromoteStagingConfig(opts *bind.TransactOpts, configId [32]byte, isGreenProduction bool) (*types.Transaction, error) { + return _ExposedConfigurator.contract.Transact(opts, "promoteStagingConfig", configId, isGreenProduction) +} + +func (_ExposedConfigurator *ExposedConfiguratorSession) PromoteStagingConfig(configId [32]byte, isGreenProduction bool) (*types.Transaction, error) { + return _ExposedConfigurator.Contract.PromoteStagingConfig(&_ExposedConfigurator.TransactOpts, configId, isGreenProduction) +} + +func (_ExposedConfigurator *ExposedConfiguratorTransactorSession) PromoteStagingConfig(configId [32]byte, isGreenProduction bool) (*types.Transaction, error) { + return _ExposedConfigurator.Contract.PromoteStagingConfig(&_ExposedConfigurator.TransactOpts, configId, isGreenProduction) +} + +func (_ExposedConfigurator *ExposedConfiguratorTransactor) SetProductionConfig(opts *bind.TransactOpts, configId [32]byte, signers [][]byte, offchainTransmitters [][32]byte, f uint8, onchainConfig []byte, offchainConfigVersion uint64, offchainConfig []byte) (*types.Transaction, error) { + return _ExposedConfigurator.contract.Transact(opts, "setProductionConfig", configId, signers, offchainTransmitters, f, onchainConfig, offchainConfigVersion, offchainConfig) +} + +func (_ExposedConfigurator *ExposedConfiguratorSession) SetProductionConfig(configId [32]byte, signers [][]byte, offchainTransmitters [][32]byte, f uint8, onchainConfig []byte, offchainConfigVersion uint64, offchainConfig []byte) (*types.Transaction, error) { + return _ExposedConfigurator.Contract.SetProductionConfig(&_ExposedConfigurator.TransactOpts, configId, signers, offchainTransmitters, f, onchainConfig, offchainConfigVersion, offchainConfig) +} + +func (_ExposedConfigurator *ExposedConfiguratorTransactorSession) SetProductionConfig(configId [32]byte, signers [][]byte, offchainTransmitters [][32]byte, f uint8, onchainConfig []byte, offchainConfigVersion uint64, offchainConfig []byte) (*types.Transaction, error) { + return _ExposedConfigurator.Contract.SetProductionConfig(&_ExposedConfigurator.TransactOpts, configId, signers, offchainTransmitters, f, onchainConfig, offchainConfigVersion, offchainConfig) +} + +func (_ExposedConfigurator *ExposedConfiguratorTransactor) SetStagingConfig(opts *bind.TransactOpts, configId [32]byte, signers [][]byte, offchainTransmitters [][32]byte, f uint8, onchainConfig []byte, offchainConfigVersion uint64, offchainConfig []byte) (*types.Transaction, error) { + return _ExposedConfigurator.contract.Transact(opts, "setStagingConfig", configId, signers, offchainTransmitters, f, onchainConfig, offchainConfigVersion, offchainConfig) +} + +func (_ExposedConfigurator *ExposedConfiguratorSession) SetStagingConfig(configId [32]byte, signers [][]byte, offchainTransmitters [][32]byte, f uint8, onchainConfig []byte, offchainConfigVersion uint64, offchainConfig []byte) (*types.Transaction, error) { + return _ExposedConfigurator.Contract.SetStagingConfig(&_ExposedConfigurator.TransactOpts, configId, signers, offchainTransmitters, f, onchainConfig, offchainConfigVersion, offchainConfig) +} + +func (_ExposedConfigurator *ExposedConfiguratorTransactorSession) SetStagingConfig(configId [32]byte, signers [][]byte, offchainTransmitters [][32]byte, f uint8, onchainConfig []byte, offchainConfigVersion uint64, offchainConfig []byte) (*types.Transaction, error) { + return _ExposedConfigurator.Contract.SetStagingConfig(&_ExposedConfigurator.TransactOpts, configId, signers, offchainTransmitters, f, onchainConfig, offchainConfigVersion, offchainConfig) +} + +func (_ExposedConfigurator *ExposedConfiguratorTransactor) TransferOwnership(opts *bind.TransactOpts, to common.Address) (*types.Transaction, error) { + return _ExposedConfigurator.contract.Transact(opts, "transferOwnership", to) +} + +func (_ExposedConfigurator *ExposedConfiguratorSession) TransferOwnership(to common.Address) (*types.Transaction, error) { + return _ExposedConfigurator.Contract.TransferOwnership(&_ExposedConfigurator.TransactOpts, to) +} + +func (_ExposedConfigurator *ExposedConfiguratorTransactorSession) TransferOwnership(to common.Address) (*types.Transaction, error) { + return _ExposedConfigurator.Contract.TransferOwnership(&_ExposedConfigurator.TransactOpts, to) +} + +type ExposedConfiguratorOwnershipTransferRequestedIterator struct { + Event *ExposedConfiguratorOwnershipTransferRequested + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *ExposedConfiguratorOwnershipTransferRequestedIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(ExposedConfiguratorOwnershipTransferRequested) + 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(ExposedConfiguratorOwnershipTransferRequested) + 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 *ExposedConfiguratorOwnershipTransferRequestedIterator) Error() error { + return it.fail +} + +func (it *ExposedConfiguratorOwnershipTransferRequestedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type ExposedConfiguratorOwnershipTransferRequested struct { + From common.Address + To common.Address + Raw types.Log +} + +func (_ExposedConfigurator *ExposedConfiguratorFilterer) FilterOwnershipTransferRequested(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*ExposedConfiguratorOwnershipTransferRequestedIterator, 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 := _ExposedConfigurator.contract.FilterLogs(opts, "OwnershipTransferRequested", fromRule, toRule) + if err != nil { + return nil, err + } + return &ExposedConfiguratorOwnershipTransferRequestedIterator{contract: _ExposedConfigurator.contract, event: "OwnershipTransferRequested", logs: logs, sub: sub}, nil +} + +func (_ExposedConfigurator *ExposedConfiguratorFilterer) WatchOwnershipTransferRequested(opts *bind.WatchOpts, sink chan<- *ExposedConfiguratorOwnershipTransferRequested, 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 := _ExposedConfigurator.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(ExposedConfiguratorOwnershipTransferRequested) + if err := _ExposedConfigurator.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 (_ExposedConfigurator *ExposedConfiguratorFilterer) ParseOwnershipTransferRequested(log types.Log) (*ExposedConfiguratorOwnershipTransferRequested, error) { + event := new(ExposedConfiguratorOwnershipTransferRequested) + if err := _ExposedConfigurator.contract.UnpackLog(event, "OwnershipTransferRequested", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +type ExposedConfiguratorOwnershipTransferredIterator struct { + Event *ExposedConfiguratorOwnershipTransferred + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *ExposedConfiguratorOwnershipTransferredIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(ExposedConfiguratorOwnershipTransferred) + 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(ExposedConfiguratorOwnershipTransferred) + 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 *ExposedConfiguratorOwnershipTransferredIterator) Error() error { + return it.fail +} + +func (it *ExposedConfiguratorOwnershipTransferredIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type ExposedConfiguratorOwnershipTransferred struct { + From common.Address + To common.Address + Raw types.Log +} + +func (_ExposedConfigurator *ExposedConfiguratorFilterer) FilterOwnershipTransferred(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*ExposedConfiguratorOwnershipTransferredIterator, 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 := _ExposedConfigurator.contract.FilterLogs(opts, "OwnershipTransferred", fromRule, toRule) + if err != nil { + return nil, err + } + return &ExposedConfiguratorOwnershipTransferredIterator{contract: _ExposedConfigurator.contract, event: "OwnershipTransferred", logs: logs, sub: sub}, nil +} + +func (_ExposedConfigurator *ExposedConfiguratorFilterer) WatchOwnershipTransferred(opts *bind.WatchOpts, sink chan<- *ExposedConfiguratorOwnershipTransferred, 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 := _ExposedConfigurator.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(ExposedConfiguratorOwnershipTransferred) + if err := _ExposedConfigurator.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 (_ExposedConfigurator *ExposedConfiguratorFilterer) ParseOwnershipTransferred(log types.Log) (*ExposedConfiguratorOwnershipTransferred, error) { + event := new(ExposedConfiguratorOwnershipTransferred) + if err := _ExposedConfigurator.contract.UnpackLog(event, "OwnershipTransferred", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +type ExposedConfiguratorProductionConfigSetIterator struct { + Event *ExposedConfiguratorProductionConfigSet + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *ExposedConfiguratorProductionConfigSetIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(ExposedConfiguratorProductionConfigSet) + 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(ExposedConfiguratorProductionConfigSet) + 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 *ExposedConfiguratorProductionConfigSetIterator) Error() error { + return it.fail +} + +func (it *ExposedConfiguratorProductionConfigSetIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type ExposedConfiguratorProductionConfigSet struct { + ConfigId [32]byte + PreviousConfigBlockNumber uint32 + ConfigDigest [32]byte + ConfigCount uint64 + Signers [][]byte + OffchainTransmitters [][32]byte + F uint8 + OnchainConfig []byte + OffchainConfigVersion uint64 + OffchainConfig []byte + IsGreenProduction bool + Raw types.Log +} + +func (_ExposedConfigurator *ExposedConfiguratorFilterer) FilterProductionConfigSet(opts *bind.FilterOpts, configId [][32]byte) (*ExposedConfiguratorProductionConfigSetIterator, error) { + + var configIdRule []interface{} + for _, configIdItem := range configId { + configIdRule = append(configIdRule, configIdItem) + } + + logs, sub, err := _ExposedConfigurator.contract.FilterLogs(opts, "ProductionConfigSet", configIdRule) + if err != nil { + return nil, err + } + return &ExposedConfiguratorProductionConfigSetIterator{contract: _ExposedConfigurator.contract, event: "ProductionConfigSet", logs: logs, sub: sub}, nil +} + +func (_ExposedConfigurator *ExposedConfiguratorFilterer) WatchProductionConfigSet(opts *bind.WatchOpts, sink chan<- *ExposedConfiguratorProductionConfigSet, configId [][32]byte) (event.Subscription, error) { + + var configIdRule []interface{} + for _, configIdItem := range configId { + configIdRule = append(configIdRule, configIdItem) + } + + logs, sub, err := _ExposedConfigurator.contract.WatchLogs(opts, "ProductionConfigSet", configIdRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + + event := new(ExposedConfiguratorProductionConfigSet) + if err := _ExposedConfigurator.contract.UnpackLog(event, "ProductionConfigSet", 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 (_ExposedConfigurator *ExposedConfiguratorFilterer) ParseProductionConfigSet(log types.Log) (*ExposedConfiguratorProductionConfigSet, error) { + event := new(ExposedConfiguratorProductionConfigSet) + if err := _ExposedConfigurator.contract.UnpackLog(event, "ProductionConfigSet", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +type ExposedConfiguratorPromoteStagingConfigIterator struct { + Event *ExposedConfiguratorPromoteStagingConfig + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *ExposedConfiguratorPromoteStagingConfigIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(ExposedConfiguratorPromoteStagingConfig) + 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(ExposedConfiguratorPromoteStagingConfig) + 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 *ExposedConfiguratorPromoteStagingConfigIterator) Error() error { + return it.fail +} + +func (it *ExposedConfiguratorPromoteStagingConfigIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type ExposedConfiguratorPromoteStagingConfig struct { + ConfigId [32]byte + RetiredConfigDigest [32]byte + IsGreenProduction bool + Raw types.Log +} + +func (_ExposedConfigurator *ExposedConfiguratorFilterer) FilterPromoteStagingConfig(opts *bind.FilterOpts, configId [][32]byte, retiredConfigDigest [][32]byte) (*ExposedConfiguratorPromoteStagingConfigIterator, error) { + + var configIdRule []interface{} + for _, configIdItem := range configId { + configIdRule = append(configIdRule, configIdItem) + } + var retiredConfigDigestRule []interface{} + for _, retiredConfigDigestItem := range retiredConfigDigest { + retiredConfigDigestRule = append(retiredConfigDigestRule, retiredConfigDigestItem) + } + + logs, sub, err := _ExposedConfigurator.contract.FilterLogs(opts, "PromoteStagingConfig", configIdRule, retiredConfigDigestRule) + if err != nil { + return nil, err + } + return &ExposedConfiguratorPromoteStagingConfigIterator{contract: _ExposedConfigurator.contract, event: "PromoteStagingConfig", logs: logs, sub: sub}, nil +} + +func (_ExposedConfigurator *ExposedConfiguratorFilterer) WatchPromoteStagingConfig(opts *bind.WatchOpts, sink chan<- *ExposedConfiguratorPromoteStagingConfig, configId [][32]byte, retiredConfigDigest [][32]byte) (event.Subscription, error) { + + var configIdRule []interface{} + for _, configIdItem := range configId { + configIdRule = append(configIdRule, configIdItem) + } + var retiredConfigDigestRule []interface{} + for _, retiredConfigDigestItem := range retiredConfigDigest { + retiredConfigDigestRule = append(retiredConfigDigestRule, retiredConfigDigestItem) + } + + logs, sub, err := _ExposedConfigurator.contract.WatchLogs(opts, "PromoteStagingConfig", configIdRule, retiredConfigDigestRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + + event := new(ExposedConfiguratorPromoteStagingConfig) + if err := _ExposedConfigurator.contract.UnpackLog(event, "PromoteStagingConfig", 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 (_ExposedConfigurator *ExposedConfiguratorFilterer) ParsePromoteStagingConfig(log types.Log) (*ExposedConfiguratorPromoteStagingConfig, error) { + event := new(ExposedConfiguratorPromoteStagingConfig) + if err := _ExposedConfigurator.contract.UnpackLog(event, "PromoteStagingConfig", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +type ExposedConfiguratorStagingConfigSetIterator struct { + Event *ExposedConfiguratorStagingConfigSet + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *ExposedConfiguratorStagingConfigSetIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(ExposedConfiguratorStagingConfigSet) + 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(ExposedConfiguratorStagingConfigSet) + 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 *ExposedConfiguratorStagingConfigSetIterator) Error() error { + return it.fail +} + +func (it *ExposedConfiguratorStagingConfigSetIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type ExposedConfiguratorStagingConfigSet struct { + ConfigId [32]byte + PreviousConfigBlockNumber uint32 + ConfigDigest [32]byte + ConfigCount uint64 + Signers [][]byte + OffchainTransmitters [][32]byte + F uint8 + OnchainConfig []byte + OffchainConfigVersion uint64 + OffchainConfig []byte + IsGreenProduction bool + Raw types.Log +} + +func (_ExposedConfigurator *ExposedConfiguratorFilterer) FilterStagingConfigSet(opts *bind.FilterOpts, configId [][32]byte) (*ExposedConfiguratorStagingConfigSetIterator, error) { + + var configIdRule []interface{} + for _, configIdItem := range configId { + configIdRule = append(configIdRule, configIdItem) + } + + logs, sub, err := _ExposedConfigurator.contract.FilterLogs(opts, "StagingConfigSet", configIdRule) + if err != nil { + return nil, err + } + return &ExposedConfiguratorStagingConfigSetIterator{contract: _ExposedConfigurator.contract, event: "StagingConfigSet", logs: logs, sub: sub}, nil +} + +func (_ExposedConfigurator *ExposedConfiguratorFilterer) WatchStagingConfigSet(opts *bind.WatchOpts, sink chan<- *ExposedConfiguratorStagingConfigSet, configId [][32]byte) (event.Subscription, error) { + + var configIdRule []interface{} + for _, configIdItem := range configId { + configIdRule = append(configIdRule, configIdItem) + } + + logs, sub, err := _ExposedConfigurator.contract.WatchLogs(opts, "StagingConfigSet", configIdRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + + event := new(ExposedConfiguratorStagingConfigSet) + if err := _ExposedConfigurator.contract.UnpackLog(event, "StagingConfigSet", 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 (_ExposedConfigurator *ExposedConfiguratorFilterer) ParseStagingConfigSet(log types.Log) (*ExposedConfiguratorStagingConfigSet, error) { + event := new(ExposedConfiguratorStagingConfigSet) + if err := _ExposedConfigurator.contract.UnpackLog(event, "StagingConfigSet", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +func (_ExposedConfigurator *ExposedConfigurator) ParseLog(log types.Log) (generated.AbigenLog, error) { + switch log.Topics[0] { + case _ExposedConfigurator.abi.Events["OwnershipTransferRequested"].ID: + return _ExposedConfigurator.ParseOwnershipTransferRequested(log) + case _ExposedConfigurator.abi.Events["OwnershipTransferred"].ID: + return _ExposedConfigurator.ParseOwnershipTransferred(log) + case _ExposedConfigurator.abi.Events["ProductionConfigSet"].ID: + return _ExposedConfigurator.ParseProductionConfigSet(log) + case _ExposedConfigurator.abi.Events["PromoteStagingConfig"].ID: + return _ExposedConfigurator.ParsePromoteStagingConfig(log) + case _ExposedConfigurator.abi.Events["StagingConfigSet"].ID: + return _ExposedConfigurator.ParseStagingConfigSet(log) + + default: + return nil, fmt.Errorf("abigen wrapper received unknown log topic: %v", log.Topics[0]) + } +} + +func (ExposedConfiguratorOwnershipTransferRequested) Topic() common.Hash { + return common.HexToHash("0xed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae1278") +} + +func (ExposedConfiguratorOwnershipTransferred) Topic() common.Hash { + return common.HexToHash("0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0") +} + +func (ExposedConfiguratorProductionConfigSet) Topic() common.Hash { + return common.HexToHash("0x261b20c2ecd99d86d6e936279e4f78db34603a3de3a4a84d6f3d4e0dd55e2478") +} + +func (ExposedConfiguratorPromoteStagingConfig) Topic() common.Hash { + return common.HexToHash("0x1062aa08ac6046a0e69e3eafdf12d1eba63a67b71a874623e86eb06348a1d84f") +} + +func (ExposedConfiguratorStagingConfigSet) Topic() common.Hash { + return common.HexToHash("0xef1b5f9d1b927b0fe871b12c7e7846457602d67b2bc36b0bc95feaf480e89056") +} + +func (_ExposedConfigurator *ExposedConfigurator) Address() common.Address { + return _ExposedConfigurator.address +} + +type ExposedConfiguratorInterface interface { + ExposedConfigDigestFromConfigData(opts *bind.CallOpts, _configId [32]byte, _chainId *big.Int, _contractAddress common.Address, _configCount uint64, _signers [][]byte, _offchainTransmitters [][32]byte, _f uint8, _onchainConfig []byte, _encodedConfigVersion uint64, _encodedConfig []byte) ([32]byte, error) + + ExposedReadConfigurationStates(opts *bind.CallOpts, configId [32]byte) (ConfiguratorConfigurationState, error) + + Owner(opts *bind.CallOpts) (common.Address, error) + + SupportsInterface(opts *bind.CallOpts, interfaceId [4]byte) (bool, error) + + TypeAndVersion(opts *bind.CallOpts) (string, error) + + AcceptOwnership(opts *bind.TransactOpts) (*types.Transaction, error) + + ExposedSetConfigurationState(opts *bind.TransactOpts, configId [32]byte, state ConfiguratorConfigurationState) (*types.Transaction, error) + + ExposedSetIsGreenProduction(opts *bind.TransactOpts, configId [32]byte, isGreenProduction bool) (*types.Transaction, error) + + PromoteStagingConfig(opts *bind.TransactOpts, configId [32]byte, isGreenProduction bool) (*types.Transaction, error) + + SetProductionConfig(opts *bind.TransactOpts, configId [32]byte, signers [][]byte, offchainTransmitters [][32]byte, f uint8, onchainConfig []byte, offchainConfigVersion uint64, offchainConfig []byte) (*types.Transaction, error) + + SetStagingConfig(opts *bind.TransactOpts, configId [32]byte, signers [][]byte, offchainTransmitters [][32]byte, f uint8, onchainConfig []byte, offchainConfigVersion uint64, offchainConfig []byte) (*types.Transaction, error) + + TransferOwnership(opts *bind.TransactOpts, to common.Address) (*types.Transaction, error) + + FilterOwnershipTransferRequested(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*ExposedConfiguratorOwnershipTransferRequestedIterator, error) + + WatchOwnershipTransferRequested(opts *bind.WatchOpts, sink chan<- *ExposedConfiguratorOwnershipTransferRequested, from []common.Address, to []common.Address) (event.Subscription, error) + + ParseOwnershipTransferRequested(log types.Log) (*ExposedConfiguratorOwnershipTransferRequested, error) + + FilterOwnershipTransferred(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*ExposedConfiguratorOwnershipTransferredIterator, error) + + WatchOwnershipTransferred(opts *bind.WatchOpts, sink chan<- *ExposedConfiguratorOwnershipTransferred, from []common.Address, to []common.Address) (event.Subscription, error) + + ParseOwnershipTransferred(log types.Log) (*ExposedConfiguratorOwnershipTransferred, error) + + FilterProductionConfigSet(opts *bind.FilterOpts, configId [][32]byte) (*ExposedConfiguratorProductionConfigSetIterator, error) + + WatchProductionConfigSet(opts *bind.WatchOpts, sink chan<- *ExposedConfiguratorProductionConfigSet, configId [][32]byte) (event.Subscription, error) + + ParseProductionConfigSet(log types.Log) (*ExposedConfiguratorProductionConfigSet, error) + + FilterPromoteStagingConfig(opts *bind.FilterOpts, configId [][32]byte, retiredConfigDigest [][32]byte) (*ExposedConfiguratorPromoteStagingConfigIterator, error) + + WatchPromoteStagingConfig(opts *bind.WatchOpts, sink chan<- *ExposedConfiguratorPromoteStagingConfig, configId [][32]byte, retiredConfigDigest [][32]byte) (event.Subscription, error) + + ParsePromoteStagingConfig(log types.Log) (*ExposedConfiguratorPromoteStagingConfig, error) + + FilterStagingConfigSet(opts *bind.FilterOpts, configId [][32]byte) (*ExposedConfiguratorStagingConfigSetIterator, error) + + WatchStagingConfigSet(opts *bind.WatchOpts, sink chan<- *ExposedConfiguratorStagingConfigSet, configId [][32]byte) (event.Subscription, error) + + ParseStagingConfigSet(log types.Log) (*ExposedConfiguratorStagingConfigSet, error) + + ParseLog(log types.Log) (generated.AbigenLog, error) + + Address() common.Address +} diff --git a/core/gethwrappers/llo-feeds/generation/generated-wrapper-dependency-versions-do-not-edit.txt b/core/gethwrappers/llo-feeds/generation/generated-wrapper-dependency-versions-do-not-edit.txt index 7d7d655ddce..f7b08f0f478 100644 --- a/core/gethwrappers/llo-feeds/generation/generated-wrapper-dependency-versions-do-not-edit.txt +++ b/core/gethwrappers/llo-feeds/generation/generated-wrapper-dependency-versions-do-not-edit.txt @@ -2,13 +2,14 @@ GETH_VERSION: 1.13.8 channel_config_store: ../../../contracts/solc/v0.8.19/ChannelConfigStore/ChannelConfigStore.abi ../../../contracts/solc/v0.8.19/ChannelConfigStore/ChannelConfigStore.bin 3fafe83ea21d50488f5533962f62683988ffa6fd1476dccbbb9040be2369cb37 channel_config_verifier_proxy: ../../../contracts/solc/v0.8.19/ChannelVerifierProxy/ChannelVerifierProxy.abi ../../../contracts/solc/v0.8.19/ChannelVerifierProxy/ChannelVerifierProxy.bin 655658e5f61dfadfe3268de04f948b7e690ad03ca45676e645d6cd6018154661 channel_verifier: ../../../contracts/solc/v0.8.19/ChannelVerifier/ChannelVerifier.abi ../../../contracts/solc/v0.8.19/ChannelVerifier/ChannelVerifier.bin e6020553bd8e3e6b250fcaffe7efd22aea955c8c1a0eb05d282fdeb0ab6550b7 -configurator: ../../../contracts/solc/v0.8.19/Configurator/Configurator.abi ../../../contracts/solc/v0.8.19/Configurator/Configurator.bin 75b9809e6e1cd00d1438028df3bbc7ce00d667dfd882a348081b4286edcdc4e0 +configurator: ../../../contracts/solc/v0.8.19/Configurator/Configurator.abi ../../../contracts/solc/v0.8.19/Configurator/Configurator.bin ee5ed0cd4f42636b6e008a12a8952c0efe3381094974e97269928eb13329c636 destination_fee_manager: ../../../contracts/solc/v0.8.19/DestinationFeeManager/DestinationFeeManager.abi ../../../contracts/solc/v0.8.19/DestinationFeeManager/DestinationFeeManager.bin a56ae53e35e6610269f086b1e915ca1e80f5d0bf5695d09156e82fccfc2d77b3 destination_reward_manager: ../../../contracts/solc/v0.8.19/DestinationRewardManager/DestinationRewardManager.abi ../../../contracts/solc/v0.8.19/DestinationRewardManager/DestinationRewardManager.bin 77874e97a54ecbd9c61132964da5b053f0b584dc7b774d75dd51baedd2bc7c40 destination_verifier: ../../../contracts/solc/v0.8.19/DestinationVerifier/DestinationVerifier.abi ../../../contracts/solc/v0.8.19/DestinationVerifier/DestinationVerifier.bin 369323ce520923b9eb31ed90885f5ebd0f46b6799288fbf4da5d6ede7d697aef destination_verifier_proxy: ../../../contracts/solc/v0.8.19/DestinationVerifierProxy/DestinationVerifierProxy.abi ../../../contracts/solc/v0.8.19/DestinationVerifierProxy/DestinationVerifierProxy.bin 4e255301cf6657777e7292eccea3e4c0ce65281404341e9248e095703a9fe392 errored_verifier: ../../../contracts/solc/v0.8.19/ErroredVerifier/ErroredVerifier.abi ../../../contracts/solc/v0.8.19/ErroredVerifier/ErroredVerifier.bin ad8ac8d6b99890081725e2304d79d1ba7dd5212b89d130aa9689f4269eed4691 exposed_channel_verifier: ../../../contracts/solc/v0.8.19/ExposedChannelVerifier/ExposedChannelVerifier.abi ../../../contracts/solc/v0.8.19/ExposedChannelVerifier/ExposedChannelVerifier.bin c21cde078900241c06de69e2bc5d906c5ef558b52db66caa68bed065940a2253 +exposed_configurator: ../../../contracts/solc/v0.8.19/ExposedConfigurator/ExposedConfigurator.abi ../../../contracts/solc/v0.8.19/ExposedConfigurator/ExposedConfigurator.bin f43362e7ef7588ecbd4d7ebd45b750cc4308e89c3d9e54fba1383e792213bbef exposed_verifier: ../../../contracts/solc/v0.8.19/ExposedVerifier/ExposedVerifier.abi ../../../contracts/solc/v0.8.19/ExposedVerifier/ExposedVerifier.bin 00816ab345f768e522c79abadeadf9155c2c688067e18f8f73e5d6ab71037663 fee_manager: ../../../contracts/solc/v0.8.19/FeeManager/FeeManager.abi ../../../contracts/solc/v0.8.19/FeeManager/FeeManager.bin edc85f34294ae7c90d45c4c71eb5c105c60a4842dfbbf700c692870ffcc403a1 llo_feeds: ../../../contracts/solc/v0.8.19/FeeManager.abi ../../../contracts/solc/v0.8.19/FeeManager.bin cb71e018f67e49d7bc0e194c822204dfd59f79ff42e4fc8fd8ab63f3acd71361 diff --git a/core/gethwrappers/llo-feeds/go_generate.go b/core/gethwrappers/llo-feeds/go_generate.go index ec950124e96..5bc29f36dea 100644 --- a/core/gethwrappers/llo-feeds/go_generate.go +++ b/core/gethwrappers/llo-feeds/go_generate.go @@ -16,3 +16,5 @@ package gethwrappers //go:generate go run ../generation/generate/wrap.go ../../../contracts/solc/v0.8.19/DestinationFeeManager/DestinationFeeManager.abi ../../../contracts/solc/v0.8.19/DestinationFeeManager/DestinationFeeManager.bin DestinationFeeManager destination_fee_manager //go:generate go run ../generation/generate/wrap.go ../../../contracts/solc/v0.8.19/DestinationRewardManager/DestinationRewardManager.abi ../../../contracts/solc/v0.8.19/DestinationRewardManager/DestinationRewardManager.bin DestinationRewardManager destination_reward_manager //go:generate go run ../generation/generate/wrap.go ../../../contracts/solc/v0.8.19/Configurator/Configurator.abi ../../../contracts/solc/v0.8.19/Configurator/Configurator.bin Configurator configurator + +//go:generate go run ../generation/generate/wrap.go ../../../contracts/solc/v0.8.19/ExposedConfigurator/ExposedConfigurator.abi ../../../contracts/solc/v0.8.19/ExposedConfigurator/ExposedConfigurator.bin ExposedConfigurator exposed_configurator diff --git a/core/internal/cltest/cltest.go b/core/internal/cltest/cltest.go index f1eb1970c05..d388363cd7c 100644 --- a/core/internal/cltest/cltest.go +++ b/core/internal/cltest/cltest.go @@ -41,6 +41,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/capabilities" remotetypes "github.com/smartcontractkit/chainlink/v2/core/capabilities/remote/types" + "github.com/smartcontractkit/chainlink/v2/core/services/llo" p2ptypes "github.com/smartcontractkit/chainlink/v2/core/services/p2p/types" "github.com/smartcontractkit/chainlink-common/pkg/loop" @@ -400,13 +401,15 @@ func NewApplicationWithConfig(t testing.TB, cfg chainlink.GeneralConfig, flagsAn }) c := clhttptest.NewTestLocalOnlyHTTPClient() + retirementReportCache := llo.NewRetirementReportCache(lggr, ds) relayerFactory := chainlink.RelayerFactory{ - Logger: lggr, - LoopRegistry: loopRegistry, - GRPCOpts: loop.GRPCOpts{}, - MercuryPool: mercuryPool, - CapabilitiesRegistry: capabilitiesRegistry, - HTTPClient: c, + Logger: lggr, + LoopRegistry: loopRegistry, + GRPCOpts: loop.GRPCOpts{}, + MercuryPool: mercuryPool, + CapabilitiesRegistry: capabilitiesRegistry, + HTTPClient: c, + RetirementReportCache: retirementReportCache, } evmOpts := chainlink.EVMFactoryConfig{ @@ -489,6 +492,7 @@ func NewApplicationWithConfig(t testing.TB, cfg chainlink.GeneralConfig, flagsAn CapabilitiesDispatcher: dispatcher, CapabilitiesPeerWrapper: peerWrapper, NewOracleFactoryFn: newOracleFactoryFn, + RetirementReportCache: retirementReportCache, }) require.NoError(t, err) diff --git a/core/scripts/go.mod b/core/scripts/go.mod index febf6c32d64..31bf042caa4 100644 --- a/core/scripts/go.mod +++ b/core/scripts/go.mod @@ -24,7 +24,7 @@ require ( github.com/prometheus/client_golang v1.20.0 github.com/shopspring/decimal v1.4.0 github.com/smartcontractkit/chainlink-automation v0.8.0 - github.com/smartcontractkit/chainlink-common v0.3.1-0.20241017135127-b283b1e14fa6 + github.com/smartcontractkit/chainlink-common v0.3.1-0.20241018143728-5248d7c4468a github.com/smartcontractkit/chainlink/integration-tests v0.0.0-00010101000000-000000000000 github.com/smartcontractkit/chainlink/v2 v2.0.0-00010101000000-000000000000 github.com/smartcontractkit/libocr v0.0.0-20241007185508-adbe57025f12 @@ -289,7 +289,7 @@ require ( github.com/smartcontractkit/chain-selectors v1.0.27 // indirect github.com/smartcontractkit/chainlink-ccip v0.0.0-20241017140434-6757be193e1c // indirect github.com/smartcontractkit/chainlink-cosmos v0.5.2-0.20241017133723-5277829bd53f // indirect - github.com/smartcontractkit/chainlink-data-streams v0.1.0 // indirect + github.com/smartcontractkit/chainlink-data-streams v0.1.1-0.20241018134907-a00ba3729b5e // indirect github.com/smartcontractkit/chainlink-feeds v0.1.1 // indirect github.com/smartcontractkit/chainlink-protos/orchestrator v0.3.0 // indirect github.com/smartcontractkit/chainlink-solana v1.1.1-0.20241017134533-5459a1034ecd // indirect diff --git a/core/scripts/go.sum b/core/scripts/go.sum index 1b2a1f16abb..575b341548e 100644 --- a/core/scripts/go.sum +++ b/core/scripts/go.sum @@ -1074,12 +1074,12 @@ github.com/smartcontractkit/chainlink-automation v0.8.0 h1:hFz2EHU06bkEfhcqhK8Jd github.com/smartcontractkit/chainlink-automation v0.8.0/go.mod h1:ObdjDfgGIaiE48Bb3yYcx1CeGBm392WlEw92U83LlUA= github.com/smartcontractkit/chainlink-ccip v0.0.0-20241017140434-6757be193e1c h1:BvzX0A659a9fShyW69P/jV3iVlA4/wlGbZ/4XXE3pxI= github.com/smartcontractkit/chainlink-ccip v0.0.0-20241017140434-6757be193e1c/go.mod h1:4adKaHNaxFsRvV/lYfqtbsWyyvIPUMLR0FdOJN/ljis= -github.com/smartcontractkit/chainlink-common v0.3.1-0.20241017135127-b283b1e14fa6 h1:SHwvqXq1gdXOG/f1sQGupOH6ICZRuzMy5QkD3Um/k+8= -github.com/smartcontractkit/chainlink-common v0.3.1-0.20241017135127-b283b1e14fa6/go.mod h1:tsGgeEJc5SUSlfVGSX0wR0EkRU3pM58D6SKF97V68ko= +github.com/smartcontractkit/chainlink-common v0.3.1-0.20241018143728-5248d7c4468a h1:X0+2AbgCmPgfwY2dTvAK37KO8UvWLHMgfAFL3MA4BEs= +github.com/smartcontractkit/chainlink-common v0.3.1-0.20241018143728-5248d7c4468a/go.mod h1:tsGgeEJc5SUSlfVGSX0wR0EkRU3pM58D6SKF97V68ko= github.com/smartcontractkit/chainlink-cosmos v0.5.2-0.20241017133723-5277829bd53f h1:BwrIaQIx5Iy6eT+DfLhFfK2XqjxRm74mVdlX8gbu4dw= github.com/smartcontractkit/chainlink-cosmos v0.5.2-0.20241017133723-5277829bd53f/go.mod h1:wHtwSR3F1CQSJJZDQKuqaqFYnvkT+kMyget7dl8Clvo= -github.com/smartcontractkit/chainlink-data-streams v0.1.0 h1:wcRJRm7eqfbgN+Na+GjAe0/IUn6XwmSagFHqIWHHBGk= -github.com/smartcontractkit/chainlink-data-streams v0.1.0/go.mod h1:lmdRVjg49Do+5tkk9V5iAhi+Jm2kXhjZXWAbzh7xg7o= +github.com/smartcontractkit/chainlink-data-streams v0.1.1-0.20241018134907-a00ba3729b5e h1:JiETqdNM0bktAUGMc62COwXIaw3rR3M77Me6bBLG0Fg= +github.com/smartcontractkit/chainlink-data-streams v0.1.1-0.20241018134907-a00ba3729b5e/go.mod h1:iK3BNHKCLgSgkOyiu3iE7sfZ20Qnuk7xwjV/yO/6gnQ= github.com/smartcontractkit/chainlink-feeds v0.1.1 h1:JzvUOM/OgGQA1sOqTXXl52R6AnNt+Wg64sVG+XSA49c= github.com/smartcontractkit/chainlink-feeds v0.1.1/go.mod h1:55EZ94HlKCfAsUiKUTNI7QlE/3d3IwTlsU3YNa/nBb4= github.com/smartcontractkit/chainlink-protos/orchestrator v0.3.0 h1:PBUaFfPLm+Efq7H9kdfGBivH+QhJ6vB5EZTR/sCZsxI= diff --git a/core/services/chainlink/application.go b/core/services/chainlink/application.go index 1e41bb40da6..a0a50477614 100644 --- a/core/services/chainlink/application.go +++ b/core/services/chainlink/application.go @@ -52,6 +52,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/services/job" "github.com/smartcontractkit/chainlink/v2/core/services/keeper" "github.com/smartcontractkit/chainlink/v2/core/services/keystore" + "github.com/smartcontractkit/chainlink/v2/core/services/llo" "github.com/smartcontractkit/chainlink/v2/core/services/ocr" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2" "github.com/smartcontractkit/chainlink/v2/core/services/ocrbootstrap" @@ -182,6 +183,7 @@ type ApplicationOpts struct { LoopRegistry *plugins.LoopRegistry GRPCOpts loop.GRPCOpts MercuryPool wsrpc.Pool + RetirementReportCache llo.RetirementReportCache CapabilitiesRegistry *capabilities.Registry CapabilitiesDispatcher remotetypes.Dispatcher CapabilitiesPeerWrapper p2ptypes.PeerWrapper @@ -334,6 +336,9 @@ func NewApplication(opts ApplicationOpts) (Application, error) { if opts.MercuryPool != nil { srvcs = append(srvcs, opts.MercuryPool) } + if opts.RetirementReportCache != nil { + srvcs = append(srvcs, opts.RetirementReportCache) + } // EVM chains are used all over the place. This will need to change for fully EVM extraction // TODO: BCF-2510, BCF-2511 @@ -530,22 +535,25 @@ func NewApplication(opts ApplicationOpts) (Application, error) { ocr2DelegateConfig := ocr2.NewDelegateConfig(cfg.OCR2(), cfg.Mercury(), cfg.Threshold(), cfg.Insecure(), cfg.JobPipeline(), loopRegistrarConfig) delegates[job.OffchainReporting2] = ocr2.NewDelegate( - opts.DS, - jobORM, - bridgeORM, - mercuryORM, - pipelineRunner, - streamRegistry, - peerWrapper, - telemetryManager, - legacyEVMChains, - globalLogger, + ocr2.DelegateOpts{ + Ds: opts.DS, + JobORM: jobORM, + BridgeORM: bridgeORM, + MercuryORM: mercuryORM, + PipelineRunner: pipelineRunner, + StreamRegistry: streamRegistry, + PeerWrapper: peerWrapper, + MonitoringEndpointGen: telemetryManager, + LegacyChains: legacyEVMChains, + Lggr: globalLogger, + Ks: keyStore.OCR2(), + EthKs: keyStore.Eth(), + Relayers: opts.RelayerChainInteroperators, + MailMon: mailMon, + CapabilitiesRegistry: opts.CapabilitiesRegistry, + RetirementReportCache: opts.RetirementReportCache, + }, ocr2DelegateConfig, - keyStore.OCR2(), - keyStore.Eth(), - opts.RelayerChainInteroperators, - mailMon, - opts.CapabilitiesRegistry, ) delegates[job.Bootstrap] = ocrbootstrap.NewDelegateBootstrap( opts.DS, diff --git a/core/services/chainlink/relayer_factory.go b/core/services/chainlink/relayer_factory.go index bbd9f283add..3740878fd19 100644 --- a/core/services/chainlink/relayer_factory.go +++ b/core/services/chainlink/relayer_factory.go @@ -25,6 +25,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/config/env" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/keystore" + "github.com/smartcontractkit/chainlink/v2/core/services/llo" "github.com/smartcontractkit/chainlink/v2/core/services/relay" "github.com/smartcontractkit/chainlink/v2/core/services/relay/dummy" evmrelay "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm" @@ -36,9 +37,10 @@ type RelayerFactory struct { logger.Logger *plugins.LoopRegistry loop.GRPCOpts - MercuryPool wsrpc.Pool - CapabilitiesRegistry coretypes.CapabilitiesRegistry - HTTPClient *http.Client + MercuryPool wsrpc.Pool + CapabilitiesRegistry coretypes.CapabilitiesRegistry + HTTPClient *http.Client + RetirementReportCache llo.RetirementReportCache } type DummyFactoryConfig struct { @@ -78,12 +80,13 @@ func (r *RelayerFactory) NewEVM(ctx context.Context, config EVMFactoryConfig) (m chain := chain relayerOpts := evmrelay.RelayerOpts{ - DS: ccOpts.DS, - CSAETHKeystore: config.CSAETHKeystore, - MercuryPool: r.MercuryPool, - TransmitterConfig: config.MercuryTransmitter, - CapabilitiesRegistry: r.CapabilitiesRegistry, - HTTPClient: r.HTTPClient, + DS: ccOpts.DS, + CSAETHKeystore: config.CSAETHKeystore, + MercuryPool: r.MercuryPool, + TransmitterConfig: config.MercuryTransmitter, + CapabilitiesRegistry: r.CapabilitiesRegistry, + HTTPClient: r.HTTPClient, + RetirementReportCache: r.RetirementReportCache, } relayer, err2 := evmrelay.NewRelayer(ctx, lggr.Named(relayID.ChainID), chain, relayerOpts) if err2 != nil { diff --git a/core/services/job/spawner_test.go b/core/services/job/spawner_test.go index 9dccd87d006..f1fe338c2b7 100644 --- a/core/services/job/spawner_test.go +++ b/core/services/job/spawner_test.go @@ -309,8 +309,7 @@ func TestSpawner_CreateJobDeleteJob(t *testing.T) { processConfig := plugins.NewRegistrarConfig(loop.GRPCOpts{}, func(name string) (*plugins.RegisteredLoop, error) { return nil, nil }, func(loopId string) {}) ocr2DelegateConfig := ocr2.NewDelegateConfig(config.OCR2(), config.Mercury(), config.Threshold(), config.Insecure(), config.JobPipeline(), processConfig) - d := ocr2.NewDelegate(nil, orm, nil, nil, nil, nil, nil, monitoringEndpoint, legacyChains, lggr, ocr2DelegateConfig, - keyStore.OCR2(), ethKeyStore, testRelayGetter, mailMon, capabilities.NewRegistry(lggr)) + d := ocr2.NewDelegate(ocr2.DelegateOpts{JobORM: orm, MonitoringEndpointGen: monitoringEndpoint, LegacyChains: legacyChains, Lggr: lggr, Ks: keyStore.OCR2(), EthKs: ethKeyStore, Relayers: testRelayGetter, MailMon: mailMon, CapabilitiesRegistry: capabilities.NewRegistry(lggr)}, ocr2DelegateConfig) delegateOCR2 := &delegate{jobOCR2Keeper.Type, []job.ServiceCtx{}, 0, nil, d} spawner := job.NewSpawner(orm, config.Database(), noopChecker{}, map[job.Type]job.Delegate{ diff --git a/core/services/llo/attested_retirement_report.pb.go b/core/services/llo/attested_retirement_report.pb.go new file mode 100644 index 00000000000..b59f623e984 --- /dev/null +++ b/core/services/llo/attested_retirement_report.pb.go @@ -0,0 +1,213 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.35.1 +// protoc v4.25.1 +// source: attested_retirement_report.proto + +package llo + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type AttestedRetirementReport struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + RetirementReport []byte `protobuf:"bytes,1,opt,name=retirementReport,proto3" json:"retirementReport,omitempty"` + SeqNr uint64 `protobuf:"varint,2,opt,name=seqNr,proto3" json:"seqNr,omitempty"` + Sigs []*AttributedOnchainSignature `protobuf:"bytes,3,rep,name=sigs,proto3" json:"sigs,omitempty"` +} + +func (x *AttestedRetirementReport) Reset() { + *x = AttestedRetirementReport{} + mi := &file_attested_retirement_report_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AttestedRetirementReport) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AttestedRetirementReport) ProtoMessage() {} + +func (x *AttestedRetirementReport) ProtoReflect() protoreflect.Message { + mi := &file_attested_retirement_report_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AttestedRetirementReport.ProtoReflect.Descriptor instead. +func (*AttestedRetirementReport) Descriptor() ([]byte, []int) { + return file_attested_retirement_report_proto_rawDescGZIP(), []int{0} +} + +func (x *AttestedRetirementReport) GetRetirementReport() []byte { + if x != nil { + return x.RetirementReport + } + return nil +} + +func (x *AttestedRetirementReport) GetSeqNr() uint64 { + if x != nil { + return x.SeqNr + } + return 0 +} + +func (x *AttestedRetirementReport) GetSigs() []*AttributedOnchainSignature { + if x != nil { + return x.Sigs + } + return nil +} + +type AttributedOnchainSignature struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Signature []byte `protobuf:"bytes,1,opt,name=signature,proto3" json:"signature,omitempty"` + Signer uint32 `protobuf:"varint,2,opt,name=signer,proto3" json:"signer,omitempty"` +} + +func (x *AttributedOnchainSignature) Reset() { + *x = AttributedOnchainSignature{} + mi := &file_attested_retirement_report_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AttributedOnchainSignature) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AttributedOnchainSignature) ProtoMessage() {} + +func (x *AttributedOnchainSignature) ProtoReflect() protoreflect.Message { + mi := &file_attested_retirement_report_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AttributedOnchainSignature.ProtoReflect.Descriptor instead. +func (*AttributedOnchainSignature) Descriptor() ([]byte, []int) { + return file_attested_retirement_report_proto_rawDescGZIP(), []int{1} +} + +func (x *AttributedOnchainSignature) GetSignature() []byte { + if x != nil { + return x.Signature + } + return nil +} + +func (x *AttributedOnchainSignature) GetSigner() uint32 { + if x != nil { + return x.Signer + } + return 0 +} + +var File_attested_retirement_report_proto protoreflect.FileDescriptor + +var file_attested_retirement_report_proto_rawDesc = []byte{ + 0x0a, 0x20, 0x61, 0x74, 0x74, 0x65, 0x73, 0x74, 0x65, 0x64, 0x5f, 0x72, 0x65, 0x74, 0x69, 0x72, + 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x12, 0x03, 0x6c, 0x6c, 0x6f, 0x22, 0x91, 0x01, 0x0a, 0x18, 0x41, 0x74, 0x74, 0x65, + 0x73, 0x74, 0x65, 0x64, 0x52, 0x65, 0x74, 0x69, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, + 0x70, 0x6f, 0x72, 0x74, 0x12, 0x2a, 0x0a, 0x10, 0x72, 0x65, 0x74, 0x69, 0x72, 0x65, 0x6d, 0x65, + 0x6e, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x10, + 0x72, 0x65, 0x74, 0x69, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, + 0x12, 0x14, 0x0a, 0x05, 0x73, 0x65, 0x71, 0x4e, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, + 0x05, 0x73, 0x65, 0x71, 0x4e, 0x72, 0x12, 0x33, 0x0a, 0x04, 0x73, 0x69, 0x67, 0x73, 0x18, 0x03, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x6c, 0x6c, 0x6f, 0x2e, 0x41, 0x74, 0x74, 0x72, 0x69, + 0x62, 0x75, 0x74, 0x65, 0x64, 0x4f, 0x6e, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x53, 0x69, 0x67, 0x6e, + 0x61, 0x74, 0x75, 0x72, 0x65, 0x52, 0x04, 0x73, 0x69, 0x67, 0x73, 0x22, 0x52, 0x0a, 0x1a, 0x41, + 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x64, 0x4f, 0x6e, 0x63, 0x68, 0x61, 0x69, 0x6e, + 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x69, 0x67, + 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x69, + 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x69, 0x67, 0x6e, 0x65, + 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x72, 0x42, + 0x3c, 0x5a, 0x3a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x6d, + 0x61, 0x72, 0x74, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x6b, 0x69, 0x74, 0x2f, 0x63, + 0x68, 0x61, 0x69, 0x6e, 0x6c, 0x69, 0x6e, 0x6b, 0x2f, 0x76, 0x32, 0x2f, 0x63, 0x6f, 0x72, 0x65, + 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2f, 0x6c, 0x6c, 0x6f, 0x62, 0x06, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_attested_retirement_report_proto_rawDescOnce sync.Once + file_attested_retirement_report_proto_rawDescData = file_attested_retirement_report_proto_rawDesc +) + +func file_attested_retirement_report_proto_rawDescGZIP() []byte { + file_attested_retirement_report_proto_rawDescOnce.Do(func() { + file_attested_retirement_report_proto_rawDescData = protoimpl.X.CompressGZIP(file_attested_retirement_report_proto_rawDescData) + }) + return file_attested_retirement_report_proto_rawDescData +} + +var file_attested_retirement_report_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_attested_retirement_report_proto_goTypes = []any{ + (*AttestedRetirementReport)(nil), // 0: llo.AttestedRetirementReport + (*AttributedOnchainSignature)(nil), // 1: llo.AttributedOnchainSignature +} +var file_attested_retirement_report_proto_depIdxs = []int32{ + 1, // 0: llo.AttestedRetirementReport.sigs:type_name -> llo.AttributedOnchainSignature + 1, // [1:1] is the sub-list for method output_type + 1, // [1:1] is the sub-list for method input_type + 1, // [1:1] is the sub-list for extension type_name + 1, // [1:1] is the sub-list for extension extendee + 0, // [0:1] is the sub-list for field type_name +} + +func init() { file_attested_retirement_report_proto_init() } +func file_attested_retirement_report_proto_init() { + if File_attested_retirement_report_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_attested_retirement_report_proto_rawDesc, + NumEnums: 0, + NumMessages: 2, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_attested_retirement_report_proto_goTypes, + DependencyIndexes: file_attested_retirement_report_proto_depIdxs, + MessageInfos: file_attested_retirement_report_proto_msgTypes, + }.Build() + File_attested_retirement_report_proto = out.File + file_attested_retirement_report_proto_rawDesc = nil + file_attested_retirement_report_proto_goTypes = nil + file_attested_retirement_report_proto_depIdxs = nil +} diff --git a/core/services/llo/attested_retirement_report.proto b/core/services/llo/attested_retirement_report.proto new file mode 100644 index 00000000000..42020b15bb7 --- /dev/null +++ b/core/services/llo/attested_retirement_report.proto @@ -0,0 +1,17 @@ +syntax = "proto3"; + +option go_package = "github.com/smartcontractkit/chainlink/v2/core/services/llo"; + +package llo; + +message AttestedRetirementReport { + bytes retirementReport = 1; + uint64 seqNr = 2; + repeated AttributedOnchainSignature sigs = 3; +} + +message AttributedOnchainSignature { + bytes signature = 1; + uint32 signer = 2; +} + diff --git a/core/services/llo/cleanup.go b/core/services/llo/cleanup.go index 892e925f372..c7f8d546db6 100644 --- a/core/services/llo/cleanup.go +++ b/core/services/llo/cleanup.go @@ -15,7 +15,7 @@ func Cleanup(ctx context.Context, lp LogPoller, addr common.Address, donID uint3 if err := lp.UnregisterFilter(ctx, filterName(addr, donID)); err != nil { return fmt.Errorf("failed to unregister filter: %w", err) } - orm := NewORM(ds, chainSelector) + orm := NewChainScopedORM(ds, chainSelector) if err := orm.CleanupChannelDefinitions(ctx, addr, donID); err != nil { return fmt.Errorf("failed to cleanup channel definitions: %w", err) } diff --git a/core/services/llo/cleanup_test.go b/core/services/llo/cleanup_test.go index 7c7f13e02f9..9f00c09d8b3 100644 --- a/core/services/llo/cleanup_test.go +++ b/core/services/llo/cleanup_test.go @@ -46,7 +46,7 @@ func Test_Cleanup(t *testing.T) { chainSelector := uint64(3) // add some channel definitions - cdcorm := NewORM(ds, chainSelector) + cdcorm := NewChainScopedORM(ds, chainSelector) { err := cdcorm.StoreChannelDefinitions(ctx, addr1, donID1, 1, llotypes.ChannelDefinitions{}, 1) require.NoError(t, err) diff --git a/core/services/llo/codecs.go b/core/services/llo/codecs.go index a67b30d2f21..7813c8923ea 100644 --- a/core/services/llo/codecs.go +++ b/core/services/llo/codecs.go @@ -8,7 +8,7 @@ import ( ) // NOTE: All supported codecs must be specified here -func NewCodecs() map[llotypes.ReportFormat]llo.ReportCodec { +func NewReportCodecs() map[llotypes.ReportFormat]llo.ReportCodec { codecs := make(map[llotypes.ReportFormat]llo.ReportCodec) codecs[llotypes.ReportFormatJSON] = llo.JSONReportCodec{} diff --git a/core/services/llo/codecs_test.go b/core/services/llo/codecs_test.go index d26b72dd35c..4a7f3f65571 100644 --- a/core/services/llo/codecs_test.go +++ b/core/services/llo/codecs_test.go @@ -8,8 +8,8 @@ import ( llotypes "github.com/smartcontractkit/chainlink-common/pkg/types/llo" ) -func Test_NewCodecs(t *testing.T) { - c := NewCodecs() +func Test_NewReportCodecs(t *testing.T) { + c := NewReportCodecs() assert.Contains(t, c, llotypes.ReportFormatJSON, "expected JSON to be supported") assert.Contains(t, c, llotypes.ReportFormatEVMPremiumLegacy, "expected EVMPremiumLegacy to be supported") diff --git a/core/services/llo/data_source.go b/core/services/llo/data_source.go index bad1bf0896a..ef333f821a1 100644 --- a/core/services/llo/data_source.go +++ b/core/services/llo/data_source.go @@ -11,10 +11,10 @@ import ( "github.com/shopspring/decimal" "golang.org/x/exp/maps" + "github.com/smartcontractkit/chainlink-common/pkg/logger" llotypes "github.com/smartcontractkit/chainlink-common/pkg/types/llo" "github.com/smartcontractkit/chainlink-data-streams/llo" - "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/pipeline" "github.com/smartcontractkit/chainlink/v2/core/services/streams" "github.com/smartcontractkit/chainlink/v2/core/utils" @@ -80,7 +80,7 @@ func NewDataSource(lggr logger.Logger, registry Registry, t Telemeter) llo.DataS } func newDataSource(lggr logger.Logger, registry Registry, t Telemeter) *dataSource { - return &dataSource{lggr.Named("DataSource"), registry, t} + return &dataSource{logger.Named(lggr, "DataSource"), registry, t} } // Observe looks up all streams in the registry and populates a map of stream ID => value diff --git a/core/services/llo/delegate.go b/core/services/llo/delegate.go index ab55c6b2a95..70df901c730 100644 --- a/core/services/llo/delegate.go +++ b/core/services/llo/delegate.go @@ -12,12 +12,13 @@ import ( ocr2types "github.com/smartcontractkit/libocr/offchainreporting2plus/types" "gopkg.in/guregu/null.v4" + "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink-common/pkg/services" "github.com/smartcontractkit/chainlink-common/pkg/sqlutil" llotypes "github.com/smartcontractkit/chainlink-common/pkg/types/llo" "github.com/smartcontractkit/chainlink-data-streams/llo" + datastreamsllo "github.com/smartcontractkit/chainlink-data-streams/llo" - "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/job" "github.com/smartcontractkit/chainlink/v2/core/services/streams" ) @@ -31,15 +32,14 @@ type Closer interface { type delegate struct { services.StateMachine - cfg DelegateConfig - codecs map[llotypes.ReportFormat]llo.ReportCodec + cfg DelegateConfig + reportCodecs map[llotypes.ReportFormat]datastreamsllo.ReportCodec - prrc llo.PredecessorRetirementReportCache - src llo.ShouldRetireCache - ds llo.DataSource - t services.Service + src datastreamsllo.ShouldRetireCache + ds datastreamsllo.DataSource + t services.Service - oracle Closer + oracles []Closer } type DelegateConfig struct { @@ -52,24 +52,28 @@ type DelegateConfig struct { // LLO ChannelDefinitionCache llotypes.ChannelDefinitionCache - ReportingPluginConfig llo.Config + ReportingPluginConfig datastreamsllo.Config + RetirementReportCache RetirementReportCache + RetirementReportCodec datastreamsllo.RetirementReportCodec + ShouldRetireCache datastreamsllo.ShouldRetireCache // OCR3 + TraceLogging bool BinaryNetworkEndpointFactory ocr2types.BinaryNetworkEndpointFactory V2Bootstrappers []ocrcommontypes.BootstrapperLocator - ContractConfigTracker ocr2types.ContractConfigTracker - ContractTransmitter ocr3types.ContractTransmitter[llotypes.ReportInfo] - Database ocr3types.Database - OCRLogger ocrcommontypes.Logger - MonitoringEndpoint ocrcommontypes.MonitoringEndpoint - OffchainConfigDigester ocr2types.OffchainConfigDigester - OffchainKeyring ocr2types.OffchainKeyring - OnchainKeyring ocr3types.OnchainKeyring[llotypes.ReportInfo] - LocalConfig ocr2types.LocalConfig + // One Oracle will be started for each ContractConfigTracker + ContractConfigTrackers []ocr2types.ContractConfigTracker + ContractTransmitter ocr3types.ContractTransmitter[llotypes.ReportInfo] + Database ocr3types.Database + MonitoringEndpoint ocrcommontypes.MonitoringEndpoint + OffchainConfigDigester ocr2types.OffchainConfigDigester + OffchainKeyring ocr2types.OffchainKeyring + OnchainKeyring ocr3types.OnchainKeyring[llotypes.ReportInfo] + LocalConfig ocr2types.LocalConfig } func NewDelegate(cfg DelegateConfig) (job.ServiceCtx, error) { - lggr := cfg.Logger.With("jobName", cfg.JobName.ValueOrZero()) + lggr := logger.Sugared(cfg.Logger).With("jobName", cfg.JobName.ValueOrZero()) if cfg.DataSource == nil { return nil, errors.New("DataSource must not be nil") } @@ -79,54 +83,82 @@ func NewDelegate(cfg DelegateConfig) (job.ServiceCtx, error) { if cfg.Registry == nil { return nil, errors.New("Registry must not be nil") } - codecs := NewCodecs() + if cfg.RetirementReportCache == nil { + return nil, errors.New("RetirementReportCache must not be nil") + } + if cfg.ShouldRetireCache == nil { + return nil, errors.New("ShouldRetireCache must not be nil") + } + reportCodecs := NewReportCodecs() - // TODO: Do these services need starting? - // https://smartcontract-it.atlassian.net/browse/MERC-3386 - prrc := llo.NewPredecessorRetirementReportCache() - src := llo.NewShouldRetireCache() var t TelemeterService if cfg.CaptureEATelemetry { t = NewTelemeterService(lggr, cfg.MonitoringEndpoint) } else { t = NullTelemeter } - ds := newDataSource(lggr.Named("DataSource"), cfg.Registry, t) + ds := newDataSource(logger.Named(lggr, "DataSource"), cfg.Registry, t) - return &delegate{services.StateMachine{}, cfg, codecs, prrc, src, ds, t, nil}, nil + return &delegate{services.StateMachine{}, cfg, reportCodecs, cfg.ShouldRetireCache, ds, t, []Closer{}}, nil } func (d *delegate) Start(ctx context.Context) error { return d.StartOnce("LLODelegate", func() error { // create the oracle from config values - oracle, err := ocr2plus.NewOracle(ocr2plus.OCR3OracleArgs[llotypes.ReportInfo]{ - BinaryNetworkEndpointFactory: d.cfg.BinaryNetworkEndpointFactory, - V2Bootstrappers: d.cfg.V2Bootstrappers, - ContractConfigTracker: d.cfg.ContractConfigTracker, - ContractTransmitter: d.cfg.ContractTransmitter, - Database: d.cfg.Database, - LocalConfig: d.cfg.LocalConfig, - Logger: d.cfg.OCRLogger, - MonitoringEndpoint: d.cfg.MonitoringEndpoint, - OffchainConfigDigester: d.cfg.OffchainConfigDigester, - OffchainKeyring: d.cfg.OffchainKeyring, - OnchainKeyring: d.cfg.OnchainKeyring, - ReportingPluginFactory: llo.NewPluginFactory( - d.cfg.ReportingPluginConfig, d.prrc, d.src, d.cfg.ChannelDefinitionCache, d.ds, d.cfg.Logger.Named("LLOReportingPlugin"), d.codecs, - ), - MetricsRegisterer: prometheus.WrapRegistererWith(map[string]string{"job_name": d.cfg.JobName.ValueOrZero()}, prometheus.DefaultRegisterer), - }) - - if err != nil { - return fmt.Errorf("%w: failed to create new OCR oracle", err) + if !(len(d.cfg.ContractConfigTrackers) == 1 || len(d.cfg.ContractConfigTrackers) == 2) { + return fmt.Errorf("expected either 1 or 2 ContractConfigTrackers, got: %d", len(d.cfg.ContractConfigTrackers)) + } + var merr error + psrrc := NewPluginScopedRetirementReportCache(d.cfg.RetirementReportCache, d.cfg.OnchainKeyring, d.cfg.RetirementReportCodec) + for i, configTracker := range d.cfg.ContractConfigTrackers { + lggr := logger.Named(d.cfg.Logger, fmt.Sprintf("%d", i)) + switch i { + case 0: + lggr = logger.With(lggr, "instanceType", "Blue") + case 1: + lggr = logger.With(lggr, "instanceType", "Green") + } + ocrLogger := logger.NewOCRWrapper(lggr, d.cfg.TraceLogging, func(msg string) { + // TODO: do we actually need to DB-persist errors? + // MERC-3524 + }) + + oracle, err := ocr2plus.NewOracle(ocr2plus.OCR3OracleArgs[llotypes.ReportInfo]{ + BinaryNetworkEndpointFactory: d.cfg.BinaryNetworkEndpointFactory, + V2Bootstrappers: d.cfg.V2Bootstrappers, + ContractConfigTracker: configTracker, + ContractTransmitter: d.cfg.ContractTransmitter, + Database: d.cfg.Database, + LocalConfig: d.cfg.LocalConfig, + Logger: ocrLogger, + MonitoringEndpoint: d.cfg.MonitoringEndpoint, + OffchainConfigDigester: d.cfg.OffchainConfigDigester, + OffchainKeyring: d.cfg.OffchainKeyring, + OnchainKeyring: d.cfg.OnchainKeyring, + ReportingPluginFactory: datastreamsllo.NewPluginFactory( + d.cfg.ReportingPluginConfig, psrrc, d.src, d.cfg.RetirementReportCodec, d.cfg.ChannelDefinitionCache, d.ds, logger.Named(lggr, "LLOReportingPlugin"), llo.EVMOnchainConfigCodec{}, d.reportCodecs, + ), + MetricsRegisterer: prometheus.WrapRegistererWith(map[string]string{"job_name": d.cfg.JobName.ValueOrZero()}, prometheus.DefaultRegisterer), + }) + + if err != nil { + return fmt.Errorf("%w: failed to create new OCR oracle", err) + } + + d.oracles = append(d.oracles, oracle) + + merr = errors.Join(merr, oracle.Start()) } - d.oracle = oracle - - return oracle.Start() + return merr }) } func (d *delegate) Close() error { - return d.StopOnce("LLODelegate", d.oracle.Close) + return d.StopOnce("LLODelegate", func() (err error) { + for _, oracle := range d.oracles { + err = errors.Join(err, oracle.Close()) + } + return err + }) } diff --git a/core/services/llo/generate.go b/core/services/llo/generate.go new file mode 100644 index 00000000000..ae26d695430 --- /dev/null +++ b/core/services/llo/generate.go @@ -0,0 +1,3 @@ +package llo + +//go:generate protoc --go_out=. --go_opt=paths=source_relative attested_retirement_report.proto diff --git a/core/services/llo/mercurytransmitter/server.go b/core/services/llo/mercurytransmitter/server.go index d025e65efa6..72ff8b669ba 100644 --- a/core/services/llo/mercurytransmitter/server.go +++ b/core/services/llo/mercurytransmitter/server.go @@ -14,6 +14,7 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink-common/pkg/services" llotypes "github.com/smartcontractkit/chainlink-common/pkg/types/llo" + "github.com/smartcontractkit/chainlink-data-streams/llo" "github.com/smartcontractkit/chainlink/v2/core/services/llo/evm" "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/wsrpc" @@ -121,7 +122,7 @@ func (s *server) runDeleteQueueLoop(stopCh services.StopChan, wg *sync.WaitGroup case hash := <-s.deleteQueue: for { if err := s.pm.orm.Delete(runloopCtx, [][32]byte{hash}); err != nil { - s.lggr.Errorw("Failed to delete transmission record", "err", err, "transmissionHash", hash) + s.lggr.Errorw("Failed to delete transmission record", "err", err, "transmissionHash", fmt.Sprintf("%x", hash)) s.transmitQueueDeleteErrorCount.Inc() select { case <-time.After(b.Duration()): @@ -218,9 +219,7 @@ func (s *server) transmit(ctx context.Context, t *Transmission) (*pb.TransmitRes switch t.Report.Info.ReportFormat { case llotypes.ReportFormatJSON: - // TODO: exactly how to handle JSON here? - // https://smartcontract-it.atlassian.net/browse/MERC-3659 - fallthrough + payload, err = llo.JSONReportCodec{}.Pack(t.ConfigDigest, t.SeqNr, t.Report.Report, t.Sigs) case llotypes.ReportFormatEVMPremiumLegacy: payload, err = evm.ReportCodecPremiumLegacy{}.Pack(t.ConfigDigest, t.SeqNr, t.Report.Report, t.Sigs) default: diff --git a/core/services/llo/mercurytransmitter/transmitter.go b/core/services/llo/mercurytransmitter/transmitter.go index b84eddea7b9..33090ed9574 100644 --- a/core/services/llo/mercurytransmitter/transmitter.go +++ b/core/services/llo/mercurytransmitter/transmitter.go @@ -234,7 +234,7 @@ func (mt *transmitter) Transmit( g := new(errgroup.Group) for i := range transmissions { t := transmissions[i] - mt.lggr.Debugw("LLOMercuryTransmit", "digest", digest.Hex(), "seqNr", seqNr, "reportFormat", report.Info.ReportFormat, "reportLifeCycleStage", report.Info.LifeCycleStage, "transmissionHash", t.Hash()) + mt.lggr.Debugw("LLOMercuryTransmit", "digest", digest.Hex(), "seqNr", seqNr, "reportFormat", report.Info.ReportFormat, "reportLifeCycleStage", report.Info.LifeCycleStage, "transmissionHash", fmt.Sprintf("%x", t.Hash())) g.Go(func() error { s := mt.servers[t.ServerURL] if ok := s.q.Push(t); !ok { diff --git a/core/services/llo/never_retire_cache.go b/core/services/llo/never_retire_cache.go new file mode 100644 index 00000000000..864d649bd4f --- /dev/null +++ b/core/services/llo/never_retire_cache.go @@ -0,0 +1,17 @@ +package llo + +import ( + ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + + llotypes "github.com/smartcontractkit/chainlink-common/pkg/types/llo" +) + +type neverShouldRetireCache struct{} + +func NewNeverShouldRetireCache() llotypes.ShouldRetireCache { + return &neverShouldRetireCache{} +} + +func (n *neverShouldRetireCache) ShouldRetire(digest ocrtypes.ConfigDigest) (bool, error) { + return false, nil +} diff --git a/core/services/llo/null_retirement_report_cache.go b/core/services/llo/null_retirement_report_cache.go new file mode 100644 index 00000000000..e1763e4a178 --- /dev/null +++ b/core/services/llo/null_retirement_report_cache.go @@ -0,0 +1,25 @@ +package llo + +import ( + "context" + + "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + ocr2types "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + + datastreamsllo "github.com/smartcontractkit/chainlink-data-streams/llo" +) + +type NullRetirementReportCache struct{} + +func (n *NullRetirementReportCache) StoreAttestedRetirementReport(ctx context.Context, cd ocr2types.ConfigDigest, retirementReport []byte, sigs []types.AttributedOnchainSignature) error { + return nil +} +func (n *NullRetirementReportCache) StoreConfig(ctx context.Context, cd ocr2types.ConfigDigest, signers [][]byte, f uint8) error { + return nil +} +func (n *NullRetirementReportCache) AttestedRetirementReport(predecessorConfigDigest ocr2types.ConfigDigest) ([]byte, error) { + return nil, nil +} +func (n *NullRetirementReportCache) CheckAttestedRetirementReport(predecessorConfigDigest ocr2types.ConfigDigest, attestedRetirementReport []byte) (datastreamsllo.RetirementReport, error) { + return datastreamsllo.RetirementReport{}, nil +} diff --git a/core/services/llo/onchain_channel_definition_cache.go b/core/services/llo/onchain_channel_definition_cache.go index 6bcc84ad424..31b3d87bb41 100644 --- a/core/services/llo/onchain_channel_definition_cache.go +++ b/core/services/llo/onchain_channel_definition_cache.go @@ -66,10 +66,10 @@ type ChannelDefinitionCacheORM interface { var _ llotypes.ChannelDefinitionCache = &channelDefinitionCache{} type LogPoller interface { - UnregisterFilter(ctx context.Context, filterName string) error - RegisterFilter(ctx context.Context, filter logpoller.Filter) error LatestBlock(ctx context.Context) (logpoller.LogPollerBlock, error) LogsWithSigs(ctx context.Context, start, end int64, eventSigs []common.Hash, address common.Address) ([]logpoller.Log, error) + RegisterFilter(ctx context.Context, filter logpoller.Filter) error + UnregisterFilter(ctx context.Context, filterName string) error } type Option func(*channelDefinitionCache) @@ -217,6 +217,8 @@ func (c *channelDefinitionCache) readLogs(ctx context.Context) (err error) { } // NOTE: We assume that log poller returns logs in order of block_num, log_index ASC + // TODO: Could improve performance a little bit here by adding a don ID topic filter + // MERC-3524 logs, err := c.lp.LogsWithSigs(ctx, fromBlock, toBlock, allTopics, c.addr) if err != nil { return err @@ -239,10 +241,7 @@ func (c *channelDefinitionCache) readLogs(ctx context.Context) (err error) { unpacked.DonId = new(big.Int).SetBytes(log.Topics[1]) if unpacked.DonId.Cmp(big.NewInt(int64(c.donID))) != 0 { - c.lggr.Warnw("Got log for unexpected donID", "donID", unpacked.DonId.String(), "expectedDonID", c.donID) - // ignore logs for other donIDs - // NOTE: shouldn't happen anyway since log poller filters on - // donID + // skip logs for other donIDs continue } diff --git a/core/services/llo/onchain_channel_definition_cache_test.go b/core/services/llo/onchain_channel_definition_cache_test.go index 7ebd2ef380d..33fc60313c4 100644 --- a/core/services/llo/onchain_channel_definition_cache_test.go +++ b/core/services/llo/onchain_channel_definition_cache_test.go @@ -58,9 +58,9 @@ func (m *mockHTTPClient) Do(req *http.Request) (*http.Response, error) { return m.resp, m.err } -var _ ChannelDefinitionCacheORM = &mockORM{} +var _ ChannelDefinitionCacheORM = &mockCDCORM{} -type mockORM struct { +type mockCDCORM struct { err error lastPersistedAddr common.Address @@ -70,10 +70,10 @@ type mockORM struct { lastPersistedBlockNum int64 } -func (m *mockORM) LoadChannelDefinitions(ctx context.Context, addr common.Address, donID uint32) (pd *PersistedDefinitions, err error) { +func (m *mockCDCORM) LoadChannelDefinitions(ctx context.Context, addr common.Address, donID uint32) (pd *PersistedDefinitions, err error) { panic("not implemented") } -func (m *mockORM) StoreChannelDefinitions(ctx context.Context, addr common.Address, donID, version uint32, dfns llotypes.ChannelDefinitions, blockNum int64) (err error) { +func (m *mockCDCORM) StoreChannelDefinitions(ctx context.Context, addr common.Address, donID, version uint32, dfns llotypes.ChannelDefinitions, blockNum int64) (err error) { m.lastPersistedAddr = addr m.lastPersistedDonID = donID m.lastPersistedVersion = version @@ -82,7 +82,7 @@ func (m *mockORM) StoreChannelDefinitions(ctx context.Context, addr common.Addre return m.err } -func (m *mockORM) CleanupChannelDefinitions(ctx context.Context, addr common.Address, donID uint32) (err error) { +func (m *mockCDCORM) CleanupChannelDefinitions(ctx context.Context, addr common.Address, donID uint32) (err error) { panic("not implemented") } @@ -422,7 +422,7 @@ func Test_ChannelDefinitionCache(t *testing.T) { assert.Equal(t, uint32(42), cdc.persistedVersion) }) - orm := &mockORM{} + orm := &mockCDCORM{} cdc.orm = orm t.Run("returns error on db failure and does not update persisted version", func(t *testing.T) { diff --git a/core/services/llo/orm.go b/core/services/llo/orm.go index 5b132e6537b..f98955ff121 100644 --- a/core/services/llo/orm.go +++ b/core/services/llo/orm.go @@ -13,7 +13,7 @@ import ( llotypes "github.com/smartcontractkit/chainlink-common/pkg/types/llo" ) -type ORM interface { +type ChainScopedORM interface { ChannelDefinitionCacheORM } @@ -28,18 +28,18 @@ type PersistedDefinitions struct { UpdatedAt time.Time `db:"updated_at"` } -var _ ORM = &orm{} +var _ ChainScopedORM = &chainScopedORM{} -type orm struct { +type chainScopedORM struct { ds sqlutil.DataSource chainSelector uint64 } -func NewORM(ds sqlutil.DataSource, chainSelector uint64) ORM { - return &orm{ds, chainSelector} +func NewChainScopedORM(ds sqlutil.DataSource, chainSelector uint64) ChainScopedORM { + return &chainScopedORM{ds, chainSelector} } -func (o *orm) LoadChannelDefinitions(ctx context.Context, addr common.Address, donID uint32) (pd *PersistedDefinitions, err error) { +func (o *chainScopedORM) LoadChannelDefinitions(ctx context.Context, addr common.Address, donID uint32) (pd *PersistedDefinitions, err error) { pd = new(PersistedDefinitions) err = o.ds.GetContext(ctx, pd, "SELECT * FROM channel_definitions WHERE chain_selector = $1 AND addr = $2 AND don_id = $3", o.chainSelector, addr, donID) if errors.Is(err, sql.ErrNoRows) { @@ -53,7 +53,7 @@ func (o *orm) LoadChannelDefinitions(ctx context.Context, addr common.Address, d // StoreChannelDefinitions will store a ChannelDefinitions list for a given chain_selector, addr, don_id // It only updates if the new version is greater than the existing record -func (o *orm) StoreChannelDefinitions(ctx context.Context, addr common.Address, donID, version uint32, dfns llotypes.ChannelDefinitions, blockNum int64) error { +func (o *chainScopedORM) StoreChannelDefinitions(ctx context.Context, addr common.Address, donID, version uint32, dfns llotypes.ChannelDefinitions, blockNum int64) error { _, err := o.ds.ExecContext(ctx, ` INSERT INTO channel_definitions (chain_selector, addr, don_id, definitions, block_num, version, updated_at) VALUES ($1, $2, $3, $4, $5, $6, NOW()) @@ -67,7 +67,7 @@ WHERE EXCLUDED.version > channel_definitions.version return nil } -func (o *orm) CleanupChannelDefinitions(ctx context.Context, addr common.Address, donID uint32) error { +func (o *chainScopedORM) CleanupChannelDefinitions(ctx context.Context, addr common.Address, donID uint32) error { _, err := o.ds.ExecContext(ctx, "DELETE FROM channel_definitions WHERE chain_selector = $1 AND addr = $2 AND don_id = $3", o.chainSelector, addr, donID) if err != nil { return fmt.Errorf("failed to CleanupChannelDefinitions; %w", err) diff --git a/core/services/llo/orm_test.go b/core/services/llo/orm_test.go index ec3c06e6e64..6a4fd8f4c7d 100644 --- a/core/services/llo/orm_test.go +++ b/core/services/llo/orm_test.go @@ -19,7 +19,7 @@ func Test_ORM(t *testing.T) { const OtherChainSelector uint64 = 1234567890 db := pgtest.NewSqlxDB(t) - orm := NewORM(db, ETHMainnetChainSelector) + orm := NewChainScopedORM(db, ETHMainnetChainSelector) ctx := testutils.Context(t) addr1 := testutils.NewAddress() diff --git a/core/services/llo/plugin_scoped_retirement_report_cache.go b/core/services/llo/plugin_scoped_retirement_report_cache.go new file mode 100644 index 00000000000..74370f8d2a2 --- /dev/null +++ b/core/services/llo/plugin_scoped_retirement_report_cache.go @@ -0,0 +1,84 @@ +package llo + +import ( + "fmt" + + "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3types" + "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + ocr2types "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + "google.golang.org/protobuf/proto" + + llotypes "github.com/smartcontractkit/chainlink-common/pkg/types/llo" + + datastreamsllo "github.com/smartcontractkit/chainlink-data-streams/llo" +) + +type RetirementReportVerifier interface { + Verify(key types.OnchainPublicKey, digest types.ConfigDigest, seqNr uint64, r ocr3types.ReportWithInfo[llotypes.ReportInfo], signature []byte) bool +} + +// PluginScopedRetirementReportCache is a wrapper around RetirementReportCache +// that implements CheckAttestedRetirementReport +// +// This is necessary because while config digest keys are globally unique, +// different plugins may implement different signing/verification strategies +var _ datastreamsllo.PredecessorRetirementReportCache = &pluginScopedRetirementReportCache{} + +type pluginScopedRetirementReportCache struct { + rrc RetirementReportCacheReader + verifier RetirementReportVerifier + codec datastreamsllo.RetirementReportCodec +} + +func NewPluginScopedRetirementReportCache(rrc RetirementReportCacheReader, verifier RetirementReportVerifier, codec datastreamsllo.RetirementReportCodec) datastreamsllo.PredecessorRetirementReportCache { + return &pluginScopedRetirementReportCache{ + rrc: rrc, + verifier: verifier, + codec: codec, + } +} + +func (pr *pluginScopedRetirementReportCache) CheckAttestedRetirementReport(predecessorConfigDigest ocr2types.ConfigDigest, serializedAttestedRetirementReport []byte) (datastreamsllo.RetirementReport, error) { + config, exists := pr.rrc.Config(predecessorConfigDigest) + if !exists { + return datastreamsllo.RetirementReport{}, fmt.Errorf("Verify failed; predecessor config not found for config digest %x", predecessorConfigDigest[:]) + } + + var arr AttestedRetirementReport + if err := proto.Unmarshal(serializedAttestedRetirementReport, &arr); err != nil { + return datastreamsllo.RetirementReport{}, fmt.Errorf("Verify failed; failed to unmarshal protobuf: %w", err) + } + + validSigs := 0 + for _, sig := range arr.Sigs { + // #nosec G115 + if sig.Signer >= uint32(len(config.Signers)) { + return datastreamsllo.RetirementReport{}, fmt.Errorf("Verify failed; attested report signer index out of bounds (got: %d, max: %d)", sig.Signer, len(config.Signers)-1) + } + signer := config.Signers[sig.Signer] + valid := pr.verifier.Verify(types.OnchainPublicKey(signer), predecessorConfigDigest, arr.SeqNr, ocr3types.ReportWithInfo[llotypes.ReportInfo]{ + Report: arr.RetirementReport, + Info: llotypes.ReportInfo{ReportFormat: llotypes.ReportFormatRetirement}, + }, sig.Signature) + if !valid { + continue + } + validSigs++ + } + if validSigs <= int(config.F) { + return datastreamsllo.RetirementReport{}, fmt.Errorf("Verify failed; not enough valid signatures (got: %d, need: %d)", validSigs, config.F+1) + } + decoded, err := pr.codec.Decode(arr.RetirementReport) + if err != nil { + return datastreamsllo.RetirementReport{}, fmt.Errorf("Verify failed; failed to decode retirement report: %w", err) + } + return decoded, nil +} + +func (pr *pluginScopedRetirementReportCache) AttestedRetirementReport(predecessorConfigDigest ocr2types.ConfigDigest) ([]byte, error) { + arr, exists := pr.rrc.AttestedRetirementReport(predecessorConfigDigest) + if !exists { + return nil, nil + } + return arr, nil +} diff --git a/core/services/llo/plugin_scoped_retirement_report_cache_test.go b/core/services/llo/plugin_scoped_retirement_report_cache_test.go new file mode 100644 index 00000000000..e1afb203fd2 --- /dev/null +++ b/core/services/llo/plugin_scoped_retirement_report_cache_test.go @@ -0,0 +1,164 @@ +package llo + +import ( + "errors" + "testing" + + "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3types" + "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + ocr2types "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/proto" + + datastreamsllo "github.com/smartcontractkit/chainlink-data-streams/llo" + + llotypes "github.com/smartcontractkit/chainlink-common/pkg/types/llo" +) + +type mockRetirementReportCache struct { + arr []byte + cfg Config + exists bool +} + +func (m *mockRetirementReportCache) AttestedRetirementReport(digest ocr2types.ConfigDigest) ([]byte, bool) { + return m.arr, m.exists +} +func (m *mockRetirementReportCache) Config(cd ocr2types.ConfigDigest) (Config, bool) { + return m.cfg, m.exists +} + +type mockVerifier struct { + verify func(key types.OnchainPublicKey, digest types.ConfigDigest, seqNr uint64, r ocr3types.ReportWithInfo[llotypes.ReportInfo], signature []byte) bool +} + +func (m *mockVerifier) Verify(key types.OnchainPublicKey, digest types.ConfigDigest, seqNr uint64, r ocr3types.ReportWithInfo[llotypes.ReportInfo], signature []byte) bool { + return m.verify(key, digest, seqNr, r, signature) +} + +type mockCodec struct { + decode func([]byte) (datastreamsllo.RetirementReport, error) +} + +func (m *mockCodec) Encode(datastreamsllo.RetirementReport) ([]byte, error) { + panic("not implemented") +} +func (m *mockCodec) Decode(b []byte) (datastreamsllo.RetirementReport, error) { + return m.decode(b) +} + +func Test_PluginScopedRetirementReportCache(t *testing.T) { + rrc := &mockRetirementReportCache{} + v := &mockVerifier{} + c := &mockCodec{} + psrrc := NewPluginScopedRetirementReportCache(rrc, v, c) + exampleDigest := ocr2types.ConfigDigest{1} + exampleDigest2 := ocr2types.ConfigDigest{2} + + exampleUnattestedSerializedRetirementReport := []byte("foo example unattested retirement report") + + validArr := AttestedRetirementReport{ + RetirementReport: exampleUnattestedSerializedRetirementReport, + SeqNr: 42, + Sigs: []*AttributedOnchainSignature{ + { + Signer: 0, + Signature: []byte("bar0"), + }, + { + Signer: 1, + Signature: []byte("bar1"), + }, + { + Signer: 2, + Signature: []byte("bar2"), + }, + { + Signer: 3, + Signature: []byte("bar3"), + }, + }, + } + serializedValidArr, err := proto.Marshal(&validArr) + require.NoError(t, err) + + t.Run("CheckAttestedRetirementReport", func(t *testing.T) { + t.Run("invalid", func(t *testing.T) { + // config missing + _, err := psrrc.CheckAttestedRetirementReport(exampleDigest, []byte("not valid")) + assert.EqualError(t, err, "Verify failed; predecessor config not found for config digest 0100000000000000000000000000000000000000000000000000000000000000") + + rrc.cfg = Config{Digest: exampleDigest} + rrc.exists = true + + // unmarshal failure + _, err = psrrc.CheckAttestedRetirementReport(exampleDigest, []byte("not valid")) + require.Error(t, err) + assert.Contains(t, err.Error(), "Verify failed; failed to unmarshal protobuf: proto") + + // config is invalid (no signers) + _, err = psrrc.CheckAttestedRetirementReport(exampleDigest, serializedValidArr) + assert.EqualError(t, err, "Verify failed; attested report signer index out of bounds (got: 0, max: -1)") + + rrc.cfg = Config{Digest: exampleDigest, Signers: [][]byte{[]byte{0}, []byte{1}, []byte{2}, []byte{3}}, F: 1} + + // no valid sigs + v.verify = func(key types.OnchainPublicKey, digest types.ConfigDigest, seqNr uint64, r ocr3types.ReportWithInfo[llotypes.ReportInfo], signature []byte) bool { + return false + } + _, err = psrrc.CheckAttestedRetirementReport(exampleDigest, serializedValidArr) + assert.EqualError(t, err, "Verify failed; not enough valid signatures (got: 0, need: 2)") + + // not enough valid sigs + v.verify = func(key types.OnchainPublicKey, digest types.ConfigDigest, seqNr uint64, r ocr3types.ReportWithInfo[llotypes.ReportInfo], signature []byte) bool { + return string(signature) == "bar0" + } + _, err = psrrc.CheckAttestedRetirementReport(exampleDigest, serializedValidArr) + assert.EqualError(t, err, "Verify failed; not enough valid signatures (got: 1, need: 2)") + + // enough valid sigs, but codec decode fails + v.verify = func(key types.OnchainPublicKey, digest types.ConfigDigest, seqNr uint64, r ocr3types.ReportWithInfo[llotypes.ReportInfo], signature []byte) bool { + if string(signature) == "bar0" || string(signature) == "bar3" { + return true + } + return false + } + c.decode = func([]byte) (datastreamsllo.RetirementReport, error) { + return datastreamsllo.RetirementReport{}, errors.New("codec decode failed") + } + _, err = psrrc.CheckAttestedRetirementReport(exampleDigest, serializedValidArr) + assert.EqualError(t, err, "Verify failed; failed to decode retirement report: codec decode failed") + + exampleRetirementReport := datastreamsllo.RetirementReport{ValidAfterSeconds: map[llotypes.ChannelID]uint32{ + 0: 1, + }, + } + + // enough valid sigs and codec decode succeeds + c.decode = func(b []byte) (datastreamsllo.RetirementReport, error) { + assert.Equal(t, exampleUnattestedSerializedRetirementReport, b) + return exampleRetirementReport, nil + } + decoded, err := psrrc.CheckAttestedRetirementReport(exampleDigest, serializedValidArr) + assert.NoError(t, err) + assert.Equal(t, exampleRetirementReport, decoded) + }) + }) + t.Run("AttestedRetirementReport", func(t *testing.T) { + rrc.arr = []byte("foo") + rrc.exists = true + + // exists + arr, err := psrrc.AttestedRetirementReport(exampleDigest) + assert.NoError(t, err) + assert.Equal(t, rrc.arr, arr) + + rrc.exists = false + + // doesn't exist + arr, err = psrrc.AttestedRetirementReport(exampleDigest2) + assert.NoError(t, err) + assert.Nil(t, arr) + }) +} diff --git a/core/services/llo/retirement_report_cache.go b/core/services/llo/retirement_report_cache.go new file mode 100644 index 00000000000..daf894a056f --- /dev/null +++ b/core/services/llo/retirement_report_cache.go @@ -0,0 +1,151 @@ +package llo + +import ( + "context" + "fmt" + sync "sync" + + "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + ocr2types "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + "google.golang.org/protobuf/proto" + + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink-common/pkg/services" + "github.com/smartcontractkit/chainlink-common/pkg/sqlutil" +) + +// RetirementReportCacheReader is used by the plugin-scoped +// RetirementReportCache +type RetirementReportCacheReader interface { + AttestedRetirementReport(cd ocr2types.ConfigDigest) ([]byte, bool) + Config(cd ocr2types.ConfigDigest) (Config, bool) +} + +// RetirementReportCache is intended to be a global singleton that is wrapped +// by a PluginScopedRetirementReportCache for a given plugin +type RetirementReportCache interface { + services.Service + StoreAttestedRetirementReport(ctx context.Context, cd ocrtypes.ConfigDigest, seqNr uint64, retirementReport []byte, sigs []types.AttributedOnchainSignature) error + StoreConfig(ctx context.Context, cd ocr2types.ConfigDigest, signers [][]byte, f uint8) error + RetirementReportCacheReader +} + +type retirementReportCache struct { + services.Service + eng *services.Engine + + mu sync.RWMutex + arrs map[ocr2types.ConfigDigest][]byte + configs map[ocr2types.ConfigDigest]Config + + orm RetirementReportCacheORM +} + +func NewRetirementReportCache(lggr logger.Logger, ds sqlutil.DataSource) RetirementReportCache { + orm := &retirementReportCacheORM{ds: ds} + return newRetirementReportCache(lggr, orm) +} + +func newRetirementReportCache(lggr logger.Logger, orm RetirementReportCacheORM) *retirementReportCache { + r := &retirementReportCache{ + arrs: make(map[ocr2types.ConfigDigest][]byte), + configs: make(map[ocr2types.ConfigDigest]Config), + orm: orm, + } + r.Service, r.eng = services.Config{ + Name: "RetirementReportCache", + Start: r.start, + }.NewServiceEngine(lggr) + return r +} + +// NOTE: Could do this lazily instead if we wanted to avoid a performance hit +// or potential tables missing etc on application startup (since +// RetirementReportCache is global) +func (r *retirementReportCache) start(ctx context.Context) (err error) { + // Load all attested retirement reports from the ORM + // and store them in the cache + r.arrs, err = r.orm.LoadAttestedRetirementReports(ctx) + if err != nil { + return fmt.Errorf("failed to load attested retirement reports: %w", err) + } + configs, err := r.orm.LoadConfigs(ctx) + if err != nil { + return fmt.Errorf("failed to load configs: %w", err) + } + for _, c := range configs { + r.configs[c.Digest] = c + } + return nil +} + +func (r *retirementReportCache) StoreAttestedRetirementReport(ctx context.Context, cd ocr2types.ConfigDigest, seqNr uint64, retirementReport []byte, sigs []types.AttributedOnchainSignature) error { + r.mu.RLock() + if _, ok := r.arrs[cd]; ok { + r.mu.RUnlock() + return nil + } + r.mu.RUnlock() + + var pbSigs []*AttributedOnchainSignature + for _, s := range sigs { + pbSigs = append(pbSigs, &AttributedOnchainSignature{ + Signer: uint32(s.Signer), + Signature: s.Signature, + }) + } + attestedRetirementReport := AttestedRetirementReport{ + RetirementReport: retirementReport, + SeqNr: seqNr, + Sigs: pbSigs, + } + + serialized, err := proto.Marshal(&attestedRetirementReport) + if err != nil { + return fmt.Errorf("StoreAttestedRetirementReport failed; failed to marshal protobuf: %w", err) + } + + if err := r.orm.StoreAttestedRetirementReport(ctx, cd, serialized); err != nil { + return fmt.Errorf("StoreAttestedRetirementReport failed; failed to persist to ORM: %w", err) + } + + r.mu.Lock() + r.arrs[cd] = serialized + r.mu.Unlock() + + return nil +} + +func (r *retirementReportCache) StoreConfig(ctx context.Context, cd ocr2types.ConfigDigest, signers [][]byte, f uint8) error { + r.mu.RLock() + if _, ok := r.configs[cd]; ok { + r.mu.RUnlock() + return nil + } + r.mu.RUnlock() + + r.mu.Lock() + r.configs[cd] = Config{ + Digest: cd, + Signers: signers, + F: f, + } + r.mu.Unlock() + + return r.orm.StoreConfig(ctx, cd, signers, f) +} + +func (r *retirementReportCache) AttestedRetirementReport(predecessorConfigDigest ocr2types.ConfigDigest) ([]byte, bool) { + r.mu.RLock() + defer r.mu.RUnlock() + arr, exists := r.arrs[predecessorConfigDigest] + return arr, exists +} + +func (r *retirementReportCache) Config(cd ocr2types.ConfigDigest) (Config, bool) { + r.mu.RLock() + defer r.mu.RUnlock() + c, exists := r.configs[cd] + return c, exists +} diff --git a/core/services/llo/retirement_report_cache_test.go b/core/services/llo/retirement_report_cache_test.go new file mode 100644 index 00000000000..616c5e0e519 --- /dev/null +++ b/core/services/llo/retirement_report_cache_test.go @@ -0,0 +1,170 @@ +package llo + +import ( + "context" + "errors" + "testing" + + "github.com/stretchr/testify/assert" + + ocr2types "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + + "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" + + "github.com/smartcontractkit/chainlink/v2/core/logger" +) + +type mockORM struct { + storedAttestedRetirementReports map[ocr2types.ConfigDigest][]byte + storedConfigs map[ocr2types.ConfigDigest]Config + + err error +} + +func (m *mockORM) StoreAttestedRetirementReport(ctx context.Context, cd ocr2types.ConfigDigest, attestedRetirementReport []byte) error { + m.storedAttestedRetirementReports[cd] = attestedRetirementReport + return m.err +} +func (m *mockORM) LoadAttestedRetirementReports(ctx context.Context) (map[ocr2types.ConfigDigest][]byte, error) { + return m.storedAttestedRetirementReports, m.err +} +func (m *mockORM) StoreConfig(ctx context.Context, cd ocr2types.ConfigDigest, signers [][]byte, f uint8) error { + m.storedConfigs[cd] = Config{Signers: signers, F: f, Digest: cd} + return m.err +} +func (m *mockORM) LoadConfigs(ctx context.Context) ([]Config, error) { + configs := make([]Config, 0, len(m.storedConfigs)) + for _, config := range m.storedConfigs { + configs = append(configs, config) + } + return configs, m.err +} + +func Test_RetirementReportCache(t *testing.T) { + t.Parallel() + + ctx := tests.Context(t) + lggr := logger.TestLogger(t) + orm := &mockORM{ + make(map[ocrtypes.ConfigDigest][]byte), + make(map[ocrtypes.ConfigDigest]Config), + nil, + } + exampleRetirementReport := []byte{1, 2, 3} + exampleRetirementReport2 := []byte{4, 5, 6} + exampleSignatures := []ocrtypes.AttributedOnchainSignature{ + {Signature: []byte("signature0"), Signer: 0}, + {Signature: []byte("signature1"), Signer: 1}, + {Signature: []byte("signature2"), Signer: 2}, + {Signature: []byte("signature3"), Signer: 3}, + } + // this is a serialized protobuf of report with 4 signers + exampleAttestedRetirementReport := []byte{0xa, 0x3, 0x1, 0x2, 0x3, 0x10, 0x64, 0x1a, 0xc, 0xa, 0xa, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x30, 0x1a, 0xe, 0xa, 0xa, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x31, 0x10, 0x1, 0x1a, 0xe, 0xa, 0xa, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x32, 0x10, 0x2, 0x1a, 0xe, 0xa, 0xa, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x33, 0x10, 0x3} + exampleDigest := ocrtypes.ConfigDigest{1} + exampleDigest2 := ocrtypes.ConfigDigest{2} + + seqNr := uint64(100) + + t.Run("start loads from ORM", func(t *testing.T) { + rrc := newRetirementReportCache(lggr, orm) + + t.Run("orm failure, errors", func(t *testing.T) { + orm.err = errors.New("orm failed") + err := rrc.start(ctx) + assert.EqualError(t, err, "failed to load attested retirement reports: orm failed") + }) + t.Run("orm success, loads both configs and attestedRetirementReports from orm", func(t *testing.T) { + orm.err = nil + orm.storedAttestedRetirementReports = map[ocr2types.ConfigDigest][]byte{ + exampleDigest: exampleAttestedRetirementReport, + exampleDigest2: exampleAttestedRetirementReport, + } + config1 := Config{Digest: exampleDigest, Signers: [][]byte{{1}, {2}, {3}, {4}}, F: 1} + config2 := Config{Digest: exampleDigest2, Signers: [][]byte{{5}, {6}, {7}, {8}}, F: 2} + orm.storedConfigs[exampleDigest] = config1 + orm.storedConfigs[exampleDigest2] = config2 + + err := rrc.start(ctx) + assert.NoError(t, err) + + assert.Len(t, rrc.arrs, 2) + assert.Equal(t, exampleAttestedRetirementReport, rrc.arrs[exampleDigest]) + assert.Equal(t, exampleAttestedRetirementReport, rrc.arrs[exampleDigest2]) + + assert.Len(t, rrc.configs, 2) + assert.Equal(t, config1, rrc.configs[exampleDigest]) + assert.Equal(t, config2, rrc.configs[exampleDigest2]) + }) + }) + + t.Run("StoreAttestedRetirementReport", func(t *testing.T) { + rrc := newRetirementReportCache(lggr, orm) + + err := rrc.StoreAttestedRetirementReport(ctx, exampleDigest, seqNr, exampleRetirementReport, exampleSignatures) + assert.NoError(t, err) + + assert.Len(t, rrc.arrs, 1) + assert.Equal(t, exampleAttestedRetirementReport, rrc.arrs[exampleDigest]) + assert.Equal(t, exampleAttestedRetirementReport, orm.storedAttestedRetirementReports[exampleDigest]) + + t.Run("does nothing if retirement report already exists for the given config digest", func(t *testing.T) { + err = rrc.StoreAttestedRetirementReport(ctx, exampleDigest, seqNr, exampleRetirementReport2, exampleSignatures) + assert.NoError(t, err) + assert.Len(t, rrc.arrs, 1) + assert.Equal(t, exampleAttestedRetirementReport, rrc.arrs[exampleDigest]) + }) + + t.Run("returns error if ORM store fails", func(t *testing.T) { + orm.err = errors.New("failed to store") + err = rrc.StoreAttestedRetirementReport(ctx, exampleDigest2, seqNr, exampleRetirementReport, exampleSignatures) + assert.Error(t, err) + + // it wasn't cached + assert.Len(t, rrc.arrs, 1) + }) + + t.Run("second retirement report succeeds when orm starts working again", func(t *testing.T) { + orm.err = nil + err := rrc.StoreAttestedRetirementReport(ctx, exampleDigest2, seqNr, exampleRetirementReport, exampleSignatures) + assert.NoError(t, err) + + assert.Len(t, rrc.arrs, 2) + assert.Equal(t, exampleAttestedRetirementReport, rrc.arrs[exampleDigest2]) + assert.Equal(t, exampleAttestedRetirementReport, orm.storedAttestedRetirementReports[exampleDigest2]) + + assert.Len(t, orm.storedAttestedRetirementReports, 2) + }) + }) + t.Run("AttestedRetirementReport", func(t *testing.T) { + rrc := newRetirementReportCache(lggr, orm) + + attestedRetirementReport, exists := rrc.AttestedRetirementReport(exampleDigest) + assert.False(t, exists) + assert.Nil(t, attestedRetirementReport) + + rrc.arrs[exampleDigest] = exampleAttestedRetirementReport + + attestedRetirementReport, exists = rrc.AttestedRetirementReport(exampleDigest) + assert.True(t, exists) + assert.Equal(t, exampleAttestedRetirementReport, attestedRetirementReport) + }) + t.Run("StoreConfig", func(t *testing.T) { + rrc := newRetirementReportCache(lggr, orm) + + signers := [][]byte{{1}, {2}, {3}, {4}} + + err := rrc.StoreConfig(ctx, exampleDigest, signers, 1) + assert.NoError(t, err) + + assert.Len(t, rrc.configs, 1) + assert.Equal(t, Config{Digest: exampleDigest, Signers: [][]byte{{1}, {2}, {3}, {4}}, F: 1}, rrc.configs[exampleDigest]) + assert.Equal(t, Config{Digest: exampleDigest, Signers: [][]byte{{1}, {2}, {3}, {4}}, F: 1}, orm.storedConfigs[exampleDigest]) + + t.Run("Config", func(t *testing.T) { + config, exists := rrc.Config(exampleDigest) + assert.True(t, exists) + assert.Equal(t, Config{Digest: exampleDigest, Signers: [][]byte{{1}, {2}, {3}, {4}}, F: 1}, config) + }) + }) +} diff --git a/core/services/llo/retirement_report_orm.go b/core/services/llo/retirement_report_orm.go new file mode 100644 index 00000000000..5601e4fcd2f --- /dev/null +++ b/core/services/llo/retirement_report_orm.go @@ -0,0 +1,110 @@ +package llo + +import ( + "context" + "errors" + "fmt" + + "github.com/lib/pq" + ocr2types "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + + "github.com/smartcontractkit/chainlink-common/pkg/sqlutil" +) + +type RetirementReportCacheORM interface { + StoreAttestedRetirementReport(ctx context.Context, cd ocr2types.ConfigDigest, attestedRetirementReport []byte) error + LoadAttestedRetirementReports(ctx context.Context) (map[ocr2types.ConfigDigest][]byte, error) + StoreConfig(ctx context.Context, cd ocr2types.ConfigDigest, signers [][]byte, f uint8) error + LoadConfigs(ctx context.Context) ([]Config, error) +} + +type retirementReportCacheORM struct { + ds sqlutil.DataSource +} + +func (o *retirementReportCacheORM) StoreAttestedRetirementReport(ctx context.Context, cd ocr2types.ConfigDigest, attestedRetirementReport []byte) error { + _, err := o.ds.ExecContext(ctx, ` +INSERT INTO llo_retirement_report_cache (config_digest, attested_retirement_report, updated_at) +VALUES ($1, $2, NOW()) +ON CONFLICT (config_digest) DO NOTHING +`, cd, attestedRetirementReport) + if err != nil { + return fmt.Errorf("StoreAttestedRetirementReport failed: %w", err) + } + return nil +} + +func (o *retirementReportCacheORM) LoadAttestedRetirementReports(ctx context.Context) (map[ocr2types.ConfigDigest][]byte, error) { + rows, err := o.ds.QueryContext(ctx, "SELECT config_digest, attested_retirement_report FROM llo_retirement_report_cache") + if err != nil { + return nil, fmt.Errorf("LoadAttestedRetirementReports failed: %w", err) + } + defer rows.Close() + + reports := make(map[ocr2types.ConfigDigest][]byte) + for rows.Next() { + var rawCd []byte + var arr []byte + if err := rows.Scan(&rawCd, &arr); err != nil { + return nil, fmt.Errorf("LoadAttestedRetirementReports failed: %w", err) + } + cd, err := ocr2types.BytesToConfigDigest(rawCd) + if err != nil { + return nil, fmt.Errorf("LoadAttestedRetirementReports failed to scan config digest: %w", err) + } + reports[cd] = arr + } + if err := rows.Err(); err != nil { + return nil, fmt.Errorf("LoadAttestedRetirementReports failed: %w", err) + } + + return reports, nil +} + +func (o *retirementReportCacheORM) StoreConfig(ctx context.Context, cd ocr2types.ConfigDigest, signers [][]byte, f uint8) error { + _, err := o.ds.ExecContext(ctx, `INSERT INTO llo_retirement_report_cache_configs (config_digest, signers, f, updated_at) VALUES ($1, $2, $3, NOW())`, cd, signers, f) + return err +} + +type Config struct { + Digest [32]byte `db:"config_digest"` + Signers pq.ByteaArray `db:"signers"` + F uint8 `db:"f"` +} + +type scannableConfigDigest [32]byte + +func (s *scannableConfigDigest) Scan(src interface{}) error { + b, ok := src.([]byte) + if !ok { + return errors.New("type assertion to []byte failed") + } + + cd, err := ocr2types.BytesToConfigDigest(b) + if err != nil { + return err + } + copy(s[:], cd[:]) + return nil +} + +func (o *retirementReportCacheORM) LoadConfigs(ctx context.Context) (configs []Config, err error) { + type config struct { + Digest scannableConfigDigest `db:"config_digest"` + Signers pq.ByteaArray `db:"signers"` + F uint8 `db:"f"` + } + var rawCfgs []config + err = o.ds.SelectContext(ctx, &rawCfgs, `SELECT config_digest, signers, f FROM llo_retirement_report_cache_configs ORDER BY config_digest`) + if err != nil { + return nil, fmt.Errorf("LoadConfigs failed: %w", err) + } + for _, rawCfg := range rawCfgs { + var cfg Config + copy(cfg.Digest[:], rawCfg.Digest[:]) + cfg.Signers = rawCfg.Signers + cfg.F = rawCfg.F + configs = append(configs, cfg) + } + return +} diff --git a/core/services/llo/retirement_report_orm_test.go b/core/services/llo/retirement_report_orm_test.go new file mode 100644 index 00000000000..835bfaaef76 --- /dev/null +++ b/core/services/llo/retirement_report_orm_test.go @@ -0,0 +1,62 @@ +package llo + +import ( + "testing" + + ocr2types "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" +) + +func Test_RetirementReportCache_ORM(t *testing.T) { + db := pgtest.NewSqlxDB(t) + orm := &retirementReportCacheORM{db} + ctx := tests.Context(t) + + cd := ocr2types.ConfigDigest{1} + attestedRetirementReport := []byte("report1") + cd2 := ocr2types.ConfigDigest{2} + attestedRetirementReport2 := []byte("report2") + + t.Run("StoreAttestedRetirementReport", func(t *testing.T) { + err := orm.StoreAttestedRetirementReport(ctx, cd, attestedRetirementReport) + require.NoError(t, err) + err = orm.StoreAttestedRetirementReport(ctx, cd2, attestedRetirementReport2) + require.NoError(t, err) + }) + t.Run("LoadAttestedRetirementReports", func(t *testing.T) { + arrs, err := orm.LoadAttestedRetirementReports(ctx) + require.NoError(t, err) + + require.Len(t, arrs, 2) + assert.Equal(t, attestedRetirementReport, arrs[cd]) + assert.Equal(t, attestedRetirementReport2, arrs[cd2]) + }) + t.Run("StoreConfig", func(t *testing.T) { + signers := [][]byte{[]byte("signer1"), []byte("signer2")} + err := orm.StoreConfig(ctx, cd, signers, 1) + require.NoError(t, err) + + err = orm.StoreConfig(ctx, cd2, signers, 2) + require.NoError(t, err) + }) + t.Run("LoadConfigs", func(t *testing.T) { + configs, err := orm.LoadConfigs(ctx) + require.NoError(t, err) + + require.Len(t, configs, 2) + assert.Equal(t, Config{ + Digest: cd, + Signers: [][]byte{[]byte("signer1"), []byte("signer2")}, + F: 1, + }, configs[0]) + assert.Equal(t, Config{ + Digest: cd2, + Signers: [][]byte{[]byte("signer1"), []byte("signer2")}, + F: 2, + }, configs[1]) + }) +} diff --git a/core/services/llo/transmitter.go b/core/services/llo/transmitter.go index d0a45ba8f86..7696c69c291 100644 --- a/core/services/llo/transmitter.go +++ b/core/services/llo/transmitter.go @@ -2,10 +2,12 @@ package llo import ( "context" + "fmt" "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3types" "github.com/smartcontractkit/libocr/offchainreporting2plus/types" ocr2types "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" "golang.org/x/sync/errgroup" "github.com/smartcontractkit/chainlink/v2/core/services/llo/mercurytransmitter" @@ -39,18 +41,24 @@ type Transmitter interface { services.Service } +type TransmitterRetirementReportCacheWriter interface { + StoreAttestedRetirementReport(ctx context.Context, cd ocrtypes.ConfigDigest, seqNr uint64, retirementReport []byte, sigs []types.AttributedOnchainSignature) error +} + type transmitter struct { services.StateMachine lggr logger.Logger fromAccount string - subTransmitters []Transmitter + subTransmitters []Transmitter + retirementReportCache TransmitterRetirementReportCacheWriter } type TransmitterOpts struct { Lggr logger.Logger FromAccount string MercuryTransmitterOpts mercurytransmitter.Opts + RetirementReportCache TransmitterRetirementReportCacheWriter } // The transmitter will handle starting and stopping the subtransmitters @@ -63,6 +71,7 @@ func NewTransmitter(opts TransmitterOpts) Transmitter { opts.Lggr, opts.FromAccount, subTransmitters, + opts.RetirementReportCache, } } @@ -105,6 +114,15 @@ func (t *transmitter) Transmit( report ocr3types.ReportWithInfo[llotypes.ReportInfo], sigs []types.AttributedOnchainSignature, ) (err error) { + if report.Info.ReportFormat == llotypes.ReportFormatRetirement { + // Retirement reports don't get transmitted; rather, they are stored in + // the RetirementReportCache + t.lggr.Debugw("Storing retirement report", "digest", digest, "seqNr", seqNr) + if err := t.retirementReportCache.StoreAttestedRetirementReport(ctx, digest, seqNr, report.Report, sigs); err != nil { + return fmt.Errorf("failed to write retirement report to cache: %w", err) + } + return nil + } g := new(errgroup.Group) for _, st := range t.subTransmitters { st := st diff --git a/core/services/ocr2/delegate.go b/core/services/ocr2/delegate.go index 2536e11a402..cf45822cb37 100644 --- a/core/services/ocr2/delegate.go +++ b/core/services/ocr2/delegate.go @@ -120,8 +120,9 @@ type Delegate struct { ks keystore.OCR2 ethKs keystore.Eth RelayGetter - isNewlyCreatedJob bool // Set to true if this is a new job freshly added, false if job was present already on node boot. - mailMon *mailbox.Monitor + isNewlyCreatedJob bool // Set to true if this is a new job freshly added, false if job was present already on node boot. + mailMon *mailbox.Monitor + retirementReportCache llo.RetirementReportCache legacyChains legacyevm.LegacyChainContainer // legacy: use relayers instead capabilitiesRegistry core.CapabilitiesRegistry @@ -214,42 +215,48 @@ func NewDelegateConfig(ocr2Cfg ocr2Config, m coreconfig.Mercury, t coreconfig.Th var _ job.Delegate = (*Delegate)(nil) +type DelegateOpts struct { + Ds sqlutil.DataSource + JobORM job.ORM + BridgeORM bridges.ORM + MercuryORM evmmercury.ORM + PipelineRunner pipeline.Runner + StreamRegistry streams.Getter + PeerWrapper *ocrcommon.SingletonPeerWrapper + MonitoringEndpointGen telemetry.MonitoringEndpointGenerator + LegacyChains legacyevm.LegacyChainContainer + Lggr logger.Logger + Ks keystore.OCR2 + EthKs keystore.Eth + Relayers RelayGetter + MailMon *mailbox.Monitor + CapabilitiesRegistry core.CapabilitiesRegistry + RetirementReportCache llo.RetirementReportCache +} + func NewDelegate( - ds sqlutil.DataSource, - jobORM job.ORM, - bridgeORM bridges.ORM, - mercuryORM evmmercury.ORM, - pipelineRunner pipeline.Runner, - streamRegistry streams.Getter, - peerWrapper *ocrcommon.SingletonPeerWrapper, - monitoringEndpointGen telemetry.MonitoringEndpointGenerator, - legacyChains legacyevm.LegacyChainContainer, - lggr logger.Logger, + opts DelegateOpts, cfg DelegateConfig, - ks keystore.OCR2, - ethKs keystore.Eth, - relayers RelayGetter, - mailMon *mailbox.Monitor, - capabilitiesRegistry core.CapabilitiesRegistry, ) *Delegate { return &Delegate{ - ds: ds, - jobORM: jobORM, - bridgeORM: bridgeORM, - mercuryORM: mercuryORM, - pipelineRunner: pipelineRunner, - streamRegistry: streamRegistry, - peerWrapper: peerWrapper, - monitoringEndpointGen: monitoringEndpointGen, - legacyChains: legacyChains, + ds: opts.Ds, + jobORM: opts.JobORM, + bridgeORM: opts.BridgeORM, + mercuryORM: opts.MercuryORM, + pipelineRunner: opts.PipelineRunner, + streamRegistry: opts.StreamRegistry, + peerWrapper: opts.PeerWrapper, + monitoringEndpointGen: opts.MonitoringEndpointGen, + legacyChains: opts.LegacyChains, cfg: cfg, - lggr: lggr.Named("OCR2"), - ks: ks, - ethKs: ethKs, - RelayGetter: relayers, + lggr: opts.Lggr.Named("OCR2"), + ks: opts.Ks, + ethKs: opts.EthKs, + RelayGetter: opts.Relayers, isNewlyCreatedJob: false, - mailMon: mailMon, - capabilitiesRegistry: capabilitiesRegistry, + mailMon: opts.MailMon, + capabilitiesRegistry: opts.CapabilitiesRegistry, + retirementReportCache: opts.RetirementReportCache, } } @@ -999,7 +1006,11 @@ func (d *Delegate) newServicesLLO( // Use the default key bundle if not specified // NOTE: Only JSON and EVMPremiumLegacy supported for now // https://smartcontract-it.atlassian.net/browse/MERC-3722 - for _, rf := range []llotypes.ReportFormat{llotypes.ReportFormatJSON, llotypes.ReportFormatEVMPremiumLegacy} { + // + // Also re-use EVM keys for signing the retirement report. This isn't + // required, just seems easiest since it's the only key type available for + // now. + for _, rf := range []llotypes.ReportFormat{llotypes.ReportFormatJSON, llotypes.ReportFormatEVMPremiumLegacy, llotypes.ReportFormatRetirement} { if _, exists := kbm[rf]; !exists { // Use the first if unspecified kbs, err3 := d.ks.GetAllOfType("evm") @@ -1023,10 +1034,6 @@ func (d *Delegate) newServicesLLO( lggr.Infof("Using on-chain signing keys for LLO job %d (%s): %v", jb.ID, jb.Name.ValueOrZero(), kbm) kr := llo.NewOnchainKeyring(lggr, kbm) - ocrLogger := ocrcommon.NewOCRWrapper(lggr, d.cfg.OCR2().TraceLogging(), func(ctx context.Context, msg string) { - lggr.ErrorIf(d.jobORM.RecordError(ctx, jb.ID, msg), "unable to record error") - }) - cfg := llo.DelegateConfig{ Logger: lggr, DataSource: d.ds, @@ -1037,18 +1044,21 @@ func (d *Delegate) newServicesLLO( CaptureEATelemetry: jb.OCR2OracleSpec.CaptureEATelemetry, ChannelDefinitionCache: provider.ChannelDefinitionCache(), + RetirementReportCache: d.retirementReportCache, + ShouldRetireCache: provider.ShouldRetireCache(), + RetirementReportCodec: datastreamsllo.StandardRetirementReportCodec{}, + TraceLogging: d.cfg.OCR2().TraceLogging(), BinaryNetworkEndpointFactory: d.peerWrapper.Peer2, V2Bootstrappers: bootstrapPeers, ContractTransmitter: provider.ContractTransmitter(), - ContractConfigTracker: provider.ContractConfigTracker(), + ContractConfigTrackers: provider.ContractConfigTrackers(), Database: ocrDB, LocalConfig: lc, MonitoringEndpoint: d.monitoringEndpointGen.GenMonitoringEndpoint(rid.Network, rid.ChainID, fmt.Sprintf("%d", pluginCfg.DonID), synchronization.EnhancedEAMercury), OffchainConfigDigester: provider.OffchainConfigDigester(), OffchainKeyring: kb, OnchainKeyring: kr, - OCRLogger: ocrLogger, // Enable verbose logging if either Mercury.VerboseLogging is on or OCR2.TraceLogging is on ReportingPluginConfig: datastreamsllo.Config{VerboseLogging: d.cfg.Mercury().VerboseLogging() || d.cfg.OCR2().TraceLogging()}, @@ -1057,7 +1067,7 @@ func (d *Delegate) newServicesLLO( if err != nil { return nil, err } - return []job.ServiceCtx{provider, ocrLogger, oracle}, nil + return []job.ServiceCtx{provider, oracle}, nil } func (d *Delegate) newServicesMedian( diff --git a/core/services/ocr2/plugins/llo/integration_test.go b/core/services/ocr2/plugins/llo/integration_test.go index c25b36c95d6..7ab735bf122 100644 --- a/core/services/ocr2/plugins/llo/integration_test.go +++ b/core/services/ocr2/plugins/llo/integration_test.go @@ -8,6 +8,7 @@ import ( "math/big" "net/http" "net/http/httptest" + "sort" "strings" "testing" "time" @@ -34,15 +35,21 @@ import ( datastreamsllo "github.com/smartcontractkit/chainlink-data-streams/llo" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/link_token_interface" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/llo-feeds/generated/channel_config_store" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/llo-feeds/generated/configurator" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/llo-feeds/generated/destination_verifier" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/llo-feeds/generated/destination_verifier_proxy" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/llo-feeds/generated/fee_manager" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/llo-feeds/generated/reward_manager" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/llo-feeds/generated/verifier" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/llo-feeds/generated/verifier_proxy" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/csakey" lloevm "github.com/smartcontractkit/chainlink/v2/core/services/llo/evm" "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm" + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/llo" "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury" reportcodecv3 "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/v3/reportcodec" mercuryverifier "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/verifier" @@ -53,7 +60,22 @@ var ( nNodes = 4 // number of nodes (not including bootstrap) ) -func setupBlockchain(t *testing.T) (*bind.TransactOpts, *backends.SimulatedBackend, *configurator.Configurator, common.Address, *destination_verifier.DestinationVerifier, common.Address, *destination_verifier_proxy.DestinationVerifierProxy, common.Address, *channel_config_store.ChannelConfigStore, common.Address) { +func setupBlockchain(t *testing.T) ( + *bind.TransactOpts, + *backends.SimulatedBackend, + *configurator.Configurator, + common.Address, + *destination_verifier.DestinationVerifier, + common.Address, + *destination_verifier_proxy.DestinationVerifierProxy, + common.Address, + *channel_config_store.ChannelConfigStore, + common.Address, + *verifier.Verifier, + common.Address, + *verifier_proxy.VerifierProxy, + common.Address, +) { steve := testutils.MustNewSimTransactor(t) // config contract deployer and owner genesisData := core.GenesisAlloc{steve.From: {Balance: assets.Ether(1000).ToInt()}} backend := cltest.NewSimulatedBackend(t, genesisData, uint32(ethconfig.Defaults.Miner.GasCeil)) @@ -65,22 +87,52 @@ func setupBlockchain(t *testing.T) (*bind.TransactOpts, *backends.SimulatedBacke require.NoError(t, err) // DestinationVerifierProxy - verifierProxyAddr, _, verifierProxy, err := destination_verifier_proxy.DeployDestinationVerifierProxy(steve, backend) + destinationVerifierProxyAddr, _, verifierProxy, err := destination_verifier_proxy.DeployDestinationVerifierProxy(steve, backend) require.NoError(t, err) // DestinationVerifier - verifierAddr, _, verifier, err := destination_verifier.DeployDestinationVerifier(steve, backend, verifierProxyAddr) + destinationVerifierAddr, _, destinationVerifier, err := destination_verifier.DeployDestinationVerifier(steve, backend, destinationVerifierProxyAddr) require.NoError(t, err) // AddVerifier - _, err = verifierProxy.SetVerifier(steve, verifierAddr) + _, err = verifierProxy.SetVerifier(steve, destinationVerifierAddr) require.NoError(t, err) + // Legacy mercury verifier + legacyVerifier, legacyVerifierAddr, legacyVerifierProxy, legacyVerifierProxyAddr := setupLegacyMercuryVerifier(t, steve, backend) + // ChannelConfigStore configStoreAddress, _, configStore, err := channel_config_store.DeployChannelConfigStore(steve, backend) require.NoError(t, err) backend.Commit() - return steve, backend, configurator, configuratorAddress, verifier, verifierAddr, verifierProxy, verifierProxyAddr, configStore, configStoreAddress + return steve, backend, configurator, configuratorAddress, destinationVerifier, destinationVerifierAddr, verifierProxy, destinationVerifierProxyAddr, configStore, configStoreAddress, legacyVerifier, legacyVerifierAddr, legacyVerifierProxy, legacyVerifierProxyAddr +} + +func setupLegacyMercuryVerifier(t *testing.T, steve *bind.TransactOpts, backend *backends.SimulatedBackend) (*verifier.Verifier, common.Address, *verifier_proxy.VerifierProxy, common.Address) { + linkTokenAddress, _, linkToken, err := link_token_interface.DeployLinkToken(steve, backend) + require.NoError(t, err) + _, err = linkToken.Transfer(steve, steve.From, big.NewInt(1000)) + require.NoError(t, err) + nativeTokenAddress, _, nativeToken, err := link_token_interface.DeployLinkToken(steve, backend) + require.NoError(t, err) + _, err = nativeToken.Transfer(steve, steve.From, big.NewInt(1000)) + require.NoError(t, err) + verifierProxyAddr, _, verifierProxy, err := verifier_proxy.DeployVerifierProxy(steve, backend, common.Address{}) // zero address for access controller disables access control + require.NoError(t, err) + verifierAddress, _, verifier, err := verifier.DeployVerifier(steve, backend, verifierProxyAddr) + require.NoError(t, err) + _, err = verifierProxy.InitializeVerifier(steve, verifierAddress) + require.NoError(t, err) + rewardManagerAddr, _, rewardManager, err := reward_manager.DeployRewardManager(steve, backend, linkTokenAddress) + require.NoError(t, err) + feeManagerAddr, _, _, err := fee_manager.DeployFeeManager(steve, backend, linkTokenAddress, nativeTokenAddress, verifierProxyAddr, rewardManagerAddr) + require.NoError(t, err) + _, err = verifierProxy.SetFeeManager(steve, feeManagerAddr) + require.NoError(t, err) + _, err = rewardManager.SetFeeManager(steve, feeManagerAddr) + require.NoError(t, err) + + return verifier, verifierAddress, verifierProxy, verifierProxyAddr } type Stream struct { @@ -122,28 +174,44 @@ var ( } ) -func generateConfig(t *testing.T, oracles []confighelper.OracleIdentityExtra) ( +func generateBlueGreenConfig(t *testing.T, oracles []confighelper.OracleIdentityExtra, predecessorConfigDigest *ocr2types.ConfigDigest) ( signers []types.OnchainPublicKey, transmitters []types.Account, f uint8, onchainConfig []byte, offchainConfigVersion uint64, offchainConfig []byte, +) { + onchainConfig, err := (&datastreamsllo.EVMOnchainConfigCodec{}).Encode(datastreamsllo.OnchainConfig{ + Version: 1, + PredecessorConfigDigest: predecessorConfigDigest, + }) + require.NoError(t, err) + return generateConfig(t, oracles, onchainConfig) +} + +func generateConfig(t *testing.T, oracles []confighelper.OracleIdentityExtra, inOnchainConfig []byte) ( + signers []types.OnchainPublicKey, + transmitters []types.Account, + f uint8, + outOnchainConfig []byte, + offchainConfigVersion uint64, + offchainConfig []byte, ) { rawReportingPluginConfig := datastreamsllo.OffchainConfig{} reportingPluginConfig, err := rawReportingPluginConfig.Encode() require.NoError(t, err) - signers, transmitters, f, onchainConfig, offchainConfigVersion, offchainConfig, err = ocr3confighelper.ContractSetConfigArgsForTests( - 2*time.Second, // DeltaProgress - 20*time.Second, // DeltaResend - 400*time.Millisecond, // DeltaInitial - 1000*time.Millisecond, // DeltaRound - 500*time.Millisecond, // DeltaGrace - 300*time.Millisecond, // DeltaCertifiedCommitRequest - 1*time.Minute, // DeltaStage - 100, // rMax - []int{len(oracles)}, // S + signers, transmitters, f, outOnchainConfig, offchainConfigVersion, offchainConfig, err = ocr3confighelper.ContractSetConfigArgsForTests( + 2*time.Second, // DeltaProgress + 20*time.Second, // DeltaResend + 400*time.Millisecond, // DeltaInitial + 500*time.Millisecond, // DeltaRound + 250*time.Millisecond, // DeltaGrace + 300*time.Millisecond, // DeltaCertifiedCommitRequest + 1*time.Minute, // DeltaStage + 100, // rMax + []int{len(oracles)}, // S oracles, reportingPluginConfig, // reportingPluginConfig []byte, nil, // maxDurationInitialization @@ -152,7 +220,7 @@ func generateConfig(t *testing.T, oracles []confighelper.OracleIdentityExtra) ( 0, // maxDurationShouldAcceptAttestedReport 0, // maxDurationShouldTransmitAcceptedReport int(fNodes), // f - onchainConfig, // encoded onchain config + inOnchainConfig, // encoded onchain config ) require.NoError(t, err) @@ -160,8 +228,14 @@ func generateConfig(t *testing.T, oracles []confighelper.OracleIdentityExtra) ( return } -func setConfig(t *testing.T, donID uint32, steve *bind.TransactOpts, backend *backends.SimulatedBackend, configurator *configurator.Configurator, configuratorAddress common.Address, nodes []Node, oracles []confighelper.OracleIdentityExtra) ocr2types.ConfigDigest { - signers, _, _, _, offchainConfigVersion, offchainConfig := generateConfig(t, oracles) +func setLegacyConfig(t *testing.T, donID uint32, steve *bind.TransactOpts, backend *backends.SimulatedBackend, legacyVerifier *verifier.Verifier, legacyVerifierAddr common.Address, nodes []Node, oracles []confighelper.OracleIdentityExtra) ocr2types.ConfigDigest { + onchainConfig, err := (&datastreamsllo.EVMOnchainConfigCodec{}).Encode(datastreamsllo.OnchainConfig{ + Version: 1, + PredecessorConfigDigest: nil, + }) + require.NoError(t, err) + + signers, _, _, onchainConfig, offchainConfigVersion, offchainConfig := generateConfig(t, oracles, onchainConfig) signerAddresses, err := evm.OnchainPublicKeyToAddress(signers) require.NoError(t, err) @@ -169,8 +243,8 @@ func setConfig(t *testing.T, donID uint32, steve *bind.TransactOpts, backend *ba for i := 0; i < nNodes; i++ { offchainTransmitters[i] = nodes[i].ClientPubKey } - donIDPadded := evm.DonIDToBytes32(donID) - _, err = configurator.SetConfig(steve, donIDPadded, signerAddresses, offchainTransmitters, fNodes, offchainConfig, offchainConfigVersion, offchainConfig) + donIDPadded := llo.DonIDToBytes32(donID) + _, err = legacyVerifier.SetConfig(steve, donIDPadded, signerAddresses, offchainTransmitters, fNodes, onchainConfig, offchainConfigVersion, offchainConfig, nil) require.NoError(t, err) // libocr requires a few confirmations to accept the config @@ -179,23 +253,81 @@ func setConfig(t *testing.T, donID uint32, steve *bind.TransactOpts, backend *ba backend.Commit() backend.Commit() - logs, err := backend.FilterLogs(testutils.Context(t), ethereum.FilterQuery{Addresses: []common.Address{configuratorAddress}, Topics: [][]common.Hash{[]common.Hash{mercury.FeedScopedConfigSet, donIDPadded}}}) + l, err := legacyVerifier.LatestConfigDigestAndEpoch(&bind.CallOpts{}, donIDPadded) + require.NoError(t, err) + + return l.ConfigDigest +} + +func setStagingConfig(t *testing.T, donID uint32, steve *bind.TransactOpts, backend *backends.SimulatedBackend, configurator *configurator.Configurator, configuratorAddress common.Address, nodes []Node, oracles []confighelper.OracleIdentityExtra, predecessorConfigDigest ocr2types.ConfigDigest) ocr2types.ConfigDigest { + return setBlueGreenConfig(t, donID, steve, backend, configurator, configuratorAddress, nodes, oracles, &predecessorConfigDigest) +} + +func setProductionConfig(t *testing.T, donID uint32, steve *bind.TransactOpts, backend *backends.SimulatedBackend, configurator *configurator.Configurator, configuratorAddress common.Address, nodes []Node, oracles []confighelper.OracleIdentityExtra) ocr2types.ConfigDigest { + return setBlueGreenConfig(t, donID, steve, backend, configurator, configuratorAddress, nodes, oracles, nil) +} + +func setBlueGreenConfig(t *testing.T, donID uint32, steve *bind.TransactOpts, backend *backends.SimulatedBackend, configurator *configurator.Configurator, configuratorAddress common.Address, nodes []Node, oracles []confighelper.OracleIdentityExtra, predecessorConfigDigest *ocr2types.ConfigDigest) ocr2types.ConfigDigest { + signers, _, _, onchainConfig, offchainConfigVersion, offchainConfig := generateBlueGreenConfig(t, oracles, predecessorConfigDigest) + + var onchainPubKeys [][]byte + for _, signer := range signers { + onchainPubKeys = append(onchainPubKeys, signer) + } + offchainTransmitters := make([][32]byte, nNodes) + for i := 0; i < nNodes; i++ { + offchainTransmitters[i] = nodes[i].ClientPubKey + } + donIDPadded := llo.DonIDToBytes32(donID) + isProduction := predecessorConfigDigest == nil + var err error + if isProduction { + _, err = configurator.SetProductionConfig(steve, donIDPadded, onchainPubKeys, offchainTransmitters, fNodes, onchainConfig, offchainConfigVersion, offchainConfig) + } else { + _, err = configurator.SetStagingConfig(steve, donIDPadded, onchainPubKeys, offchainTransmitters, fNodes, onchainConfig, offchainConfigVersion, offchainConfig) + } require.NoError(t, err) - require.Len(t, logs, 1) - cfg, err := mercury.ConfigFromLog(logs[0].Data) + // libocr requires a few confirmations to accept the config + backend.Commit() + backend.Commit() + backend.Commit() + backend.Commit() + + var topic common.Hash + if isProduction { + topic = llo.ProductionConfigSet + } else { + topic = llo.StagingConfigSet + } + logs, err := backend.FilterLogs(testutils.Context(t), ethereum.FilterQuery{Addresses: []common.Address{configuratorAddress}, Topics: [][]common.Hash{[]common.Hash{topic, donIDPadded}}}) + require.NoError(t, err) + require.GreaterOrEqual(t, len(logs), 1) + + cfg, err := mercury.ConfigFromLog(logs[len(logs)-1].Data) require.NoError(t, err) return cfg.ConfigDigest } +func promoteStagingConfig(t *testing.T, donID uint32, steve *bind.TransactOpts, backend *backends.SimulatedBackend, configurator *configurator.Configurator, configuratorAddress common.Address, isGreenProduction bool) { + donIDPadded := llo.DonIDToBytes32(donID) + _, err := configurator.PromoteStagingConfig(steve, donIDPadded, isGreenProduction) + require.NoError(t, err) + + // libocr requires a few confirmations to accept the config + backend.Commit() + backend.Commit() + backend.Commit() + backend.Commit() +} + func TestIntegration_LLO(t *testing.T) { testStartTimeStamp := time.Now() - donID := uint32(995544) multiplier := decimal.New(1, 18) expirationWindow := time.Hour / time.Second - reqs := make(chan request) + reqs := make(chan request, 100000) serverKey := csakey.MustNewV2XXXTestingOnly(big.NewInt(-1)) serverPubKey := serverKey.PublicKey srv := NewMercuryServer(t, ed25519.PrivateKey(serverKey.Raw()), reqs) @@ -208,9 +340,8 @@ func TestIntegration_LLO(t *testing.T) { clientCSAKeys[i] = key clientPubKeys[i] = key.PublicKey } - serverURL := startMercuryServer(t, srv, clientPubKeys) - steve, backend, configurator, configuratorAddress, verifier, _, verifierProxy, _, configStore, configStoreAddress := setupBlockchain(t) + steve, backend, configurator, configuratorAddress, verifier, _, verifierProxy, _, configStore, configStoreAddress, legacyVerifier, legacyVerifierAddr, _, _ := setupBlockchain(t) fromBlock := 1 // Setup bootstrap @@ -219,7 +350,10 @@ func TestIntegration_LLO(t *testing.T) { appBootstrap, bootstrapPeerID, _, bootstrapKb, _ := setupNode(t, bootstrapNodePort, "bootstrap_llo", backend, bootstrapCSAKey) bootstrapNode := Node{App: appBootstrap, KeyBundle: bootstrapKb} - t.Run("produces reports in v0.3 format", func(t *testing.T) { + t.Run("using legacy verifier configuration contract, produces reports in v0.3 format", func(t *testing.T) { + serverURL := startMercuryServer(t, srv, clientPubKeys) + + donID := uint32(995544) streams := []Stream{ethStream, linkStream, quoteStream1, quoteStream2} streamMap := make(map[uint32]Stream) for _, strm := range streams { @@ -227,28 +361,7 @@ func TestIntegration_LLO(t *testing.T) { } // Setup oracle nodes - var ( - oracles []confighelper.OracleIdentityExtra - nodes []Node - ) - ports := freeport.GetN(t, nNodes) - for i := 0; i < nNodes; i++ { - app, peerID, transmitter, kb, observedLogs := setupNode(t, ports[i], fmt.Sprintf("oracle_streams_%d", i), backend, clientCSAKeys[i]) - - nodes = append(nodes, Node{ - app, transmitter, kb, observedLogs, - }) - offchainPublicKey, _ := hex.DecodeString(strings.TrimPrefix(kb.OnChainPublicKey(), "0x")) - oracles = append(oracles, confighelper.OracleIdentityExtra{ - OracleIdentity: confighelper.OracleIdentity{ - OnchainPublicKey: offchainPublicKey, - TransmitAccount: ocr2types.Account(fmt.Sprintf("%x", transmitter[:])), - OffchainPublicKey: kb.OffchainPublicKey(), - PeerID: peerID, - }, - ConfigEncryptionPublicKey: kb.ConfigEncryptionPublicKey(), - }) - } + oracles, nodes := setupNodes(t, nNodes, backend, clientCSAKeys, streams) chainID := testutils.SimulatedChainID relayType := "evm" @@ -258,7 +371,7 @@ fromBlock = %d lloDonID = %d lloConfigMode = "mercury" `, chainID, fromBlock, donID) - addBootstrapJob(t, bootstrapNode, configuratorAddress, "job-2", relayType, relayConfig) + addBootstrapJob(t, bootstrapNode, legacyVerifierAddr, "job-2", relayType, relayConfig) // Channel definitions channelDefinitions := llotypes.ChannelDefinitions{ @@ -300,24 +413,10 @@ lloConfigMode = "mercury" }, } - channelDefinitionsJSON, err := json.MarshalIndent(channelDefinitions, "", " ") - require.NoError(t, err) - channelDefinitionsSHA := sha3.Sum256(channelDefinitionsJSON) - - // Set up channel definitions server - channelDefinitionsServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, "/channel-definitions", r.URL.Path) - assert.Equal(t, "GET", r.Method) - assert.Equal(t, "application/json", r.Header.Get("Content-Type")) - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) - _, err := w.Write(channelDefinitionsJSON) - require.NoError(t, err) - })) - t.Cleanup(channelDefinitionsServer.Close) + url, sha := newChannelDefinitionsServer(t, channelDefinitions) // Set channel definitions - _, err = configStore.SetChannelDefinitions(steve, donID, channelDefinitionsServer.URL+"/channel-definitions", channelDefinitionsSHA) + _, err := configStore.SetChannelDefinitions(steve, donID, url, sha) require.NoError(t, err) backend.Commit() @@ -325,11 +424,11 @@ lloConfigMode = "mercury" donID = %d channelDefinitionsContractAddress = "0x%x" channelDefinitionsContractFromBlock = %d`, serverURL, serverPubKey, donID, configStoreAddress, fromBlock) - addOCRJobsEVMPremiumLegacy(t, streams, serverPubKey, serverURL, configuratorAddress, bootstrapPeerID, bootstrapNodePort, nodes, configStoreAddress, clientPubKeys, pluginConfig, relayType, relayConfig) + addOCRJobsEVMPremiumLegacy(t, streams, serverPubKey, serverURL, legacyVerifierAddr, bootstrapPeerID, bootstrapNodePort, nodes, configStoreAddress, clientPubKeys, pluginConfig, relayType, relayConfig) // Set config on configurator - setConfig( - t, donID, steve, backend, configurator, configuratorAddress, nodes, oracles, + setLegacyConfig( + t, donID, steve, backend, legacyVerifier, legacyVerifierAddr, nodes, oracles, ) // Set config on the destination verifier @@ -411,15 +510,17 @@ channelDefinitionsContractFromBlock = %d`, serverURL, serverPubKey, donID, confi }) t.Run(fmt.Sprintf("test on-chain verification - node %x", req.pk), func(t *testing.T) { - _, err = verifierProxy.Verify(steve, req.req.Payload, []byte{}) - require.NoError(t, err) + t.Run("destination verifier", func(t *testing.T) { + _, err = verifierProxy.Verify(steve, req.req.Payload, []byte{}) + require.NoError(t, err) + }) }) - t.Logf("oracle %x reported for 0x%x", req.pk, feedID) + t.Logf("oracle %x reported for 0x%x", req.pk[:], feedID[:]) seen[feedID][req.pk] = struct{}{} if len(seen[feedID]) == nNodes { - t.Logf("all oracles reported for 0x%x", feedID) + t.Logf("all oracles reported for 0x%x", feedID[:]) delete(seen, feedID) if len(seen) == 0 { break // saw all oracles; success! @@ -427,9 +528,314 @@ channelDefinitionsContractFromBlock = %d`, serverURL, serverPubKey, donID, confi } } }) + }) + + t.Run("Blue/Green lifecycle (using JSON report format)", func(t *testing.T) { + serverURL := startMercuryServer(t, srv, clientPubKeys) + + donID := uint32(888333) + streams := []Stream{ethStream, linkStream} + streamMap := make(map[uint32]Stream) + for _, strm := range streams { + streamMap[strm.id] = strm + } + + // Setup oracle nodes + oracles, nodes := setupNodes(t, nNodes, backend, clientCSAKeys, streams) + + chainID := testutils.SimulatedChainID + relayType := "evm" + relayConfig := fmt.Sprintf(` +chainID = "%s" +fromBlock = %d +lloDonID = %d +lloConfigMode = "bluegreen" +`, chainID, fromBlock, donID) + addBootstrapJob(t, bootstrapNode, configuratorAddress, "job-3", relayType, relayConfig) + + // Channel definitions + channelDefinitions := llotypes.ChannelDefinitions{ + 1: { + ReportFormat: llotypes.ReportFormatJSON, + Streams: []llotypes.Stream{ + { + StreamID: ethStreamID, + Aggregator: llotypes.AggregatorMedian, + }, + }, + }, + } + url, sha := newChannelDefinitionsServer(t, channelDefinitions) + + // Set channel definitions + _, err := configStore.SetChannelDefinitions(steve, donID, url, sha) + require.NoError(t, err) + backend.Commit() - t.Run("deleting LLO jobs cleans up resources", func(t *testing.T) { - t.Skip("TODO - https://smartcontract-it.atlassian.net/browse/MERC-3653") + pluginConfig := fmt.Sprintf(`servers = { "%s" = "%x" } +donID = %d +channelDefinitionsContractAddress = "0x%x" +channelDefinitionsContractFromBlock = %d`, serverURL, serverPubKey, donID, configStoreAddress, fromBlock) + addOCRJobsEVMPremiumLegacy(t, streams, serverPubKey, serverURL, configuratorAddress, bootstrapPeerID, bootstrapNodePort, nodes, configStoreAddress, clientPubKeys, pluginConfig, relayType, relayConfig) + + var blueDigest ocr2types.ConfigDigest + var greenDigest ocr2types.ConfigDigest + + allReports := make(map[types.ConfigDigest][]datastreamsllo.Report) + t.Run("start off with blue=production, green=staging (specimen reports)", func(t *testing.T) { + // Set config on configurator + blueDigest = setProductionConfig( + t, donID, steve, backend, configurator, configuratorAddress, nodes, oracles, + ) + + // NOTE: Wait until blue produces a report + + for req := range reqs { + _, _, r, _, err := (datastreamsllo.JSONReportCodec{}).UnpackDecode(req.req.Payload) + require.NoError(t, err) + + allReports[r.ConfigDigest] = append(allReports[r.ConfigDigest], r) + + assert.Equal(t, blueDigest, r.ConfigDigest) + assert.False(t, r.Specimen) + assert.Len(t, r.Values, 1) + assert.Equal(t, "2976.39", r.Values[0].(*datastreamsllo.Decimal).String()) + break + } + }) + t.Run("setStagingConfig does not affect production", func(t *testing.T) { + greenDigest = setStagingConfig( + t, donID, steve, backend, configurator, configuratorAddress, nodes, oracles, blueDigest, + ) + + // NOTE: Wait until green produces the first "specimen" report + + for req := range reqs { + _, _, r, _, err := (datastreamsllo.JSONReportCodec{}).UnpackDecode(req.req.Payload) + require.NoError(t, err) + + allReports[r.ConfigDigest] = append(allReports[r.ConfigDigest], r) + if r.Specimen { + assert.Len(t, r.Values, 1) + assert.Equal(t, "2976.39", r.Values[0].(*datastreamsllo.Decimal).String()) + + assert.Equal(t, greenDigest, r.ConfigDigest) + break + } + assert.Equal(t, blueDigest, r.ConfigDigest) + } + }) + t.Run("promoteStagingConfig flow has clean and gapless hand off from old production to newly promoted staging instance, leaving old production instance in 'retired' state", func(t *testing.T) { + promoteStagingConfig(t, donID, steve, backend, configurator, configuratorAddress, false) + + // NOTE: Wait for first non-specimen report for the newly promoted (green) instance + + for req := range reqs { + _, _, r, _, err := (datastreamsllo.JSONReportCodec{}).UnpackDecode(req.req.Payload) + require.NoError(t, err) + + allReports[r.ConfigDigest] = append(allReports[r.ConfigDigest], r) + + if !r.Specimen && r.ConfigDigest == greenDigest { + break + } + } + + initialPromotedGreenReport := allReports[greenDigest][len(allReports[greenDigest])-1] + finalBlueReport := allReports[blueDigest][len(allReports[blueDigest])-1] + + for _, digest := range []ocr2types.ConfigDigest{blueDigest, greenDigest} { + // Transmissions are not guaranteed to be in order + sort.Slice(allReports[digest], func(i, j int) bool { + return allReports[digest][i].SeqNr < allReports[digest][j].SeqNr + }) + seenSeqNr := uint64(0) + highestObservationTs := uint32(0) + highestValidAfterSeconds := uint32(0) + for i := 0; i < len(allReports[digest]); i++ { + r := allReports[digest][i] + switch digest { + case greenDigest: + if i == len(allReports[digest])-1 { + assert.False(t, r.Specimen) + } else { + assert.True(t, r.Specimen) + } + case blueDigest: + assert.False(t, r.Specimen) + } + if r.SeqNr > seenSeqNr { + // skip first one + if highestObservationTs > 0 { + if digest == greenDigest && i == len(allReports[digest])-1 { + // NOTE: This actually CHANGES on the staging + // handover and can go backwards - the gapless + // handover test is handled below + break + } + assert.Equal(t, highestObservationTs, r.ValidAfterSeconds, "%d: (n-1)ObservationsTimestampSeconds->(n)ValidAfterSeconds should be gapless, got: %d vs %d", i, highestObservationTs, r.ValidAfterSeconds) + assert.Greater(t, r.ObservationTimestampSeconds, highestObservationTs, "%d: overlapping/duplicate report ObservationTimestampSeconds, got: %d vs %d", i, r.ObservationTimestampSeconds, highestObservationTs) + assert.Greater(t, r.ValidAfterSeconds, highestValidAfterSeconds, "%d: overlapping/duplicate report ValidAfterSeconds, got: %d vs %d", i, r.ValidAfterSeconds, highestValidAfterSeconds) + assert.Less(t, r.ValidAfterSeconds, r.ObservationTimestampSeconds) + } + seenSeqNr = r.SeqNr + highestObservationTs = r.ObservationTimestampSeconds + highestValidAfterSeconds = r.ValidAfterSeconds + } + } + } + + // Gapless handover + assert.Less(t, finalBlueReport.ValidAfterSeconds, finalBlueReport.ObservationTimestampSeconds) + assert.Equal(t, finalBlueReport.ObservationTimestampSeconds, initialPromotedGreenReport.ValidAfterSeconds) + assert.Less(t, initialPromotedGreenReport.ValidAfterSeconds, initialPromotedGreenReport.ObservationTimestampSeconds) + }) + t.Run("retired instance does not produce reports", func(t *testing.T) { + // NOTE: Wait for five "green" reports to be produced and assert no "blue" reports + + i := 0 + for req := range reqs { + i++ + if i == 5 { + break + } + _, _, r, _, err := (datastreamsllo.JSONReportCodec{}).UnpackDecode(req.req.Payload) + require.NoError(t, err) + + allReports[r.ConfigDigest] = append(allReports[r.ConfigDigest], r) + assert.False(t, r.Specimen) + assert.Equal(t, greenDigest, r.ConfigDigest) + } + }) + t.Run("setStagingConfig replaces 'retired' instance with new config and starts producing specimen reports again", func(t *testing.T) { + blueDigest = setStagingConfig( + t, donID, steve, backend, configurator, configuratorAddress, nodes, oracles, greenDigest, + ) + + // NOTE: Wait until blue produces the first "specimen" report + + for req := range reqs { + _, _, r, _, err := (datastreamsllo.JSONReportCodec{}).UnpackDecode(req.req.Payload) + require.NoError(t, err) + + allReports[r.ConfigDigest] = append(allReports[r.ConfigDigest], r) + if r.Specimen { + assert.Equal(t, blueDigest, r.ConfigDigest) + break + } + assert.Equal(t, greenDigest, r.ConfigDigest) + } + }) + t.Run("promoteStagingConfig swaps the instances again", func(t *testing.T) { + // TODO: Check that once an instance enters 'retired' state, it + // doesn't produce reports or bother making observations + promoteStagingConfig(t, donID, steve, backend, configurator, configuratorAddress, true) + + // NOTE: Wait for first non-specimen report for the newly promoted (blue) instance + + for req := range reqs { + _, _, r, _, err := (datastreamsllo.JSONReportCodec{}).UnpackDecode(req.req.Payload) + require.NoError(t, err) + + allReports[r.ConfigDigest] = append(allReports[r.ConfigDigest], r) + + if !r.Specimen && r.ConfigDigest == blueDigest { + break + } + } + + initialPromotedBlueReport := allReports[blueDigest][len(allReports[blueDigest])-1] + finalGreenReport := allReports[greenDigest][len(allReports[greenDigest])-1] + + // Gapless handover + assert.Less(t, finalGreenReport.ValidAfterSeconds, finalGreenReport.ObservationTimestampSeconds) + assert.Equal(t, finalGreenReport.ObservationTimestampSeconds, initialPromotedBlueReport.ValidAfterSeconds) + assert.Less(t, initialPromotedBlueReport.ValidAfterSeconds, initialPromotedBlueReport.ObservationTimestampSeconds) + }) + t.Run("adding a new channel definition is picked up on the fly", func(t *testing.T) { + channelDefinitions[2] = llotypes.ChannelDefinition{ + ReportFormat: llotypes.ReportFormatJSON, + Streams: []llotypes.Stream{ + { + StreamID: linkStreamID, + Aggregator: llotypes.AggregatorMedian, + }, + }, + } + + url, sha := newChannelDefinitionsServer(t, channelDefinitions) + + // Set channel definitions + _, err := configStore.SetChannelDefinitions(steve, donID, url, sha) + require.NoError(t, err) + backend.Commit() + + // NOTE: Wait until the first report for the new channel definition is produced + + for req := range reqs { + _, _, r, _, err := (datastreamsllo.JSONReportCodec{}).UnpackDecode(req.req.Payload) + require.NoError(t, err) + + allReports[r.ConfigDigest] = append(allReports[r.ConfigDigest], r) + + // Green is retired, it shouldn't be producing anything + assert.Equal(t, blueDigest, r.ConfigDigest) + assert.False(t, r.Specimen) + + if r.ChannelID == 2 { + assert.Len(t, r.Values, 1) + assert.Equal(t, "13.25", r.Values[0].(*datastreamsllo.Decimal).String()) + break + } + assert.Len(t, r.Values, 1) + assert.Equal(t, "2976.39", r.Values[0].(*datastreamsllo.Decimal).String()) + } + }) + t.Run("deleting the jobs turns off oracles and cleans up resources", func(t *testing.T) { + t.Skip("TODO - MERC-3524") + }) + t.Run("adding new jobs again picks up the correct configs", func(t *testing.T) { + t.Skip("TODO - MERC-3524") }) }) } + +func setupNodes(t *testing.T, nNodes int, backend *backends.SimulatedBackend, clientCSAKeys []csakey.KeyV2, streams []Stream) (oracles []confighelper.OracleIdentityExtra, nodes []Node) { + ports := freeport.GetN(t, nNodes) + for i := 0; i < nNodes; i++ { + app, peerID, transmitter, kb, observedLogs := setupNode(t, ports[i], fmt.Sprintf("oracle_streams_%d", i), backend, clientCSAKeys[i]) + + nodes = append(nodes, Node{ + app, transmitter, kb, observedLogs, + }) + offchainPublicKey, _ := hex.DecodeString(strings.TrimPrefix(kb.OnChainPublicKey(), "0x")) + oracles = append(oracles, confighelper.OracleIdentityExtra{ + OracleIdentity: confighelper.OracleIdentity{ + OnchainPublicKey: offchainPublicKey, + TransmitAccount: ocr2types.Account(fmt.Sprintf("%x", transmitter[:])), + OffchainPublicKey: kb.OffchainPublicKey(), + PeerID: peerID, + }, + ConfigEncryptionPublicKey: kb.ConfigEncryptionPublicKey(), + }) + } + return +} + +func newChannelDefinitionsServer(t *testing.T, channelDefinitions llotypes.ChannelDefinitions) (url string, sha [32]byte) { + channelDefinitionsJSON, err := json.MarshalIndent(channelDefinitions, "", " ") + require.NoError(t, err) + channelDefinitionsSHA := sha3.Sum256(channelDefinitionsJSON) + + // Set up channel definitions server + channelDefinitionsServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, "GET", r.Method) + assert.Equal(t, "application/json", r.Header.Get("Content-Type")) + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + _, err := w.Write(channelDefinitionsJSON) + require.NoError(t, err) + })) + t.Cleanup(channelDefinitionsServer.Close) + return channelDefinitionsServer.URL, channelDefinitionsSHA +} diff --git a/core/services/ocr2/plugins/llo/onchain_channel_definition_cache_integration_test.go b/core/services/ocr2/plugins/llo/onchain_channel_definition_cache_integration_test.go index ec918e2375d..30460d4e6af 100644 --- a/core/services/ocr2/plugins/llo/onchain_channel_definition_cache_integration_test.go +++ b/core/services/ocr2/plugins/llo/onchain_channel_definition_cache_integration_test.go @@ -129,7 +129,7 @@ func Test_ChannelDefinitionCache_Integration(t *testing.T) { lggr, observedLogs := logger.TestLoggerObserved(t, zapcore.DebugLevel) db := pgtest.NewSqlxDB(t) const ETHMainnetChainSelector uint64 = 5009297550715157269 - orm := llo.NewORM(db, ETHMainnetChainSelector) + orm := llo.NewChainScopedORM(db, ETHMainnetChainSelector) steve := testutils.MustNewSimTransactor(t) // config contract deployer and owner genesisData := core.GenesisAlloc{steve.From: {Balance: assets.Ether(1000).ToInt()}} diff --git a/core/services/relay/dummy/llo_provider.go b/core/services/relay/dummy/llo_provider.go index 88f72588157..1e4c4952577 100644 --- a/core/services/relay/dummy/llo_provider.go +++ b/core/services/relay/dummy/llo_provider.go @@ -25,6 +25,7 @@ type lloProvider struct { transmitter Transmitter logger logger.Logger channelDefinitionCache llotypes.ChannelDefinitionCache + shouldRetireCache llotypes.ShouldRetireCache ms services.MultiStart } @@ -34,12 +35,14 @@ func NewLLOProvider( cp commontypes.ConfigProvider, transmitter Transmitter, channelDefinitionCache llotypes.ChannelDefinitionCache, + shouldRetireCache llotypes.ShouldRetireCache, ) relaytypes.LLOProvider { return &lloProvider{ cp, transmitter, lggr.Named("LLOProvider"), channelDefinitionCache, + shouldRetireCache, services.MultiStart{}, } } @@ -69,8 +72,8 @@ func (p *lloProvider) HealthReport() map[string]error { return report } -func (p *lloProvider) ContractConfigTracker() ocrtypes.ContractConfigTracker { - return p.cp.ContractConfigTracker() +func (p *lloProvider) ContractConfigTrackers() (cps []ocrtypes.ContractConfigTracker) { + return []ocrtypes.ContractConfigTracker{p.cp.ContractConfigTracker()} } func (p *lloProvider) OffchainConfigDigester() ocrtypes.OffchainConfigDigester { @@ -84,3 +87,7 @@ func (p *lloProvider) ContractTransmitter() llotypes.Transmitter { func (p *lloProvider) ChannelDefinitionCache() llotypes.ChannelDefinitionCache { return p.channelDefinitionCache } + +func (p *lloProvider) ShouldRetireCache() llotypes.ShouldRetireCache { + return p.shouldRetireCache +} diff --git a/core/services/relay/dummy/relayer.go b/core/services/relay/dummy/relayer.go index a9b90b9a2f5..3275272b46f 100644 --- a/core/services/relay/dummy/relayer.go +++ b/core/services/relay/dummy/relayer.go @@ -62,7 +62,8 @@ func (r *relayer) NewLLOProvider(ctx context.Context, rargs types.RelayArgs, par if err != nil { return nil, err } - return NewLLOProvider(r.lggr, cp, transmitter, cdc), nil + src := llo.NewNeverShouldRetireCache() + return NewLLOProvider(r.lggr, cp, transmitter, cdc, src), nil } func (r *relayer) LatestHead(_ context.Context) (types.Head, error) { return types.Head{}, nil diff --git a/core/services/relay/evm/evm.go b/core/services/relay/evm/evm.go index 0f270328bb9..19be4b670b3 100644 --- a/core/services/relay/evm/evm.go +++ b/core/services/relay/evm/evm.go @@ -153,7 +153,8 @@ type Relayer struct { triggerCapability *triggers.MercuryTriggerService // LLO/data streams - cdcFactory func() (llo.ChannelDefinitionCacheFactory, error) + cdcFactory func() (llo.ChannelDefinitionCacheFactory, error) + retirementReportCache llo.RetirementReportCache } type CSAETHKeystore interface { @@ -164,10 +165,11 @@ type CSAETHKeystore interface { type RelayerOpts struct { DS sqlutil.DataSource CSAETHKeystore - MercuryPool wsrpc.Pool - TransmitterConfig mercury.TransmitterConfig - CapabilitiesRegistry coretypes.CapabilitiesRegistry - HTTPClient *http.Client + MercuryPool wsrpc.Pool + RetirementReportCache llo.RetirementReportCache + TransmitterConfig mercury.TransmitterConfig + CapabilitiesRegistry coretypes.CapabilitiesRegistry + HTTPClient *http.Client } func (c RelayerOpts) Validate() error { @@ -199,19 +201,20 @@ func NewRelayer(ctx context.Context, lggr logger.Logger, chain legacyevm.Chain, if err != nil { return nil, fmt.Errorf("failed to get chain selector for chain id %s: %w", chain.ID(), err) } - lloORM := llo.NewORM(opts.DS, chainSelector) + lloORM := llo.NewChainScopedORM(opts.DS, chainSelector) return llo.NewChannelDefinitionCacheFactory(sugared, lloORM, chain.LogPoller(), opts.HTTPClient), nil }) relayer := &Relayer{ - ds: opts.DS, - chain: chain, - lggr: sugared, - ks: opts.CSAETHKeystore, - mercuryPool: opts.MercuryPool, - cdcFactory: cdcFactory, - mercuryORM: mercuryORM, - transmitterCfg: opts.TransmitterConfig, - capabilitiesRegistry: opts.CapabilitiesRegistry, + ds: opts.DS, + chain: chain, + lggr: sugared, + ks: opts.CSAETHKeystore, + mercuryPool: opts.MercuryPool, + cdcFactory: cdcFactory, + retirementReportCache: opts.RetirementReportCache, + mercuryORM: mercuryORM, + transmitterCfg: opts.TransmitterConfig, + capabilitiesRegistry: opts.CapabilitiesRegistry, } // Initialize write target capability if configuration is defined @@ -495,8 +498,10 @@ func (r *Relayer) NewLLOProvider(ctx context.Context, rargs commontypes.RelayArg if relayConfig.LLODONID == 0 { return nil, errors.New("donID must be specified in relayConfig for LLO jobs") } - if relayConfig.LLOConfigMode != types.LLOConfigModeMercury { - return nil, fmt.Errorf("LLOConfigMode must be specified in relayConfig for LLO jobs (only %q is currently supported)", types.LLOConfigModeMercury) + switch relayConfig.LLOConfigMode { + case types.LLOConfigModeMercury, types.LLOConfigModeBlueGreen: + default: + return nil, fmt.Errorf("LLOConfigMode must be specified in relayConfig for LLO jobs (only %q or %q is currently supported)", types.LLOConfigModeMercury, types.LLOConfigModeBlueGreen) } var lloCfg lloconfig.PluginConfig @@ -506,15 +511,19 @@ func (r *Relayer) NewLLOProvider(ctx context.Context, rargs commontypes.RelayArg if err := lloCfg.Validate(); err != nil { return nil, err } - + relayConfig, err := relayOpts.RelayConfig() + if err != nil { + return nil, fmt.Errorf("failed to get relay config: %w", err) + } + if relayConfig.LLODONID == 0 { + return nil, errors.New("donID must be specified in relayConfig for LLO jobs") + } + if relayConfig.LLOConfigMode == "" { + return nil, fmt.Errorf("LLOConfigMode must be specified in relayConfig for LLO jobs (can be either: %q or %q)", types.LLOConfigModeMercury, types.LLOConfigModeBlueGreen) + } if relayConfig.ChainID.String() != r.chain.ID().String() { return nil, fmt.Errorf("internal error: chain id in spec does not match this relayer's chain: have %s expected %s", relayConfig.ChainID.String(), r.chain.ID().String()) } - cp, err := newLLOConfigProvider(ctx, r.lggr, r.chain, relayOpts) - if err != nil { - return nil, pkgerrors.WithStack(err) - } - if !relayConfig.EffectiveTransmitterID.Valid { return nil, pkgerrors.New("EffectiveTransmitterID must be specified") } @@ -549,6 +558,7 @@ func (r *Relayer) NewLLOProvider(ctx context.Context, rargs commontypes.RelayArg DonID: relayConfig.LLODONID, ORM: mercurytransmitter.NewORM(r.ds, relayConfig.LLODONID), }, + RetirementReportCache: r.retirementReportCache, }) } @@ -560,7 +570,9 @@ func (r *Relayer) NewLLOProvider(ctx context.Context, rargs commontypes.RelayArg if err != nil { return nil, err } - return NewLLOProvider(cp, transmitter, r.lggr, cdc), nil + + configuratorAddress := common.HexToAddress(relayOpts.ContractID) + return NewLLOProvider(context.Background(), transmitter, r.lggr, r.retirementReportCache, r.chain, configuratorAddress, cdc, relayConfig, relayOpts) } func (r *Relayer) NewFunctionsProvider(ctx context.Context, rargs commontypes.RelayArgs, pargs commontypes.PluginArgs) (commontypes.FunctionsProvider, error) { @@ -600,7 +612,10 @@ func (r *Relayer) NewConfigProvider(ctx context.Context, args commontypes.RelayA case "mercury": configProvider, err = newMercuryConfigProvider(ctx, lggr, r.chain, relayOpts) case "llo": - configProvider, err = newLLOConfigProvider(ctx, lggr, r.chain, relayOpts) + // Use NullRetirementReportCache since we never run LLO jobs on + // bootstrap nodes, and there's no need to introduce a failure mode or + // performance hit no matter how minor. + configProvider, err = newLLOConfigProvider(ctx, lggr, r.chain, &llo.NullRetirementReportCache{}, relayOpts) case "ocr3-capability": configProvider, err = newOCR3CapabilityConfigProvider(ctx, r.lggr, r.chain, relayOpts) default: @@ -671,7 +686,7 @@ func newConfigWatcher(lggr logger.Logger, func (c *configWatcher) start(ctx context.Context) error { if c.runReplay && c.fromBlock != 0 { - // Only replay if it's a brand runReplay job. + // Only replay if it's a brand new job. c.eng.Go(func(ctx context.Context) { c.eng.Infow("starting replay for config", "fromBlock", c.fromBlock) if err := c.configPoller.Replay(ctx, int64(c.fromBlock)); err != nil { diff --git a/core/services/relay/evm/llo/abi.go b/core/services/relay/evm/llo/abi.go new file mode 100644 index 00000000000..3de25709eaa --- /dev/null +++ b/core/services/relay/evm/llo/abi.go @@ -0,0 +1,29 @@ +package llo + +import ( + "strings" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/llo-feeds/generated/configurator" +) + +var ( + ProductionConfigSet common.Hash + StagingConfigSet common.Hash + PromoteStagingConfig common.Hash + + configuratorABI abi.ABI +) + +func init() { + var err error + configuratorABI, err = abi.JSON(strings.NewReader(configurator.ConfiguratorABI)) + if err != nil { + panic(err) + } + ProductionConfigSet = configuratorABI.Events["ProductionConfigSet"].ID + StagingConfigSet = configuratorABI.Events["StagingConfigSet"].ID + PromoteStagingConfig = configuratorABI.Events["PromoteStagingConfig"].ID +} diff --git a/core/services/relay/evm/llo/config_poller.go b/core/services/relay/evm/llo/config_poller.go new file mode 100644 index 00000000000..feeff015862 --- /dev/null +++ b/core/services/relay/evm/llo/config_poller.go @@ -0,0 +1,276 @@ +package llo + +import ( + "bytes" + "context" + "database/sql" + "fmt" + "math" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/pkg/errors" + ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink-common/pkg/services" + + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/llo-feeds/generated/configurator" +) + +type InstanceType string + +const ( + InstanceTypeBlue InstanceType = InstanceType("Blue") + InstanceTypeGreen InstanceType = InstanceType("Green") +) + +type ConfigPollerService interface { + services.Service + ocrtypes.ContractConfigTracker +} + +type LogPoller interface { + IndexedLogsByBlockRange(ctx context.Context, start, end int64, eventSig common.Hash, address common.Address, topicIndex int, topicValues []common.Hash) ([]logpoller.Log, error) + LatestBlock(ctx context.Context) (logpoller.LogPollerBlock, error) + LogsWithSigs(ctx context.Context, start, end int64, eventSigs []common.Hash, address common.Address) ([]logpoller.Log, error) +} + +// ConfigCache is most likely the global RetirementReportCache. Every config +// ever seen by this tracker will be stored at least once in the cache. +type ConfigCache interface { + StoreConfig(ctx context.Context, cd ocrtypes.ConfigDigest, signers [][]byte, f uint8) error +} + +type configPoller struct { + services.Service + eng *services.Engine + + lp LogPoller + cc ConfigCache + addr common.Address + donID uint32 + donIDHash [32]byte + + fromBlock uint64 + + instanceType InstanceType +} + +func DonIDToBytes32(donID uint32) [32]byte { + var b [32]byte + copy(b[:], common.LeftPadBytes(big.NewInt(int64(donID)).Bytes(), 32)) + return b +} + +// NewConfigPoller creates a new LLOConfigPoller +func NewConfigPoller(lggr logger.Logger, lp LogPoller, cc ConfigCache, addr common.Address, donID uint32, instanceType InstanceType, fromBlock uint64) ConfigPollerService { + return newConfigPoller(lggr, lp, cc, addr, donID, instanceType, fromBlock) +} + +func newConfigPoller(lggr logger.Logger, lp LogPoller, cc ConfigCache, addr common.Address, donID uint32, instanceType InstanceType, fromBlock uint64) *configPoller { + cp := &configPoller{ + lp: lp, + cc: cc, + addr: addr, + donID: donID, + donIDHash: DonIDToBytes32(donID), + instanceType: instanceType, + fromBlock: fromBlock, + } + cp.Service, cp.eng = services.Config{ + Name: "LLOConfigPoller", + }.NewServiceEngine(logger.Sugared(lggr).Named(string(instanceType)).With("instanceType", instanceType)) + + return cp +} + +func (cp *configPoller) Notify() <-chan struct{} { + return nil // rely on libocr's builtin config polling +} + +// LatestConfigDetails returns the latest config details from the logs +func (cp *configPoller) LatestConfigDetails(ctx context.Context) (changedInBlock uint64, configDigest ocrtypes.ConfigDigest, err error) { + latestConfig, log, err := cp.latestConfig(ctx, int64(cp.fromBlock), math.MaxInt64) // #nosec G115 + if err != nil { + return 0, ocrtypes.ConfigDigest{}, fmt.Errorf("failed to get latest config: %w", err) + } + return uint64(log.BlockNumber), latestConfig.ConfigDigest, nil +} + +func (cp *configPoller) latestConfig(ctx context.Context, fromBlock, toBlock int64) (latestConfig FullConfigFromLog, latestLog logpoller.Log, err error) { + // Get all config set logs run through them forwards + // TODO: This could probably be optimized with a 'latestBlockNumber' cache or something to avoid reading from `fromBlock` on every call + // TODO: Actually we only care about the latest of each type here + // MERC-3524 + logs, err := cp.lp.LogsWithSigs(ctx, fromBlock, toBlock, []common.Hash{ProductionConfigSet, StagingConfigSet}, cp.addr) + if err != nil { + return latestConfig, latestLog, fmt.Errorf("failed to get logs: %w", err) + } + for _, log := range logs { + // TODO: This can be optimized probably by adding donIDHash to the logpoller lookup + // MERC-3524 + if !bytes.Equal(log.Topics[1], cp.donIDHash[:]) { + continue + } + switch log.EventSig { + case ProductionConfigSet: + event, err := DecodeProductionConfigSetLog(log.Data) + if err != nil { + return latestConfig, log, fmt.Errorf("failed to unpack ProductionConfigSet log data: %w", err) + } + + if err = cp.cc.StoreConfig(ctx, event.ConfigDigest, event.Signers, event.F); err != nil { + cp.eng.SugaredLogger.Errorf("failed to store production config: %v", err) + } + + isProduction := (cp.instanceType != InstanceTypeBlue) == event.IsGreenProduction + if isProduction { + latestLog = log + latestConfig, err = FullConfigFromProductionConfigSet(event) + if err != nil { + return latestConfig, latestLog, fmt.Errorf("FullConfigFromProductionConfigSet failed: %w", err) + } + } + case StagingConfigSet: + event, err := DecodeStagingConfigSetLog(log.Data) + if err != nil { + return latestConfig, latestLog, fmt.Errorf("failed to unpack ProductionConfigSet log data: %w", err) + } + + if err = cp.cc.StoreConfig(ctx, event.ConfigDigest, event.Signers, event.F); err != nil { + cp.eng.SugaredLogger.Errorf("failed to store staging config: %v", err) + } + + isProduction := (cp.instanceType != InstanceTypeBlue) == event.IsGreenProduction + if !isProduction { + latestLog = log + latestConfig, err = FullConfigFromStagingConfigSet(event) + if err != nil { + return latestConfig, latestLog, fmt.Errorf("FullConfigFromStagingConfigSet failed: %w", err) + } + } + default: + // ignore unknown log types + continue + } + } + + return +} + +// LatestConfig returns the latest config from the logs starting from a certain block +func (cp *configPoller) LatestConfig(ctx context.Context, changedInBlock uint64) (ocrtypes.ContractConfig, error) { + cfg, _, err := cp.latestConfig(ctx, int64(changedInBlock), math.MaxInt64) // #nosec G115 + if err != nil { + return ocrtypes.ContractConfig{}, fmt.Errorf("failed to get latest config: %w", err) + } + return cfg.ContractConfig, nil +} + +// LatestBlockHeight returns the latest block height from the logs +func (cp *configPoller) LatestBlockHeight(ctx context.Context) (blockHeight uint64, err error) { + latest, err := cp.lp.LatestBlock(ctx) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + return 0, nil + } + return 0, err + } + return uint64(latest.BlockNumber), nil +} + +func (cp *configPoller) InstanceType() InstanceType { + return cp.instanceType +} + +// FullConfigFromLog defines the contract config with the donID +type FullConfigFromLog struct { + ocrtypes.ContractConfig + donID uint32 +} + +func FullConfigFromProductionConfigSet(unpacked configurator.ConfiguratorProductionConfigSet) (FullConfigFromLog, error) { + var transmitAccounts []ocrtypes.Account + for _, addr := range unpacked.OffchainTransmitters { + transmitAccounts = append(transmitAccounts, ocrtypes.Account(fmt.Sprintf("%x", addr))) + } + var signers []ocrtypes.OnchainPublicKey + for _, addr := range unpacked.Signers { + addr := addr + signers = append(signers, addr[:]) + } + + donIDBig := common.Hash(unpacked.ConfigId).Big() + if donIDBig.Cmp(big.NewInt(math.MaxUint32)) > 0 { + return FullConfigFromLog{}, errors.Errorf("donID %s is too large", donIDBig) + } + donID := uint32(donIDBig.Uint64()) // #nosec G115 + + return FullConfigFromLog{ + donID: donID, + ContractConfig: ocrtypes.ContractConfig{ + ConfigDigest: unpacked.ConfigDigest, + ConfigCount: unpacked.ConfigCount, + Signers: signers, + Transmitters: transmitAccounts, + F: unpacked.F, + OnchainConfig: unpacked.OnchainConfig, + OffchainConfigVersion: unpacked.OffchainConfigVersion, + OffchainConfig: unpacked.OffchainConfig, + }, + }, nil +} + +func FullConfigFromStagingConfigSet(unpacked configurator.ConfiguratorStagingConfigSet) (FullConfigFromLog, error) { + var transmitAccounts []ocrtypes.Account + for _, addr := range unpacked.OffchainTransmitters { + transmitAccounts = append(transmitAccounts, ocrtypes.Account(fmt.Sprintf("%x", addr))) + } + var signers []ocrtypes.OnchainPublicKey + for _, addr := range unpacked.Signers { + addr := addr + signers = append(signers, addr[:]) + } + + donIDBig := common.Hash(unpacked.ConfigId).Big() + if donIDBig.Cmp(big.NewInt(math.MaxUint32)) > 0 { + return FullConfigFromLog{}, errors.Errorf("donID %s is too large", donIDBig) + } + donID := uint32(donIDBig.Uint64()) // #nosec G115 + + return FullConfigFromLog{ + donID: donID, + ContractConfig: ocrtypes.ContractConfig{ + ConfigDigest: unpacked.ConfigDigest, + ConfigCount: unpacked.ConfigCount, + Signers: signers, + Transmitters: transmitAccounts, + F: unpacked.F, + OnchainConfig: unpacked.OnchainConfig, + OffchainConfigVersion: unpacked.OffchainConfigVersion, + OffchainConfig: unpacked.OffchainConfig, + }, + }, nil +} + +func DecodeProductionConfigSetLog(d []byte) (configurator.ConfiguratorProductionConfigSet, error) { + unpacked := new(configurator.ConfiguratorProductionConfigSet) + + err := configuratorABI.UnpackIntoInterface(unpacked, "ProductionConfigSet", d) + if err != nil { + return configurator.ConfiguratorProductionConfigSet{}, errors.Wrap(err, "failed to unpack log data") + } + return *unpacked, nil +} + +func DecodeStagingConfigSetLog(d []byte) (configurator.ConfiguratorStagingConfigSet, error) { + unpacked := new(configurator.ConfiguratorStagingConfigSet) + + err := configuratorABI.UnpackIntoInterface(unpacked, "StagingConfigSet", d) + if err != nil { + return configurator.ConfiguratorStagingConfigSet{}, errors.Wrap(err, "failed to unpack log data") + } + return *unpacked, nil +} diff --git a/core/services/relay/evm/llo/config_poller_test.go b/core/services/relay/evm/llo/config_poller_test.go new file mode 100644 index 00000000000..c1430b7c150 --- /dev/null +++ b/core/services/relay/evm/llo/config_poller_test.go @@ -0,0 +1,400 @@ +package llo + +import ( + "context" + "math/rand/v2" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/lib/pq" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + ocr2types "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + + "github.com/smartcontractkit/chainlink-common/pkg/logger" + + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" +) + +var _ LogPoller = (*mockLogPoller)(nil) + +type mockLogPoller struct { + logs []logpoller.Log + latestBlock int64 +} + +func (m *mockLogPoller) LatestBlock(ctx context.Context) (logpoller.LogPollerBlock, error) { + return logpoller.LogPollerBlock{BlockNumber: m.latestBlock}, nil +} +func (m *mockLogPoller) RegisterFilter(ctx context.Context, filter logpoller.Filter) error { + return nil +} +func (m *mockLogPoller) Replay(ctx context.Context, fromBlock int64) error { + return nil +} +func (m *mockLogPoller) LogsWithSigs(ctx context.Context, start, end int64, eventSigs []common.Hash, address common.Address) ([]logpoller.Log, error) { + logs := make([]logpoller.Log, 0) + for _, log := range m.logs { + if log.BlockNumber >= start && log.BlockNumber <= end && log.Address == address { + for _, sig := range eventSigs { + if log.EventSig == sig { + logs = append(logs, log) + } + } + } + } + + return logs, nil +} +func (m *mockLogPoller) IndexedLogsByBlockRange(ctx context.Context, start, end int64, eventSig common.Hash, address common.Address, topicIndex int, topicValues []common.Hash) ([]logpoller.Log, error) { + return m.LogsWithSigs(ctx, start, end, []common.Hash{eventSig}, address) +} + +type cfg struct { + cd ocrtypes.ConfigDigest + signers [][]byte + f uint8 +} + +type mockConfigCache struct { + configs map[ocrtypes.ConfigDigest]cfg +} + +func (m *mockConfigCache) StoreConfig(ctx context.Context, cd ocrtypes.ConfigDigest, signers [][]byte, f uint8) error { + m.configs[cd] = cfg{cd, signers, f} + return nil +} + +func Test_ConfigPoller(t *testing.T) { + ctx := testutils.Context(t) + lggr := logger.Test(t) + lp := &mockLogPoller{make([]logpoller.Log, 0), 0} + addr := common.Address{1} + donID := uint32(1) + donIDHash := DonIDToBytes32(donID) + fromBlock := uint64(1) + + cc := &mockConfigCache{make(map[ocrtypes.ConfigDigest]cfg)} + + cpBlue := newConfigPoller(lggr, lp, cc, addr, donID, InstanceTypeBlue, fromBlock) + cpGreen := newConfigPoller(lggr, lp, cc, addr, donID, InstanceTypeGreen, fromBlock) + + cfgCount := uint64(0) + + signers := [][]byte{(common.Address{1}).Bytes(), (common.Address{2}).Bytes()} + transmitters := []common.Hash{common.Hash{1}, common.Hash{2}} + f := uint8(1) + onchainConfig := []byte{5} + offchainConfigVersion := uint64(6) + offchainConfig := []byte{7} + + t.Run("Blue/Green config poller follow production and staging configs respectively initially", func(t *testing.T) { + t.Run("LatestConfigDetails", func(t *testing.T) { + t.Run("without any logs, returns zero values", func(t *testing.T) { + changedInBlock, digest, err := cpBlue.LatestConfigDetails(ctx) + require.NoError(t, err) + + assert.Equal(t, uint64(0), changedInBlock) + assert.Equal(t, ocr2types.ConfigDigest{}, digest) + + changedInBlock, digest, err = cpGreen.LatestConfigDetails(ctx) + require.NoError(t, err) + + assert.Equal(t, uint64(0), changedInBlock) + assert.Equal(t, ocr2types.ConfigDigest{}, digest) + }) + + t.Run("with isGreenProduction=false", func(t *testing.T) { + isGreenProduction := false + + t.Run("with ProductionConfigSet event, blue starts returning the production config (green still returns zeroes)", func(t *testing.T) { + cfgCount++ + lp.logs = append(lp.logs, logpoller.Log{Topics: pq.ByteaArray{ProductionConfigSet[:], donIDHash[:]}, Address: addr, EventSig: ProductionConfigSet, BlockNumber: 100, Data: makeConfigSetLogData( + t, + 0, + common.Hash{1}, + cfgCount, + signers, + transmitters, + f, + onchainConfig, + offchainConfigVersion, + offchainConfig, + isGreenProduction, + )}) + + changedInBlock, digest, err := cpBlue.LatestConfigDetails(ctx) + require.NoError(t, err) + + assert.Equal(t, uint64(100), changedInBlock) + assert.Equal(t, ocr2types.ConfigDigest{1}, digest) + + changedInBlock, digest, err = cpGreen.LatestConfigDetails(ctx) + require.NoError(t, err) + + assert.Equal(t, uint64(0), changedInBlock) + assert.Equal(t, ocr2types.ConfigDigest{}, digest) + }) + t.Run("with StagingConfigSet event, green starts returning the staging config (blue still returns the production config)", func(t *testing.T) { + cfgCount++ + lp.logs = append(lp.logs, logpoller.Log{Topics: pq.ByteaArray{StagingConfigSet[:], donIDHash[:]}, Address: addr, EventSig: StagingConfigSet, BlockNumber: 101, Data: makeConfigSetLogData( + t, + 0, + common.Hash{2}, + cfgCount, + signers, + transmitters, + f, + onchainConfig, + offchainConfigVersion, + offchainConfig, + isGreenProduction, + )}) + changedInBlock, digest, err := cpBlue.LatestConfigDetails(ctx) + require.NoError(t, err) + + assert.Equal(t, uint64(100), changedInBlock) + assert.Equal(t, ocr2types.ConfigDigest{1}, digest) + + changedInBlock, digest, err = cpGreen.LatestConfigDetails(ctx) + require.NoError(t, err) + + assert.Equal(t, uint64(101), changedInBlock) + assert.Equal(t, ocr2types.ConfigDigest{2}, digest) + }) + }) + + t.Run("with isGreenProduction=true (after PromoteStagingConfig)", func(t *testing.T) { + isGreenProduction := true + t.Run("if we ProductionConfigSet again, it now affects green since that is the production instance (blue remains unchanged)", func(t *testing.T) { + cfgCount++ + lp.logs = append(lp.logs, logpoller.Log{Topics: pq.ByteaArray{ProductionConfigSet[:], donIDHash[:]}, Address: addr, EventSig: ProductionConfigSet, BlockNumber: 103, Data: makeConfigSetLogData( + t, + 0, + common.Hash{3}, + cfgCount, + signers, + transmitters, + f, + onchainConfig, + offchainConfigVersion, + offchainConfig, + isGreenProduction, + )}) + + changedInBlock, digest, err := cpBlue.LatestConfigDetails(ctx) + require.NoError(t, err) + + assert.Equal(t, uint64(100), changedInBlock) + assert.Equal(t, ocr2types.ConfigDigest{1}, digest) + + changedInBlock, digest, err = cpGreen.LatestConfigDetails(ctx) + require.NoError(t, err) + + assert.Equal(t, uint64(103), changedInBlock) + assert.Equal(t, ocr2types.ConfigDigest{3}, digest) + }) + + t.Run("if we StagingConfigSet again, it now affects blue since that is the staging instance (green remains unchanged)", func(t *testing.T) { + cfgCount++ + lp.logs = append(lp.logs, logpoller.Log{Topics: pq.ByteaArray{StagingConfigSet[:], donIDHash[:]}, Address: addr, EventSig: StagingConfigSet, BlockNumber: 104, Data: makeConfigSetLogData( + t, + 0, + common.Hash{4}, + cfgCount, + signers, + transmitters, + f, + onchainConfig, + offchainConfigVersion, + offchainConfig, + isGreenProduction, + )}) + + changedInBlock, digest, err := cpBlue.LatestConfigDetails(ctx) + require.NoError(t, err) + + assert.Equal(t, uint64(104), changedInBlock) + assert.Equal(t, ocr2types.ConfigDigest{4}, digest) + + changedInBlock, digest, err = cpGreen.LatestConfigDetails(ctx) + require.NoError(t, err) + + assert.Equal(t, uint64(103), changedInBlock) + assert.Equal(t, ocr2types.ConfigDigest{3}, digest) + }) + + t.Run("if we StagingConfigSet and ProductionConfigSet again, it sets the config for blue and green respectively", func(t *testing.T) { + cfgCount++ + lp.logs = append(lp.logs, logpoller.Log{Topics: pq.ByteaArray{StagingConfigSet[:], donIDHash[:]}, Address: addr, EventSig: StagingConfigSet, BlockNumber: 105, Data: makeConfigSetLogData( + t, + 0, + common.Hash{5}, + cfgCount, + signers, + transmitters, + f, + onchainConfig, + offchainConfigVersion, + offchainConfig, + isGreenProduction, + )}) + cfgCount++ + lp.logs = append(lp.logs, logpoller.Log{Topics: pq.ByteaArray{ProductionConfigSet[:], donIDHash[:]}, Address: addr, EventSig: ProductionConfigSet, BlockNumber: 106, Data: makeConfigSetLogData( + t, + 0, + common.Hash{6}, + cfgCount, + signers, + transmitters, + f, + onchainConfig, + offchainConfigVersion, + offchainConfig, + isGreenProduction, + )}) + + changedInBlock, digest, err := cpBlue.LatestConfigDetails(ctx) + require.NoError(t, err) + + assert.Equal(t, uint64(105), changedInBlock) + assert.Equal(t, ocr2types.ConfigDigest{5}, digest) + + changedInBlock, digest, err = cpGreen.LatestConfigDetails(ctx) + require.NoError(t, err) + + assert.Equal(t, uint64(106), changedInBlock) + assert.Equal(t, ocr2types.ConfigDigest{6}, digest) + }) + }) + t.Run("isGreenProduction=false again (another PromoteStagingConfig", func(t *testing.T) { + isGreenProduction := false + t.Run("if we PromoteStagingConfig again it re-flips the instances", func(t *testing.T) { + cfgCount++ + lp.logs = append(lp.logs, logpoller.Log{LogIndex: 1, Topics: pq.ByteaArray{ProductionConfigSet[:], donIDHash[:]}, Address: addr, EventSig: ProductionConfigSet, BlockNumber: 107, Data: makeConfigSetLogData( + t, + 0, + common.Hash{7}, + cfgCount, + signers, + transmitters, + f, + onchainConfig, + offchainConfigVersion, + offchainConfig, + isGreenProduction, + )}) + cfgCount++ + lp.logs = append(lp.logs, logpoller.Log{LogIndex: 2, Topics: pq.ByteaArray{StagingConfigSet[:], donIDHash[:]}, Address: addr, EventSig: StagingConfigSet, BlockNumber: 107, Data: makeConfigSetLogData( + t, + 0, + common.Hash{8}, + cfgCount, + signers, + transmitters, + f, + onchainConfig, + offchainConfigVersion, + offchainConfig, + isGreenProduction, + )}) + + changedInBlock, digest, err := cpBlue.LatestConfigDetails(ctx) + require.NoError(t, err) + + assert.Equal(t, uint64(107), changedInBlock) + assert.Equal(t, ocr2types.ConfigDigest{7}, digest) + + changedInBlock, digest, err = cpGreen.LatestConfigDetails(ctx) + require.NoError(t, err) + + assert.Equal(t, uint64(107), changedInBlock) + assert.Equal(t, ocr2types.ConfigDigest{8}, digest) + }) + }) + t.Run("Stores all seen configs in cache", func(t *testing.T) { + require.Len(t, cc.configs, 8) + for i := 1; i <= 8; i++ { + assert.Contains(t, cc.configs, ocr2types.ConfigDigest{byte(i)}) + assert.Equal(t, cfg{ + cd: ocr2types.ConfigDigest{byte(i)}, + signers: signers, + f: f, + }, cc.configs[ocr2types.ConfigDigest{byte(i)}]) + } + }) + }) + t.Run("LatestConfig", func(t *testing.T) { + t.Run("changedInBlock in future, returns nothing", func(t *testing.T) { + cfg, err := cpBlue.LatestConfig(ctx, 200) + require.NoError(t, err) + assert.Zero(t, cfg) + + cfg, err = cpGreen.LatestConfig(ctx, 200) + require.NoError(t, err) + assert.Zero(t, cfg) + }) + t.Run("changedInBlock corresponds to a block in which a log was emitted, returns the config", func(t *testing.T) { + expectedSigners := []ocr2types.OnchainPublicKey{ocr2types.OnchainPublicKey{0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, ocr2types.OnchainPublicKey{0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}} + expectedTransmitters := []ocr2types.Account{"0100000000000000000000000000000000000000000000000000000000000000", "0200000000000000000000000000000000000000000000000000000000000000"} + + cfg, err := cpBlue.LatestConfig(ctx, 107) + require.NoError(t, err) + assert.Equal(t, ocr2types.ConfigDigest{7}, cfg.ConfigDigest) + assert.Equal(t, uint64(7), cfg.ConfigCount) + assert.Equal(t, expectedSigners, cfg.Signers) + assert.Equal(t, expectedTransmitters, cfg.Transmitters) + assert.Equal(t, f, cfg.F) + assert.Equal(t, onchainConfig, cfg.OnchainConfig) + assert.Equal(t, offchainConfigVersion, cfg.OffchainConfigVersion) + assert.Equal(t, offchainConfig, cfg.OffchainConfig) + + cfg, err = cpGreen.LatestConfig(ctx, 107) + require.NoError(t, err) + assert.Equal(t, ocr2types.ConfigDigest{8}, cfg.ConfigDigest) + assert.Equal(t, uint64(8), cfg.ConfigCount) + assert.Equal(t, expectedSigners, cfg.Signers) + assert.Equal(t, expectedTransmitters, cfg.Transmitters) + assert.Equal(t, f, cfg.F) + assert.Equal(t, onchainConfig, cfg.OnchainConfig) + assert.Equal(t, offchainConfigVersion, cfg.OffchainConfigVersion) + assert.Equal(t, offchainConfig, cfg.OffchainConfig) + }) + }) + t.Run("LatestBlockHeight", func(t *testing.T) { + t.Run("returns the latest block from log poller", func(t *testing.T) { + latest := rand.Int64() + lp.latestBlock = latest + + latestBlock, err := cpBlue.LatestBlockHeight(ctx) + require.NoError(t, err) + assert.Equal(t, uint64(latest), latestBlock) + + latestBlock, err = cpGreen.LatestBlockHeight(ctx) + require.NoError(t, err) + assert.Equal(t, uint64(latest), latestBlock) + }) + }) + }) +} + +func makeConfigSetLogData(t *testing.T, + previousConfigBlockNumber uint32, + configDigest common.Hash, + configCount uint64, + signers [][]byte, + offchainTransmitters []common.Hash, + f uint8, + onchainConfig []byte, + offchainConfigVersion uint64, + offchainConfig []byte, + isGreenProduction bool, +) []byte { + event := configuratorABI.Events["ProductionConfigSet"] + data, err := event.Inputs.NonIndexed().Pack(previousConfigBlockNumber, configDigest, configCount, signers, offchainTransmitters, f, onchainConfig, offchainConfigVersion, offchainConfig, isGreenProduction) + require.NoError(t, err) + return data +} diff --git a/core/services/relay/evm/llo/offchain_config_digester.go b/core/services/relay/evm/llo/offchain_config_digester.go new file mode 100644 index 00000000000..08356619a25 --- /dev/null +++ b/core/services/relay/evm/llo/offchain_config_digester.go @@ -0,0 +1,129 @@ +package llo + +import ( + "context" + "crypto/ed25519" + "encoding/binary" + "encoding/hex" + "fmt" + "math/big" + "strings" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/pkg/errors" + + "github.com/smartcontractkit/libocr/offchainreporting2/types" + ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + "github.com/smartcontractkit/wsrpc/credentials" + + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/llo-feeds/generated/exposed_configurator" +) + +// Originally sourced from: https://github.com/smartcontractkit/offchain-reporting/blob/991ebe1462fd56826a1ddfb34287d542acb2baee/lib/offchainreporting2/chains/evmutil/offchain_config_digester.go + +var _ ocrtypes.OffchainConfigDigester = OffchainConfigDigester{} + +func NewOffchainConfigDigester(configID common.Hash, chainID *big.Int, contractAddress common.Address, prefix ocrtypes.ConfigDigestPrefix) OffchainConfigDigester { + return OffchainConfigDigester{configID, chainID, contractAddress, prefix} +} + +type OffchainConfigDigester struct { + ConfigID common.Hash + ChainID *big.Int + ContractAddress common.Address + Prefix ocrtypes.ConfigDigestPrefix +} + +func (d OffchainConfigDigester) ConfigDigest(_ context.Context, cc ocrtypes.ContractConfig) (ocrtypes.ConfigDigest, error) { + onchainPubKeys := make([][]byte, len(cc.Signers)) + for i, signer := range cc.Signers { + // Onchainpubkeys can be anything + // TODO: Implement and enforce MultiChainKeyBundle format? + // MERC-3594 + onchainPubKeys[i] = signer + } + transmitters := []credentials.StaticSizedPublicKey{} + for i, transmitter := range cc.Transmitters { + if len(transmitter) != 2*ed25519.PublicKeySize { + return ocrtypes.ConfigDigest{}, errors.Errorf("%v-th evm transmitter should be a 64 character hex-encoded ed25519 public key, but got '%v' (%d chars)", i, transmitter, len(transmitter)) + } + var t credentials.StaticSizedPublicKey + b, err := hex.DecodeString(string(transmitter)) + if err != nil { + return ocrtypes.ConfigDigest{}, errors.Wrapf(err, "%v-th evm transmitter is not valid hex, got: %q", i, transmitter) + } + copy(t[:], b) + + transmitters = append(transmitters, t) + } + + return configDigest( + d.ConfigID, + d.ChainID, + d.ContractAddress, + cc.ConfigCount, + onchainPubKeys, + transmitters, + cc.F, + cc.OnchainConfig, + cc.OffchainConfigVersion, + cc.OffchainConfig, + d.Prefix, + ), nil +} + +func (d OffchainConfigDigester) ConfigDigestPrefix(context.Context) (ocrtypes.ConfigDigestPrefix, error) { + return d.Prefix, nil +} + +func makeConfigDigestArgs() abi.Arguments { + abi, err := abi.JSON(strings.NewReader(exposed_configurator.ExposedConfiguratorABI)) + if err != nil { + // assertion + panic(fmt.Sprintf("could not parse configurator ABI: %s", err.Error())) + } + return abi.Methods["exposedConfigDigestFromConfigData"].Inputs +} + +var configDigestArgs = makeConfigDigestArgs() + +func configDigest( + feedID common.Hash, + chainID *big.Int, + contractAddress common.Address, + configCount uint64, + onchainPubKeys [][]byte, + transmitters []credentials.StaticSizedPublicKey, + f uint8, + onchainConfig []byte, + offchainConfigVersion uint64, + offchainConfig []byte, + prefix types.ConfigDigestPrefix, +) types.ConfigDigest { + msg, err := configDigestArgs.Pack( + feedID, + chainID, + contractAddress, + configCount, + onchainPubKeys, + transmitters, + f, + onchainConfig, + offchainConfigVersion, + offchainConfig, + ) + if err != nil { + // assertion + panic(err) + } + rawHash := crypto.Keccak256(msg) + configDigest := types.ConfigDigest{} + if n := copy(configDigest[:], rawHash); n != len(configDigest) { + // assertion + panic("copy too little data") + } + binary.BigEndian.PutUint16(configDigest[:2], uint16(prefix)) + return configDigest +} diff --git a/core/services/relay/evm/llo/offchain_config_digester_test.go b/core/services/relay/evm/llo/offchain_config_digester_test.go new file mode 100644 index 00000000000..65e888310c7 --- /dev/null +++ b/core/services/relay/evm/llo/offchain_config_digester_test.go @@ -0,0 +1,65 @@ +package llo + +import ( + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" +) + +func Test_OffchainConfigDigester_ConfigDigest(t *testing.T) { + ctx := tests.Context(t) + // ChainID and ContractAddress are taken into account for computation + cd1, err := OffchainConfigDigester{ChainID: big.NewInt(0)}.ConfigDigest(ctx, types.ContractConfig{}) + require.NoError(t, err) + cd2, err := OffchainConfigDigester{ChainID: big.NewInt(0)}.ConfigDigest(ctx, types.ContractConfig{}) + require.NoError(t, err) + cd3, err := OffchainConfigDigester{ChainID: big.NewInt(1)}.ConfigDigest(ctx, types.ContractConfig{}) + require.NoError(t, err) + cd4, err := OffchainConfigDigester{ChainID: big.NewInt(1), ContractAddress: common.Address{1}}.ConfigDigest(ctx, types.ContractConfig{}) + require.NoError(t, err) + + require.Equal(t, cd1, cd2) + require.NotEqual(t, cd2, cd3) + require.NotEqual(t, cd2, cd4) + require.NotEqual(t, cd3, cd4) + + configID := common.HexToHash("0x1") + chainID := big.NewInt(2) + addr := common.HexToAddress("0x3") + prefix := ocrtypes.ConfigDigestPrefix(4) + + digester := NewOffchainConfigDigester(configID, chainID, addr, prefix) + // any signers ok + _, err = digester.ConfigDigest(ctx, types.ContractConfig{ + Signers: []types.OnchainPublicKey{{1, 2}}, + }) + require.NoError(t, err) + + // malformed transmitters + _, err = digester.ConfigDigest(ctx, types.ContractConfig{ + Transmitters: []types.Account{"0x"}, + }) + require.Error(t, err) + + _, err = digester.ConfigDigest(ctx, types.ContractConfig{ + Transmitters: []types.Account{"7343581f55146951b0f678dc6cfa8fd360e2f353"}, + }) + require.Error(t, err) + + _, err = digester.ConfigDigest(ctx, types.ContractConfig{ + Transmitters: []types.Account{"7343581f55146951b0f678dc6cfa8fd360e2f353aabbccddeeffaaccddeeffaz"}, + }) + require.Error(t, err) + + // well-formed transmitters + _, err = digester.ConfigDigest(ctx, types.ContractConfig{ + Transmitters: []types.Account{"7343581f55146951b0f678dc6cfa8fd360e2f353aabbccddeeffaaccddeeffaa"}, + }) + require.NoError(t, err) +} diff --git a/core/services/relay/evm/llo/should_retire_cache.go b/core/services/relay/evm/llo/should_retire_cache.go new file mode 100644 index 00000000000..05b33a27fbb --- /dev/null +++ b/core/services/relay/evm/llo/should_retire_cache.go @@ -0,0 +1,120 @@ +package llo + +import ( + "bytes" + "context" + "math" + "sync" + "time" + + "github.com/ethereum/go-ethereum/common" + ocr2types "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink-common/pkg/services" + llotypes "github.com/smartcontractkit/chainlink-common/pkg/types/llo" +) + +type ShouldRetireCacheService interface { + services.Service + llotypes.ShouldRetireCache +} + +type shouldRetireCache struct { + services.Service + eng *services.Engine + + lp LogPoller + addr common.Address + donID uint32 + donIDHash common.Hash + + pollPeriod time.Duration + + mu sync.RWMutex + latestBlockNum int64 + m map[ocrtypes.ConfigDigest]struct{} +} + +func NewShouldRetireCache(lggr logger.Logger, lp LogPoller, addr common.Address, donID uint32) ShouldRetireCacheService { + return newShouldRetireCache(lggr, lp, addr, donID) +} + +func newShouldRetireCache(lggr logger.Logger, lp LogPoller, addr common.Address, donID uint32) *shouldRetireCache { + s := &shouldRetireCache{ + lp: lp, + addr: addr, + donID: donID, + donIDHash: DonIDToBytes32(donID), + m: make(map[ocrtypes.ConfigDigest]struct{}), + pollPeriod: 1 * time.Second, + } + s.Service, s.eng = services.Config{ + Name: "LLOShouldRetireCache", + Start: s.start, + }.NewServiceEngine(lggr) + + return s +} + +func (s *shouldRetireCache) ShouldRetire(digest ocr2types.ConfigDigest) (bool, error) { + s.mu.RLock() + defer s.mu.RUnlock() + _, exists := s.m[digest] + if exists { + s.eng.SugaredLogger.Debugw("ShouldRetire", "digest", digest, "shouldRetire", exists) + } + return exists, nil +} + +func (s *shouldRetireCache) start(ctx context.Context) error { + t := services.TickerConfig{ + Initial: 0, + JitterPct: services.DefaultJitter, + }.NewTicker(s.pollPeriod) + s.eng.GoTick(t, s.checkShouldRetire) + return nil +} + +func (s *shouldRetireCache) checkShouldRetire(ctx context.Context) { + fromBlock := s.latestBlockNum + 1 + logs, err := s.lp.LogsWithSigs(ctx, fromBlock, math.MaxInt64, []common.Hash{PromoteStagingConfig}, s.addr) + if err != nil { + s.eng.SugaredLogger.Errorw("checkShouldRetire: IndexedLogs", "err", err) + return + } + + for _, log := range logs { + // TODO: This can probably be optimized + // MERC-3524 + if !bytes.Equal(log.Topics[1], s.donIDHash[:]) { + continue + } + digestBytes := log.Topics[2] + digest, err := ocrtypes.BytesToConfigDigest(digestBytes) + if err != nil { + s.eng.SugaredLogger.Errorw("checkShouldRetire: BytesToConfigDigest failed", "err", err) + return + } + s.eng.SugaredLogger.Infow("markRetired: Got retired config digest", "blockNum", log.BlockNumber, "configDigest", digest) + s.markRetired(digest) + if log.BlockNumber > s.latestBlockNum { + s.latestBlockNum = log.BlockNumber + } + } +} + +func (s *shouldRetireCache) markRetired(configDigest ocrtypes.ConfigDigest) { + s.mu.RLock() + _, exists := s.m[configDigest] + s.mu.RUnlock() + if exists { + // already marked as retired + return + } + + s.mu.Lock() + defer s.mu.Unlock() + s.m[configDigest] = struct{}{} +} diff --git a/core/services/relay/evm/llo/should_retire_cache_test.go b/core/services/relay/evm/llo/should_retire_cache_test.go new file mode 100644 index 00000000000..2583ecc3c83 --- /dev/null +++ b/core/services/relay/evm/llo/should_retire_cache_test.go @@ -0,0 +1,50 @@ +package llo + +import ( + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/lib/pq" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap/zapcore" + + ocr2types "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink-common/pkg/services/servicetest" + + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" +) + +func Test_ShouldRetireCache(t *testing.T) { + lggr, observedLogs := logger.TestObserved(t, zapcore.DebugLevel) + lp := &mockLogPoller{make([]logpoller.Log, 0), 0} + addr := common.Address{1} + donID := uint32(1) + donIDHash := DonIDToBytes32(donID) + retiredConfigDigest := ocr2types.ConfigDigest{1, 2, 3, 4} + + lp.logs = append(lp.logs, logpoller.Log{Address: addr, Topics: pq.ByteaArray{PromoteStagingConfig[:], donIDHash[:], retiredConfigDigest[:]}, EventSig: PromoteStagingConfig, BlockNumber: 100, Data: makePromoteStagingConfigData(t, false)}) + + src := newShouldRetireCache(lggr, lp, addr, donID) + + servicetest.Run(t, src) + + testutils.WaitForLogMessage(t, observedLogs, "markRetired: Got retired config digest") + + shouldRetire, err := src.ShouldRetire(retiredConfigDigest) + require.NoError(t, err) + assert.True(t, shouldRetire, "Should retire") + shouldRetire, err = src.ShouldRetire(ocr2types.ConfigDigest{9}) + require.NoError(t, err) + assert.False(t, shouldRetire, "Should not retire") +} + +func makePromoteStagingConfigData(t *testing.T, isGreenProduction bool) []byte { + event := configuratorABI.Events["PromoteStagingConfig"] + data, err := event.Inputs.NonIndexed().Pack(isGreenProduction) + require.NoError(t, err) + return data +} diff --git a/core/services/relay/evm/llo_config_provider.go b/core/services/relay/evm/llo_config_provider.go index 0fe98d1cf71..99f2824a5c9 100644 --- a/core/services/relay/evm/llo_config_provider.go +++ b/core/services/relay/evm/llo_config_provider.go @@ -3,27 +3,53 @@ package evm import ( "context" "fmt" - "math/big" "github.com/ethereum/go-ethereum/common" "github.com/pkg/errors" - ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" "github.com/smartcontractkit/chainlink-common/pkg/logger" - + "github.com/smartcontractkit/chainlink-common/pkg/services" + commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" "github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm" - "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury" + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/llo" "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/types" ) -func DonIDToBytes32(donID uint32) [32]byte { - var b [32]byte - copy(b[:], common.LeftPadBytes(big.NewInt(int64(donID)).Bytes(), 32)) - return b +// This is only used for the bootstrap job + +var _ commontypes.ConfigProvider = (*lloConfigProvider)(nil) + +type lloConfigProvider struct { + services.Service + eng *services.Engine + + lp FilterRegisterer + cps []llo.ConfigPollerService + digester ocrtypes.OffchainConfigDigester + runReplay bool + replayFromBlock uint64 + + ms services.MultiStart +} + +func (l *lloConfigProvider) OffchainConfigDigester() ocrtypes.OffchainConfigDigester { + return l.digester +} +func (l *lloConfigProvider) ContractConfigTracker() ocrtypes.ContractConfigTracker { + // FIXME: Only return Blue for now. This is a hack to make the bootstrap + // job work, needs to support multiple config trackers here + // MERC-5954 + return l.cps[0] } -func newLLOConfigProvider(ctx context.Context, lggr logger.Logger, chain legacyevm.Chain, opts *types.RelayOpts) (*configWatcher, error) { +func newLLOConfigProvider( + ctx context.Context, + lggr logger.Logger, + chain legacyevm.Chain, + cc llo.ConfigCache, + opts *types.RelayOpts, +) (commontypes.ConfigProvider, error) { if !common.IsHexAddress(opts.ContractID) { return nil, errors.New("invalid contractID, expected hex address") } @@ -34,22 +60,48 @@ func newLLOConfigProvider(ctx context.Context, lggr logger.Logger, chain legacye if err != nil { return nil, fmt.Errorf("failed to get relay config: %w", err) } - if relayConfig.LLODONID == 0 { + donID := relayConfig.LLODONID + if donID == 0 { return nil, errors.New("donID must be specified in relayConfig for LLO jobs") } - donIDPadded := DonIDToBytes32(relayConfig.LLODONID) - cp, err := mercury.NewConfigPoller( - ctx, - logger.Named(lggr, fmt.Sprintf("LLO-%d", relayConfig.LLODONID)), - chain.LogPoller(), - configuratorAddress, - donIDPadded, - // TODO: Does LLO need to support config contract? MERC-1827 - ) + + lp := chain.LogPoller() + + cps, digester, err := newLLOConfigPollers(ctx, lggr, cc, lp, chain.Config().EVM().ChainID(), configuratorAddress, relayConfig) if err != nil { return nil, err } - configDigester := mercury.NewOffchainConfigDigester(donIDPadded, chain.Config().EVM().ChainID(), configuratorAddress, ocrtypes.ConfigDigestPrefixLLO) - return newConfigWatcher(lggr, configuratorAddress, configDigester, cp, chain, relayConfig.FromBlock, opts.New), nil + p := &lloConfigProvider{nil, nil, lp, cps, digester, opts.New, relayConfig.FromBlock, services.MultiStart{}} + p.Service, p.eng = services.Config{ + Name: "LLOConfigProvider", + Start: p.start, + Close: p.close, + }.NewServiceEngine(lggr) + return p, nil +} + +func (l *lloConfigProvider) start(ctx context.Context) error { + if l.runReplay && l.replayFromBlock != 0 { + // Only replay if it's a brand new job. + l.eng.Go(func(ctx context.Context) { + l.eng.Infow("starting replay for config", "fromBlock", l.replayFromBlock) + // #nosec G115 + if err := l.lp.Replay(ctx, int64(l.replayFromBlock)); err != nil { + l.eng.Errorw("error replaying for config", "err", err) + } else { + l.eng.Infow("completed replaying for config", "replayFromBlock", l.replayFromBlock) + } + }) + } + srvs := []services.StartClose{} + for _, cp := range l.cps { + srvs = append(srvs, cp) + } + err := l.ms.Start(ctx, srvs...) + return err +} + +func (l *lloConfigProvider) close() error { + return l.ms.Close() } diff --git a/core/services/relay/evm/llo_provider.go b/core/services/relay/evm/llo_provider.go index 2cf6163047f..c98b92582b1 100644 --- a/core/services/relay/evm/llo_provider.go +++ b/core/services/relay/evm/llo_provider.go @@ -3,9 +3,18 @@ package evm import ( "context" "errors" + "fmt" + "math/big" + "github.com/ethereum/go-ethereum/common" ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" + "github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm" + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/llo" + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury" + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/types" + "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink-common/pkg/services" commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" @@ -20,41 +29,117 @@ type LLOTransmitter interface { llotypes.Transmitter } +type FilterRegisterer interface { + Replay(ctx context.Context, fromBlock int64) error + RegisterFilter(ctx context.Context, filter logpoller.Filter) error +} + type lloProvider struct { - cp commontypes.ConfigProvider + services.Service + eng *services.Engine + + cps []llo.ConfigPollerService + transmitter LLOTransmitter logger logger.Logger channelDefinitionCache llotypes.ChannelDefinitionCache + digester ocrtypes.OffchainConfigDigester + shouldRetireCache llo.ShouldRetireCacheService + + lp FilterRegisterer + runReplay bool + replayFromBlock uint64 ms services.MultiStart } +func lloProviderConfiguratorFilterName(addr common.Address, donID uint32) string { + return logpoller.FilterName("LLOProvider Configurator", addr.String(), fmt.Sprintf("%d", donID)) +} + func NewLLOProvider( - cp commontypes.ConfigProvider, + ctx context.Context, transmitter LLOTransmitter, lggr logger.Logger, + cc llo.ConfigCache, + chain legacyevm.Chain, + configuratorAddress common.Address, channelDefinitionCache llotypes.ChannelDefinitionCache, -) relaytypes.LLOProvider { - return &lloProvider{ - cp, + relayConfig types.RelayConfig, + relayOpts *types.RelayOpts, +) (relaytypes.LLOProvider, error) { + donID := relayConfig.LLODONID + lp := chain.LogPoller() + lggr = logger.Sugared(lggr).With("configMode", relayConfig.LLOConfigMode, "configuratorAddress", configuratorAddress, "donID", donID) + lggr = logger.Named(lggr, fmt.Sprintf("LLO-%d", donID)) + + cps, digester, err := newLLOConfigPollers(ctx, lggr, cc, lp, chain.Config().EVM().ChainID(), configuratorAddress, relayConfig) + if err != nil { + return nil, err + } + src := llo.NewShouldRetireCache(lggr, lp, configuratorAddress, donID) + + p := &lloProvider{ + nil, + nil, + cps, transmitter, logger.Named(lggr, "LLOProvider"), channelDefinitionCache, + digester, + src, + lp, + relayOpts.New, + relayConfig.FromBlock, services.MultiStart{}, } + + p.Service, p.eng = services.Config{ + Name: "LLOProvider", + Start: p.start, + Close: p.close, + }.NewServiceEngine(lggr) + + return p, nil + } -func (p *lloProvider) Start(ctx context.Context) error { - err := p.ms.Start(ctx, p.cp, p.transmitter, p.channelDefinitionCache) +func (p *lloProvider) start(ctx context.Context) error { + // NOTE: Remember that all filters must be registered first for this replay + // to be effective + // 1. Replay + // 2. Start all services + if p.runReplay && p.replayFromBlock != 0 { + // Only replay if it's a brand new job. + p.eng.Go(func(ctx context.Context) { + p.eng.Infow("starting replay for config", "fromBlock", p.replayFromBlock) + // #nosec G115 + if err := p.lp.Replay(ctx, int64(p.replayFromBlock)); err != nil { + p.eng.Errorw("error replaying for config", "err", err) + } else { + p.eng.Infow("completed replaying for config", "replayFromBlock", p.replayFromBlock) + } + }) + } + srvs := []services.StartClose{p.transmitter, p.channelDefinitionCache, p.shouldRetireCache} + for _, cp := range p.cps { + srvs = append(srvs, cp) + } + err := p.ms.Start(ctx, srvs...) return err } -func (p *lloProvider) Close() error { +func (p *lloProvider) close() error { return p.ms.Close() } func (p *lloProvider) Ready() error { - return errors.Join(p.cp.Ready(), p.transmitter.Ready(), p.channelDefinitionCache.Ready()) + errs := make([]error, len(p.cps)) + for i, cp := range p.cps { + errs[i] = cp.Ready() + } + errs = append(errs, p.transmitter.Ready(), p.channelDefinitionCache.Ready(), p.shouldRetireCache.Ready()) + return errors.Join(errs...) } func (p *lloProvider) Name() string { @@ -63,18 +148,25 @@ func (p *lloProvider) Name() string { func (p *lloProvider) HealthReport() map[string]error { report := map[string]error{} - services.CopyHealth(report, p.cp.HealthReport()) + for _, cp := range p.cps { + services.CopyHealth(report, cp.HealthReport()) + } services.CopyHealth(report, p.transmitter.HealthReport()) services.CopyHealth(report, p.channelDefinitionCache.HealthReport()) + services.CopyHealth(report, p.shouldRetireCache.HealthReport()) return report } -func (p *lloProvider) ContractConfigTracker() ocrtypes.ContractConfigTracker { - return p.cp.ContractConfigTracker() +func (p *lloProvider) ContractConfigTrackers() (cps []ocrtypes.ContractConfigTracker) { + cps = make([]ocrtypes.ContractConfigTracker, len(p.cps)) + for i, cp := range p.cps { + cps[i] = cp + } + return } func (p *lloProvider) OffchainConfigDigester() ocrtypes.OffchainConfigDigester { - return p.cp.OffchainConfigDigester() + return p.digester } func (p *lloProvider) ContractTransmitter() llotypes.Transmitter { @@ -84,3 +176,98 @@ func (p *lloProvider) ContractTransmitter() llotypes.Transmitter { func (p *lloProvider) ChannelDefinitionCache() llotypes.ChannelDefinitionCache { return p.channelDefinitionCache } + +func (p *lloProvider) ShouldRetireCache() llotypes.ShouldRetireCache { + return p.shouldRetireCache +} + +// wrapper is needed to turn mercury config poller into a service +type mercuryConfigPollerWrapper struct { + *mercury.ConfigPoller + services.Service + eng *services.Engine + + runReplay bool + fromBlock uint64 +} + +func newMercuryConfigPollerWrapper(lggr logger.Logger, cp *mercury.ConfigPoller, fromBlock uint64, runReplay bool) *mercuryConfigPollerWrapper { + w := &mercuryConfigPollerWrapper{cp, nil, nil, runReplay, fromBlock} + w.Service, w.eng = services.Config{ + Name: "LLOMercuryConfigWrapper", + Start: w.start, + Close: w.close, + }.NewServiceEngine(lggr) + return w +} + +func (w *mercuryConfigPollerWrapper) Start(ctx context.Context) error { + return w.Service.Start(ctx) +} + +func (w *mercuryConfigPollerWrapper) start(ctx context.Context) error { + w.ConfigPoller.Start() + return nil +} + +func (w *mercuryConfigPollerWrapper) Close() error { + return w.Service.Close() +} + +func (w *mercuryConfigPollerWrapper) close() error { + return w.ConfigPoller.Close() +} + +func newLLOConfigPollers(ctx context.Context, lggr logger.Logger, cc llo.ConfigCache, lp logpoller.LogPoller, chainID *big.Int, configuratorAddress common.Address, relayConfig types.RelayConfig) (cps []llo.ConfigPollerService, configDigester ocrtypes.OffchainConfigDigester, err error) { + donID := relayConfig.LLODONID + donIDHash := llo.DonIDToBytes32(donID) + // TODO: Can we auto-detect or verify based on if the contract implements `setConfig` or `setProductionConfig` interfaces? + // MERC-3524 + switch relayConfig.LLOConfigMode { + case types.LLOConfigModeMercury: + // NOTE: This uses the old config digest prefix for compatibility with legacy contracts + configDigester = mercury.NewOffchainConfigDigester(donIDHash, chainID, configuratorAddress, ocrtypes.ConfigDigestPrefixMercuryV02) + // Mercury config poller will register its own filter + mcp, err := mercury.NewConfigPoller( + ctx, + lggr, + lp, + configuratorAddress, + llo.DonIDToBytes32(donID), + ) + if err != nil { + return nil, nil, err + } + // don't need to replay in the wrapper since the provider will handle it + w := newMercuryConfigPollerWrapper(lggr, mcp, relayConfig.FromBlock, false) + cps = []llo.ConfigPollerService{w} + case types.LLOConfigModeBlueGreen: + // NOTE: Register filter here because the config poller doesn't do it on its own + err := lp.RegisterFilter(ctx, logpoller.Filter{Name: lloProviderConfiguratorFilterName(configuratorAddress, donID), EventSigs: []common.Hash{llo.ProductionConfigSet, llo.StagingConfigSet, llo.PromoteStagingConfig}, Topic2: []common.Hash{donIDHash}, Addresses: []common.Address{configuratorAddress}}) + if err != nil { + return nil, nil, fmt.Errorf("failed to register filter: %w", err) + } + + configDigester = llo.NewOffchainConfigDigester(donIDHash, chainID, configuratorAddress, ocrtypes.ConfigDigestPrefixLLO) + blueCP := llo.NewConfigPoller( + lggr, + lp, + cc, + configuratorAddress, + donID, + llo.InstanceTypeBlue, + relayConfig.FromBlock, + ) + greenCP := llo.NewConfigPoller( + lggr, + lp, + cc, + configuratorAddress, + donID, + llo.InstanceTypeGreen, + relayConfig.FromBlock, + ) + cps = []llo.ConfigPollerService{blueCP, greenCP} + } + return cps, configDigester, nil +} diff --git a/core/services/relay/evm/types/types.go b/core/services/relay/evm/types/types.go index 17fc885153d..2a5e59fe66e 100644 --- a/core/services/relay/evm/types/types.go +++ b/core/services/relay/evm/types/types.go @@ -184,7 +184,8 @@ func (r *ReadType) UnmarshalText(text []byte) error { type LLOConfigMode string const ( - LLOConfigModeMercury LLOConfigMode = "mercury" + LLOConfigModeMercury LLOConfigMode = "mercury" + LLOConfigModeBlueGreen LLOConfigMode = "bluegreen" ) func (c LLOConfigMode) String() string { diff --git a/core/store/migrate/migrations/0257_add_llo_retirement_report_cache.sql b/core/store/migrate/migrations/0257_add_llo_retirement_report_cache.sql new file mode 100644 index 00000000000..d5527d43674 --- /dev/null +++ b/core/store/migrate/migrations/0257_add_llo_retirement_report_cache.sql @@ -0,0 +1,24 @@ +-- +goose Up +-- +goose StatementBegin + +CREATE TABLE llo_retirement_report_cache ( + config_digest BYTEA NOT NULL CHECK (OCTET_LENGTH(config_digest) = 32) PRIMARY KEY, + attested_retirement_report BYTEA NOT NULL, + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); +CREATE TABLE llo_retirement_report_cache_configs ( + config_digest BYTEA CHECK (octet_length(config_digest) = 32) PRIMARY KEY, + signers BYTEA[] NOT NULL, + f INT NOT NULL, + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +-- +goose StatementEnd + +-- +goose Down +-- +goose StatementBegin + +DROP TABLE llo_retirement_report_cache_configs; +DROP TABLE llo_retirement_report_cache; + +-- +goose StatementEnd diff --git a/core/web/testdata/body/health.html b/core/web/testdata/body/health.html index d96677518ae..2bf427f5e00 100644 --- a/core/web/testdata/body/health.html +++ b/core/web/testdata/body/health.html @@ -120,6 +120,9 @@ BridgeCache +
+ RetirementReportCache +
Solana
diff --git a/core/web/testdata/body/health.json b/core/web/testdata/body/health.json index ddbf0e3557a..d573e0bd5fc 100644 --- a/core/web/testdata/body/health.json +++ b/core/web/testdata/body/health.json @@ -216,6 +216,15 @@ "output": "" } }, + { + "type": "checks", + "id": "RetirementReportCache", + "attributes": { + "name": "RetirementReportCache", + "status": "passing", + "output": "" + } + }, { "type": "checks", "id": "Solana.Bar.Chain", diff --git a/core/web/testdata/body/health.txt b/core/web/testdata/body/health.txt index 4686371a567..fde038dfc63 100644 --- a/core/web/testdata/body/health.txt +++ b/core/web/testdata/body/health.txt @@ -23,6 +23,7 @@ ok Mercury.WSRPCPool.CacheSet ok PipelineORM ok PipelineRunner ok PipelineRunner.BridgeCache +ok RetirementReportCache ok Solana.Bar.Chain ok Solana.Bar.Relayer ok Solana.Bar.Txm diff --git a/go.mod b/go.mod index 6e7cbe4b09b..e193812e51d 100644 --- a/go.mod +++ b/go.mod @@ -76,9 +76,9 @@ require ( github.com/smartcontractkit/chain-selectors v1.0.27 github.com/smartcontractkit/chainlink-automation v0.8.0 github.com/smartcontractkit/chainlink-ccip v0.0.0-20241017140434-6757be193e1c - github.com/smartcontractkit/chainlink-common v0.3.1-0.20241017135127-b283b1e14fa6 + github.com/smartcontractkit/chainlink-common v0.3.1-0.20241018143728-5248d7c4468a github.com/smartcontractkit/chainlink-cosmos v0.5.2-0.20241017133723-5277829bd53f - github.com/smartcontractkit/chainlink-data-streams v0.1.0 + github.com/smartcontractkit/chainlink-data-streams v0.1.1-0.20241018134907-a00ba3729b5e github.com/smartcontractkit/chainlink-feeds v0.1.1 github.com/smartcontractkit/chainlink-protos/orchestrator v0.3.0 github.com/smartcontractkit/chainlink-solana v1.1.1-0.20241017134533-5459a1034ecd diff --git a/go.sum b/go.sum index e739d8b6472..62b3036f249 100644 --- a/go.sum +++ b/go.sum @@ -1057,12 +1057,12 @@ github.com/smartcontractkit/chainlink-automation v0.8.0 h1:hFz2EHU06bkEfhcqhK8Jd github.com/smartcontractkit/chainlink-automation v0.8.0/go.mod h1:ObdjDfgGIaiE48Bb3yYcx1CeGBm392WlEw92U83LlUA= github.com/smartcontractkit/chainlink-ccip v0.0.0-20241017140434-6757be193e1c h1:BvzX0A659a9fShyW69P/jV3iVlA4/wlGbZ/4XXE3pxI= github.com/smartcontractkit/chainlink-ccip v0.0.0-20241017140434-6757be193e1c/go.mod h1:4adKaHNaxFsRvV/lYfqtbsWyyvIPUMLR0FdOJN/ljis= -github.com/smartcontractkit/chainlink-common v0.3.1-0.20241017135127-b283b1e14fa6 h1:SHwvqXq1gdXOG/f1sQGupOH6ICZRuzMy5QkD3Um/k+8= -github.com/smartcontractkit/chainlink-common v0.3.1-0.20241017135127-b283b1e14fa6/go.mod h1:tsGgeEJc5SUSlfVGSX0wR0EkRU3pM58D6SKF97V68ko= +github.com/smartcontractkit/chainlink-common v0.3.1-0.20241018143728-5248d7c4468a h1:X0+2AbgCmPgfwY2dTvAK37KO8UvWLHMgfAFL3MA4BEs= +github.com/smartcontractkit/chainlink-common v0.3.1-0.20241018143728-5248d7c4468a/go.mod h1:tsGgeEJc5SUSlfVGSX0wR0EkRU3pM58D6SKF97V68ko= github.com/smartcontractkit/chainlink-cosmos v0.5.2-0.20241017133723-5277829bd53f h1:BwrIaQIx5Iy6eT+DfLhFfK2XqjxRm74mVdlX8gbu4dw= github.com/smartcontractkit/chainlink-cosmos v0.5.2-0.20241017133723-5277829bd53f/go.mod h1:wHtwSR3F1CQSJJZDQKuqaqFYnvkT+kMyget7dl8Clvo= -github.com/smartcontractkit/chainlink-data-streams v0.1.0 h1:wcRJRm7eqfbgN+Na+GjAe0/IUn6XwmSagFHqIWHHBGk= -github.com/smartcontractkit/chainlink-data-streams v0.1.0/go.mod h1:lmdRVjg49Do+5tkk9V5iAhi+Jm2kXhjZXWAbzh7xg7o= +github.com/smartcontractkit/chainlink-data-streams v0.1.1-0.20241018134907-a00ba3729b5e h1:JiETqdNM0bktAUGMc62COwXIaw3rR3M77Me6bBLG0Fg= +github.com/smartcontractkit/chainlink-data-streams v0.1.1-0.20241018134907-a00ba3729b5e/go.mod h1:iK3BNHKCLgSgkOyiu3iE7sfZ20Qnuk7xwjV/yO/6gnQ= github.com/smartcontractkit/chainlink-feeds v0.1.1 h1:JzvUOM/OgGQA1sOqTXXl52R6AnNt+Wg64sVG+XSA49c= github.com/smartcontractkit/chainlink-feeds v0.1.1/go.mod h1:55EZ94HlKCfAsUiKUTNI7QlE/3d3IwTlsU3YNa/nBb4= github.com/smartcontractkit/chainlink-protos/orchestrator v0.3.0 h1:PBUaFfPLm+Efq7H9kdfGBivH+QhJ6vB5EZTR/sCZsxI= diff --git a/integration-tests/go.mod b/integration-tests/go.mod index f18da822187..3fc511fa385 100644 --- a/integration-tests/go.mod +++ b/integration-tests/go.mod @@ -40,7 +40,7 @@ require ( github.com/smartcontractkit/chain-selectors v1.0.27 github.com/smartcontractkit/chainlink-automation v0.8.0 github.com/smartcontractkit/chainlink-ccip v0.0.0-20241017140434-6757be193e1c - github.com/smartcontractkit/chainlink-common v0.3.1-0.20241017135127-b283b1e14fa6 + github.com/smartcontractkit/chainlink-common v0.3.1-0.20241018143728-5248d7c4468a github.com/smartcontractkit/chainlink-testing-framework/havoc v1.50.0 github.com/smartcontractkit/chainlink-testing-framework/lib v1.50.11-0.20241011153842-b2804aed25b4 github.com/smartcontractkit/chainlink-testing-framework/lib/grafana v1.50.0 @@ -408,7 +408,7 @@ require ( github.com/shoenig/go-m1cpu v0.1.6 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/smartcontractkit/chainlink-cosmos v0.5.2-0.20241017133723-5277829bd53f // indirect - github.com/smartcontractkit/chainlink-data-streams v0.1.0 // indirect + github.com/smartcontractkit/chainlink-data-streams v0.1.1-0.20241018134907-a00ba3729b5e // indirect github.com/smartcontractkit/chainlink-feeds v0.1.1 // indirect github.com/smartcontractkit/chainlink-protos/orchestrator v0.3.0 // indirect github.com/smartcontractkit/chainlink-solana v1.1.1-0.20241017134533-5459a1034ecd // indirect diff --git a/integration-tests/go.sum b/integration-tests/go.sum index e6f1370bafd..d73c09ac3c5 100644 --- a/integration-tests/go.sum +++ b/integration-tests/go.sum @@ -1404,12 +1404,12 @@ github.com/smartcontractkit/chainlink-automation v0.8.0 h1:hFz2EHU06bkEfhcqhK8Jd github.com/smartcontractkit/chainlink-automation v0.8.0/go.mod h1:ObdjDfgGIaiE48Bb3yYcx1CeGBm392WlEw92U83LlUA= github.com/smartcontractkit/chainlink-ccip v0.0.0-20241017140434-6757be193e1c h1:BvzX0A659a9fShyW69P/jV3iVlA4/wlGbZ/4XXE3pxI= github.com/smartcontractkit/chainlink-ccip v0.0.0-20241017140434-6757be193e1c/go.mod h1:4adKaHNaxFsRvV/lYfqtbsWyyvIPUMLR0FdOJN/ljis= -github.com/smartcontractkit/chainlink-common v0.3.1-0.20241017135127-b283b1e14fa6 h1:SHwvqXq1gdXOG/f1sQGupOH6ICZRuzMy5QkD3Um/k+8= -github.com/smartcontractkit/chainlink-common v0.3.1-0.20241017135127-b283b1e14fa6/go.mod h1:tsGgeEJc5SUSlfVGSX0wR0EkRU3pM58D6SKF97V68ko= +github.com/smartcontractkit/chainlink-common v0.3.1-0.20241018143728-5248d7c4468a h1:X0+2AbgCmPgfwY2dTvAK37KO8UvWLHMgfAFL3MA4BEs= +github.com/smartcontractkit/chainlink-common v0.3.1-0.20241018143728-5248d7c4468a/go.mod h1:tsGgeEJc5SUSlfVGSX0wR0EkRU3pM58D6SKF97V68ko= github.com/smartcontractkit/chainlink-cosmos v0.5.2-0.20241017133723-5277829bd53f h1:BwrIaQIx5Iy6eT+DfLhFfK2XqjxRm74mVdlX8gbu4dw= github.com/smartcontractkit/chainlink-cosmos v0.5.2-0.20241017133723-5277829bd53f/go.mod h1:wHtwSR3F1CQSJJZDQKuqaqFYnvkT+kMyget7dl8Clvo= -github.com/smartcontractkit/chainlink-data-streams v0.1.0 h1:wcRJRm7eqfbgN+Na+GjAe0/IUn6XwmSagFHqIWHHBGk= -github.com/smartcontractkit/chainlink-data-streams v0.1.0/go.mod h1:lmdRVjg49Do+5tkk9V5iAhi+Jm2kXhjZXWAbzh7xg7o= +github.com/smartcontractkit/chainlink-data-streams v0.1.1-0.20241018134907-a00ba3729b5e h1:JiETqdNM0bktAUGMc62COwXIaw3rR3M77Me6bBLG0Fg= +github.com/smartcontractkit/chainlink-data-streams v0.1.1-0.20241018134907-a00ba3729b5e/go.mod h1:iK3BNHKCLgSgkOyiu3iE7sfZ20Qnuk7xwjV/yO/6gnQ= github.com/smartcontractkit/chainlink-feeds v0.1.1 h1:JzvUOM/OgGQA1sOqTXXl52R6AnNt+Wg64sVG+XSA49c= github.com/smartcontractkit/chainlink-feeds v0.1.1/go.mod h1:55EZ94HlKCfAsUiKUTNI7QlE/3d3IwTlsU3YNa/nBb4= github.com/smartcontractkit/chainlink-protos/orchestrator v0.3.0 h1:PBUaFfPLm+Efq7H9kdfGBivH+QhJ6vB5EZTR/sCZsxI= diff --git a/integration-tests/load/go.mod b/integration-tests/load/go.mod index aaf84ecf77b..9acb28d1215 100644 --- a/integration-tests/load/go.mod +++ b/integration-tests/load/go.mod @@ -15,7 +15,7 @@ require ( github.com/pkg/errors v0.9.1 github.com/rs/zerolog v1.33.0 github.com/slack-go/slack v0.12.2 - github.com/smartcontractkit/chainlink-common v0.3.1-0.20241017135127-b283b1e14fa6 + github.com/smartcontractkit/chainlink-common v0.3.1-0.20241018143728-5248d7c4468a github.com/smartcontractkit/chainlink-testing-framework/lib v1.50.11-0.20241011153842-b2804aed25b4 github.com/smartcontractkit/chainlink-testing-framework/seth v1.50.1 github.com/smartcontractkit/chainlink-testing-framework/wasp v1.50.0 @@ -407,7 +407,7 @@ require ( github.com/smartcontractkit/chain-selectors v1.0.27 // indirect github.com/smartcontractkit/chainlink-automation v0.8.0 // indirect github.com/smartcontractkit/chainlink-ccip v0.0.0-20241017140434-6757be193e1c // indirect - github.com/smartcontractkit/chainlink-data-streams v0.1.0 // indirect + github.com/smartcontractkit/chainlink-data-streams v0.1.1-0.20241018134907-a00ba3729b5e // indirect github.com/smartcontractkit/chainlink-feeds v0.1.1 // indirect github.com/smartcontractkit/chainlink-protos/orchestrator v0.3.0 // indirect github.com/smartcontractkit/chainlink-solana v1.1.1-0.20241017134533-5459a1034ecd // indirect diff --git a/integration-tests/load/go.sum b/integration-tests/load/go.sum index e1f95770c49..1a671072c5c 100644 --- a/integration-tests/load/go.sum +++ b/integration-tests/load/go.sum @@ -1381,12 +1381,12 @@ github.com/smartcontractkit/chainlink-automation v0.8.0 h1:hFz2EHU06bkEfhcqhK8Jd github.com/smartcontractkit/chainlink-automation v0.8.0/go.mod h1:ObdjDfgGIaiE48Bb3yYcx1CeGBm392WlEw92U83LlUA= github.com/smartcontractkit/chainlink-ccip v0.0.0-20241017140434-6757be193e1c h1:BvzX0A659a9fShyW69P/jV3iVlA4/wlGbZ/4XXE3pxI= github.com/smartcontractkit/chainlink-ccip v0.0.0-20241017140434-6757be193e1c/go.mod h1:4adKaHNaxFsRvV/lYfqtbsWyyvIPUMLR0FdOJN/ljis= -github.com/smartcontractkit/chainlink-common v0.3.1-0.20241017135127-b283b1e14fa6 h1:SHwvqXq1gdXOG/f1sQGupOH6ICZRuzMy5QkD3Um/k+8= -github.com/smartcontractkit/chainlink-common v0.3.1-0.20241017135127-b283b1e14fa6/go.mod h1:tsGgeEJc5SUSlfVGSX0wR0EkRU3pM58D6SKF97V68ko= +github.com/smartcontractkit/chainlink-common v0.3.1-0.20241018143728-5248d7c4468a h1:X0+2AbgCmPgfwY2dTvAK37KO8UvWLHMgfAFL3MA4BEs= +github.com/smartcontractkit/chainlink-common v0.3.1-0.20241018143728-5248d7c4468a/go.mod h1:tsGgeEJc5SUSlfVGSX0wR0EkRU3pM58D6SKF97V68ko= github.com/smartcontractkit/chainlink-cosmos v0.5.2-0.20241017133723-5277829bd53f h1:BwrIaQIx5Iy6eT+DfLhFfK2XqjxRm74mVdlX8gbu4dw= github.com/smartcontractkit/chainlink-cosmos v0.5.2-0.20241017133723-5277829bd53f/go.mod h1:wHtwSR3F1CQSJJZDQKuqaqFYnvkT+kMyget7dl8Clvo= -github.com/smartcontractkit/chainlink-data-streams v0.1.0 h1:wcRJRm7eqfbgN+Na+GjAe0/IUn6XwmSagFHqIWHHBGk= -github.com/smartcontractkit/chainlink-data-streams v0.1.0/go.mod h1:lmdRVjg49Do+5tkk9V5iAhi+Jm2kXhjZXWAbzh7xg7o= +github.com/smartcontractkit/chainlink-data-streams v0.1.1-0.20241018134907-a00ba3729b5e h1:JiETqdNM0bktAUGMc62COwXIaw3rR3M77Me6bBLG0Fg= +github.com/smartcontractkit/chainlink-data-streams v0.1.1-0.20241018134907-a00ba3729b5e/go.mod h1:iK3BNHKCLgSgkOyiu3iE7sfZ20Qnuk7xwjV/yO/6gnQ= github.com/smartcontractkit/chainlink-feeds v0.1.1 h1:JzvUOM/OgGQA1sOqTXXl52R6AnNt+Wg64sVG+XSA49c= github.com/smartcontractkit/chainlink-feeds v0.1.1/go.mod h1:55EZ94HlKCfAsUiKUTNI7QlE/3d3IwTlsU3YNa/nBb4= github.com/smartcontractkit/chainlink-protos/orchestrator v0.3.0 h1:PBUaFfPLm+Efq7H9kdfGBivH+QhJ6vB5EZTR/sCZsxI= diff --git a/testdata/scripts/health/default.txtar b/testdata/scripts/health/default.txtar index 8480345e273..a7db2308e35 100644 --- a/testdata/scripts/health/default.txtar +++ b/testdata/scripts/health/default.txtar @@ -39,6 +39,7 @@ ok Mercury.WSRPCPool.CacheSet ok PipelineORM ok PipelineRunner ok PipelineRunner.BridgeCache +ok RetirementReportCache ok TelemetryManager -- out.json -- @@ -116,6 +117,15 @@ ok TelemetryManager "output": "" } }, + { + "type": "checks", + "id": "RetirementReportCache", + "attributes": { + "name": "RetirementReportCache", + "status": "passing", + "output": "" + } + }, { "type": "checks", "id": "TelemetryManager", @@ -126,4 +136,4 @@ ok TelemetryManager } } ] -} \ No newline at end of file +} diff --git a/testdata/scripts/health/multi-chain.txtar b/testdata/scripts/health/multi-chain.txtar index 5d1c25d18fd..f53bbfebf8c 100644 --- a/testdata/scripts/health/multi-chain.txtar +++ b/testdata/scripts/health/multi-chain.txtar @@ -93,6 +93,7 @@ ok Mercury.WSRPCPool.CacheSet ok PipelineORM ok PipelineRunner ok PipelineRunner.BridgeCache +ok RetirementReportCache ok Solana.Bar.Chain ok Solana.Bar.Relayer ok Solana.Bar.Txm @@ -324,6 +325,15 @@ ok TelemetryManager "output": "" } }, + { + "type": "checks", + "id": "RetirementReportCache", + "attributes": { + "name": "RetirementReportCache", + "status": "passing", + "output": "" + } + }, { "type": "checks", "id": "Solana.Bar.Chain",