diff --git a/.changeset/big-camels-report.md b/.changeset/big-camels-report.md new file mode 100644 index 00000000000..f81f66b9138 --- /dev/null +++ b/.changeset/big-camels-report.md @@ -0,0 +1,5 @@ +--- +"chainlink": patch +--- + +#bugfix fix non-idempotent loopp registry.Register diff --git a/.changeset/brave-cooks-itch.md b/.changeset/brave-cooks-itch.md new file mode 100644 index 00000000000..1ed3dd7e117 --- /dev/null +++ b/.changeset/brave-cooks-itch.md @@ -0,0 +1,5 @@ +--- +"chainlink": minor +--- + +#updated feat(job-distributor): support tron chain type on sync diff --git a/.changeset/five-gifts-end.md b/.changeset/five-gifts-end.md new file mode 100644 index 00000000000..dd13fda476d --- /dev/null +++ b/.changeset/five-gifts-end.md @@ -0,0 +1,5 @@ +--- +"chainlink": patch +--- + +#added stream job delete capability diff --git a/.changeset/fuzzy-yaks-deny.md b/.changeset/fuzzy-yaks-deny.md new file mode 100644 index 00000000000..6de0c8d096c --- /dev/null +++ b/.changeset/fuzzy-yaks-deny.md @@ -0,0 +1,5 @@ +--- +"chainlink": patch +--- + +#added Lens Sepolia config diff --git a/.changeset/giant-eels-jump.md b/.changeset/giant-eels-jump.md new file mode 100644 index 00000000000..5ab8ca875ca --- /dev/null +++ b/.changeset/giant-eels-jump.md @@ -0,0 +1,5 @@ +--- +"chainlink": patch +--- + +Add error handling for Arbitrum RPC server timeouts. #added diff --git a/.changeset/spotty-seals-give.md b/.changeset/spotty-seals-give.md new file mode 100644 index 00000000000..1e3874a783f --- /dev/null +++ b/.changeset/spotty-seals-give.md @@ -0,0 +1,5 @@ +--- +"chainlink": patch +--- + +Switching CCIP to observed ChainReader for HomeChainReader #internal diff --git a/.changeset/tiny-kangaroos-switch.md b/.changeset/tiny-kangaroos-switch.md new file mode 100644 index 00000000000..000f5b6bde5 --- /dev/null +++ b/.changeset/tiny-kangaroos-switch.md @@ -0,0 +1,5 @@ +--- +"chainlink": patch +--- + +Added new fatal error cases for transactions to the Solana TXM. #added diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index c2d6208d0e3..9f19d52b7ea 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -13,7 +13,7 @@ # Services /core/services/directrequest @smartcontractkit/foundations -/core/services/feeds @smartcontractkit/deployment-automation @eutopian @yevshev @smartcontractkit/core +/core/services/feeds @smartcontractkit/deployment-automation @eutopian @smartcontractkit/core /core/services/synchronization/telem @smartcontractkit/realtime @smartcontractkit/core /core/capabilities/ @smartcontractkit/keystone @smartcontractkit/capabilities-team /core/capabilities/ccip @smartcontractkit/ccip-offchain @@ -140,8 +140,8 @@ core/scripts/gateway @smartcontractkit/dev-services # Deployment tooling /deployment @smartcontractkit/ccip @smartcontractkit/keystone @smartcontractkit/core @smartcontractkit/deployment-automation -/deployment/ccip @smartcontractkit/ccip @smartcontractkit/core -/deployment/keystone @smartcontractkit/keystone @smartcontractkit/core +/deployment/ccip @smartcontractkit/ccip @smartcontractkit/core @smartcontractkit/deployment-automation +/deployment/keystone @smartcontractkit/keystone @smartcontractkit/core @smartcontractkit/deployment-automation # TODO: As more products add their deployment logic here, add the team as an owner # CI/CD diff --git a/.github/e2e-tests.yml b/.github/e2e-tests.yml index ebe5b62a709..675fa315dfa 100644 --- a/.github/e2e-tests.yml +++ b/.github/e2e-tests.yml @@ -10,7 +10,7 @@ runner-test-matrix: # START: OCR tests # Example of 1 runner for all tests in integration-tests/smoke/ocr_test.go - - id: smoke/ocr_test.go:* + - id: smoke/ocr_test.go:* path: integration-tests/smoke/ocr_test.go test_env_type: docker runs_on: ubuntu-latest @@ -27,7 +27,7 @@ runner-test-matrix: runs_on: ubuntu-latest test_cmd: cd integration-tests/ && go test soak/ocr_test.go -v -test.run ^TestOCRv1Soak$ -test.parallel=1 -timeout 900h -count=1 -json test_cmd_opts: 2>&1 | tee /tmp/gotest.log | gotestloghelper -ci -singlepackage -hidepassingtests=false - test_secrets_required: true + test_secrets_required: true test_env_vars: TEST_SUITE: soak @@ -60,7 +60,7 @@ runner-test-matrix: test_config_override_path: integration-tests/testconfig/ocr2/overrides/base_sepolia_quick_smoke_test.toml test_secrets_required: true test_env_vars: - TEST_SUITE: soak + TEST_SUITE: soak - id: soak/ocr_test.go:TestForwarderOCRv1Soak path: integration-tests/soak/ocr_test.go @@ -79,7 +79,7 @@ runner-test-matrix: test_secrets_required: true test_env_vars: TEST_SUITE: soak - + - id: soak/ocr_test.go:TestOCRSoak_GethReorgBelowFinality_FinalityTagDisabled path: integration-tests/soak/ocr_test.go test_env_type: k8s-remote-runner @@ -87,7 +87,7 @@ runner-test-matrix: test_cmd: cd integration-tests/ && go test soak/ocr_test.go -v -test.run TestOCRSoak_GethReorgBelowFinality_FinalityTagDisabled -test.parallel=1 -timeout 900h -count=1 -json test_secrets_required: true test_env_vars: - TEST_SUITE: soak + TEST_SUITE: soak - id: soak/ocr_test.go:TestOCRSoak_GethReorgBelowFinality_FinalityTagEnabled path: integration-tests/soak/ocr_test.go @@ -96,7 +96,7 @@ runner-test-matrix: test_cmd: cd integration-tests/ && go test soak/ocr_test.go -v -test.run ^TestOCRSoak_GethReorgBelowFinality_FinalityTagEnabled$ -test.parallel=1 -timeout 900h -count=1 -json test_secrets_required: true test_env_vars: - TEST_SUITE: soak + TEST_SUITE: soak - id: soak/ocr_test.go:TestOCRSoak_GasSpike path: integration-tests/soak/ocr_test.go @@ -105,7 +105,7 @@ runner-test-matrix: test_cmd: cd integration-tests/ && go test soak/ocr_test.go -v -test.run ^TestOCRSoak_GasSpike$ -test.parallel=1 -timeout 900h -count=1 -json test_secrets_required: true test_env_vars: - TEST_SUITE: soak + TEST_SUITE: soak - id: soak/ocr_test.go:TestOCRSoak_ChangeBlockGasLimit path: integration-tests/soak/ocr_test.go @@ -114,7 +114,7 @@ runner-test-matrix: test_cmd: cd integration-tests/ && go test soak/ocr_test.go -v -test.run ^TestOCRSoak_ChangeBlockGasLimit$ -test.parallel=1 -timeout 900h -count=1 -json test_secrets_required: true test_env_vars: - TEST_SUITE: soak + TEST_SUITE: soak - id: soak/ocr_test.go:TestOCRSoak_RPCDownForAllCLNodes path: integration-tests/soak/ocr_test.go @@ -123,7 +123,7 @@ runner-test-matrix: test_cmd: cd integration-tests/ && go test soak/ocr_test.go -v -test.run ^TestOCRSoak_RPCDownForAllCLNodes$ -test.parallel=1 -timeout 900h -count=1 -json test_secrets_required: true test_env_vars: - TEST_SUITE: soak + TEST_SUITE: soak - id: soak/ocr_test.go:TestOCRSoak_RPCDownForHalfCLNodes path: integration-tests/soak/ocr_test.go @@ -132,7 +132,7 @@ runner-test-matrix: test_cmd: cd integration-tests/ && go test soak/ocr_test.go -v -test.run ^TestOCRSoak_RPCDownForHalfCLNodes$ -test.parallel=1 -timeout 900h -count=1 -json test_secrets_required: true test_env_vars: - TEST_SUITE: soak + TEST_SUITE: soak - id: smoke/forwarder_ocr_test.go:* path: integration-tests/smoke/forwarder_ocr_test.go @@ -168,7 +168,7 @@ runner-test-matrix: pyroscope_env: ci-smoke-ocr2-evm-simulated test_env_vars: E2E_TEST_CHAINLINK_VERSION: '{{ env.DEFAULT_CHAINLINK_PLUGINS_VERSION }}' # This is the chainlink version that has the plugins - + - id: smoke/ocr2_test.go:*-plugins path: integration-tests/smoke/ocr2_test.go test_env_type: docker @@ -193,11 +193,12 @@ runner-test-matrix: test_cmd: cd integration-tests/chaos && DETACH_RUNNER=false go test -test.run "^TestOCRChaos$" -v -test.parallel=10 -timeout 60m -count=1 -json test_env_vars: TEST_SUITE: chaos + CHAINLINK_USER_TEAM: Foundations # END: OCR tests # START: Automation tests - + - id: smoke/automation_test.go:^TestAutomationBasic/registry_2_0|TestAutomationBasic/registry_2_1_conditional|TestAutomationBasic/registry_2_1_logtrigger$ path: integration-tests/smoke/automation_test.go test_env_type: docker @@ -273,7 +274,7 @@ runner-test-matrix: - Merge Queue E2E Core Tests - Nightly E2E Tests test_cmd: cd integration-tests/smoke && go test -test.run "^TestAutomationBasic/registry_2_3_with_mercury_v03_link|TestAutomationBasic/registry_2_3_with_logtrigger_and_mercury_v02_link$" -test.parallel=2 -timeout 30m -count=1 -json - pyroscope_env: ci-smoke-automation-evm-simulated + pyroscope_env: ci-smoke-automation-evm-simulated - id: smoke/automation_test.go:^TestSetUpkeepTriggerConfig$ path: integration-tests/smoke/automation_test.go @@ -284,7 +285,7 @@ runner-test-matrix: - Merge Queue E2E Core Tests - Nightly E2E Tests test_cmd: cd integration-tests/smoke && go test -test.run ^TestSetUpkeepTriggerConfig$ -test.parallel=2 -timeout 30m -count=1 -json - pyroscope_env: ci-smoke-automation-evm-simulated + pyroscope_env: ci-smoke-automation-evm-simulated - id: smoke/automation_test.go:^TestAutomationAddFunds$ path: integration-tests/smoke/automation_test.go @@ -295,7 +296,7 @@ runner-test-matrix: - Merge Queue E2E Core Tests - Nightly E2E Tests test_cmd: cd integration-tests/smoke && go test -test.run ^TestAutomationAddFunds$ -test.parallel=3 -timeout 30m -count=1 -json - pyroscope_env: ci-smoke-automation-evm-simulated + pyroscope_env: ci-smoke-automation-evm-simulated - id: smoke/automation_test.go:^TestAutomationPauseUnPause$ path: integration-tests/smoke/automation_test.go @@ -306,7 +307,7 @@ runner-test-matrix: - Merge Queue E2E Core Tests - Nightly E2E Tests test_cmd: cd integration-tests/smoke && go test -test.run ^TestAutomationPauseUnPause$ -test.parallel=3 -timeout 30m -count=1 -json - pyroscope_env: ci-smoke-automation-evm-simulated + pyroscope_env: ci-smoke-automation-evm-simulated - id: smoke/automation_test.go:^TestAutomationRegisterUpkeep$ path: integration-tests/smoke/automation_test.go @@ -317,7 +318,7 @@ runner-test-matrix: - Merge Queue E2E Core Tests - Nightly E2E Tests test_cmd: cd integration-tests/smoke && go test -test.run ^TestAutomationRegisterUpkeep$ -test.parallel=3 -timeout 30m -count=1 -json - pyroscope_env: ci-smoke-automation-evm-simulated + pyroscope_env: ci-smoke-automation-evm-simulated - id: smoke/automation_test.go:^TestAutomationPauseRegistry$ path: integration-tests/smoke/automation_test.go @@ -328,7 +329,7 @@ runner-test-matrix: - Merge Queue E2E Core Tests - Nightly E2E Tests test_cmd: cd integration-tests/smoke && go test -test.run ^TestAutomationPauseRegistry$ -test.parallel=3 -timeout 30m -count=1 -json - pyroscope_env: ci-smoke-automation-evm-simulated + pyroscope_env: ci-smoke-automation-evm-simulated - id: smoke/automation_test.go:^TestAutomationKeeperNodesDown$ path: integration-tests/smoke/automation_test.go @@ -339,7 +340,7 @@ runner-test-matrix: - Merge Queue E2E Core Tests - Nightly E2E Tests test_cmd: cd integration-tests/smoke && go test -test.run ^TestAutomationKeeperNodesDown$ -test.parallel=3 -timeout 30m -count=1 -json - pyroscope_env: ci-smoke-automation-evm-simulated + pyroscope_env: ci-smoke-automation-evm-simulated - id: smoke/automation_test.go:^TestAutomationPerformSimulation$ path: integration-tests/smoke/automation_test.go @@ -350,7 +351,7 @@ runner-test-matrix: - Merge Queue E2E Core Tests - Nightly E2E Tests test_cmd: cd integration-tests/smoke && go test -test.run ^TestAutomationPerformSimulation$ -test.parallel=3 -timeout 30m -count=1 -json - pyroscope_env: ci-smoke-automation-evm-simulated + pyroscope_env: ci-smoke-automation-evm-simulated - id: smoke/automation_test.go:^TestAutomationCheckPerformGasLimit$ path: integration-tests/smoke/automation_test.go @@ -361,7 +362,7 @@ runner-test-matrix: - Merge Queue E2E Core Tests - Nightly E2E Tests test_cmd: cd integration-tests/smoke && go test -test.run ^TestAutomationCheckPerformGasLimit$ -test.parallel=3 -timeout 30m -count=1 -json - pyroscope_env: ci-smoke-automation-evm-simulated + pyroscope_env: ci-smoke-automation-evm-simulated - id: smoke/automation_test.go:^TestUpdateCheckData$ path: integration-tests/smoke/automation_test.go @@ -372,7 +373,7 @@ runner-test-matrix: - Merge Queue E2E Core Tests - Nightly E2E Tests test_cmd: cd integration-tests/smoke && go test -test.run ^TestUpdateCheckData$ -test.parallel=3 -timeout 30m -count=1 -json - pyroscope_env: ci-smoke-automation-evm-simulated + pyroscope_env: ci-smoke-automation-evm-simulated - id: smoke/automation_test.go:^TestSetOffchainConfigWithMaxGasPrice$ path: integration-tests/smoke/automation_test.go @@ -383,7 +384,7 @@ runner-test-matrix: - Merge Queue E2E Core Tests - Nightly E2E Tests test_cmd: cd integration-tests/smoke && go test -test.run ^TestSetOffchainConfigWithMaxGasPrice$ -test.parallel=2 -timeout 30m -count=1 -json - pyroscope_env: ci-smoke-automation-evm-simulated + pyroscope_env: ci-smoke-automation-evm-simulated - id: smoke/keeper_test.go:^TestKeeperBasicSmoke$ path: integration-tests/smoke/keeper_test.go @@ -393,7 +394,7 @@ runner-test-matrix: - Merge Queue E2E Core Tests - Nightly E2E Tests test_cmd: cd integration-tests/smoke && go test -test.run ^TestKeeperBasicSmoke$ -test.parallel=3 -timeout 30m -count=1 -json - pyroscope_env: ci-smoke-keeper-evm-simulated + pyroscope_env: ci-smoke-keeper-evm-simulated - id: smoke/keeper_test.go:^TestKeeperBlockCountPerTurn$ path: integration-tests/smoke/keeper_test.go @@ -403,7 +404,7 @@ runner-test-matrix: - Merge Queue E2E Core Tests - Nightly E2E Tests test_cmd: cd integration-tests/smoke && go test -test.run ^TestKeeperBlockCountPerTurn$ -test.parallel=3 -timeout 30m -count=1 -json - pyroscope_env: ci-smoke-keeper-evm-simulated + pyroscope_env: ci-smoke-keeper-evm-simulated - id: smoke/keeper_test.go:^TestKeeperSimulation$ path: integration-tests/smoke/keeper_test.go @@ -413,7 +414,7 @@ runner-test-matrix: - Merge Queue E2E Core Tests - Nightly E2E Tests test_cmd: cd integration-tests/smoke && go test -test.run ^TestKeeperSimulation$ -test.parallel=2 -timeout 30m -count=1 -json - pyroscope_env: ci-smoke-keeper-evm-simulated + pyroscope_env: ci-smoke-keeper-evm-simulated - id: smoke/keeper_test.go:^TestKeeperCheckPerformGasLimit$ path: integration-tests/smoke/keeper_test.go @@ -423,7 +424,7 @@ runner-test-matrix: - Merge Queue E2E Core Tests - Nightly E2E Tests test_cmd: cd integration-tests/smoke && go test -test.run ^TestKeeperCheckPerformGasLimit$ -test.parallel=2 -timeout 30m -count=1 -json - pyroscope_env: ci-smoke-keeper-evm-simulated + pyroscope_env: ci-smoke-keeper-evm-simulated - id: smoke/keeper_test.go:^TestKeeperRegisterUpkeep$ path: integration-tests/smoke/keeper_test.go @@ -433,7 +434,7 @@ runner-test-matrix: - Merge Queue E2E Core Tests - Nightly E2E Tests test_cmd: cd integration-tests/smoke && go test -test.run ^TestKeeperRegisterUpkeep$ -test.parallel=3 -timeout 30m -count=1 -json - pyroscope_env: ci-smoke-keeper-evm-simulated + pyroscope_env: ci-smoke-keeper-evm-simulated - id: smoke/keeper_test.go:^TestKeeperAddFunds$ path: integration-tests/smoke/keeper_test.go @@ -443,7 +444,7 @@ runner-test-matrix: - Merge Queue E2E Core Tests - Nightly E2E Tests test_cmd: cd integration-tests/smoke && go test -test.run ^TestKeeperAddFunds$ -test.parallel=3 -timeout 30m -count=1 -json - pyroscope_env: ci-smoke-keeper-evm-simulated + pyroscope_env: ci-smoke-keeper-evm-simulated - id: smoke/keeper_test.go:^TestKeeperRemove$ path: integration-tests/smoke/keeper_test.go @@ -453,8 +454,8 @@ runner-test-matrix: - Merge Queue E2E Core Tests - Nightly E2E Tests test_cmd: cd integration-tests/smoke && go test -test.run ^TestKeeperRemove$ -test.parallel=3 -timeout 30m -count=1 -json - pyroscope_env: ci-smoke-keeper-evm-simulated - + pyroscope_env: ci-smoke-keeper-evm-simulated + - id: smoke/keeper_test.go:^TestKeeperPauseRegistry$ path: integration-tests/smoke/keeper_test.go test_env_type: docker @@ -463,7 +464,7 @@ runner-test-matrix: - Merge Queue E2E Core Tests - Nightly E2E Tests test_cmd: cd integration-tests/smoke && go test -test.run ^TestKeeperPauseRegistry$ -test.parallel=2 -timeout 30m -count=1 -json - pyroscope_env: ci-smoke-keeper-evm-simulated + pyroscope_env: ci-smoke-keeper-evm-simulated - id: smoke/keeper_test.go:^TestKeeperMigrateRegistry$ path: integration-tests/smoke/keeper_test.go @@ -473,7 +474,7 @@ runner-test-matrix: - Merge Queue E2E Core Tests - Nightly E2E Tests test_cmd: cd integration-tests/smoke && go test -test.run ^TestKeeperMigrateRegistry$ -test.parallel=1 -timeout 30m -count=1 -json - pyroscope_env: ci-smoke-keeper-evm-simulated + pyroscope_env: ci-smoke-keeper-evm-simulated - id: smoke/keeper_test.go:^TestKeeperNodeDown$ path: integration-tests/smoke/keeper_test.go @@ -483,7 +484,7 @@ runner-test-matrix: - Merge Queue E2E Core Tests - Nightly E2E Tests test_cmd: cd integration-tests/smoke && go test -test.run ^TestKeeperNodeDown$ -test.parallel=3 -timeout 30m -count=1 -json - pyroscope_env: ci-smoke-keeper-evm-simulated + pyroscope_env: ci-smoke-keeper-evm-simulated - id: smoke/keeper_test.go:^TestKeeperPauseUnPauseUpkeep$ path: integration-tests/smoke/keeper_test.go @@ -493,7 +494,7 @@ runner-test-matrix: - Merge Queue E2E Core Tests - Nightly E2E Tests test_cmd: cd integration-tests/smoke && go test -test.run ^TestKeeperPauseUnPauseUpkeep$ -test.parallel=1 -timeout 30m -count=1 -json - pyroscope_env: ci-smoke-keeper-evm-simulated + pyroscope_env: ci-smoke-keeper-evm-simulated - id: smoke/keeper_test.go:^TestKeeperUpdateCheckData$ path: integration-tests/smoke/keeper_test.go @@ -503,7 +504,7 @@ runner-test-matrix: - Merge Queue E2E Core Tests - Nightly E2E Tests test_cmd: cd integration-tests/smoke && go test -test.run ^TestKeeperUpdateCheckData$ -test.parallel=1 -timeout 30m -count=1 -json - pyroscope_env: ci-smoke-keeper-evm-simulated + pyroscope_env: ci-smoke-keeper-evm-simulated - id: smoke/keeper_test.go:^TestKeeperJobReplacement$ path: integration-tests/smoke/keeper_test.go @@ -513,7 +514,7 @@ runner-test-matrix: - Merge Queue E2E Core Tests - Nightly E2E Tests test_cmd: cd integration-tests/smoke && go test -test.run ^TestKeeperJobReplacement$ -test.parallel=1 -timeout 30m -count=1 -json - pyroscope_env: ci-smoke-keeper-evm-simulated + pyroscope_env: ci-smoke-keeper-evm-simulated - id: load/automationv2_1/automationv2_1_test.go:TestLogTrigger path: integration-tests/load/automationv2_1/automationv2_1_test.go @@ -546,7 +547,7 @@ runner-test-matrix: test_env_type: docker runs_on: ubuntu22.04-8cores-32GB triggers: - - Automation Nightly Tests + - Automation Nightly Tests test_cmd: cd integration-tests/smoke && go test -test.run ^TestAutomationNodeUpgrade/registry_2_1 -test.parallel=5 -timeout 60m -count=1 -json test_env_vars: E2E_TEST_CHAINLINK_IMAGE: public.ecr.aws/chainlink/chainlink @@ -624,6 +625,7 @@ runner-test-matrix: pyroscope_env: ci-automation-on-demand-chaos test_env_vars: TEST_SUITE: chaos + CHAINLINK_USER_TEAM: Automation - id: benchmark/automation_test.go:TestAutomationBenchmark path: integration-tests/benchmark/automation_test.go @@ -676,7 +678,7 @@ runner-test-matrix: test_env_vars: TEST_TYPE: Smoke triggers: - - On Demand VRFV2 Plus Performance Test + - On Demand VRFV2 Plus Performance Test - id: load/vrfv2plus/vrfv2plus_test.go:^TestVRFV2PlusBHSPerformance$Smoke path: integration-tests/load/vrfv2plus/vrfv2plus_test.go @@ -688,7 +690,7 @@ runner-test-matrix: test_env_vars: TEST_TYPE: Smoke triggers: - - On Demand VRFV2 Plus Performance Test + - On Demand VRFV2 Plus Performance Test - id: load/vrfv2/vrfv2_test.go:^TestVRFV2Performance$Smoke path: integration-tests/load/vrfv2/vrfv2_test.go @@ -698,9 +700,9 @@ runner-test-matrix: test_config_override_required: true test_secrets_required: true test_env_vars: - TEST_TYPE: Smoke + TEST_TYPE: Smoke triggers: - - On Demand VRFV2 Performance Test + - On Demand VRFV2 Performance Test - id: load/vrfv2/vrfv2_test.go:^TestVRFV2PlusBHSPerformance$Smoke path: integration-tests/load/vrfv2/vrfv2_test.go @@ -892,7 +894,7 @@ runner-test-matrix: - Merge Queue E2E Core Tests - Nightly E2E Tests test_cmd: cd integration-tests/ && go test smoke/flux_test.go -timeout 30m -count=1 -json - pyroscope_env: ci-smoke-flux-evm-simulated + pyroscope_env: ci-smoke-flux-evm-simulated - id: smoke/reorg_above_finality_test.go:* path: integration-tests/smoke/reorg_above_finality_test.go @@ -904,7 +906,7 @@ runner-test-matrix: - Nightly E2E Tests test_cmd: cd integration-tests/ && go test smoke/reorg_above_finality_test.go -timeout 30m -count=1 -json pyroscope_env: ci-smoke-reorg-above-finality-evm-simulated - + - id: migration/upgrade_version_test.go:* path: integration-tests/migration/upgrade_version_test.go test_env_type: docker @@ -935,19 +937,6 @@ runner-test-matrix: # START: CCIPv1.6 tests - - id: smoke/ccip/ccip_message_limitations_test.go:* - path: integration-tests/smoke/ccip/ccip_message_limitations_test.go - test_env_type: docker - runs_on: ubuntu-latest - triggers: - - PR E2E Core Tests - - Nightly E2E Tests - test_cmd: cd integration-tests/smoke/ccip && go test -run '^Test_CCIPMessageLimitations' -timeout 18m -test.parallel=1 -count=1 -json ./... - pyroscope_env: ci-smoke-ccipv1_6-evm-simulated - test_env_vars: - E2E_TEST_SELECTED_NETWORK: SIMULATED_1,SIMULATED_2 - E2E_JD_VERSION: 0.6.0 - - id: smoke/ccip/ccip_token_price_updates_test.go:* path: integration-tests/smoke/ccip/ccip_token_price_updates_test.go test_env_type: docker @@ -960,6 +949,7 @@ runner-test-matrix: test_env_vars: E2E_TEST_SELECTED_NETWORK: SIMULATED_1,SIMULATED_2 E2E_JD_VERSION: 0.6.0 + CCIP_V16_TEST_ENV: docker - id: smoke/ccip/ccip_gas_price_updates_test.go:* path: integration-tests/smoke/ccip/ccip_gas_price_updates_test.go @@ -973,11 +963,12 @@ runner-test-matrix: test_env_vars: E2E_TEST_SELECTED_NETWORK: SIMULATED_1,SIMULATED_2 E2E_JD_VERSION: 0.6.0 + CCIP_V16_TEST_ENV: docker - id: smoke/ccip/ccip_rmn_test.go:^TestRMN_TwoMessagesOnTwoLanesIncludingBatching$ path: integration-tests/smoke/ccip/ccip_rmn_test.go test_env_type: docker - runs_on: ubuntu-latest + runs_on: ubuntu20.04-8cores-32GB triggers: - PR E2E Core Tests - Nightly E2E Tests @@ -988,11 +979,12 @@ runner-test-matrix: E2E_JD_VERSION: 0.6.0 E2E_RMN_RAGEPROXY_VERSION: master-f461a9e E2E_RMN_AFN2PROXY_VERSION: master-f461a9e + CCIP_V16_TEST_ENV: docker - id: smoke/ccip/ccip_rmn_test.go:^TestRMN_MultipleMessagesOnOneLaneNoWaitForExec$ path: integration-tests/smoke/ccip/ccip_rmn_test.go test_env_type: docker - runs_on: ubuntu-latest + runs_on: ubuntu20.04-8cores-32GB triggers: - PR E2E Core Tests - Nightly E2E Tests @@ -1003,11 +995,12 @@ runner-test-matrix: E2E_JD_VERSION: 0.6.0 E2E_RMN_RAGEPROXY_VERSION: master-f461a9e E2E_RMN_AFN2PROXY_VERSION: master-f461a9e + CCIP_V16_TEST_ENV: docker - id: smoke/ccip/ccip_rmn_test.go:^TestRMN_NotEnoughObservers$ path: integration-tests/smoke/ccip/ccip_rmn_test.go test_env_type: docker - runs_on: ubuntu-latest + runs_on: ubuntu20.04-8cores-32GB triggers: - PR E2E Core Tests - Nightly E2E Tests @@ -1018,11 +1011,12 @@ runner-test-matrix: E2E_JD_VERSION: 0.6.0 E2E_RMN_RAGEPROXY_VERSION: master-f461a9e E2E_RMN_AFN2PROXY_VERSION: master-f461a9e + CCIP_V16_TEST_ENV: docker - id: smoke/ccip/ccip_rmn_test.go:^TestRMN_DifferentSigners$ path: integration-tests/smoke/ccip/ccip_rmn_test.go test_env_type: docker - runs_on: ubuntu-latest + runs_on: ubuntu20.04-8cores-32GB triggers: - PR E2E Core Tests - Nightly E2E Tests @@ -1033,11 +1027,12 @@ runner-test-matrix: E2E_JD_VERSION: 0.6.0 E2E_RMN_RAGEPROXY_VERSION: master-f461a9e E2E_RMN_AFN2PROXY_VERSION: master-f461a9e + CCIP_V16_TEST_ENV: docker - id: smoke/ccip/ccip_rmn_test.go:^TestRMN_NotEnoughSigners$ path: integration-tests/smoke/ccip/ccip_rmn_test.go test_env_type: docker - runs_on: ubuntu-latest + runs_on: ubuntu20.04-8cores-32GB triggers: - PR E2E Core Tests - Nightly E2E Tests @@ -1048,11 +1043,12 @@ runner-test-matrix: E2E_JD_VERSION: 0.6.0 E2E_RMN_RAGEPROXY_VERSION: master-f461a9e E2E_RMN_AFN2PROXY_VERSION: master-f461a9e + CCIP_V16_TEST_ENV: docker - id: smoke/ccip/ccip_rmn_test.go:^TestRMN_DifferentRmnNodesForDifferentChains$ path: integration-tests/smoke/ccip/ccip_rmn_test.go test_env_type: docker - runs_on: ubuntu-latest + runs_on: ubuntu20.04-8cores-32GB triggers: - PR E2E Core Tests - Nightly E2E Tests @@ -1063,11 +1059,12 @@ runner-test-matrix: E2E_JD_VERSION: 0.6.0 E2E_RMN_RAGEPROXY_VERSION: master-f461a9e E2E_RMN_AFN2PROXY_VERSION: master-f461a9e + CCIP_V16_TEST_ENV: docker - id: smoke/ccip/ccip_rmn_test.go:^TestRMN_TwoMessagesOneSourceChainCursed$ path: integration-tests/smoke/ccip/ccip_rmn_test.go test_env_type: docker - runs_on: ubuntu-latest + runs_on: ubuntu20.04-8cores-32GB triggers: - PR E2E Core Tests - Nightly E2E Tests @@ -1078,11 +1075,12 @@ runner-test-matrix: E2E_JD_VERSION: 0.6.0 E2E_RMN_RAGEPROXY_VERSION: master-f461a9e E2E_RMN_AFN2PROXY_VERSION: master-f461a9e + CCIP_V16_TEST_ENV: docker - id: smoke/ccip/ccip_rmn_test.go:^TestRMN_GlobalCurseTwoMessagesOnTwoLanes$ path: integration-tests/smoke/ccip/ccip_rmn_test.go test_env_type: docker - runs_on: ubuntu-latest + runs_on: ubuntu20.04-8cores-32GB triggers: - PR E2E Core Tests - Nightly E2E Tests @@ -1093,6 +1091,7 @@ runner-test-matrix: E2E_JD_VERSION: 0.6.0 E2E_RMN_RAGEPROXY_VERSION: master-f461a9e E2E_RMN_AFN2PROXY_VERSION: master-f461a9e + CCIP_V16_TEST_ENV: docker # END: CCIPv1.6 tests @@ -1109,7 +1108,8 @@ runner-test-matrix: test_cmd: cd integration-tests/ccip-tests/smoke && go test ccip_test.go -test.run ^TestSmokeCCIPForBidirectionalLane$ -timeout 30m -count=1 -test.parallel=1 -json test_env_vars: E2E_TEST_SELECTED_NETWORK: SIMULATED_1,SIMULATED_2 - + CHAINLINK_USER_TEAM: CCIP + - id: ccip-smoke-usdc path: integration-tests/ccip-tests/smoke/ccip_test.go test_env_type: docker @@ -1121,6 +1121,7 @@ runner-test-matrix: test_cmd: cd integration-tests/ccip-tests/smoke && go test ccip_test.go -test.run ^TestSmokeCCIPForBidirectionalLane$ -timeout 30m -count=1 -test.parallel=1 -json test_env_vars: E2E_TEST_SELECTED_NETWORK: SIMULATED_1,SIMULATED_2 + CHAINLINK_USER_TEAM: CCIP test_config_override_path: integration-tests/ccip-tests/testconfig/tomls/usdc_mock_deployment.toml - id: ccip-smoke-db-compatibility @@ -1134,6 +1135,7 @@ runner-test-matrix: test_cmd: cd integration-tests/ccip-tests/smoke && go test ccip_test.go -test.run ^TestSmokeCCIPForBidirectionalLane$ -timeout 30m -count=1 -test.parallel=1 -json test_env_vars: E2E_TEST_SELECTED_NETWORK: SIMULATED_1,SIMULATED_2 + CHAINLINK_USER_TEAM: CCIP test_config_override_path: integration-tests/ccip-tests/testconfig/tomls/db-compatibility.toml - id: ccip-smoke-leader-lane @@ -1160,6 +1162,7 @@ runner-test-matrix: test_cmd: cd integration-tests/ccip-tests/smoke && go test ccip_test.go -test.run ^TestSmokeCCIPTokenPoolRateLimits$ -timeout 30m -count=1 -test.parallel=1 -json test_env_vars: E2E_TEST_SELECTED_NETWORK: SIMULATED_1,SIMULATED_2 + CHAINLINK_USER_TEAM: CCIP - id: ccip-tests/smoke/ccip_test.go:^TestSmokeCCIPMulticall$ path: integration-tests/ccip-tests/smoke/ccip_test.go @@ -1172,6 +1175,7 @@ runner-test-matrix: test_cmd: cd integration-tests/ccip-tests/smoke && go test ccip_test.go -test.run ^TestSmokeCCIPMulticall$ -timeout 30m -count=1 -test.parallel=1 -json test_env_vars: E2E_TEST_SELECTED_NETWORK: SIMULATED_1,SIMULATED_2 + CHAINLINK_USER_TEAM: CCIP - id: ccip-tests/smoke/ccip_test.go:^TestSmokeCCIPManuallyExecuteAfterExecutionFailingDueToInsufficientGas$ path: integration-tests/ccip-tests/smoke/ccip_test.go @@ -1184,6 +1188,7 @@ runner-test-matrix: test_cmd: cd integration-tests/ccip-tests/smoke && go test ccip_test.go -test.run ^TestSmokeCCIPManuallyExecuteAfterExecutionFailingDueToInsufficientGas$ -timeout 30m -count=1 -test.parallel=1 -json test_env_vars: E2E_TEST_SELECTED_NETWORK: SIMULATED_1,SIMULATED_2 + CHAINLINK_USER_TEAM: CCIP - id: ccip-tests/smoke/ccip_test.go:^TestSmokeCCIPOnRampLimits$ path: integration-tests/ccip-tests/smoke/ccip_test.go @@ -1196,6 +1201,7 @@ runner-test-matrix: test_cmd: cd integration-tests/ccip-tests/smoke && go test ccip_test.go -test.run ^TestSmokeCCIPOnRampLimits$ -timeout 30m -count=1 -test.parallel=1 -json test_env_vars: E2E_TEST_SELECTED_NETWORK: SIMULATED_1,SIMULATED_2 + CHAINLINK_USER_TEAM: CCIP - id: ccip-tests/smoke/ccip_test.go:^TestSmokeCCIPOffRampCapacityLimit$ path: integration-tests/ccip-tests/smoke/ccip_test.go @@ -1205,7 +1211,8 @@ runner-test-matrix: - Nightly E2E Tests test_cmd: cd integration-tests/ccip-tests/smoke && go test ccip_test.go -test.run ^TestSmokeCCIPOffRampCapacityLimit$ -timeout 30m -count=1 -test.parallel=1 -json test_env_vars: - E2E_TEST_SELECTED_NETWORK: SIMULATED_1,SIMULATED_2 + E2E_TEST_SELECTED_NETWORK: SIMULATED_1,SIMULATED_2 + CHAINLINK_USER_TEAM: CCIP - id: ccip-tests/smoke/ccip_test.go:^TestSmokeCCIPOffRampAggRateLimit$ path: integration-tests/ccip-tests/smoke/ccip_test.go @@ -1216,6 +1223,7 @@ runner-test-matrix: test_cmd: cd integration-tests/ccip-tests/smoke && go test ccip_test.go -test.run ^TestSmokeCCIPOffRampAggRateLimit$ -timeout 30m -count=1 -test.parallel=1 -json test_env_vars: E2E_TEST_SELECTED_NETWORK: SIMULATED_1,SIMULATED_2 + CHAINLINK_USER_TEAM: CCIP - id: ccip-tests/smoke/ccip_test.go:^TestSmokeCCIPReorgBelowFinality$ path: integration-tests/ccip-tests/smoke/ccip_test.go @@ -1228,6 +1236,7 @@ runner-test-matrix: test_cmd: cd integration-tests/ccip-tests/smoke && go test ccip_test.go -test.run ^TestSmokeCCIPReorgBelowFinality$ -timeout 30m -count=1 -test.parallel=1 -json test_env_vars: E2E_TEST_SELECTED_NETWORK: SIMULATED_1,SIMULATED_2 + CHAINLINK_USER_TEAM: CCIP test_config_override_path: integration-tests/ccip-tests/testconfig/tomls/ccip-reorg.toml - id: ccip-tests/smoke/ccip_test.go:^TestSmokeCCIPReorgAboveFinalityAtDestination$ @@ -1241,6 +1250,7 @@ runner-test-matrix: test_cmd: cd integration-tests/ccip-tests/smoke && go test ccip_test.go -test.run ^TestSmokeCCIPReorgAboveFinalityAtDestination$ -timeout 30m -count=1 -test.parallel=1 -json test_env_vars: E2E_TEST_SELECTED_NETWORK: SIMULATED_1,SIMULATED_2 + CHAINLINK_USER_TEAM: CCIP test_config_override_path: integration-tests/ccip-tests/testconfig/tomls/ccip-reorg.toml - id: ccip-tests/smoke/ccip_test.go:^TestSmokeCCIPReorgAboveFinalityAtSource$ @@ -1254,6 +1264,7 @@ runner-test-matrix: test_cmd: cd integration-tests/ccip-tests/smoke && go test ccip_test.go -test.run ^TestSmokeCCIPReorgAboveFinalityAtSource$ -timeout 30m -count=1 -test.parallel=1 -json test_env_vars: E2E_TEST_SELECTED_NETWORK: SIMULATED_1,SIMULATED_2 + CHAINLINK_USER_TEAM: CCIP test_config_override_path: integration-tests/ccip-tests/testconfig/tomls/ccip-reorg.toml - id: integration-tests/ccip-tests/load/ccip_test.go:TestLoadCCIPStableRPS @@ -1265,8 +1276,8 @@ runner-test-matrix: TEST_SUITE: ccip-load E2E_TEST_GRAFANA_DASHBOARD_URL: "/d/6vjVx-1V8/ccip-long-running-tests" triggers: - - E2E CCIP Load Tests - test_artifacts_on_failure: + - E2E CCIP Load Tests + test_artifacts_on_failure: - ./integration-tests/load/logs/payload_ccip.json # Enable when CCIP-2277 is resolved @@ -1280,8 +1291,8 @@ runner-test-matrix: # test_env_vars: # E2E_TEST_GRAFANA_DASHBOARD_URL: "/d/6vjVx-1V8/ccip-long-running-tests" # triggers: - # - E2E CCIP Load Tests - # test_artifacts_on_failure: + # - E2E CCIP Load Tests + # test_artifacts_on_failure: # - ./integration-tests/load/logs/payload_ccip.json - id: ccip-tests/chaos/ccip_test.go @@ -1309,5 +1320,5 @@ runner-test-matrix: TEST_TRIGGERED_BY: ccip-cron-chaos-eth TEST_LOG_LEVEL: debug E2E_TEST_GRAFANA_DASHBOARD_URL: /d/6vjVx-1V8/ccip-long-running-tests - + # END: CCIP tests diff --git a/.github/integration-in-memory-tests.yml b/.github/integration-in-memory-tests.yml index b7522274d85..9550d74f21f 100644 --- a/.github/integration-in-memory-tests.yml +++ b/.github/integration-in-memory-tests.yml @@ -23,6 +23,14 @@ runner-test-matrix: triggers: - PR Integration CCIP Tests test_cmd: cd integration-tests/smoke/ccip && go test ccip_messaging_test.go -timeout 12m -test.parallel=2 -count=1 -json + + - id: smoke/ccip/ccip_message_limitations_test.go:* + path: integration-tests/smoke/ccip/ccip_message_limitations_test.go + test_env_type: in-memory + runs_on: ubuntu-latest + triggers: + - PR Integration CCIP Tests + test_cmd: cd integration-tests/smoke/ccip && go test -run "Test_CCIPMessageLimitations" -timeout 12m -test.parallel=2 -count=1 -json - id: smoke/ccip/ccip_fee_boosting_test.go:* path: integration-tests/smoke/ccip/ccip_fee_boosting_test.go diff --git a/.github/workflows/ci-core-partial.yml b/.github/workflows/ci-core-partial.yml index c9752d4e1e4..35f689090e8 100644 --- a/.github/workflows/ci-core-partial.yml +++ b/.github/workflows/ci-core-partial.yml @@ -46,6 +46,7 @@ jobs: permissions: id-token: write contents: write + actions: write strategy: fail-fast: false matrix: @@ -86,7 +87,7 @@ jobs: go-mod-download-directory: ${{ matrix.type.test-suite == 'ccip-deployment' && matrix.type.module-directory || '' }} - name: Build Tests - uses: smartcontractkit/.github/apps/go-conditional-tests@37882e110590e636627a26371bdbd56ddfcce821 # go-conditional-tests@0.1.0 + uses: smartcontractkit/.github/apps/go-conditional-tests@57f99fbea73056c490c766d50ef582a13ec4f3bb # go-conditional-tests@0.2.0 timeout-minutes: 10 with: pipeline-step: "build" @@ -98,7 +99,7 @@ jobs: build-flags: ${{ matrix.type.build-flags }} - name: Run Tests - uses: smartcontractkit/.github/apps/go-conditional-tests@37882e110590e636627a26371bdbd56ddfcce821 # go-conditional-tests@0.1.0 + uses: smartcontractkit/.github/apps/go-conditional-tests@57f99fbea73056c490c766d50ef582a13ec4f3bb # go-conditional-tests@0.2.0 timeout-minutes: 15 env: CL_DATABASE_URL: ${{ env.DB_URL }} @@ -112,7 +113,7 @@ jobs: github-token: ${{ secrets.GITHUB_TOKEN }} - name: Update Test Index - uses: smartcontractkit/.github/apps/go-conditional-tests@37882e110590e636627a26371bdbd56ddfcce821 # go-conditional-tests@0.1.0 + uses: smartcontractkit/.github/apps/go-conditional-tests@57f99fbea73056c490c766d50ef582a13ec4f3bb # go-conditional-tests@0.2.0 with: pipeline-step: "update" collect-coverage: ${{ needs.filter.outputs.should-collect-coverage }} @@ -130,7 +131,7 @@ jobs: if: ${{ needs.filter.outputs.should-collect-coverage == 'true' }} runs-on: ubuntu-latest steps: - - name: Checkout the repo + - name: Checkout the repo uses: actions/checkout@v4.2.1 with: # fetches all history for all tags and branches to provide more metadata for sonar reports diff --git a/.github/workflows/ci-core.yml b/.github/workflows/ci-core.yml index c38ecd918ae..726b6b14074 100644 --- a/.github/workflows/ci-core.yml +++ b/.github/workflows/ci-core.yml @@ -463,9 +463,10 @@ jobs: findByTestFilesDiff: true findByAffectedPackages: false slackNotificationAfterTestsChannelId: 'C07TRF65CNS' #flaky-test-detector-notifications - extraArgs: '{ "skipped_tests": "TestChainComponents", "run_with_race": "true", "print_failed_tests": "true", "test_repeat_count": "3", "min_pass_ratio": "0.01" }' + extraArgs: '{ "skipped_tests": "TestChainComponents", "run_with_race": "true", "print_failed_tests": "true", "test_repeat_count": "3", "omit_test_outputs_on_success": "true" }' secrets: SLACK_BOT_TOKEN: ${{ secrets.QA_SLACK_API_KEY }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} trigger-flaky-test-detection-for-deployment-project: name: Flakeguard Deployment Project @@ -481,9 +482,10 @@ jobs: findByTestFilesDiff: true findByAffectedPackages: false slackNotificationAfterTestsChannelId: 'C07TRF65CNS' #flaky-test-detector-notifications - extraArgs: '{ "skipped_tests": "TestAddLane", "run_with_race": "true", "print_failed_tests": "true", "test_repeat_count": "3", "min_pass_ratio": "0.01" }' + extraArgs: '{ "skipped_tests": "TestAddLane", "run_with_race": "true", "print_failed_tests": "true", "test_repeat_count": "3", "omit_test_outputs_on_success": "true" }' secrets: SLACK_BOT_TOKEN: ${{ secrets.QA_SLACK_API_KEY }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} clean: name: Clean Go Tidy & Generate diff --git a/.github/workflows/client-compatibility-tests.yml b/.github/workflows/client-compatibility-tests.yml index 03c5b893cca..5f986ccf16c 100644 --- a/.github/workflows/client-compatibility-tests.yml +++ b/.github/workflows/client-compatibility-tests.yml @@ -668,9 +668,6 @@ jobs: E2E_TEST_PYROSCOPE_KEY: ${{ secrets.QA_PYROSCOPE_KEY }} E2E_TEST_PYROSCOPE_ENVIRONMENT: ci-client-compatability-${{ matrix.eth_client }}-testnet E2E_TEST_PYROSCOPE_ENABLED: "true" - E2E_TEST_LOGGING_RUN_ID: ${{ github.run_id }} - E2E_TEST_LOG_COLLECT: ${{ vars.TEST_LOG_COLLECT }} - E2E_TEST_LOG_STREAM_LOG_TARGETS: ${{ vars.LOGSTREAM_LOG_TARGETS }} E2E_TEST_PRIVATE_ETHEREUM_EXECUTION_LAYER: ${{ matrix.evm_node.eth_implementation || 'geth' }} E2E_TEST_PRIVATE_ETHEREUM_ETHEREUM_VERSION: auto_fill # Auto fill the version based on the docker image E2E_TEST_PRIVATE_ETHEREUM_CUSTOM_DOCKER_IMAGE: ${{ matrix.evm_node.docker_image }} diff --git a/.github/workflows/flakeguard-nightly.yml b/.github/workflows/flakeguard-nightly.yml index 37c00fa0a8f..178d43d809a 100644 --- a/.github/workflows/flakeguard-nightly.yml +++ b/.github/workflows/flakeguard-nightly.yml @@ -20,3 +20,5 @@ jobs: slackNotificationAfterTestsChannelId: 'C07TRF65CNS' #flaky-test-detector-notifications secrets: SLACK_BOT_TOKEN: ${{ secrets.QA_SLACK_API_KEY }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + diff --git a/.github/workflows/flakeguard-on-demand.yml b/.github/workflows/flakeguard-on-demand.yml index fe972894594..4508da30e6b 100644 --- a/.github/workflows/flakeguard-on-demand.yml +++ b/.github/workflows/flakeguard-on-demand.yml @@ -14,14 +14,13 @@ on: description: 'The path to the project to run the flaky test detection.' default: '.' baseRef: - required: true + required: false type: string - description: 'The base reference or branch to compare changes for detecting flaky tests.' - default: 'origin/develop' + description: 'The base reference or branch to compare changes for detecting flaky tests. Set only when running diffs between branches. E.g. (develop)' headRef: required: false type: string - description: 'The head reference or branch to compare changes for detecting flaky tests. Default is the current branch.' + description: 'The head reference or branch to compare changes for detecting flaky tests. Default is the current branch. E.g. (develop)' runAllTests: required: false type: boolean @@ -69,4 +68,5 @@ jobs: extraArgs: ${{ inputs.extraArgs }} secrets: SLACK_BOT_TOKEN: ${{ secrets.QA_SLACK_API_KEY }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/flakeguard.yml b/.github/workflows/flakeguard.yml index ecc56e2f291..3951c356a3b 100644 --- a/.github/workflows/flakeguard.yml +++ b/.github/workflows/flakeguard.yml @@ -15,11 +15,11 @@ on: baseRef: required: false type: string - description: 'The base reference or branch to compare changes for detecting flaky tests.' + description: 'The base reference or branch to compare changes for detecting flaky tests. Set only when running diffs between branches. E.g. (develop)' headRef: required: false type: string - description: 'The head reference or branch to compare changes for detecting flaky tests. Default is the current branch.' + description: 'The head reference or branch to compare changes for detecting flaky tests. Default is the current branch. E.g. (develop)' runAllTests: required: false type: boolean @@ -52,21 +52,23 @@ on: secrets: SLACK_BOT_TOKEN: required: false + GH_TOKEN: + required: true env: + GIT_BASE_REF: ${{ inputs.baseRef }} GIT_HEAD_REF: ${{ inputs.headRef || github.ref }} - SKIPPED_TESTS: ${{ fromJson(inputs.extraArgs)['skipped_tests'] || '' }} # Comma separated list of test names to skip running in the flaky detector. Related issue: TT-1823 - DEFAULT_MAX_RUNNER_COUNT: ${{ fromJson(inputs.extraArgs)['default_max_runner_count'] || '8' }} # The default maximum number of GitHub runners to use for parallel test execution. - ALL_TESTS_RUNNER_COUNT: ${{ fromJson(inputs.extraArgs)['all_tests_runner_count'] || '2' }} # The number of GitHub runners to use when running all tests `runAllTests=true`. - TEST_REPEAT_COUNT: ${{ fromJson(inputs.extraArgs)['test_repeat_count'] || '5' }} # The number of times each runner should run a test to detect flaky tests. - RUN_WITH_RACE: ${{ fromJson(inputs.extraArgs)['run_with_race'] || 'true' }} # Whether to run tests with -race flag. - RUN_WITH_SHUFFLE: ${{ fromJson(inputs.extraArgs)['run_with_shuffle'] || 'false' }} # Whether to run tests with -shuffle flag. - SHUFFLE_SEED: ${{ fromJson(inputs.extraArgs)['shuffle_seed'] || '999' }} # The seed to use when -shuffle flag is enabled. Requires RUN_WITH_SHUFFLE to be true. - ALL_TESTS_RUNNER: ${{ fromJson(inputs.extraArgs)['all_tests_runner'] || 'ubuntu22.04-32cores-128GB' }} # The runner to use for running all tests. + SKIPPED_TESTS: ${{ fromJSON(inputs.extraArgs)['skipped_tests'] || '' }} # Comma separated list of test names to skip running in the flaky detector. Related issue: TT-1823 + DEFAULT_MAX_RUNNER_COUNT: ${{ fromJSON(inputs.extraArgs)['default_max_runner_count'] || '8' }} # The default maximum number of GitHub runners to use for parallel test execution. + ALL_TESTS_RUNNER_COUNT: ${{ fromJSON(inputs.extraArgs)['all_tests_runner_count'] || '2' }} # The number of GitHub runners to use when running all tests `runAllTests=true`. + TEST_REPEAT_COUNT: ${{ fromJSON(inputs.extraArgs)['test_repeat_count'] || '5' }} # The number of times each runner should run a test to detect flaky tests. + RUN_WITH_RACE: ${{ fromJSON(inputs.extraArgs)['run_with_race'] || 'true' }} # Whether to run tests with -race flag. + RUN_WITH_SHUFFLE: ${{ fromJSON(inputs.extraArgs)['run_with_shuffle'] || 'false' }} # Whether to run tests with -shuffle flag. + SHUFFLE_SEED: ${{ fromJSON(inputs.extraArgs)['shuffle_seed'] || '999' }} # The seed to use when -shuffle flag is enabled. Requires RUN_WITH_SHUFFLE to be true. + ALL_TESTS_RUNNER: ${{ fromJSON(inputs.extraArgs)['all_tests_runner'] || 'ubuntu22.04-32cores-128GB' }} # The runner to use for running all tests. DEFAULT_RUNNER: 'ubuntu-latest' # The default runner to use for running tests. - UPLOAD_ALL_TEST_RESULTS: ${{ fromJson(inputs.extraArgs)['upload_all_test_results'] || 'false' }} # Whether to upload all test results as artifacts. - PRINT_FAILED_TESTS: ${{ fromJson(inputs.extraArgs)['print_failed_tests'] || 'false' }} # Whether to print failed tests in the GitHub console. - + UPLOAD_ALL_TEST_RESULTS: ${{ fromJSON(inputs.extraArgs)['upload_all_test_results'] || 'false' }} # Whether to upload all test results as artifacts. + OMIT_TEST_OUTPUTS_ON_SUCCESS: ${{ fromJSON(inputs.extraArgs)['omit_test_outputs_on_success'] || 'true' }} jobs: get-tests: @@ -79,6 +81,7 @@ jobs: affected_test_packages: ${{ steps.get-tests.outputs.packages }} git_head_sha: ${{ steps.get_commit_sha.outputs.git_head_sha }} git_head_short_sha: ${{ steps.get_commit_sha.outputs.git_head_short_sha }} + git_base_sha: ${{ steps.get_commit_sha.outputs.git_base_sha }} steps: - name: Checkout repository uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 @@ -86,14 +89,33 @@ jobs: fetch-depth: 0 ref: ${{ env.GIT_HEAD_REF }} - - name: Get commit SHA + - name: Get SHA id: get_commit_sha run: | + # Resolve HEAD SHA git_head_sha=$(git rev-parse HEAD) git_head_short_sha=$(git rev-parse --short HEAD) echo "git_head_sha=$git_head_sha" >> $GITHUB_OUTPUT echo "git_head_short_sha=$git_head_short_sha" >> $GITHUB_OUTPUT + # Print HEAD SHAs to the console + echo "HEAD SHA: $git_head_sha" + echo "HEAD Short SHA: $git_head_short_sha" + + # Conditionally resolve BASE SHA + if [ -n "${{ env.GIT_BASE_REF }}" ]; then + git fetch origin ${{ env.GIT_BASE_REF }} --quiet + + git_base_sha=$(git rev-parse origin/${{ env.GIT_BASE_REF }}) + echo "git_base_sha=$git_base_sha" >> $GITHUB_OUTPUT + + # Print BASE SHA to the console + echo "BASE SHA: $git_base_sha" + else + echo "BASE SHA not provided." + echo "git_base_sha=" >> $GITHUB_OUTPUT + fi + - name: Set up Go 1.21.9 uses: actions/setup-go@v5.0.2 with: @@ -101,7 +123,7 @@ jobs: - name: Install flakeguard shell: bash - run: go install github.com/smartcontractkit/chainlink-testing-framework/tools/flakeguard@9e40f2765df01f20b3bf53f0fb3ead920e3a1f4a # flakguard@0.1.0 + run: go install github.com/smartcontractkit/chainlink-testing-framework/tools/flakeguard@ea4ffd8c51ce02efebf5ea6bca503fe10b6cee92 # flakguard@0.1.0 - name: Find new or updated test packages if: ${{ inputs.runAllTests == false }} @@ -114,7 +136,7 @@ jobs: PATH=$PATH:$(go env GOPATH)/bin export PATH - PACKAGES=$(flakeguard find --find-by-test-files-diff=${{ inputs.findByTestFilesDiff }} --find-by-affected-packages=${{ inputs.findByAffectedPackages }} --base-ref=origin/${{ inputs.baseRef }} --project-path=${{ inputs.projectPath }}) + PACKAGES=$(flakeguard find --find-by-test-files-diff=${{ inputs.findByTestFilesDiff }} --find-by-affected-packages=${{ inputs.findByAffectedPackages }} --base-ref=origin/${{ env.GIT_BASE_REF }} --project-path=${{ inputs.projectPath }}) echo $PACKAGES echo "packages=$PACKAGES" >> $GITHUB_OUTPUT @@ -129,7 +151,7 @@ jobs: PATH=$PATH:$(go env GOPATH)/bin export PATH - TEST_FILES=$(flakeguard find --only-show-changed-test-files=true --base-ref=origin/${{ inputs.baseRef }} --project-path=${{ inputs.projectPath }}) + TEST_FILES=$(flakeguard find --only-show-changed-test-files=true --base-ref=origin/${{ env.GIT_BASE_REF }} --project-path=${{ inputs.projectPath }}) echo $TEST_FILES echo "test_files=$TEST_FILES" >> $GITHUB_OUTPUT @@ -196,11 +218,11 @@ jobs: needs: get-tests runs-on: ${{ matrix.runs_on }} if: ${{ needs.get-tests.outputs.matrix != '' && needs.get-tests.outputs.matrix != '[]' }} - timeout-minutes: 90 + timeout-minutes: 180 strategy: fail-fast: false matrix: - include: ${{ fromJson(needs.get-tests.outputs.matrix) }} + include: ${{ fromJSON(needs.get-tests.outputs.matrix) }} env: DB_URL: postgresql://postgres:postgres@localhost:5432/chainlink_test?sslmode=disable steps: @@ -260,11 +282,11 @@ jobs: - name: Install flakeguard shell: bash - run: go install github.com/smartcontractkit/chainlink-testing-framework/tools/flakeguard@9e40f2765df01f20b3bf53f0fb3ead920e3a1f4a # flakguard@0.1.0 + run: go install github.com/smartcontractkit/chainlink-testing-framework/tools/flakeguard@ea4ffd8c51ce02efebf5ea6bca503fe10b6cee92 # flakguard@0.1.0 - name: Run tests with flakeguard shell: bash - run: flakeguard run --project-path=${{ inputs.projectPath }} --test-packages=${{ matrix.testPackages }} --run-count=${{ env.TEST_REPEAT_COUNT }} --max-pass-ratio=${{ inputs.maxPassRatio }} --race=${{ env.RUN_WITH_RACE }} --shuffle=${{ env.RUN_WITH_SHUFFLE }} --shuffle-seed=${{ env.SHUFFLE_SEED }} --skip-tests=${{ env.SKIPPED_TESTS }} --print-failed-tests=${{ env.PRINT_FAILED_TESTS }} --output-json=test-result.json + run: flakeguard run --project-path=${{ inputs.projectPath }} --test-packages=${{ matrix.testPackages }} --run-count=${{ env.TEST_REPEAT_COUNT }} --max-pass-ratio=${{ inputs.maxPassRatio }} --race=${{ env.RUN_WITH_RACE }} --shuffle=${{ env.RUN_WITH_SHUFFLE }} --shuffle-seed=${{ env.SHUFFLE_SEED }} --skip-tests=${{ env.SKIPPED_TESTS }} --output-json=test-result.json --omit-test-outputs-on-success=${{ env.OMIT_TEST_OUTPUTS_ON_SUCCESS }} env: CL_DATABASE_URL: ${{ env.DB_URL }} @@ -282,7 +304,7 @@ jobs: name: Report runs-on: ubuntu-latest outputs: - test_results: ${{ steps.set_test_results.outputs.results }} + test_results: ${{ steps.results.outputs.results }} steps: - name: Checkout repository uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 @@ -307,136 +329,131 @@ jobs: - name: Install flakeguard shell: bash - run: go install github.com/smartcontractkit/chainlink-testing-framework/tools/flakeguard@9e40f2765df01f20b3bf53f0fb3ead920e3a1f4a # flakguard@0.1.0 + run: go install github.com/smartcontractkit/chainlink-testing-framework/tools/flakeguard@ea4ffd8c51ce02efebf5ea6bca503fe10b6cee92 # flakguard@0.1.0 - - name: Set combined test results - id: set_test_results + - name: Aggregate Flakeguard Results + id: results shell: bash run: | set -e # Exit immediately if a command exits with a non-zero status. - - if [ -d "ci_test_results" ]; then - cd ci_test_results - ls -R . - - # Fix flakeguard binary path - PATH=$PATH:$(go env GOPATH)/bin - export PATH - - # Use flakeguard to aggregate all test results - flakeguard aggregate-results --results-path . --output-results ../all_tests.json --project-path=${{ github.workspace }}/${{ inputs.projectPath }} --codeowners-path=${{ github.workspace }}/.github/CODEOWNERS - - # Count all tests - ALL_TESTS_COUNT=$(jq '.Results | length' ../all_tests.json) - echo "All tests count: $ALL_TESTS_COUNT" - echo "all_tests_count=$ALL_TESTS_COUNT" >> "$GITHUB_OUTPUT" - - # Use flakeguard to filter and output failed tests based on MaxPassRatio - flakeguard aggregate-results --filter-failed=true --max-pass-ratio=${{ inputs.maxPassRatio }} --results-path . --output-results ../failed_tests.json --output-logs ../failed_test_logs.json --project-path=${{ github.workspace }}/${{ inputs.projectPath }} --codeowners-path=${{ github.workspace }}/.github/CODEOWNERS - - # Count failed tests - if [ -f "../failed_tests.json" ]; then - FAILED_TESTS_COUNT=$(jq '.Results | length' ../failed_tests.json) - else - FAILED_TESTS_COUNT=0 - fi - echo "Failed tests count: $FAILED_TESTS_COUNT" - echo "failed_tests_count=$FAILED_TESTS_COUNT" >> "$GITHUB_OUTPUT" - - # Calculate failed ratio (failed / non-failed tests ratio in %) - if [ "$ALL_TESTS_COUNT" -gt 0 ]; then - NON_FAILED_COUNT=$((ALL_TESTS_COUNT - FAILED_TESTS_COUNT)) - - if [ "$NON_FAILED_COUNT" -gt 0 ]; then - FAILED_RATIO=$(awk "BEGIN {printf \"%.2f\", ($FAILED_TESTS_COUNT / $NON_FAILED_COUNT) * 100}") - else - FAILED_RATIO=0 - fi - else - NON_FAILED_COUNT=0 - FAILED_RATIO=0 - fi - echo "Failed tests ratio: $FAILED_RATIO%" - echo "failed_ratio=$FAILED_RATIO" >> "$GITHUB_OUTPUT" - else - echo "No test results directory found." - echo "all_tests_count=0" >> "$GITHUB_OUTPUT" - echo "failed_tests_count=0" >> "$GITHUB_OUTPUT" - echo "failed_ratio=0" >> "$GITHUB_OUTPUT" - fi - - - name: Tests Summary - if: ${{ fromJson(steps.set_test_results.outputs.all_tests_count) > 0 }} - run: | - FILE_SIZE=$(wc -c < all_tests.md) - echo "File size: $FILE_SIZE bytes" - SIZE_LIMIT=$((1024 * 1024)) - - if [ "$FILE_SIZE" -le "$SIZE_LIMIT" ]; then - cat all_tests.md >> $GITHUB_STEP_SUMMARY - else - echo "**We found flaky tests, so many flaky tests that the summary is too large for github actions step summaries!**" >> $GITHUB_STEP_SUMMARY - echo "**Please see logs, or the attached `all-summary.md` artifact**" >> $GITHUB_STEP_SUMMARY - cat all_tests.md - fi - - - name: Upload All Tests Summary as Artifact - if: ${{ fromJson(steps.set_test_results.outputs.all_tests_count) > 0 }} - uses: actions/upload-artifact@v4.4.3 - with: - path: all_tests.md - name: all-summary.md - retention-days: 90 + + # Create test results folder if it doesn't exist + mkdir -p ci_test_results + + # Fix flakeguard binary path + PATH=$PATH:$(go env GOPATH)/bin + export PATH + + # Aggregate Flakeguard test results + flakeguard aggregate-results \ + --results-path ./ci_test_results \ + --output-path ./flakeguard-report \ + --repo-path "${{ github.workspace }}" \ + --codeowners-path "${{ github.workspace }}/.github/CODEOWNERS" \ + --max-pass-ratio "${{ inputs.maxPassRatio }}" \ + --repo-url "${{ inputs.repoUrl }}" \ + --base-sha "${{ needs.get-tests.outputs.git_base_sha }}" \ + --head-sha "${{ needs.get-tests.outputs.git_head_sha }}" \ + --github-workflow-name "${{ github.workflow }}" + + # Print out the summary file + echo -e "\nFlakeguard Summary:" + jq . ./flakeguard-report/all-test-summary.json + + # Read the summary from the generated report + summary=$(jq -c '.' ./flakeguard-report/all-test-summary.json) + echo "summary=$summary" >> $GITHUB_OUTPUT - name: Upload All Test Results as Artifact - if: ${{ fromJson(steps.set_test_results.outputs.all_tests_count) > 0 }} + if: ${{ fromJSON(steps.results.outputs.summary).total_tests > 0 }} uses: actions/upload-artifact@v4.4.3 with: - path: all_tests.json + path: ./flakeguard-report/all-test-results.json name: all-test-results.json retention-days: 90 - - name: Upload Failed Tests Summary as Artifact - if: ${{ fromJson(steps.set_test_results.outputs.all_tests_count) > 0 }} - uses: actions/upload-artifact@v4.4.3 - with: - path: failed_tests.md - name: failed-summary.md - retention-days: 90 - - name: Upload Failed Test Results as Artifact - if: ${{ fromJson(steps.set_test_results.outputs.failed_tests_count) > 0 }} + if: ${{ fromJSON(steps.results.outputs.summary).failed_runs > 0 }} uses: actions/upload-artifact@v4.4.3 with: - path: failed_tests.json + path: ./flakeguard-report/failed-test-results.json name: failed-test-results.json - retention-days: 90 - - - name: Upload Failed Test Logs as Artifact - if: ${{ fromJson(steps.set_test_results.outputs.failed_tests_count) > 0 }} - uses: actions/upload-artifact@v4.4.3 - with: - path: failed_test_logs.json - name: failed-test-logs.json - retention-days: 90 - - - name: Upload All Test Results as Artifact - if: ${{ fromJson(steps.set_test_results.outputs.all_tests_count) > 0 && env.UPLOAD_ALL_TEST_RESULTS == 'true' }} + retention-days: 90 + + - name: Upload Failed Test Results With Logs as Artifact + if: ${{ fromJSON(steps.results.outputs.summary).failed_runs > 0 }} uses: actions/upload-artifact@v4.4.3 with: - path: all_tests.json - name: all-test-results.json + path: ./flakeguard-report/failed-test-results-with-logs.json + name: failed-test-results-with-logs.json retention-days: 90 + - name: Generate Flakeguard Reports + shell: bash + env: + GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} + run: | + set -e # Exit immediately if a command exits with a non-zero status. + + # Fix flakeguard binary path + PATH=$PATH:$(go env GOPATH)/bin + export PATH + + # Check if the event is a pull request + if [ "${{ github.event_name }}" = "pull_request" ]; then + flakeguard generate-report \ + --aggregated-results-path ./flakeguard-report/all-test-results.json \ + --summary-path ./flakeguard-report/all-test-summary.json \ + --output-path ./flakeguard-report \ + --github-repository "${{ github.repository }}" \ + --github-run-id "${{ github.run_id }}" \ + --failed-tests-artifact-name "failed-test-results-with-logs.json" \ + --generate-pr-comment \ + --base-branch "${{ github.event.pull_request.base.ref }}" \ + --current-branch "${{ github.head_ref }}" \ + --current-commit-sha "${{ github.event.pull_request.head.sha }}" \ + --repo-url "https://github.com/${{ github.repository }}" \ + --action-run-id "${{ github.run_id }}" \ + --max-pass-ratio "${{ inputs.maxPassRatio }}" + else + flakeguard generate-report \ + --aggregated-results-path ./flakeguard-report/all-test-results.json \ + --summary-path ./flakeguard-report/all-test-summary.json \ + --output-path ./flakeguard-report \ + --github-repository "${{ github.repository }}" \ + --github-run-id "${{ github.run_id }}" \ + --failed-tests-artifact-name "failed-test-results-with-logs.json" \ + --base-branch "${{ github.event.pull_request.base.ref }}" \ + --current-branch "${{ github.head_ref }}" \ + --current-commit-sha "${{ github.event.pull_request.head.sha }}" \ + --repo-url "https://github.com/${{ github.repository }}" \ + --action-run-id "${{ github.run_id }}" \ + --max-pass-ratio "${{ inputs.maxPassRatio }}" + fi + + - name: Add Github Summary + run: | + FILE_SIZE=$(wc -c < ./flakeguard-report/all-test-summary.md) + echo "File size: $FILE_SIZE bytes" + SIZE_LIMIT=$((1024 * 1024)) + + if [ "$FILE_SIZE" -le "$SIZE_LIMIT" ]; then + cat ./flakeguard-report/all-test-summary.md >> $GITHUB_STEP_SUMMARY + else + echo "**We found flaky tests, so many flaky tests that the summary is too large for github actions step summaries!**" >> $GITHUB_STEP_SUMMARY + echo "**Please see logs, or the attached `all-test-summary.md` artifact**" >> $GITHUB_STEP_SUMMARY + cat ./flakeguard-report/all-test-summary.md + fi + - name: Post comment on PR if flaky tests found - if: ${{ fromJson(steps.set_test_results.outputs.failed_tests_count) > 0 && github.event_name == 'pull_request' }} + if: ${{ fromJSON(steps.results.outputs.summary).flaky_tests > 0 && github.event_name == 'pull_request' }} uses: actions/github-script@v7 continue-on-error: true with: script: | const fs = require('fs'); const prNumber = context.payload.pull_request.number; - const commentBody = fs.readFileSync('all_tests.md', 'utf8'); + const commentBody = fs.readFileSync('./flakeguard-report/all-test-pr-comment.md', 'utf8'); await github.rest.issues.createComment({ owner: context.repo.owner, @@ -446,7 +463,7 @@ jobs: }); - name: Send Slack message for failed tests - if: ${{ inputs.slackNotificationAfterTestsChannelId != '' && fromJson(steps.set_test_results.outputs.failed_tests_count) > 0 }} + if: ${{ inputs.slackNotificationAfterTestsChannelId != '' && fromJSON(steps.results.outputs.summary).flaky_tests > 0 }} uses: slackapi/slack-github-action@6c661ce58804a1a20f6dc5fbee7f0381b469e001 # v1.25.0 env: SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} @@ -469,7 +486,7 @@ jobs: "type": "section", "text": { "type": "mrkdwn", - "text": "${{ inputs.runAllTests == true && format('Ran all tests for `{0}` branch.', inputs.headRef) || format('Ran changed tests between `{0}` and `{1}` (`{2}`).', inputs.baseRef, needs.get-tests.outputs.git_head_short_sha, env.GIT_HEAD_REF) }}" + "text": "${{ inputs.runAllTests == true && format('Ran all tests for `{0}` branch.', env.GIT_HEAD_REF) || format('Ran changed tests between `{0}` and `{1}` (`{2}`).', env.GIT_BASE_REF, needs.get-tests.outputs.git_head_short_sha, env.GIT_HEAD_REF) }}" } }, { @@ -477,11 +494,11 @@ jobs: "fields": [ { "type": "mrkdwn", - "text": "Total Failed Tests: ${{ steps.set_test_results.outputs.failed_tests_count }}" + "text": "Total Flaky Tests: ${{ fromJSON(steps.results.outputs.summary).flaky_tests }}" }, { "type": "mrkdwn", - "text": "Failed to Non-Failed Ratio: ${{ steps.set_test_results.outputs.failed_ratio }}%" + "text": "Flaky Tests Ratio: ${{ fromJSON(steps.results.outputs.summary).flaky_test_ratio }}" } ] }, @@ -489,7 +506,7 @@ jobs: "type": "section", "text": { "type": "mrkdwn", - "text": "${{ format('<{0}/{1}/actions/runs/{2}|View Flaky Detector Details> | <{3}/compare/{4}...{5}#files_bucket|Compare Changes>{6}', github.server_url, github.repository, github.run_id, inputs.repoUrl, inputs.baseRef, needs.get-tests.outputs.git_head_sha, github.event_name == 'pull_request' && format(' | <{0}|View PR>', github.event.pull_request.html_url) || '') }}" + "text": "${{ format('<{0}/{1}/actions/runs/{2}|View Flaky Detector Details> | <{3}/compare/{4}...{5}#files_bucket|Compare Changes>{6}', github.server_url, github.repository, github.run_id, inputs.repoUrl, env.GIT_BASE_REF, needs.get-tests.outputs.git_head_sha, github.event_name == 'pull_request' && format(' | <{0}|View PR>', github.event.pull_request.html_url) || '') }}" } } ] @@ -499,7 +516,7 @@ jobs: - name: Send general Slack message uses: slackapi/slack-github-action@6c661ce58804a1a20f6dc5fbee7f0381b469e001 # v1.25.0 - if: ${{ inputs.slackNotificationAfterTestsChannelId != '' && fromJson(steps.set_test_results.outputs.failed_tests_count) == 0 && fromJson(steps.set_test_results.outputs.all_tests_count) > 0 }} + if: ${{ inputs.slackNotificationAfterTestsChannelId != '' && fromJSON(steps.results.outputs.summary).flaky_tests == 0 && fromJSON(steps.results.outputs.summary).total_tests > 0 }} id: slack env: SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} @@ -522,7 +539,7 @@ jobs: "type": "section", "text": { "type": "mrkdwn", - "text": "${{ inputs.runAllTests == true && format('Ran all tests for `{0}` branch.', env.GIT_HEAD_REF) || format('Ran changed tests between `{0}` and `{1}` (`{2}`).', inputs.baseRef, needs.get-tests.outputs.git_head_short_sha, env.GIT_HEAD_REF) }}" + "text": "${{ inputs.runAllTests == true && format('Ran all tests for `{0}` branch.', env.GIT_HEAD_REF) || format('Ran changed tests between `{0}` and `{1}` (`{2}`).', env.GIT_BASE_REF, needs.get-tests.outputs.git_head_short_sha, env.GIT_HEAD_REF) }}" } }, { diff --git a/.github/workflows/integration-chaos-tests.yml b/.github/workflows/integration-chaos-tests.yml index 314e54a1ab8..c9f7f2661ec 100644 --- a/.github/workflows/integration-chaos-tests.yml +++ b/.github/workflows/integration-chaos-tests.yml @@ -6,11 +6,49 @@ on: tags: - "*" workflow_dispatch: + inputs: + team: + description: Team to run the tests for (e.g. BIX, CCIP) + required: true + type: string jobs: + run-e2e-tests-workflow-dispatch: + name: Run E2E Tests (Workflow Dispatch) + uses: smartcontractkit/.github/.github/workflows/run-e2e-tests.yml@0d4a2b2b009c87b5c366d0b97f7a8d7de2f60760 + if: github.event_name == 'workflow_dispatch' + with: + test_path: .github/e2e-tests.yml + chainlink_version: ${{ github.sha }} + require_chainlink_image_versions_in_qa_ecr: ${{ github.sha }} + test_trigger: E2E Chaos Tests + test_log_level: debug + team: ${{ inputs.team }} + secrets: + QA_AWS_REGION: ${{ secrets.QA_AWS_REGION }} + QA_AWS_ROLE_TO_ASSUME: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} + QA_AWS_ACCOUNT_NUMBER: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }} + PROD_AWS_ACCOUNT_NUMBER: ${{ secrets.AWS_ACCOUNT_ID_PROD }} + QA_PYROSCOPE_INSTANCE: ${{ secrets.QA_PYROSCOPE_INSTANCE }} + QA_PYROSCOPE_KEY: ${{ secrets.QA_PYROSCOPE_KEY }} + QA_KUBECONFIG: ${{ secrets.QA_KUBECONFIG }} + GRAFANA_INTERNAL_TENANT_ID: ${{ secrets.GRAFANA_INTERNAL_TENANT_ID }} + GRAFANA_INTERNAL_BASIC_AUTH: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }} + GRAFANA_INTERNAL_HOST: ${{ secrets.GRAFANA_INTERNAL_HOST }} + GRAFANA_INTERNAL_URL_SHORTENER_TOKEN: ${{ secrets.GRAFANA_INTERNAL_URL_SHORTENER_TOKEN }} + LOKI_TENANT_ID: ${{ secrets.LOKI_TENANT_ID }} + LOKI_URL: ${{ secrets.LOKI_URL }} + LOKI_BASIC_AUTH: ${{ secrets.LOKI_BASIC_AUTH }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + AWS_REGION: ${{ secrets.QA_AWS_REGION }} + AWS_OIDC_IAM_ROLE_VALIDATION_PROD_ARN: ${{ secrets.AWS_OIDC_IAM_ROLE_VALIDATION_PROD_ARN }} + AWS_API_GW_HOST_GRAFANA: ${{ secrets.AWS_API_GW_HOST_GRAFANA }} + SLACK_BOT_TOKEN: ${{ secrets.QA_SLACK_API_KEY }} + run-e2e-tests-workflow: - name: Run E2E Tests - uses: smartcontractkit/.github/.github/workflows/run-e2e-tests.yml@5412507526722a7b1c5d719fa686eed5a1bc4035 # ctf-run-tests@0.2.0 + name: Run E2E Tests (Push and Sechedule) + uses: smartcontractkit/.github/.github/workflows/run-e2e-tests.yml@0d4a2b2b009c87b5c366d0b97f7a8d7de2f60760 + if: github.event_name != 'workflow_dispatch' with: test_path: .github/e2e-tests.yml chainlink_version: ${{ github.sha }} @@ -32,8 +70,9 @@ jobs: LOKI_TENANT_ID: ${{ secrets.LOKI_TENANT_ID }} LOKI_URL: ${{ secrets.LOKI_URL }} LOKI_BASIC_AUTH: ${{ secrets.LOKI_BASIC_AUTH }} - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} AWS_REGION: ${{ secrets.QA_AWS_REGION }} AWS_OIDC_IAM_ROLE_VALIDATION_PROD_ARN: ${{ secrets.AWS_OIDC_IAM_ROLE_VALIDATION_PROD_ARN }} - AWS_API_GW_HOST_GRAFANA: ${{ secrets.AWS_API_GW_HOST_GRAFANA }} + AWS_API_GW_HOST_GRAFANA: ${{ secrets.AWS_API_GW_HOST_GRAFANA }} SLACK_BOT_TOKEN: ${{ secrets.QA_SLACK_API_KEY }} + diff --git a/.github/workflows/integration-in-memory-tests.yml b/.github/workflows/integration-in-memory-tests.yml index 8d777b41ea1..341d66f641e 100644 --- a/.github/workflows/integration-in-memory-tests.yml +++ b/.github/workflows/integration-in-memory-tests.yml @@ -73,7 +73,7 @@ jobs: contents: read needs: changes if: github.event_name == 'pull_request' && ( needs.changes.outputs.ccip_changes == 'true' || needs.changes.outputs.core_changes == 'true' || needs.changes.outputs.github_ci_changes == 'true') - uses: smartcontractkit/.github/.github/workflows/run-integration-tests.yml@57112554b9e5cfae79e795a8b1c36acf7e9dead7 + uses: smartcontractkit/.github/.github/workflows/run-integration-tests.yml@0632b5652dd5eb03bfa87e23a2b3e2911484fe59 with: workflow_name: Run CCIP Integration Tests For PR test_path: .github/integration-in-memory-tests.yml @@ -95,7 +95,7 @@ jobs: contents: read needs: changes if: github.event_name == 'merge_group' && ( needs.changes.outputs.ccip_changes == 'true' || needs.changes.outputs.core_changes == 'true' || needs.changes.outputs.github_ci_changes == 'true') - uses: smartcontractkit/.github/.github/workflows/run-integration-tests.yml@57112554b9e5cfae79e795a8b1c36acf7e9dead7 + uses: smartcontractkit/.github/.github/workflows/run-integration-tests.yml@0632b5652dd5eb03bfa87e23a2b3e2911484fe59 with: workflow_name: Run CCIP Integration Tests For Merge Queue test_path: .github/integration-in-memory-tests.yml diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index bac453eb044..27bdfa52243 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -210,7 +210,7 @@ jobs: contents: read needs: [build-chainlink, changes] if: github.event_name == 'pull_request' && ( needs.changes.outputs.core_changes == 'true' || needs.changes.outputs.github_ci_changes == 'true') - uses: smartcontractkit/.github/.github/workflows/run-e2e-tests.yml@27467f0073162e0ca77d33ce26f649b3d0f4c188 #ctf-run-tests@0.2.0 + uses: smartcontractkit/.github/.github/workflows/run-e2e-tests.yml@0632b5652dd5eb03bfa87e23a2b3e2911484fe59 with: workflow_name: Run Core E2E Tests For PR chainlink_version: ${{ inputs.evm-ref || github.sha }} @@ -251,7 +251,7 @@ jobs: contents: read needs: [build-chainlink, changes] if: github.event_name == 'merge_group' && ( needs.changes.outputs.core_changes == 'true' || needs.changes.outputs.github_ci_changes == 'true') - uses: smartcontractkit/.github/.github/workflows/run-e2e-tests.yml@27467f0073162e0ca77d33ce26f649b3d0f4c188 #ctf-run-tests@1.0.0 + uses: smartcontractkit/.github/.github/workflows/run-e2e-tests.yml@0632b5652dd5eb03bfa87e23a2b3e2911484fe59 with: workflow_name: Run Core E2E Tests For Merge Queue chainlink_version: ${{ inputs.evm-ref || github.sha }} @@ -296,7 +296,7 @@ jobs: contents: read needs: [build-chainlink, changes] if: github.event_name == 'pull_request' && (needs.changes.outputs.ccip_changes == 'true' || needs.changes.outputs.github_ci_changes == 'true') - uses: smartcontractkit/.github/.github/workflows/run-e2e-tests.yml@0d4a2b2b009c87b5c366d0b97f7a8d7de2f60760 + uses: smartcontractkit/.github/.github/workflows/run-e2e-tests.yml@0632b5652dd5eb03bfa87e23a2b3e2911484fe59 with: workflow_name: Run CCIP E2E Tests For PR chainlink_version: ${{ inputs.evm-ref || github.sha }} @@ -338,7 +338,7 @@ jobs: contents: read needs: [build-chainlink, changes] if: github.event_name == 'merge_group' && (needs.changes.outputs.ccip_changes == 'true' || needs.changes.outputs.github_ci_changes == 'true') - uses: smartcontractkit/.github/.github/workflows/run-e2e-tests.yml@0d4a2b2b009c87b5c366d0b97f7a8d7de2f60760 + uses: smartcontractkit/.github/.github/workflows/run-e2e-tests.yml@0632b5652dd5eb03bfa87e23a2b3e2911484fe59 with: workflow_name: Run CCIP E2E Tests For Merge Queue chainlink_version: ${{ inputs.evm-ref || github.sha }} @@ -382,7 +382,7 @@ jobs: results='${{ needs.run-core-e2e-tests-for-pr.outputs.test_results }}' echo "Core test results:" echo "$results" | jq . - + node_migration_tests_failed=$(echo $results | jq '[.[] | select(.id == "integration-tests/migration/upgrade_version_test.go:*" ) | select(.result != "success")] | length > 0') echo "node_migration_tests_failed=$node_migration_tests_failed" >> $GITHUB_OUTPUT @@ -730,6 +730,7 @@ jobs: env: E2E_TEST_CHAINLINK_IMAGE: ${{ env.CHAINLINK_IMAGE }} E2E_TEST_SOLANA_SECRET: thisisatestingonlysecret + CHAINLINK_USER_TEAM: "BIX" - name: Upload Coverage Data uses: actions/upload-artifact@v4.4.3 diff --git a/.github/workflows/on-demand-vrfv2-performance-test.yml b/.github/workflows/on-demand-vrfv2-performance-test.yml index aadef377718..f9aeaa0fa1f 100644 --- a/.github/workflows/on-demand-vrfv2-performance-test.yml +++ b/.github/workflows/on-demand-vrfv2-performance-test.yml @@ -67,7 +67,7 @@ jobs: run-e2e-tests-workflow: name: Run E2E Tests needs: set-tests-to-run - uses: smartcontractkit/.github/.github/workflows/run-e2e-tests.yml@5412507526722a7b1c5d719fa686eed5a1bc4035 # ctf-run-tests@0.2.0 + uses: smartcontractkit/.github/.github/workflows/run-e2e-tests.yml@0632b5652dd5eb03bfa87e23a2b3e2911484fe59 with: custom_test_list_json: ${{ needs.set-tests-to-run.outputs.test_list }} chainlink_version: ${{ inputs.chainlink_version }} diff --git a/.github/workflows/on-demand-vrfv2-smoke-tests.yml b/.github/workflows/on-demand-vrfv2-smoke-tests.yml index 4ebc38a8081..ad616dea744 100644 --- a/.github/workflows/on-demand-vrfv2-smoke-tests.yml +++ b/.github/workflows/on-demand-vrfv2-smoke-tests.yml @@ -70,7 +70,7 @@ jobs: run-e2e-tests-workflow: name: Run E2E Tests needs: set-tests-to-run - uses: smartcontractkit/.github/.github/workflows/run-e2e-tests.yml@5412507526722a7b1c5d719fa686eed5a1bc4035 # ctf-run-tests@0.2.0 + uses: smartcontractkit/.github/.github/workflows/run-e2e-tests.yml@0632b5652dd5eb03bfa87e23a2b3e2911484fe59 with: custom_test_list_json: ${{ needs.set-tests-to-run.outputs.test_list }} chainlink_version: ${{ inputs.chainlink_version }} diff --git a/.github/workflows/on-demand-vrfv2plus-performance-test.yml b/.github/workflows/on-demand-vrfv2plus-performance-test.yml index f6d120ac178..b3a820e25a0 100644 --- a/.github/workflows/on-demand-vrfv2plus-performance-test.yml +++ b/.github/workflows/on-demand-vrfv2plus-performance-test.yml @@ -67,7 +67,7 @@ jobs: run-e2e-tests-workflow: name: Run E2E Tests needs: set-tests-to-run - uses: smartcontractkit/.github/.github/workflows/run-e2e-tests.yml@5412507526722a7b1c5d719fa686eed5a1bc4035 # ctf-run-tests@0.2.0 + uses: smartcontractkit/.github/.github/workflows/run-e2e-tests.yml@0632b5652dd5eb03bfa87e23a2b3e2911484fe59 with: custom_test_list_json: ${{ needs.set-tests-to-run.outputs.test_list }} chainlink_version: ${{ inputs.chainlink_version }} diff --git a/.github/workflows/on-demand-vrfv2plus-smoke-tests.yml b/.github/workflows/on-demand-vrfv2plus-smoke-tests.yml index af26c527988..8561034b103 100644 --- a/.github/workflows/on-demand-vrfv2plus-smoke-tests.yml +++ b/.github/workflows/on-demand-vrfv2plus-smoke-tests.yml @@ -70,7 +70,7 @@ jobs: run-e2e-tests-workflow: name: Run E2E Tests needs: set-tests-to-run - uses: smartcontractkit/.github/.github/workflows/run-e2e-tests.yml@5412507526722a7b1c5d719fa686eed5a1bc4035 # ctf-run-tests@0.2.0 + uses: smartcontractkit/.github/.github/workflows/run-e2e-tests.yml@0632b5652dd5eb03bfa87e23a2b3e2911484fe59 with: custom_test_list_json: ${{ needs.set-tests-to-run.outputs.test_list }} chainlink_version: ${{ inputs.chainlink_version }} diff --git a/.github/workflows/run-nightly-e2e-tests.yml b/.github/workflows/run-nightly-e2e-tests.yml index eba1108f89f..712fb088181 100644 --- a/.github/workflows/run-nightly-e2e-tests.yml +++ b/.github/workflows/run-nightly-e2e-tests.yml @@ -20,7 +20,7 @@ on: jobs: call-run-e2e-tests-workflow: name: Run E2E Tests - uses: smartcontractkit/.github/.github/workflows/run-e2e-tests.yml@5412507526722a7b1c5d719fa686eed5a1bc4035 # ctf-run-tests@0.2.0 + uses: smartcontractkit/.github/.github/workflows/run-e2e-tests.yml@0632b5652dd5eb03bfa87e23a2b3e2911484fe59 with: chainlink_version: ${{ inputs.chainlink_version || 'develop' }} test_path: .github/e2e-tests.yml diff --git a/.github/workflows/run-selected-e2e-tests.yml b/.github/workflows/run-selected-e2e-tests.yml index 0e7c97c67fc..e95ce1cef19 100644 --- a/.github/workflows/run-selected-e2e-tests.yml +++ b/.github/workflows/run-selected-e2e-tests.yml @@ -35,7 +35,7 @@ run-name: ${{ inputs.workflow_run_name }} jobs: call-run-e2e-tests-workflow: name: Run E2E Tests - uses: smartcontractkit/.github/.github/workflows/run-e2e-tests.yml@5412507526722a7b1c5d719fa686eed5a1bc4035 # ctf-run-tests@0.2.0 + uses: smartcontractkit/.github/.github/workflows/run-e2e-tests.yml@0632b5652dd5eb03bfa87e23a2b3e2911484fe59 with: chainlink_version: ${{ github.event.inputs.chainlink_version }} test_path: .github/e2e-tests.yml diff --git a/.github/workflows/solidity-foundry.yml b/.github/workflows/solidity-foundry.yml index 850374b0cd3..f94ef29a3b8 100644 --- a/.github/workflows/solidity-foundry.yml +++ b/.github/workflows/solidity-foundry.yml @@ -34,7 +34,7 @@ jobs: { "name": "functions", "setup": { "run-coverage": false, "min-coverage": 98.5, "run-gas-snapshot": true, "run-forge-fmt": false }}, { "name": "keystone", "setup": { "run-coverage": true, "min-coverage": 72.8, "run-gas-snapshot": false, "run-forge-fmt": false }}, { "name": "l2ep", "setup": { "run-coverage": true, "min-coverage": 65.0, "run-gas-snapshot": true, "run-forge-fmt": false }}, - { "name": "liquiditymanager", "setup": { "run-coverage": true, "min-coverage": 44, "run-gas-snapshot": true, "run-forge-fmt": false }}, + { "name": "liquiditymanager", "setup": { "run-coverage": true, "min-coverage": 40, "run-gas-snapshot": true, "run-forge-fmt": false }}, { "name": "llo-feeds", "setup": { "run-coverage": true, "min-coverage": 49.3, "run-gas-snapshot": true, "run-forge-fmt": false }}, { "name": "operatorforwarder", "setup": { "run-coverage": true, "min-coverage": 55.7, "run-gas-snapshot": true, "run-forge-fmt": false }}, { "name": "shared", "setup": { "run-coverage": true, "extra-coverage-params": "--no-match-path='*CallWithExactGas*' --ir-minimum", "min-coverage": 32.6, "run-gas-snapshot": true, "run-forge-fmt": false }}, diff --git a/.mockery.yaml b/.mockery.yaml index dd9024cc066..5777ca1da92 100644 --- a/.mockery.yaml +++ b/.mockery.yaml @@ -583,12 +583,6 @@ packages: github.com/smartcontractkit/chainlink/v2/core/services/workflows/syncer: interfaces: ORM: - ContractReader: - config: - mockname: "Mock{{ .InterfaceName }}" - filename: contract_reader_mock.go - inpackage: true - dir: "{{ .InterfaceDir }}" Handler: config: mockname: "Mock{{ .InterfaceName }}" diff --git a/GNUmakefile b/GNUmakefile index 592183923e2..b765c63a3f4 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -133,17 +133,16 @@ testdb-force: ## Prepares the test database, drops any pesky user connections th testdb-user-only: ## Prepares the test database with user only. go run . local db preparetest --user-only -# Format for CI -.PHONY: presubmit -presubmit: ## Format go files and imports. - goimports -w . - gofmt -w . - go mod tidy - .PHONY: gomods gomods: ## Install gomods go install github.com/jmank88/gomods@v0.1.4 +.PHONY: gomodslocalupdate +gomodslocalupdate: gomods ## Run gomod-local-update + go install ./tools/gomod-local-update/cmd/gomod-local-update + gomods -w gomod-local-update + gomods tidy + .PHONY: mockery mockery: $(mockery) ## Install mockery. go install github.com/vektra/mockery/v2@v2.46.3 diff --git a/ccip/config/evm/Lens_Sepolia.toml b/ccip/config/evm/Lens_Sepolia.toml new file mode 100644 index 00000000000..3f41f2eeeae --- /dev/null +++ b/ccip/config/evm/Lens_Sepolia.toml @@ -0,0 +1,34 @@ +ChainID = "37111" +ChainType = "zksync" +# finality depth for this chain is very inconsistent due to low network traffic. in testing blocks every ~1-2minutes were seen +# confirmed this value with product +FinalityDepth = 40 +FinalityTagEnabled = false +# block rate is dynamic, have seen block times as low as 1s +LogPollInterval = "5s" +# sufficient time for RPC to be labelled out of sync +NoNewHeadsThreshold = "10m" + +[GasEstimator] +EIP1559DynamicFees = false +# limit default set for zk based chains +LimitDefault = 2_500_000_000 +# value given by ds&a +FeeCapDefault = "2000 gwei" +# estimators typically estimated with min of 75 with median of 86 +PriceDefault = "70 gwei" +PriceMax = "2000 gwei" +PriceMin = "70 gwei" +# bump gas aggressively to avoid high amounts of transmit errors +BumpThreshold = 1 +BumpPercent = 40 + +[GasEstimator.DAOracle] +OracleType = 'zksync' + +[Transactions] +ResendAfterThreshold = '7m0s' + +[HeadTracker] +# l1 batching is done every 8hrs with low network activity setting this value to a rough calculation of ~1tx / 2min * 8hrs +HistoryDepth = 250 \ No newline at end of file diff --git a/contracts/.changeset/yellow-mugs-explode.md b/contracts/.changeset/yellow-mugs-explode.md new file mode 100644 index 00000000000..bd488e559ef --- /dev/null +++ b/contracts/.changeset/yellow-mugs-explode.md @@ -0,0 +1,9 @@ +--- +'@chainlink/contracts': minor +--- + +#internal make gas for call exact check immutable + +PR issue: CCIP-4477 + +Solidity Review issue: CCIP-3966 \ No newline at end of file diff --git a/contracts/.changeset/young-bats-rhyme.md b/contracts/.changeset/young-bats-rhyme.md new file mode 100644 index 00000000000..e68a646b78e --- /dev/null +++ b/contracts/.changeset/young-bats-rhyme.md @@ -0,0 +1,10 @@ +--- +'@chainlink/contracts': patch +--- + +add getChainConfig to ccipHome + + +PR issue: CCIP-4517 + +Solidity Review issue: CCIP-3966 \ No newline at end of file diff --git a/contracts/GNUmakefile b/contracts/GNUmakefile index 4066c76037e..673198bb1e2 100644 --- a/contracts/GNUmakefile +++ b/contracts/GNUmakefile @@ -43,7 +43,7 @@ mockery: $(mockery) ## Install mockery. .PHONY: foundry foundry: ## Install foundry. - foundryup --version nightly-fb5f0e1c4d9b9b0861be3e3bd07963524c5ac08e + foundryup --version nightly-aa69ed1e46dd61fbf9d73399396a4db4dd527431 .PHONY: foundry-refresh foundry-refresh: foundry diff --git a/contracts/README.md b/contracts/README.md index 182891ceef7..f87f196e5dc 100644 --- a/contracts/README.md +++ b/contracts/README.md @@ -24,7 +24,7 @@ $ npm install @chainlink/contracts --save The solidity smart contracts themselves can be imported via the `src` directory of `@chainlink/contracts`: ```solidity -import '@chainlink/contracts/src/v0.8/AutomationCompatibleInterface.sol'; +import {AutomationCompatibleInterface} from '@chainlink/contracts/src/v0.8/AutomationCompatibleInterface.sol'; ``` ## Local Development @@ -42,7 +42,7 @@ $ pnpm test ## Contributing -Please try to adhere to [Solidity Style Guide](https://github.com/smartcontractkit/chainlink/blob/develop/contracts/STYLE.md). +Please adhere to the [Solidity Style Guide](https://github.com/smartcontractkit/chainlink/blob/develop/contracts/STYLE.md). Contributions are welcome! Please refer to [Chainlink's contributing guidelines](https://github.com/smartcontractkit/chainlink/blob/develop/docs/CONTRIBUTING.md) for detailed @@ -70,6 +70,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 Most of the contracts are licensed under the [MIT](https://choosealicense.com/licenses/mit/) license. An exception to this is the ccip folder, which defaults to be licensed under the [BUSL-1.1](./src/v0.8/ccip/LICENSE.md) license, however, there are a few exceptions -- `src/v0.8/ccip/applications/*` is licensed under the [MIT](./src/v0.8/ccip/LICENSE-MIT.md) license -- `src/v0.8/ccip/interfaces/*` is licensed under the [MIT](./src/v0.8/ccip/LICENSE-MIT.md) license -- `src/v0.8/ccip/libraries/{Client.sol, Internal.sol}` is licensed under the [MIT](./src/v0.8/ccip/LICENSE-MIT.md) license \ No newline at end of file +- `src/v0.8/ccip/applications/*` is licensed under the [MIT](https://choosealicense.com/licenses/mit/) license +- `src/v0.8/ccip/interfaces/*` is licensed under the [MIT](https://choosealicense.com/licenses/mit/) license +- `src/v0.8/ccip/libraries/{Client.sol, Internal.sol}` is licensed under the [MIT](https://choosealicense.com/licenses/mit/) license \ No newline at end of file diff --git a/contracts/gas-snapshots/ccip.gas-snapshot b/contracts/gas-snapshots/ccip.gas-snapshot index 4ed7d38afd6..d655e886262 100644 --- a/contracts/gas-snapshots/ccip.gas-snapshot +++ b/contracts/gas-snapshots/ccip.gas-snapshot @@ -38,30 +38,30 @@ CCIPHome__validateConfig:test__validateConfig_Success() (gas: 299797) CCIPHome__validateConfig:test__validateConfig_TooManySigners_Reverts() (gas: 773105) CCIPHome__validateConfig:test__validateConfig_ZeroP2PId_Reverts() (gas: 293455) CCIPHome__validateConfig:test__validateConfig_ZeroSignerKey_Reverts() (gas: 293503) -CCIPHome_applyChainConfigUpdates:test__applyChainConfigUpdates_FChainNotPositive_Reverts() (gas: 187738) -CCIPHome_applyChainConfigUpdates:test_applyChainConfigUpdates_addChainConfigs_Success() (gas: 349623) -CCIPHome_applyChainConfigUpdates:test_applyChainConfigUpdates_nodeNotInRegistry_Reverts() (gas: 18065) -CCIPHome_applyChainConfigUpdates:test_applyChainConfigUpdates_removeChainConfigs_Success() (gas: 272742) -CCIPHome_applyChainConfigUpdates:test_applyChainConfigUpdates_selectorNotFound_Reverts() (gas: 14952) -CCIPHome_applyChainConfigUpdates:test_getPaginatedCCIPHomes_Success() (gas: 372561) +CCIPHome_applyChainConfigUpdates:test__applyChainConfigUpdates_FChainNotPositive_Reverts() (gas: 187822) +CCIPHome_applyChainConfigUpdates:test_applyChainConfigUpdates_addChainConfigs_Success() (gas: 350051) +CCIPHome_applyChainConfigUpdates:test_applyChainConfigUpdates_nodeNotInRegistry_Reverts() (gas: 18089) +CCIPHome_applyChainConfigUpdates:test_applyChainConfigUpdates_removeChainConfigs_Success() (gas: 282212) +CCIPHome_applyChainConfigUpdates:test_applyChainConfigUpdates_selectorNotFound_Reverts() (gas: 14976) +CCIPHome_applyChainConfigUpdates:test_getPaginatedCCIPHomes_Success() (gas: 373475) CCIPHome_beforeCapabilityConfigSet:test_beforeCapabilityConfigSet_DONIdMismatch_reverts() (gas: 38098) -CCIPHome_beforeCapabilityConfigSet:test_beforeCapabilityConfigSet_InnerCallReverts_reverts() (gas: 11827) +CCIPHome_beforeCapabilityConfigSet:test_beforeCapabilityConfigSet_InnerCallReverts_reverts() (gas: 11783) CCIPHome_beforeCapabilityConfigSet:test_beforeCapabilityConfigSet_InvalidSelector_reverts() (gas: 11015) CCIPHome_beforeCapabilityConfigSet:test_beforeCapabilityConfigSet_OnlyCapabilitiesRegistryCanCall_reverts() (gas: 37072) -CCIPHome_beforeCapabilityConfigSet:test_beforeCapabilityConfigSet_success() (gas: 1455716) -CCIPHome_constructor:test_constructor_CapabilitiesRegistryAddressZero_reverts() (gas: 63767) -CCIPHome_constructor:test_constructor_success() (gas: 3455841) -CCIPHome_getAllConfigs:test_getAllConfigs_success() (gas: 2773000) -CCIPHome_getCapabilityConfiguration:test_getCapabilityConfiguration_success() (gas: 9138) -CCIPHome_getConfigDigests:test_getConfigDigests_success() (gas: 2547397) -CCIPHome_promoteCandidateAndRevokeActive:test_promoteCandidateAndRevokeActive_CanOnlySelfCall_reverts() (gas: 9087) -CCIPHome_promoteCandidateAndRevokeActive:test_promoteCandidateAndRevokeActive_ConfigDigestMismatch_reverts() (gas: 23005) -CCIPHome_promoteCandidateAndRevokeActive:test_promoteCandidateAndRevokeActive_NoOpStateTransitionNotAllowed_reverts() (gas: 8817) -CCIPHome_promoteCandidateAndRevokeActive:test_promoteCandidateAndRevokeActive_multiplePlugins_success() (gas: 5113570) -CCIPHome_revokeCandidate:test_revokeCandidate_CanOnlySelfCall_reverts() (gas: 9068) -CCIPHome_revokeCandidate:test_revokeCandidate_ConfigDigestMismatch_reverts() (gas: 19105) -CCIPHome_revokeCandidate:test_revokeCandidate_RevokingZeroDigestNotAllowed_reverts() (gas: 8817) -CCIPHome_revokeCandidate:test_revokeCandidate_success() (gas: 30674) +CCIPHome_beforeCapabilityConfigSet:test_beforeCapabilityConfigSet_success() (gas: 1455674) +CCIPHome_constructor:test_constructor_CapabilitiesRegistryAddressZero_reverts() (gas: 63865) +CCIPHome_constructor:test_constructor_success() (gas: 3531036) +CCIPHome_getAllConfigs:test_getAllConfigs_success() (gas: 2773023) +CCIPHome_getCapabilityConfiguration:test_getCapabilityConfiguration_success() (gas: 9116) +CCIPHome_getConfigDigests:test_getConfigDigests_success() (gas: 2547513) +CCIPHome_promoteCandidateAndRevokeActive:test_promoteCandidateAndRevokeActive_CanOnlySelfCall_reverts() (gas: 9110) +CCIPHome_promoteCandidateAndRevokeActive:test_promoteCandidateAndRevokeActive_ConfigDigestMismatch_reverts() (gas: 23074) +CCIPHome_promoteCandidateAndRevokeActive:test_promoteCandidateAndRevokeActive_NoOpStateTransitionNotAllowed_reverts() (gas: 8840) +CCIPHome_promoteCandidateAndRevokeActive:test_promoteCandidateAndRevokeActive_multiplePlugins_success() (gas: 5113754) +CCIPHome_revokeCandidate:test_revokeCandidate_CanOnlySelfCall_reverts() (gas: 9024) +CCIPHome_revokeCandidate:test_revokeCandidate_ConfigDigestMismatch_reverts() (gas: 19084) +CCIPHome_revokeCandidate:test_revokeCandidate_RevokingZeroDigestNotAllowed_reverts() (gas: 8773) +CCIPHome_revokeCandidate:test_revokeCandidate_success() (gas: 30676) CCIPHome_setCandidate:test_setCandidate_CanOnlySelfCall_reverts() (gas: 29383) CCIPHome_setCandidate:test_setCandidate_ConfigDigestMismatch_reverts() (gas: 1395154) CCIPHome_setCandidate:test_setCandidate_success() (gas: 1365439) @@ -347,29 +347,25 @@ MultiOCR3Base_transmit:test_UnAuthorizedTransmitter_Revert() (gas: 24193) MultiOCR3Base_transmit:test_UnauthorizedSigner_Revert() (gas: 60994) MultiOCR3Base_transmit:test_UnconfiguredPlugin_Revert() (gas: 39824) MultiOCR3Base_transmit:test_ZeroSignatures_Revert() (gas: 32920) -NonceManager_NonceIncrementation:test_getIncrementedOutboundNonce_Success() (gas: 37956) -NonceManager_NonceIncrementation:test_incrementInboundNonce_Skip() (gas: 23706) -NonceManager_NonceIncrementation:test_incrementInboundNonce_Success() (gas: 38778) -NonceManager_NonceIncrementation:test_incrementNoncesInboundAndOutbound_Success() (gas: 71901) -NonceManager_OffRampUpgrade:test_NoPrevOffRampForChain_Success() (gas: 185810) -NonceManager_OffRampUpgrade:test_UpgradedNonceNewSenderStartsAtZero_Success() (gas: 189263) -NonceManager_OffRampUpgrade:test_UpgradedNonceStartsAtV1Nonce_Success() (gas: 252318) -NonceManager_OffRampUpgrade:test_UpgradedOffRampNonceSkipsIfMsgInFlight_Success() (gas: 220605) -NonceManager_OffRampUpgrade:test_UpgradedSenderNoncesReadsPreviousRamp_Success() (gas: 60497) -NonceManager_OffRampUpgrade:test_Upgraded_Success() (gas: 152975) -NonceManager_OnRampUpgrade:test_UpgradeNonceNewSenderStartsAtZero_Success() (gas: 166167) -NonceManager_OnRampUpgrade:test_UpgradeNonceStartsAtV1Nonce_Success() (gas: 195938) -NonceManager_OnRampUpgrade:test_UpgradeSenderNoncesReadsPreviousRamp_Success() (gas: 139164) -NonceManager_OnRampUpgrade:test_Upgrade_Success() (gas: 105212) -NonceManager_applyPreviousRampsUpdates:test_MultipleRampsUpdates_success() (gas: 123604) -NonceManager_applyPreviousRampsUpdates:test_PreviousRampAlreadySetOffRamp_Revert() (gas: 43403) -NonceManager_applyPreviousRampsUpdates:test_PreviousRampAlreadySetOnRampAndOffRamp_Revert() (gas: 64752) -NonceManager_applyPreviousRampsUpdates:test_PreviousRampAlreadySetOnRamp_Revert() (gas: 43245) -NonceManager_applyPreviousRampsUpdates:test_PreviousRampAlreadySet_overrideAllowed_success() (gas: 45941) +NonceManager_applyPreviousRampsUpdates:test_MultipleRampsUpdates() (gas: 123604) +NonceManager_applyPreviousRampsUpdates:test_PreviousRampAlreadySet_overrideAllowed() (gas: 45986) NonceManager_applyPreviousRampsUpdates:test_SingleRampUpdate_success() (gas: 66889) -NonceManager_applyPreviousRampsUpdates:test_ZeroInput_success() (gas: 12213) -NonceManager_typeAndVersion:test_typeAndVersion() (gas: 9705) -OffRamp_afterOC3ConfigSet:test_afterOCR3ConfigSet_SignatureVerificationDisabled_Revert() (gas: 5872422) +NonceManager_applyPreviousRampsUpdates:test_ZeroInput() (gas: 12169) +NonceManager_getInboundNonce:test_getInboundNonce_NoPrevOffRampForChain() (gas: 185821) +NonceManager_getInboundNonce:test_getInboundNonce_Upgraded() (gas: 152976) +NonceManager_getInboundNonce:test_getInboundNonce_UpgradedNonceNewSenderStartsAtZero() (gas: 189296) +NonceManager_getInboundNonce:test_getInboundNonce_UpgradedNonceStartsAtV1Nonce() (gas: 252384) +NonceManager_getInboundNonce:test_getInboundNonce_UpgradedOffRampNonceSkipsIfMsgInFlight() (gas: 220672) +NonceManager_getInboundNonce:test_getInboundNonce_UpgradedSenderNoncesReadsPreviousRamp() (gas: 60520) +NonceManager_getIncrementedOutboundNonce:test_getIncrementedOutboundNonce() (gas: 37979) +NonceManager_getIncrementedOutboundNonce:test_incrementInboundNonce() (gas: 38756) +NonceManager_getIncrementedOutboundNonce:test_incrementInboundNonce_SkippedIncorrectNonce() (gas: 23759) +NonceManager_getIncrementedOutboundNonce:test_incrementNoncesInboundAndOutbound() (gas: 71901) +NonceManager_getOutboundNonce:test_getOutboundNonce_Upgrade() (gas: 105300) +NonceManager_getOutboundNonce:test_getOutboundNonce_UpgradeNonceNewSenderStartsAtZero() (gas: 166146) +NonceManager_getOutboundNonce:test_getOutboundNonce_UpgradeNonceStartsAtV1Nonce() (gas: 195937) +NonceManager_getOutboundNonce:test_getOutboundNonce_UpgradeSenderNoncesReadsPreviousRamp() (gas: 140158) +OffRamp_afterOC3ConfigSet:test_afterOCR3ConfigSet_SignatureVerificationDisabled_Revert() (gas: 5903354) OffRamp_applySourceChainConfigUpdates:test_AddMultipleChains_Success() (gas: 626094) OffRamp_applySourceChainConfigUpdates:test_AddNewChain_Success() (gas: 166505) OffRamp_applySourceChainConfigUpdates:test_ApplyZeroUpdates_Success() (gas: 16719) @@ -393,8 +389,8 @@ OffRamp_commit:test_FailedRMNVerification_Reverts() (gas: 63117) OffRamp_commit:test_InvalidIntervalMinLargerThanMax_Revert() (gas: 69655) OffRamp_commit:test_InvalidInterval_Revert() (gas: 65803) OffRamp_commit:test_InvalidRootRevert() (gas: 64898) -OffRamp_commit:test_NoConfigWithOtherConfigPresent_Revert() (gas: 6633259) -OffRamp_commit:test_NoConfig_Revert() (gas: 6216677) +OffRamp_commit:test_NoConfigWithOtherConfigPresent_Revert() (gas: 6664144) +OffRamp_commit:test_NoConfig_Revert() (gas: 6247562) OffRamp_commit:test_OnlyGasPriceUpdates_Success() (gas: 112728) OffRamp_commit:test_OnlyPriceUpdateStaleReport_Revert() (gas: 120561) OffRamp_commit:test_OnlyTokenPriceUpdates_Success() (gas: 112660) @@ -409,23 +405,23 @@ OffRamp_commit:test_UnauthorizedTransmitter_Revert() (gas: 125027) OffRamp_commit:test_Unhealthy_Revert() (gas: 60177) OffRamp_commit:test_ValidPriceUpdateThenStaleReportWithRoot_Success() (gas: 206221) OffRamp_commit:test_ZeroEpochAndRound_Revert() (gas: 53305) -OffRamp_constructor:test_Constructor_Success() (gas: 6179080) -OffRamp_constructor:test_SourceChainSelector_Revert() (gas: 136555) -OffRamp_constructor:test_ZeroChainSelector_Revert() (gas: 103592) -OffRamp_constructor:test_ZeroNonceManager_Revert() (gas: 101441) -OffRamp_constructor:test_ZeroOnRampAddress_Revert() (gas: 162036) -OffRamp_constructor:test_ZeroRMNRemote_Revert() (gas: 101358) -OffRamp_constructor:test_ZeroTokenAdminRegistry_Revert() (gas: 101362) +OffRamp_constructor:test_Constructor_Success() (gas: 6210339) +OffRamp_constructor:test_SourceChainSelector_Revert() (gas: 137118) +OffRamp_constructor:test_ZeroChainSelector_Revert() (gas: 103828) +OffRamp_constructor:test_ZeroNonceManager_Revert() (gas: 101677) +OffRamp_constructor:test_ZeroOnRampAddress_Revert() (gas: 162599) +OffRamp_constructor:test_ZeroRMNRemote_Revert() (gas: 101597) +OffRamp_constructor:test_ZeroTokenAdminRegistry_Revert() (gas: 101598) OffRamp_execute:test_IncorrectArrayType_Revert() (gas: 17532) OffRamp_execute:test_LargeBatch_Success() (gas: 3378447) OffRamp_execute:test_MultipleReportsWithPartialValidationFailures_Success() (gas: 371209) OffRamp_execute:test_MultipleReports_Success() (gas: 298806) -OffRamp_execute:test_NoConfigWithOtherConfigPresent_Revert() (gas: 7041684) -OffRamp_execute:test_NoConfig_Revert() (gas: 6266154) +OffRamp_execute:test_NoConfigWithOtherConfigPresent_Revert() (gas: 7072622) +OffRamp_execute:test_NoConfig_Revert() (gas: 6297092) OffRamp_execute:test_NonArray_Revert() (gas: 27572) OffRamp_execute:test_SingleReport_Success() (gas: 175631) OffRamp_execute:test_UnauthorizedTransmitter_Revert() (gas: 147790) -OffRamp_execute:test_WrongConfigWithSigners_Revert() (gas: 6933352) +OffRamp_execute:test_WrongConfigWithSigners_Revert() (gas: 6964290) OffRamp_execute:test_ZeroReports_Revert() (gas: 17248) OffRamp_executeSingleMessage:test_executeSingleMessage_NoTokens() (gas: 56213) OffRamp_executeSingleMessage:test_executeSingleMessage_NonContract() (gas: 20508) @@ -614,10 +610,8 @@ RegistryModuleOwnerCustom_registerAdminViaGetCCIPAdmin:test_registerAdminViaGetC RegistryModuleOwnerCustom_registerAdminViaGetCCIPAdmin:test_registerAdminViaGetCCIPAdmin_Success() (gas: 130126) RegistryModuleOwnerCustom_registerAdminViaOwner:test_registerAdminViaOwner_Revert() (gas: 19602) RegistryModuleOwnerCustom_registerAdminViaOwner:test_registerAdminViaOwner_Success() (gas: 129930) -Router_applyRampUpdates:test_OffRampMismatch_Revert() (gas: 89591) -Router_applyRampUpdates:test_OffRampUpdatesWithRouting() (gas: 10750087) -Router_applyRampUpdates:test_OnRampDisable() (gas: 56445) -Router_applyRampUpdates:test_OnlyOwner_Revert() (gas: 12414) +Router_applyRampUpdates:test_applyRampUpdates_OffRampUpdatesWithRouting() (gas: 10749731) +Router_applyRampUpdates:test_applyRampUpdates_OnRampDisable() (gas: 56422) Router_ccipSend:test_CCIPSendLinkFeeNoTokenSuccess_gas() (gas: 131447) Router_ccipSend:test_CCIPSendLinkFeeOneTokenSuccess_gas() (gas: 221710) Router_ccipSend:test_FeeTokenAmountTooLow_Revert() (gas: 71858) diff --git a/contracts/gas-snapshots/workflow.gas-snapshot b/contracts/gas-snapshots/workflow.gas-snapshot index 73fdfbf7187..bdfd2b24aec 100644 --- a/contracts/gas-snapshots/workflow.gas-snapshot +++ b/contracts/gas-snapshots/workflow.gas-snapshot @@ -17,9 +17,9 @@ WorkflowRegistryManager_getVersion:test_WhenVersionNumberIsRegistered() (gas: 28 WorkflowRegistryManager_getVersionNumberByContractAddressAndChainID:test_WhenAVersionIsRegisteredForTheContractAddressAndChainIDCombination() (gas: 285022) WorkflowRegistryManager_getVersionNumberByContractAddressAndChainID:test_WhenNoVersionIsRegisteredForTheContractAddressAndChainIDCombination() (gas: 286634) WorkflowRegistryManager_getVersionNumberByContractAddressAndChainID:test_WhenTheContractAddressIsInvalid() (gas: 284604) -WorkflowRegistry_activateWorkflow:test_WhenTheCallerIsAnAuthorizedAddress() (gas: 495029) -WorkflowRegistry_deleteWorkflow:test_WhenTheCallerIsAnAuthorizedAddress_AndTheDonIDIsAllowed() (gas: 403945) -WorkflowRegistry_deleteWorkflow:test_WhenTheCallerIsAnAuthorizedAddress_AndTheDonIDIsNotAllowed() (gas: 421748) +WorkflowRegistry_activateWorkflow:test_WhenTheCallerIsAnAuthorizedAddress() (gas: 517416) +WorkflowRegistry_deleteWorkflow:test_WhenTheCallerIsAnAuthorizedAddress_AndTheDonIDIsAllowed() (gas: 422157) +WorkflowRegistry_deleteWorkflow:test_WhenTheCallerIsAnAuthorizedAddress_AndTheDonIDIsNotAllowed() (gas: 439960) WorkflowRegistry_getAllAllowedDONs:test_WhenTheRegistryIsLocked() (gas: 47473) WorkflowRegistry_getAllAllowedDONs:test_WhenTheSetOfAllowedDONsIsEmpty() (gas: 25780) WorkflowRegistry_getAllAllowedDONs:test_WhenThereAreMultipleAllowedDONs() (gas: 75437) @@ -28,9 +28,9 @@ WorkflowRegistry_getAllAuthorizedAddresses:test_WhenTheRegistryIsLocked() (gas: WorkflowRegistry_getAllAuthorizedAddresses:test_WhenTheSetOfAuthorizedAddressesIsEmpty() (gas: 26152) WorkflowRegistry_getAllAuthorizedAddresses:test_WhenThereAreMultipleAuthorizedAddresses() (gas: 78270) WorkflowRegistry_getAllAuthorizedAddresses:test_WhenThereIsASingleAuthorizedAddress() (gas: 16832) -WorkflowRegistry_getWorkflowMetadata:test_WhenTheRegistryIsLocked() (gas: 519145) +WorkflowRegistry_getWorkflowMetadata:test_WhenTheRegistryIsLocked() (gas: 541532) WorkflowRegistry_getWorkflowMetadata:test_WhenTheWorkflowDoesNotExist() (gas: 17543) -WorkflowRegistry_getWorkflowMetadata:test_WhenTheWorkflowExistsWithTheOwnerAndName() (gas: 490001) +WorkflowRegistry_getWorkflowMetadata:test_WhenTheWorkflowExistsWithTheOwnerAndName() (gas: 512388) WorkflowRegistry_getWorkflowMetadataListByDON:test_WhenLimitExceedsTotalWorkflows() (gas: 128146) WorkflowRegistry_getWorkflowMetadataListByDON:test_WhenLimitIsEqualToTotalWorkflows() (gas: 128035) WorkflowRegistry_getWorkflowMetadataListByDON:test_WhenLimitIsLessThanTotalWorkflows() (gas: 90141) @@ -48,17 +48,17 @@ WorkflowRegistry_getWorkflowMetadataListByOwner:test_WhenStartIsGreaterThanOrEqu WorkflowRegistry_getWorkflowMetadataListByOwner:test_WhenTheOwnerHasNoWorkflows() (gas: 14006) WorkflowRegistry_getWorkflowMetadataListByOwner:test_WhenTheRegistryIsLocked() (gas: 165968) WorkflowRegistry_lockRegistry:test_WhenTheCallerIsTheContractOwner() (gas: 38758) -WorkflowRegistry_pauseWorkflow:test_WhenTheDonIDIsAllowed_AndTheCallerIsAnAuthorizedAddress() (gas: 494993) -WorkflowRegistry_pauseWorkflow:test_WhenTheDonIDIsAllowed_AndTheCallerIsAnUnauthorizedAddress() (gas: 502796) -WorkflowRegistry_pauseWorkflow:test_WhenTheDonIDIsNotAllowed_AndTheCallerIsAnAuthorizedAddress() (gas: 502555) -WorkflowRegistry_pauseWorkflow:test_WhenTheDonIDIsNotAllowed_AndTheCallerIsAnUnauthorizedAddress() (gas: 506966) -WorkflowRegistry_registerWorkflow:test_WhenTheWorkflowInputsAreAllValid() (gas: 549769) -WorkflowRegistry_requestForceUpdateSecrets:test_WhenTheCallerIsAnAuthorizedAddress_AndTheWorkflowIsInAnAllowedDON() (gas: 891242) -WorkflowRegistry_requestForceUpdateSecrets:test_WhenTheCallerIsAnAuthorizedAddress_AndTheWorkflowIsNotInAnAllowedDON() (gas: 488397) -WorkflowRegistry_requestForceUpdateSecrets:test_WhenTheCallerIsNotAnAuthorizedAddress() (gas: 486751) +WorkflowRegistry_pauseWorkflow:test_WhenTheDonIDIsAllowed_AndTheCallerIsAnAuthorizedAddress() (gas: 517380) +WorkflowRegistry_pauseWorkflow:test_WhenTheDonIDIsAllowed_AndTheCallerIsAnUnauthorizedAddress() (gas: 525183) +WorkflowRegistry_pauseWorkflow:test_WhenTheDonIDIsNotAllowed_AndTheCallerIsAnAuthorizedAddress() (gas: 524942) +WorkflowRegistry_pauseWorkflow:test_WhenTheDonIDIsNotAllowed_AndTheCallerIsAnUnauthorizedAddress() (gas: 529353) +WorkflowRegistry_registerWorkflow:test_WhenTheWorkflowInputsAreAllValid() (gas: 572178) +WorkflowRegistry_requestForceUpdateSecrets:test_WhenTheCallerIsAnAuthorizedAddress_AndTheWorkflowIsInAnAllowedDON() (gas: 936016) +WorkflowRegistry_requestForceUpdateSecrets:test_WhenTheCallerIsAnAuthorizedAddress_AndTheWorkflowIsNotInAnAllowedDON() (gas: 510784) +WorkflowRegistry_requestForceUpdateSecrets:test_WhenTheCallerIsNotAnAuthorizedAddress() (gas: 509138) WorkflowRegistry_unlockRegistry:test_WhenTheCallerIsTheContractOwner() (gas: 30325) WorkflowRegistry_updateAllowedDONs:test_WhenTheBoolInputIsFalse() (gas: 29739) WorkflowRegistry_updateAllowedDONs:test_WhenTheBoolInputIsTrue() (gas: 170296) WorkflowRegistry_updateAuthorizedAddresses:test_WhenTheBoolInputIsFalse() (gas: 30278) WorkflowRegistry_updateAuthorizedAddresses:test_WhenTheBoolInputIsTrue() (gas: 175515) -WorkflowRegistry_updateWorkflow:test_WhenTheWorkflowInputsAreAllValid() (gas: 479601) +WorkflowRegistry_updateWorkflow:test_WhenTheWorkflowInputsAreAllValid() (gas: 515666) diff --git a/contracts/src/v0.8/ccip/FeeQuoter.sol b/contracts/src/v0.8/ccip/FeeQuoter.sol index 58261eff499..b312e732e61 100644 --- a/contracts/src/v0.8/ccip/FeeQuoter.sol +++ b/contracts/src/v0.8/ccip/FeeQuoter.sol @@ -103,12 +103,12 @@ contract FeeQuoter is AuthorizedCallers, IFeeQuoter, ITypeAndVersion, IReceiver, // The following three properties are defaults, they can be overridden by setting the TokenTransferFeeConfig for a token. uint16 defaultTokenFeeUSDCents; // │ Default token fee charged per token transfer. uint32 defaultTokenDestGasOverhead; // ──────╯ Default gas charged to execute a token transfer on the destination chain. - uint32 defaultTxGasLimit; //─────────────────╮ Default gas limit for a tx. - uint64 gasMultiplierWeiPerEth; // │ Multiplier for gas costs, 1e18 based so 11e17 = 10% extra cost. - uint32 networkFeeUSDCents; // │ Flat network fee to charge for messages, multiples of 0.01 USD. - uint32 gasPriceStalenessThreshold; // │ The amount of time a gas price can be stale before it is considered invalid (0 means disabled). - bool enforceOutOfOrder; // │ Whether to enforce the allowOutOfOrderExecution extraArg value to be true. - bytes4 chainFamilySelector; // ──────────────╯ Selector that identifies the destination chain's family. Used to determine the correct validations to perform for the dest chain. + uint32 defaultTxGasLimit; //──────────╮ Default gas limit for a tx. + uint64 gasMultiplierWeiPerEth; // │ Multiplier for gas costs, 1e18 based so 11e17 = 10% extra cost. + uint32 networkFeeUSDCents; // │ Flat network fee to charge for messages, multiples of 0.01 USD. + uint32 gasPriceStalenessThreshold; // │ The amount of time a gas price can be stale before it is considered invalid (0 means disabled). + bool enforceOutOfOrder; // │ Whether to enforce the allowOutOfOrderExecution extraArg value to be true. + bytes4 chainFamilySelector; // ───────╯ Selector that identifies the destination chain's family. Used to determine the correct validations to perform for the dest chain. } /// @dev Struct to hold the configs and its destination chain selector. Same as DestChainConfig but with the @@ -121,13 +121,13 @@ contract FeeQuoter is AuthorizedCallers, IFeeQuoter, ITypeAndVersion, IReceiver, /// @dev Struct with transfer fee configuration for token transfers. struct TokenTransferFeeConfig { - uint32 minFeeUSDCents; // ────╮ Minimum fee to charge per token transfer, multiples of 0.01 USD. - uint32 maxFeeUSDCents; // │ Maximum fee to charge per token transfer, multiples of 0.01 USD. - uint16 deciBps; // │ Basis points charged on token transfers, multiples of 0.1bps, or 1e-5. - uint32 destGasOverhead; // │ Gas charged to execute the token transfer on the destination chain. - // │ Extra data availability bytes that are returned from the source pool and sent to - uint32 destBytesOverhead; // │ the destination pool. Must be >= Pool.CCIP_LOCK_OR_BURN_V1_RET_BYTES. - bool isEnabled; // ───────────╯ Whether this token has custom transfer fees. + uint32 minFeeUSDCents; // ───╮ Minimum fee to charge per token transfer, multiples of 0.01 USD. + uint32 maxFeeUSDCents; // │ Maximum fee to charge per token transfer, multiples of 0.01 USD. + uint16 deciBps; // │ Basis points charged on token transfers, multiples of 0.1bps, or 1e-5. + uint32 destGasOverhead; // │ Gas charged to execute the token transfer on the destination chain. + // │ Data availability bytes that are returned from the source pool and sent to the dest + uint32 destBytesOverhead; // │ pool. Must be >= Pool.CCIP_LOCK_OR_BURN_V1_RET_BYTES. Set as multiple of 32 bytes. + bool isEnabled; // ──────────╯ Whether this token has custom transfer fees. } /// @dev Struct with token transfer fee configurations for a token, same as TokenTransferFeeConfig but with the token @@ -517,6 +517,7 @@ contract FeeQuoter is AuthorizedCallers, IFeeQuoter, ITypeAndVersion, IReceiver, for (uint256 i = 0; i < feeds.length; ++i) { TokenPriceFeedConfig memory feedConfig = s_usdPriceFeedsPerToken[feeds[i].token]; + // If the token is not enabled we revert the entire report as that indicates some type of misconfiguration. if (!feedConfig.isEnabled) { revert TokenNotSupported(feeds[i].token); } @@ -710,6 +711,8 @@ contract FeeQuoter is AuthorizedCallers, IFeeQuoter, ITypeAndVersion, IReceiver, continue; } + // In the case where bpsFeeUSDWei, minFeeUSDWei, and maxFeeUSDWei are all 0, we skip the fee. This is intended + // to allow for a fee of 0 to be set. tokenTransferFeeUSDWei += bpsFeeUSDWei; } @@ -863,7 +866,9 @@ contract FeeQuoter is AuthorizedCallers, IFeeQuoter, ITypeAndVersion, IReceiver, if (evmExtraArgs.gasLimit > uint256(destChainConfig.maxPerMsgGasLimit)) revert MessageGasLimitTooHigh(); - // If the chain enforces out of order execution, the extra args must allow it, otherwise revert. + // If the chain enforces out of order execution, the extra args must allow it, otherwise revert. We cannot assume + // the user intended to use OOO on any chain that requires it as it may lead to unexpected behavior. Therefore we + // revert instead of assuming the user intended to use OOO. if (destChainConfig.enforceOutOfOrder && !evmExtraArgs.allowOutOfOrderExecution) { revert ExtraArgOutOfOrderExecutionMustBeTrue(); } diff --git a/contracts/src/v0.8/ccip/LICENSE-MIT.md b/contracts/src/v0.8/ccip/LICENSE-MIT.md deleted file mode 100644 index 812debd8e9b..00000000000 --- a/contracts/src/v0.8/ccip/LICENSE-MIT.md +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2018 SmartContract ChainLink, Ltd. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. \ No newline at end of file diff --git a/contracts/src/v0.8/ccip/NonceManager.sol b/contracts/src/v0.8/ccip/NonceManager.sol index f95380b23b2..382844c27d9 100644 --- a/contracts/src/v0.8/ccip/NonceManager.sol +++ b/contracts/src/v0.8/ccip/NonceManager.sol @@ -33,9 +33,9 @@ contract NonceManager is INonceManager, AuthorizedCallers, ITypeAndVersion { /// @dev The previous on/off ramps per chain selector. mapping(uint64 chainSelector => PreviousRamps previousRamps) private s_previousRamps; - /// @dev The current outbound nonce per sender used on the onramp. + /// @dev The current outbound nonce per sender used on the onRamp. mapping(uint64 destChainSelector => mapping(address sender => uint64 outboundNonce)) private s_outboundNonces; - /// @dev The current inbound nonce per sender used on the offramp. + /// @dev The current inbound nonce per sender used on the offRamp. /// Eventually in sync with the outbound nonce in the remote source chain NonceManager, used to enforce that messages /// are executed in the same order they are sent (assuming they are DON). mapping(uint64 sourceChainSelector => mapping(bytes sender => uint64 inboundNonce)) private s_inboundNonces; @@ -71,6 +71,8 @@ contract NonceManager is INonceManager, AuthorizedCallers, ITypeAndVersion { if (outboundNonce == 0) { address prevOnRamp = s_previousRamps[destChainSelector].prevOnRamp; if (prevOnRamp != address(0)) { + // This gets the current nonce for a sender, not the already incremented nonce like getIncrementedOutboundNonce + // would return. return IEVM2AnyOnRamp(prevOnRamp).getSenderNonce(sender); } } diff --git a/contracts/src/v0.8/ccip/capability/CCIPHome.sol b/contracts/src/v0.8/ccip/capability/CCIPHome.sol index e43e4b0d03f..829c54c5b62 100644 --- a/contracts/src/v0.8/ccip/capability/CCIPHome.sol +++ b/contracts/src/v0.8/ccip/capability/CCIPHome.sol @@ -2,7 +2,6 @@ pragma solidity 0.8.24; import {ICapabilityConfiguration} from "../../keystone/interfaces/ICapabilityConfiguration.sol"; - import {INodeInfoProvider} from "../../keystone/interfaces/INodeInfoProvider.sol"; import {ITypeAndVersion} from "../../shared/interfaces/ITypeAndVersion.sol"; @@ -111,7 +110,7 @@ contract CCIPHome is Ownable2StepMsgSender, ITypeAndVersion, ICapabilityConfigur uint64 chainSelector; // │ The (remote) chain that the configuration is for. uint8 FRoleDON; // │ The "big F" parameter for the role DON. uint64 offchainConfigVersion; // ──────╯ The version of the exec offchain configuration. - bytes offrampAddress; // The remote chain offramp address. + bytes offrampAddress; // The remote chain offRamp address. bytes rmnHomeAddress; // The home chain RMN home address. OCR3Node[] nodes; // Keys & IDs of nodes part of the role DON. bytes offchainConfig; // The offchain configuration for the OCR3 plugin. Protobuf encoded. @@ -241,10 +240,9 @@ contract CCIPHome is Ownable2StepMsgSender, ITypeAndVersion, ICapabilityConfigur } /// @inheritdoc ICapabilityConfiguration - /// @dev The CCIP capability will fetch the configuration needed directly from this contract. - /// The offchain syncer will call this function, so its important that it doesn't revert. + /// @dev This function is not used in the CCIPHome contract but the interface requires it to be implemented. function getCapabilityConfiguration( - uint32 /* donId */ + uint32 ) external pure override returns (bytes memory configuration) { return bytes(""); } @@ -533,6 +531,15 @@ contract CCIPHome is Ownable2StepMsgSender, ITypeAndVersion, ICapabilityConfigur return s_remoteChainSelectors.length(); } + /// @notice Returns the chain configuration for a given chain selector. + /// @param chainSelector The chain selector. + /// @return chainConfig The chain configuration. + function getChainConfig( + uint64 chainSelector + ) external view returns (ChainConfig memory) { + return s_chainConfigurations[chainSelector]; + } + /// @notice Returns all the chain configurations. /// @param pageIndex The page index. /// @param pageSize The page size. diff --git a/contracts/src/v0.8/ccip/interfaces/IEVM2AnyOnRamp.sol b/contracts/src/v0.8/ccip/interfaces/IEVM2AnyOnRamp.sol index 2d27bd3a25c..e40b186dda4 100644 --- a/contracts/src/v0.8/ccip/interfaces/IEVM2AnyOnRamp.sol +++ b/contracts/src/v0.8/ccip/interfaces/IEVM2AnyOnRamp.sol @@ -8,9 +8,9 @@ interface IEVM2AnyOnRamp is IEVM2AnyOnRampClient { /// @return the next sequence number to be used. function getExpectedNextSequenceNumber() external view returns (uint64); - /// @notice Get the next nonce for a given sender. + /// @notice Get the current nonce for a given sender. /// @param sender The sender to get the nonce for. - /// @return nonce The next nonce for the sender. + /// @return nonce The current nonce for the sender. function getSenderNonce( address sender ) external view returns (uint64 nonce); diff --git a/contracts/src/v0.8/ccip/libraries/Internal.sol b/contracts/src/v0.8/ccip/libraries/Internal.sol index f5bcdd434f7..25d923ee1ed 100644 --- a/contracts/src/v0.8/ccip/libraries/Internal.sol +++ b/contracts/src/v0.8/ccip/libraries/Internal.sol @@ -3,14 +3,14 @@ pragma solidity ^0.8.4; import {MerkleMultiProof} from "../libraries/MerkleMultiProof.sol"; -// Library for CCIP internal definitions common to multiple contracts. +/// @notice Library for CCIP internal definitions common to multiple contracts. +/// @dev The following is a non-exhaustive list of "known issues" for CCIP: +/// - We could implement yield claiming for Blast. This is not worth the custom code path on non-blast chains. +/// - uint32 is used for timestamps, which will overflow in 2106. This is not a concern for the current use case, as we +/// expect to have migrated to a new version by then. library Internal { error InvalidEVMAddress(bytes encodedAddress); - /// @dev The minimum amount of gas to perform the call with exact gas. - /// We include this in the offramp so that we can redeploy to adjust it should a hardfork change the gas costs of - /// relevant opcodes in callWithExactGas. - uint16 internal constant GAS_FOR_CALL_EXACT_CHECK = 5_000; /// @dev We limit return data to a selector plus 4 words. This is to avoid malicious contracts from returning /// large amounts of data and causing repeated out-of-gas scenarios. uint16 internal constant MAX_RET_BYTES = 4 + 4 * 32; @@ -40,8 +40,8 @@ library Internal { /// @notice A timestamped uint224 value that can contain several tightly packed fields. struct TimestampedPackedUint224 { - uint224 value; // ──────╮ Value in uint224, packed. - uint32 timestamp; // ───╯ Timestamp of the most recent price update. + uint224 value; // ────╮ Value in uint224, packed. + uint32 timestamp; // ─╯ Timestamp of the most recent price update. } /// @dev Gas price is stored in 112-bit unsigned int. uint224 can pack 2 prices. @@ -196,10 +196,10 @@ library Internal { /// The messageId is not expected to match hash(message), since it may originate from another ramp family. struct RampMessageHeader { bytes32 messageId; // Unique identifier for the message, generated with the source chain's encoding scheme (i.e. not necessarily abi.encoded). - uint64 sourceChainSelector; // ──╮ the chain selector of the source chain, note: not chainId. - uint64 destChainSelector; // │ the chain selector of the destination chain, note: not chainId. - uint64 sequenceNumber; // │ sequence number, not unique across lanes. - uint64 nonce; // ────────────────╯ nonce for this lane for this sender, not unique across senders/lanes. + uint64 sourceChainSelector; // ─╮ the chain selector of the source chain, note: not chainId. + uint64 destChainSelector; // │ the chain selector of the destination chain, note: not chainId. + uint64 sequenceNumber; // │ sequence number, not unique across lanes. + uint64 nonce; // ───────────────╯ nonce for this lane for this sender, not unique across senders/lanes. } struct EVM2AnyTokenTransfer { @@ -268,7 +268,7 @@ library Internal { // solhint-disable-next-line gas-struct-packing struct MerkleRoot { uint64 sourceChainSelector; // Remote source chain selector that the Merkle Root is scoped to - bytes onRampAddress; // Generic onramp address, to support arbitrary sources; for EVM, use abi.encode + bytes onRampAddress; // Generic onRamp address, to support arbitrary sources; for EVM, use abi.encode uint64 minSeqNr; // ─────────╮ Minimum sequence number, inclusive uint64 maxSeqNr; // ─────────╯ Maximum sequence number, inclusive bytes32 merkleRoot; // Merkle root covering the interval & source chain messages diff --git a/contracts/src/v0.8/ccip/offRamp/OffRamp.sol b/contracts/src/v0.8/ccip/offRamp/OffRamp.sol index d276ce26a96..76424b4e2a7 100644 --- a/contracts/src/v0.8/ccip/offRamp/OffRamp.sol +++ b/contracts/src/v0.8/ccip/offRamp/OffRamp.sol @@ -92,8 +92,9 @@ contract OffRamp is ITypeAndVersion, MultiOCR3Base { /// @dev RMN depends on this struct, if changing, please notify the RMN maintainers. // solhint-disable-next-line gas-struct-packing struct StaticConfig { - uint64 chainSelector; // ────╮ Destination chainSelector - IRMNRemote rmnRemote; // ────╯ RMN Verification Contract + uint64 chainSelector; // ───────╮ Destination chainSelector + uint16 gasForCallExactCheck; // | Gas for call exact check + IRMNRemote rmnRemote; // ───────╯ RMN Verification Contract address tokenAdminRegistry; // Token admin registry address address nonceManager; // Nonce manager address } @@ -151,6 +152,10 @@ contract OffRamp is ITypeAndVersion, MultiOCR3Base { address internal immutable i_tokenAdminRegistry; /// @dev The address of the nonce manager. address internal immutable i_nonceManager; + /// @dev The minimum amount of gas to perform the call with exact gas. + /// We include this in the offramp so that we can redeploy to adjust it should a hardfork change the gas costs of + /// relevant opcodes in callWithExactGas. + uint16 internal immutable i_gasForCallExactCheck; // DYNAMIC CONFIG DynamicConfig internal s_dynamicConfig; @@ -193,6 +198,7 @@ contract OffRamp is ITypeAndVersion, MultiOCR3Base { i_rmnRemote = staticConfig.rmnRemote; i_tokenAdminRegistry = staticConfig.tokenAdminRegistry; i_nonceManager = staticConfig.nonceManager; + i_gasForCallExactCheck = staticConfig.gasForCallExactCheck; emit StaticConfigSet(staticConfig); _setDynamicConfig(dynamicConfig); @@ -356,6 +362,8 @@ contract OffRamp is ITypeAndVersion, MultiOCR3Base { /// @param manualExecGasExecOverrides An array of gas limits to use for manual execution. /// @dev If called from the DON, this array is always empty. /// @dev If called from manual execution, this array is always same length as messages. + /// @dev This function can fully revert in some cases, reverting potentially valid other reports with it. The reasons + /// for these reverts are so severe that we prefer to revert the entire batch instead of silently failing. function _executeSingleReport( Internal.ExecutionReport memory report, GasLimitOverride[] memory manualExecGasExecOverrides @@ -577,6 +585,8 @@ contract OffRamp is ITypeAndVersion, MultiOCR3Base { destTokenAmounts: destTokenAmounts }); + // The main message interceptor is the aggregate rate limiter, but we also allow for a custom interceptor. This is + // why we always have to call into the contract when it's enabled, even when there are no tokens in the message. address messageInterceptor = s_dynamicConfig.messageInterceptor; if (messageInterceptor != address(0)) { try IMessageInterceptor(messageInterceptor).onInboundMessage(any2EvmMessage) {} @@ -601,7 +611,7 @@ contract OffRamp is ITypeAndVersion, MultiOCR3Base { (bool success, bytes memory returnData,) = s_sourceChainConfigs[message.header.sourceChainSelector] .router - .routeMessage(any2EvmMessage, Internal.GAS_FOR_CALL_EXACT_CHECK, message.gasLimit, message.receiver); + .routeMessage(any2EvmMessage, i_gasForCallExactCheck, message.gasLimit, message.receiver); // If CCIP receiver execution is not successful, revert the call including token transfers. if (!success) revert ReceiverError(returnData); } @@ -665,7 +675,7 @@ contract OffRamp is ITypeAndVersion, MultiOCR3Base { ), localPoolAddress, gasLeft, - Internal.GAS_FOR_CALL_EXACT_CHECK, + i_gasForCallExactCheck, Internal.MAX_RET_BYTES ); @@ -705,11 +715,7 @@ contract OffRamp is ITypeAndVersion, MultiOCR3Base { uint256 gasLimit ) internal returns (uint256 balance, uint256 gasLeft) { (bool success, bytes memory returnData, uint256 gasUsed) = CallWithExactGas._callWithExactGasSafeReturnData( - abi.encodeCall(IERC20.balanceOf, (receiver)), - token, - gasLimit, - Internal.GAS_FOR_CALL_EXACT_CHECK, - Internal.MAX_RET_BYTES + abi.encodeCall(IERC20.balanceOf, (receiver)), token, gasLimit, i_gasForCallExactCheck, Internal.MAX_RET_BYTES ); if (!success) revert TokenHandlingError(token, returnData); @@ -906,6 +912,7 @@ contract OffRamp is ITypeAndVersion, MultiOCR3Base { function getStaticConfig() external view returns (StaticConfig memory) { return StaticConfig({ chainSelector: i_chainSelector, + gasForCallExactCheck: i_gasForCallExactCheck, rmnRemote: i_rmnRemote, tokenAdminRegistry: i_tokenAdminRegistry, nonceManager: i_nonceManager diff --git a/contracts/src/v0.8/ccip/test/BaseTest.t.sol b/contracts/src/v0.8/ccip/test/BaseTest.t.sol index dfc9e4e1eb1..1e9fcdd2fa5 100644 --- a/contracts/src/v0.8/ccip/test/BaseTest.t.sol +++ b/contracts/src/v0.8/ccip/test/BaseTest.t.sol @@ -41,7 +41,7 @@ contract BaseTest is Test { // OffRamp uint32 internal constant MAX_DATA_SIZE = 30_000; uint16 internal constant MAX_TOKENS_LENGTH = 5; - uint16 internal constant GAS_FOR_CALL_EXACT_CHECK = 5000; + uint16 internal constant GAS_FOR_CALL_EXACT_CHECK = 5_000; uint32 internal constant MAX_GAS_LIMIT = 4_000_000; MockRMN internal s_mockRMN; diff --git a/contracts/src/v0.8/ccip/test/NonceManager.t.sol b/contracts/src/v0.8/ccip/test/NonceManager.t.sol deleted file mode 100644 index b5c3ee6bd99..00000000000 --- a/contracts/src/v0.8/ccip/test/NonceManager.t.sol +++ /dev/null @@ -1,568 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity 0.8.24; - -import {IEVM2AnyOnRamp} from "../interfaces/IEVM2AnyOnRamp.sol"; - -import {NonceManager} from "../NonceManager.sol"; -import {Client} from "../libraries/Client.sol"; -import {Internal} from "../libraries/Internal.sol"; -import {OffRamp} from "../offRamp/OffRamp.sol"; -import {OnRamp} from "../onRamp/OnRamp.sol"; -import {BaseTest} from "./BaseTest.t.sol"; -import {EVM2EVMOffRampHelper} from "./helpers/EVM2EVMOffRampHelper.sol"; -import {OnRampHelper} from "./helpers/OnRampHelper.sol"; -import {OffRampSetup} from "./offRamp/OffRamp/OffRampSetup.t.sol"; -import {OnRampSetup} from "./onRamp/OnRamp/OnRampSetup.t.sol"; - -import {Test} from "forge-std/Test.sol"; - -contract NonceManager_typeAndVersion is Test { - NonceManager private s_nonceManager; - - function setUp() public { - s_nonceManager = new NonceManager(new address[](0)); - } - - function test_typeAndVersion() public view { - assertEq(s_nonceManager.typeAndVersion(), "NonceManager 1.6.0-dev"); - } -} - -contract NonceManager_NonceIncrementation is BaseTest { - NonceManager private s_nonceManager; - - function setUp() public override { - address[] memory authorizedCallers = new address[](1); - authorizedCallers[0] = address(this); - s_nonceManager = new NonceManager(authorizedCallers); - } - - function test_getIncrementedOutboundNonce_Success() public { - address sender = address(this); - - assertEq(s_nonceManager.getOutboundNonce(DEST_CHAIN_SELECTOR, sender), 0); - - uint64 outboundNonce = s_nonceManager.getIncrementedOutboundNonce(DEST_CHAIN_SELECTOR, sender); - assertEq(outboundNonce, 1); - } - - function test_incrementInboundNonce_Success() public { - address sender = address(this); - - s_nonceManager.incrementInboundNonce(SOURCE_CHAIN_SELECTOR, 1, abi.encode(sender)); - - assertEq(s_nonceManager.getInboundNonce(SOURCE_CHAIN_SELECTOR, abi.encode(sender)), 1); - } - - function test_incrementInboundNonce_Skip() public { - address sender = address(this); - uint64 expectedNonce = 2; - - vm.expectEmit(); - emit NonceManager.SkippedIncorrectNonce(SOURCE_CHAIN_SELECTOR, expectedNonce, abi.encode(sender)); - - s_nonceManager.incrementInboundNonce(SOURCE_CHAIN_SELECTOR, expectedNonce, abi.encode(sender)); - - assertEq(s_nonceManager.getInboundNonce(SOURCE_CHAIN_SELECTOR, abi.encode(sender)), 0); - } - - function test_incrementNoncesInboundAndOutbound_Success() public { - address sender = address(this); - - assertEq(s_nonceManager.getOutboundNonce(DEST_CHAIN_SELECTOR, sender), 0); - uint64 outboundNonce = s_nonceManager.getIncrementedOutboundNonce(DEST_CHAIN_SELECTOR, sender); - assertEq(outboundNonce, 1); - - // Inbound nonce unchanged - assertEq(s_nonceManager.getInboundNonce(DEST_CHAIN_SELECTOR, abi.encode(sender)), 0); - - s_nonceManager.incrementInboundNonce(DEST_CHAIN_SELECTOR, 1, abi.encode(sender)); - assertEq(s_nonceManager.getInboundNonce(DEST_CHAIN_SELECTOR, abi.encode(sender)), 1); - - // Outbound nonce unchanged - assertEq(s_nonceManager.getOutboundNonce(DEST_CHAIN_SELECTOR, sender), 1); - } -} - -contract NonceManager_applyPreviousRampsUpdates is OnRampSetup { - function test_SingleRampUpdate_success() public { - address prevOnRamp = makeAddr("prevOnRamp"); - address prevOffRamp = makeAddr("prevOffRamp"); - NonceManager.PreviousRampsArgs[] memory previousRamps = new NonceManager.PreviousRampsArgs[](1); - previousRamps[0] = NonceManager.PreviousRampsArgs({ - remoteChainSelector: DEST_CHAIN_SELECTOR, - prevRamps: NonceManager.PreviousRamps(prevOnRamp, prevOffRamp), - overrideExistingRamps: false - }); - - vm.expectEmit(); - emit NonceManager.PreviousRampsUpdated(DEST_CHAIN_SELECTOR, previousRamps[0].prevRamps); - - s_outboundNonceManager.applyPreviousRampsUpdates(previousRamps); - - _assertPreviousRampsEqual(s_outboundNonceManager.getPreviousRamps(DEST_CHAIN_SELECTOR), previousRamps[0].prevRamps); - } - - function test_MultipleRampsUpdates_success() public { - address prevOnRamp1 = makeAddr("prevOnRamp1"); - address prevOnRamp2 = makeAddr("prevOnRamp2"); - address prevOffRamp1 = makeAddr("prevOffRamp1"); - address prevOffRamp2 = makeAddr("prevOffRamp2"); - NonceManager.PreviousRampsArgs[] memory previousRamps = new NonceManager.PreviousRampsArgs[](2); - previousRamps[0] = NonceManager.PreviousRampsArgs({ - remoteChainSelector: DEST_CHAIN_SELECTOR, - prevRamps: NonceManager.PreviousRamps(prevOnRamp1, prevOffRamp1), - overrideExistingRamps: false - }); - previousRamps[1] = NonceManager.PreviousRampsArgs({ - remoteChainSelector: DEST_CHAIN_SELECTOR + 1, - prevRamps: NonceManager.PreviousRamps(prevOnRamp2, prevOffRamp2), - overrideExistingRamps: false - }); - - vm.expectEmit(); - emit NonceManager.PreviousRampsUpdated(DEST_CHAIN_SELECTOR, previousRamps[0].prevRamps); - vm.expectEmit(); - emit NonceManager.PreviousRampsUpdated(DEST_CHAIN_SELECTOR + 1, previousRamps[1].prevRamps); - - s_outboundNonceManager.applyPreviousRampsUpdates(previousRamps); - - _assertPreviousRampsEqual(s_outboundNonceManager.getPreviousRamps(DEST_CHAIN_SELECTOR), previousRamps[0].prevRamps); - _assertPreviousRampsEqual( - s_outboundNonceManager.getPreviousRamps(DEST_CHAIN_SELECTOR + 1), previousRamps[1].prevRamps - ); - } - - function test_PreviousRampAlreadySet_overrideAllowed_success() public { - NonceManager.PreviousRampsArgs[] memory previousRamps = new NonceManager.PreviousRampsArgs[](1); - address prevOffRamp = makeAddr("prevOffRamp"); - previousRamps[0] = NonceManager.PreviousRampsArgs({ - remoteChainSelector: DEST_CHAIN_SELECTOR, - prevRamps: NonceManager.PreviousRamps(address(0), prevOffRamp), - overrideExistingRamps: true - }); - - s_outboundNonceManager.applyPreviousRampsUpdates(previousRamps); - - previousRamps[0] = NonceManager.PreviousRampsArgs({ - remoteChainSelector: DEST_CHAIN_SELECTOR, - prevRamps: NonceManager.PreviousRamps(address(0), prevOffRamp), - overrideExistingRamps: true - }); - - s_outboundNonceManager.applyPreviousRampsUpdates(previousRamps); - } - - function test_ZeroInput_success() public { - vm.recordLogs(); - s_outboundNonceManager.applyPreviousRampsUpdates(new NonceManager.PreviousRampsArgs[](0)); - - assertEq(vm.getRecordedLogs().length, 0); - } - - function test_PreviousRampAlreadySetOnRamp_Revert() public { - NonceManager.PreviousRampsArgs[] memory previousRamps = new NonceManager.PreviousRampsArgs[](1); - address prevOnRamp = makeAddr("prevOnRamp"); - previousRamps[0] = NonceManager.PreviousRampsArgs({ - remoteChainSelector: DEST_CHAIN_SELECTOR, - prevRamps: NonceManager.PreviousRamps(prevOnRamp, address(0)), - overrideExistingRamps: false - }); - - s_outboundNonceManager.applyPreviousRampsUpdates(previousRamps); - - previousRamps[0] = NonceManager.PreviousRampsArgs({ - remoteChainSelector: DEST_CHAIN_SELECTOR, - prevRamps: NonceManager.PreviousRamps(prevOnRamp, address(0)), - overrideExistingRamps: false - }); - - vm.expectRevert(NonceManager.PreviousRampAlreadySet.selector); - s_outboundNonceManager.applyPreviousRampsUpdates(previousRamps); - } - - function test_PreviousRampAlreadySetOffRamp_Revert() public { - NonceManager.PreviousRampsArgs[] memory previousRamps = new NonceManager.PreviousRampsArgs[](1); - address prevOffRamp = makeAddr("prevOffRamp"); - previousRamps[0] = NonceManager.PreviousRampsArgs({ - remoteChainSelector: DEST_CHAIN_SELECTOR, - prevRamps: NonceManager.PreviousRamps(address(0), prevOffRamp), - overrideExistingRamps: false - }); - - s_outboundNonceManager.applyPreviousRampsUpdates(previousRamps); - - previousRamps[0] = NonceManager.PreviousRampsArgs({ - remoteChainSelector: DEST_CHAIN_SELECTOR, - prevRamps: NonceManager.PreviousRamps(address(0), prevOffRamp), - overrideExistingRamps: false - }); - - vm.expectRevert(NonceManager.PreviousRampAlreadySet.selector); - s_outboundNonceManager.applyPreviousRampsUpdates(previousRamps); - } - - function test_PreviousRampAlreadySetOnRampAndOffRamp_Revert() public { - NonceManager.PreviousRampsArgs[] memory previousRamps = new NonceManager.PreviousRampsArgs[](1); - address prevOnRamp = makeAddr("prevOnRamp"); - address prevOffRamp = makeAddr("prevOffRamp"); - previousRamps[0] = NonceManager.PreviousRampsArgs({ - remoteChainSelector: DEST_CHAIN_SELECTOR, - prevRamps: NonceManager.PreviousRamps(prevOnRamp, prevOffRamp), - overrideExistingRamps: false - }); - - s_outboundNonceManager.applyPreviousRampsUpdates(previousRamps); - - previousRamps[0] = NonceManager.PreviousRampsArgs({ - remoteChainSelector: DEST_CHAIN_SELECTOR, - prevRamps: NonceManager.PreviousRamps(prevOnRamp, prevOffRamp), - overrideExistingRamps: false - }); - - vm.expectRevert(NonceManager.PreviousRampAlreadySet.selector); - s_outboundNonceManager.applyPreviousRampsUpdates(previousRamps); - } - - function _assertPreviousRampsEqual( - NonceManager.PreviousRamps memory a, - NonceManager.PreviousRamps memory b - ) internal pure { - assertEq(a.prevOnRamp, b.prevOnRamp); - assertEq(a.prevOffRamp, b.prevOffRamp); - } -} - -contract NonceManager_OnRampUpgrade is OnRampSetup { - uint256 internal constant FEE_AMOUNT = 1234567890; - OnRampHelper internal s_prevOnRamp; - - function setUp() public virtual override { - super.setUp(); - - (s_prevOnRamp,) = _deployOnRamp( - SOURCE_CHAIN_SELECTOR, s_sourceRouter, address(s_outboundNonceManager), address(s_tokenAdminRegistry) - ); - - // Since the previous onRamp is not a 1.5 ramp it doesn't have the getSenderNonce function. We mock it to return 0 - vm.mockCall(address(s_prevOnRamp), abi.encodeWithSelector(IEVM2AnyOnRamp.getSenderNonce.selector), abi.encode(0)); - - NonceManager.PreviousRampsArgs[] memory previousRamps = new NonceManager.PreviousRampsArgs[](1); - previousRamps[0] = NonceManager.PreviousRampsArgs({ - remoteChainSelector: DEST_CHAIN_SELECTOR, - prevRamps: NonceManager.PreviousRamps(address(s_prevOnRamp), address(0)), - overrideExistingRamps: false - }); - s_outboundNonceManager.applyPreviousRampsUpdates(previousRamps); - - (s_onRamp, s_metadataHash) = _deployOnRamp( - SOURCE_CHAIN_SELECTOR, s_sourceRouter, address(s_outboundNonceManager), address(s_tokenAdminRegistry) - ); - - vm.startPrank(address(s_sourceRouter)); - } - - function test_Upgrade_Success() public { - Client.EVM2AnyMessage memory message = _generateEmptyMessage(); - - vm.expectEmit(); - emit OnRamp.CCIPMessageSent(DEST_CHAIN_SELECTOR, 1, _messageToEvent(message, 1, 1, FEE_AMOUNT, OWNER)); - - s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, FEE_AMOUNT, OWNER); - } - - function test_UpgradeSenderNoncesReadsPreviousRamp_Success() public { - Client.EVM2AnyMessage memory message = _generateEmptyMessage(); - uint64 startNonce = s_outboundNonceManager.getOutboundNonce(DEST_CHAIN_SELECTOR, OWNER); - - for (uint64 i = 1; i < 4; ++i) { - s_prevOnRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, 0, OWNER); - - assertEq(startNonce + i, s_outboundNonceManager.getOutboundNonce(DEST_CHAIN_SELECTOR, OWNER)); - } - } - - function test_UpgradeNonceStartsAtV1Nonce_Success() public { - Client.EVM2AnyMessage memory message = _generateEmptyMessage(); - - uint64 startNonce = s_outboundNonceManager.getOutboundNonce(DEST_CHAIN_SELECTOR, OWNER); - - // send 1 message from previous onramp - s_prevOnRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, FEE_AMOUNT, OWNER); - - assertEq(startNonce + 1, s_outboundNonceManager.getOutboundNonce(DEST_CHAIN_SELECTOR, OWNER)); - - // new onramp nonce should start from 2, while sequence number start from 1 - vm.expectEmit(); - emit OnRamp.CCIPMessageSent(DEST_CHAIN_SELECTOR, 1, _messageToEvent(message, 1, startNonce + 2, FEE_AMOUNT, OWNER)); - s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, FEE_AMOUNT, OWNER); - - assertEq(startNonce + 2, s_outboundNonceManager.getOutboundNonce(DEST_CHAIN_SELECTOR, OWNER)); - - // after another send, nonce should be 3, and sequence number be 2 - vm.expectEmit(); - emit OnRamp.CCIPMessageSent(DEST_CHAIN_SELECTOR, 2, _messageToEvent(message, 2, startNonce + 3, FEE_AMOUNT, OWNER)); - s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, FEE_AMOUNT, OWNER); - - assertEq(startNonce + 3, s_outboundNonceManager.getOutboundNonce(DEST_CHAIN_SELECTOR, OWNER)); - } - - function test_UpgradeNonceNewSenderStartsAtZero_Success() public { - Client.EVM2AnyMessage memory message = _generateEmptyMessage(); - - // send 1 message from previous onramp from OWNER - s_prevOnRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, FEE_AMOUNT, OWNER); - - address newSender = address(1234567); - // new onramp nonce should start from 1 for new sender - vm.expectEmit(); - emit OnRamp.CCIPMessageSent(DEST_CHAIN_SELECTOR, 1, _messageToEvent(message, 1, 1, FEE_AMOUNT, newSender)); - - s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, FEE_AMOUNT, newSender); - } -} - -contract NonceManager_OffRampUpgrade is OffRampSetup { - EVM2EVMOffRampHelper internal s_prevOffRamp; - - address internal constant SINGLE_LANE_ON_RAMP_ADDRESS_1 = abi.decode(ON_RAMP_ADDRESS_1, (address)); - address internal constant SINGLE_LANE_ON_RAMP_ADDRESS_2 = abi.decode(ON_RAMP_ADDRESS_2, (address)); - address internal constant SINGLE_LANE_ON_RAMP_ADDRESS_3 = abi.decode(ON_RAMP_ADDRESS_3, (address)); - - function setUp() public virtual override { - super.setUp(); - - s_prevOffRamp = new EVM2EVMOffRampHelper(); - - NonceManager.PreviousRampsArgs[] memory previousRamps = new NonceManager.PreviousRampsArgs[](1); - previousRamps[0] = NonceManager.PreviousRampsArgs({ - remoteChainSelector: SOURCE_CHAIN_SELECTOR_1, - prevRamps: NonceManager.PreviousRamps(address(0), address(s_prevOffRamp)), - overrideExistingRamps: false - }); - - s_inboundNonceManager.applyPreviousRampsUpdates(previousRamps); - - OffRamp.SourceChainConfigArgs[] memory sourceChainConfigs = new OffRamp.SourceChainConfigArgs[](3); - sourceChainConfigs[0] = OffRamp.SourceChainConfigArgs({ - router: s_destRouter, - sourceChainSelector: SOURCE_CHAIN_SELECTOR_1, - isEnabled: true, - onRamp: ON_RAMP_ADDRESS_1 - }); - sourceChainConfigs[1] = OffRamp.SourceChainConfigArgs({ - router: s_destRouter, - sourceChainSelector: SOURCE_CHAIN_SELECTOR_2, - isEnabled: true, - onRamp: ON_RAMP_ADDRESS_2 - }); - sourceChainConfigs[2] = OffRamp.SourceChainConfigArgs({ - router: s_destRouter, - sourceChainSelector: SOURCE_CHAIN_SELECTOR_3, - isEnabled: true, - onRamp: ON_RAMP_ADDRESS_3 - }); - - _setupMultipleOffRampsFromConfigs(sourceChainConfigs); - - s_offRamp.setVerifyOverrideResult(SOURCE_CHAIN_SELECTOR_1, 1); - s_offRamp.setVerifyOverrideResult(SOURCE_CHAIN_SELECTOR_3, 1); - } - - function test_Upgraded_Success() public { - Internal.Any2EVMRampMessage[] memory messages = - _generateSingleBasicMessage(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1); - - vm.recordLogs(); - s_offRamp.executeSingleReport( - _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), new OffRamp.GasLimitOverride[](0) - ); - _assertExecutionStateChangedEventLogs( - SOURCE_CHAIN_SELECTOR_1, - messages[0].header.sequenceNumber, - messages[0].header.messageId, - _hashMessage(messages[0], ON_RAMP_ADDRESS_1), - Internal.MessageExecutionState.SUCCESS, - "" - ); - } - - function test_NoPrevOffRampForChain_Success() public { - address[] memory senders = new address[](1); - senders[0] = OWNER; - - uint64 startNonceChain3 = s_inboundNonceManager.getInboundNonce(SOURCE_CHAIN_SELECTOR_3, abi.encode(senders[0])); - s_prevOffRamp.execute(senders); - - // Nonce unchanged for chain 3 - assertEq(startNonceChain3, s_inboundNonceManager.getInboundNonce(SOURCE_CHAIN_SELECTOR_3, abi.encode(senders[0]))); - - Internal.Any2EVMRampMessage[] memory messagesChain3 = - _generateSingleBasicMessage(SOURCE_CHAIN_SELECTOR_3, ON_RAMP_ADDRESS_3); - - vm.recordLogs(); - - s_offRamp.executeSingleReport( - _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_3, messagesChain3), new OffRamp.GasLimitOverride[](0) - ); - _assertExecutionStateChangedEventLogs( - SOURCE_CHAIN_SELECTOR_3, - messagesChain3[0].header.sequenceNumber, - messagesChain3[0].header.messageId, - _hashMessage(messagesChain3[0], ON_RAMP_ADDRESS_3), - Internal.MessageExecutionState.SUCCESS, - "" - ); - - assertEq( - startNonceChain3 + 1, s_inboundNonceManager.getInboundNonce(SOURCE_CHAIN_SELECTOR_3, messagesChain3[0].sender) - ); - } - - function test_UpgradedSenderNoncesReadsPreviousRamp_Success() public { - address[] memory senders = new address[](1); - senders[0] = OWNER; - - uint64 startNonce = s_inboundNonceManager.getInboundNonce(SOURCE_CHAIN_SELECTOR_1, abi.encode(senders[0])); - - for (uint64 i = 1; i < 4; ++i) { - s_prevOffRamp.execute(senders); - - assertEq(startNonce + i, s_inboundNonceManager.getInboundNonce(SOURCE_CHAIN_SELECTOR_1, abi.encode(senders[0]))); - } - } - - function test_UpgradedNonceStartsAtV1Nonce_Success() public { - address[] memory senders = new address[](1); - senders[0] = OWNER; - - uint64 startNonce = s_inboundNonceManager.getInboundNonce(SOURCE_CHAIN_SELECTOR_1, abi.encode(senders[0])); - s_prevOffRamp.execute(senders); - - assertEq(startNonce + 1, s_inboundNonceManager.getInboundNonce(SOURCE_CHAIN_SELECTOR_1, abi.encode(senders[0]))); - - Internal.Any2EVMRampMessage[] memory messagesMultiRamp = - _generateSingleBasicMessage(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1); - - messagesMultiRamp[0].header.nonce++; - messagesMultiRamp[0].header.messageId = _hashMessage(messagesMultiRamp[0], ON_RAMP_ADDRESS_1); - - vm.recordLogs(); - - s_offRamp.executeSingleReport( - _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messagesMultiRamp), new OffRamp.GasLimitOverride[](0) - ); - - _assertExecutionStateChangedEventLogs( - SOURCE_CHAIN_SELECTOR_1, - messagesMultiRamp[0].header.sequenceNumber, - messagesMultiRamp[0].header.messageId, - _hashMessage(messagesMultiRamp[0], ON_RAMP_ADDRESS_1), - Internal.MessageExecutionState.SUCCESS, - "" - ); - - assertEq( - startNonce + 2, s_inboundNonceManager.getInboundNonce(SOURCE_CHAIN_SELECTOR_1, messagesMultiRamp[0].sender) - ); - - messagesMultiRamp[0].header.nonce++; - messagesMultiRamp[0].header.sequenceNumber++; - messagesMultiRamp[0].header.messageId = _hashMessage(messagesMultiRamp[0], ON_RAMP_ADDRESS_1); - - vm.recordLogs(); - s_offRamp.executeSingleReport( - _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messagesMultiRamp), new OffRamp.GasLimitOverride[](0) - ); - _assertExecutionStateChangedEventLogs( - SOURCE_CHAIN_SELECTOR_1, - messagesMultiRamp[0].header.sequenceNumber, - messagesMultiRamp[0].header.messageId, - _hashMessage(messagesMultiRamp[0], ON_RAMP_ADDRESS_1), - Internal.MessageExecutionState.SUCCESS, - "" - ); - - assertEq( - startNonce + 3, s_inboundNonceManager.getInboundNonce(SOURCE_CHAIN_SELECTOR_1, messagesMultiRamp[0].sender) - ); - } - - function test_UpgradedNonceNewSenderStartsAtZero_Success() public { - address[] memory senders = new address[](1); - senders[0] = OWNER; - - s_prevOffRamp.execute(senders); - - Internal.Any2EVMRampMessage[] memory messagesMultiRamp = - _generateSingleBasicMessage(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1); - - bytes memory newSender = abi.encode(address(1234567)); - messagesMultiRamp[0].sender = newSender; - messagesMultiRamp[0].header.messageId = _hashMessage(messagesMultiRamp[0], ON_RAMP_ADDRESS_1); - - // new sender nonce in new offramp should go from 0 -> 1 - assertEq(s_inboundNonceManager.getInboundNonce(SOURCE_CHAIN_SELECTOR_1, newSender), 0); - vm.recordLogs(); - s_offRamp.executeSingleReport( - _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messagesMultiRamp), new OffRamp.GasLimitOverride[](0) - ); - _assertExecutionStateChangedEventLogs( - SOURCE_CHAIN_SELECTOR_1, - messagesMultiRamp[0].header.sequenceNumber, - messagesMultiRamp[0].header.messageId, - _hashMessage(messagesMultiRamp[0], ON_RAMP_ADDRESS_1), - Internal.MessageExecutionState.SUCCESS, - "" - ); - assertEq(s_inboundNonceManager.getInboundNonce(SOURCE_CHAIN_SELECTOR_1, newSender), 1); - } - - function test_UpgradedOffRampNonceSkipsIfMsgInFlight_Success() public { - Internal.Any2EVMRampMessage[] memory messages = - _generateSingleBasicMessage(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1); - - address newSender = address(1234567); - messages[0].sender = abi.encode(newSender); - messages[0].header.nonce = 2; - messages[0].header.messageId = _hashMessage(messages[0], ON_RAMP_ADDRESS_1); - - uint64 startNonce = s_inboundNonceManager.getInboundNonce(SOURCE_CHAIN_SELECTOR_1, messages[0].sender); - - // new offramp sees msg nonce higher than senderNonce - // it waits for previous offramp to execute - vm.expectEmit(); - emit NonceManager.SkippedIncorrectNonce(SOURCE_CHAIN_SELECTOR_1, messages[0].header.nonce, messages[0].sender); - s_offRamp.executeSingleReport( - _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), new OffRamp.GasLimitOverride[](0) - ); - assertEq(startNonce, s_inboundNonceManager.getInboundNonce(SOURCE_CHAIN_SELECTOR_1, messages[0].sender)); - - address[] memory senders = new address[](1); - senders[0] = newSender; - - // previous offramp executes msg and increases nonce - s_prevOffRamp.execute(senders); - assertEq(startNonce + 1, s_inboundNonceManager.getInboundNonce(SOURCE_CHAIN_SELECTOR_1, abi.encode(senders[0]))); - - messages[0].header.nonce = 2; - messages[0].header.messageId = _hashMessage(messages[0], ON_RAMP_ADDRESS_1); - - // new offramp is able to execute - vm.recordLogs(); - s_offRamp.executeSingleReport( - _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), new OffRamp.GasLimitOverride[](0) - ); - - _assertExecutionStateChangedEventLogs( - SOURCE_CHAIN_SELECTOR_1, - messages[0].header.sequenceNumber, - messages[0].header.messageId, - _hashMessage(messages[0], ON_RAMP_ADDRESS_1), - Internal.MessageExecutionState.SUCCESS, - "" - ); - - assertEq(startNonce + 2, s_inboundNonceManager.getInboundNonce(SOURCE_CHAIN_SELECTOR_1, messages[0].sender)); - } -} diff --git a/contracts/src/v0.8/ccip/test/NonceManager/NonceManager.applyPreviousRampsUpdates.t.sol b/contracts/src/v0.8/ccip/test/NonceManager/NonceManager.applyPreviousRampsUpdates.t.sol new file mode 100644 index 00000000000..fe4316423ec --- /dev/null +++ b/contracts/src/v0.8/ccip/test/NonceManager/NonceManager.applyPreviousRampsUpdates.t.sol @@ -0,0 +1,154 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {NonceManager} from "../../NonceManager.sol"; +import {OnRampSetup} from "../onRamp/OnRamp/OnRampSetup.t.sol"; + +contract NonceManager_applyPreviousRampsUpdates is OnRampSetup { + function test_SingleRampUpdate_success() public { + address prevOnRamp = makeAddr("prevOnRamp"); + address prevOffRamp = makeAddr("prevOffRamp"); + NonceManager.PreviousRampsArgs[] memory previousRamps = new NonceManager.PreviousRampsArgs[](1); + previousRamps[0] = NonceManager.PreviousRampsArgs({ + remoteChainSelector: DEST_CHAIN_SELECTOR, + prevRamps: NonceManager.PreviousRamps(prevOnRamp, prevOffRamp), + overrideExistingRamps: false + }); + + vm.expectEmit(); + emit NonceManager.PreviousRampsUpdated(DEST_CHAIN_SELECTOR, previousRamps[0].prevRamps); + + s_outboundNonceManager.applyPreviousRampsUpdates(previousRamps); + + _assertPreviousRampsEqual(s_outboundNonceManager.getPreviousRamps(DEST_CHAIN_SELECTOR), previousRamps[0].prevRamps); + } + + function test_MultipleRampsUpdates() public { + address prevOnRamp1 = makeAddr("prevOnRamp1"); + address prevOnRamp2 = makeAddr("prevOnRamp2"); + address prevOffRamp1 = makeAddr("prevOffRamp1"); + address prevOffRamp2 = makeAddr("prevOffRamp2"); + NonceManager.PreviousRampsArgs[] memory previousRamps = new NonceManager.PreviousRampsArgs[](2); + previousRamps[0] = NonceManager.PreviousRampsArgs({ + remoteChainSelector: DEST_CHAIN_SELECTOR, + prevRamps: NonceManager.PreviousRamps(prevOnRamp1, prevOffRamp1), + overrideExistingRamps: false + }); + previousRamps[1] = NonceManager.PreviousRampsArgs({ + remoteChainSelector: DEST_CHAIN_SELECTOR + 1, + prevRamps: NonceManager.PreviousRamps(prevOnRamp2, prevOffRamp2), + overrideExistingRamps: false + }); + + vm.expectEmit(); + emit NonceManager.PreviousRampsUpdated(DEST_CHAIN_SELECTOR, previousRamps[0].prevRamps); + vm.expectEmit(); + emit NonceManager.PreviousRampsUpdated(DEST_CHAIN_SELECTOR + 1, previousRamps[1].prevRamps); + + s_outboundNonceManager.applyPreviousRampsUpdates(previousRamps); + + _assertPreviousRampsEqual(s_outboundNonceManager.getPreviousRamps(DEST_CHAIN_SELECTOR), previousRamps[0].prevRamps); + _assertPreviousRampsEqual( + s_outboundNonceManager.getPreviousRamps(DEST_CHAIN_SELECTOR + 1), previousRamps[1].prevRamps + ); + } + + function test_PreviousRampAlreadySet_overrideAllowed() public { + NonceManager.PreviousRampsArgs[] memory previousRamps = new NonceManager.PreviousRampsArgs[](1); + address prevOffRamp = makeAddr("prevOffRamp"); + previousRamps[0] = NonceManager.PreviousRampsArgs({ + remoteChainSelector: DEST_CHAIN_SELECTOR, + prevRamps: NonceManager.PreviousRamps(address(0), prevOffRamp), + overrideExistingRamps: true + }); + + s_outboundNonceManager.applyPreviousRampsUpdates(previousRamps); + + previousRamps[0] = NonceManager.PreviousRampsArgs({ + remoteChainSelector: DEST_CHAIN_SELECTOR, + prevRamps: NonceManager.PreviousRamps(address(0), prevOffRamp), + overrideExistingRamps: true + }); + + s_outboundNonceManager.applyPreviousRampsUpdates(previousRamps); + } + + function test_ZeroInput() public { + vm.recordLogs(); + s_outboundNonceManager.applyPreviousRampsUpdates(new NonceManager.PreviousRampsArgs[](0)); + + assertEq(vm.getRecordedLogs().length, 0); + } + + function test_applyPreviousRampsUpdates_RevertWhen_PreviousRampAlreadySetOnRamp() public { + NonceManager.PreviousRampsArgs[] memory previousRamps = new NonceManager.PreviousRampsArgs[](1); + address prevOnRamp = makeAddr("prevOnRamp"); + previousRamps[0] = NonceManager.PreviousRampsArgs({ + remoteChainSelector: DEST_CHAIN_SELECTOR, + prevRamps: NonceManager.PreviousRamps(prevOnRamp, address(0)), + overrideExistingRamps: false + }); + + s_outboundNonceManager.applyPreviousRampsUpdates(previousRamps); + + previousRamps[0] = NonceManager.PreviousRampsArgs({ + remoteChainSelector: DEST_CHAIN_SELECTOR, + prevRamps: NonceManager.PreviousRamps(prevOnRamp, address(0)), + overrideExistingRamps: false + }); + + vm.expectRevert(NonceManager.PreviousRampAlreadySet.selector); + s_outboundNonceManager.applyPreviousRampsUpdates(previousRamps); + } + + function test_applyPreviousRampsUpdates_RevertWhen_PreviousRampAlreadySetOffRamp() public { + NonceManager.PreviousRampsArgs[] memory previousRamps = new NonceManager.PreviousRampsArgs[](1); + address prevOffRamp = makeAddr("prevOffRamp"); + previousRamps[0] = NonceManager.PreviousRampsArgs({ + remoteChainSelector: DEST_CHAIN_SELECTOR, + prevRamps: NonceManager.PreviousRamps(address(0), prevOffRamp), + overrideExistingRamps: false + }); + + s_outboundNonceManager.applyPreviousRampsUpdates(previousRamps); + + previousRamps[0] = NonceManager.PreviousRampsArgs({ + remoteChainSelector: DEST_CHAIN_SELECTOR, + prevRamps: NonceManager.PreviousRamps(address(0), prevOffRamp), + overrideExistingRamps: false + }); + + vm.expectRevert(NonceManager.PreviousRampAlreadySet.selector); + s_outboundNonceManager.applyPreviousRampsUpdates(previousRamps); + } + + function test_applyPreviousRampsUpdates_RevertWhen_PreviousRampAlreadySetOnRampAndOffRamp_Revert() public { + NonceManager.PreviousRampsArgs[] memory previousRamps = new NonceManager.PreviousRampsArgs[](1); + address prevOnRamp = makeAddr("prevOnRamp"); + address prevOffRamp = makeAddr("prevOffRamp"); + previousRamps[0] = NonceManager.PreviousRampsArgs({ + remoteChainSelector: DEST_CHAIN_SELECTOR, + prevRamps: NonceManager.PreviousRamps(prevOnRamp, prevOffRamp), + overrideExistingRamps: false + }); + + s_outboundNonceManager.applyPreviousRampsUpdates(previousRamps); + + previousRamps[0] = NonceManager.PreviousRampsArgs({ + remoteChainSelector: DEST_CHAIN_SELECTOR, + prevRamps: NonceManager.PreviousRamps(prevOnRamp, prevOffRamp), + overrideExistingRamps: false + }); + + vm.expectRevert(NonceManager.PreviousRampAlreadySet.selector); + s_outboundNonceManager.applyPreviousRampsUpdates(previousRamps); + } + + function _assertPreviousRampsEqual( + NonceManager.PreviousRamps memory a, + NonceManager.PreviousRamps memory b + ) internal pure { + assertEq(a.prevOnRamp, b.prevOnRamp); + assertEq(a.prevOffRamp, b.prevOffRamp); + } +} diff --git a/contracts/src/v0.8/ccip/test/NonceManager/NonceManager.getInboundNonce.t.sol b/contracts/src/v0.8/ccip/test/NonceManager/NonceManager.getInboundNonce.t.sol new file mode 100644 index 00000000000..4078c3c0d01 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/NonceManager/NonceManager.getInboundNonce.t.sol @@ -0,0 +1,253 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {NonceManager} from "../../NonceManager.sol"; +import {Internal} from "../../libraries/Internal.sol"; +import {OffRamp} from "../../offRamp/OffRamp.sol"; +import {EVM2EVMOffRampHelper} from "../helpers/EVM2EVMOffRampHelper.sol"; +import {OffRampSetup} from "../offRamp/OffRamp/OffRampSetup.t.sol"; + +contract NonceManager_getInboundNonce is OffRampSetup { + EVM2EVMOffRampHelper internal s_prevOffRamp; + + address internal constant SINGLE_LANE_ON_RAMP_ADDRESS_1 = abi.decode(ON_RAMP_ADDRESS_1, (address)); + address internal constant SINGLE_LANE_ON_RAMP_ADDRESS_2 = abi.decode(ON_RAMP_ADDRESS_2, (address)); + address internal constant SINGLE_LANE_ON_RAMP_ADDRESS_3 = abi.decode(ON_RAMP_ADDRESS_3, (address)); + + function setUp() public virtual override { + super.setUp(); + + s_prevOffRamp = new EVM2EVMOffRampHelper(); + + NonceManager.PreviousRampsArgs[] memory previousRamps = new NonceManager.PreviousRampsArgs[](1); + previousRamps[0] = NonceManager.PreviousRampsArgs({ + remoteChainSelector: SOURCE_CHAIN_SELECTOR_1, + prevRamps: NonceManager.PreviousRamps(address(0), address(s_prevOffRamp)), + overrideExistingRamps: false + }); + + s_inboundNonceManager.applyPreviousRampsUpdates(previousRamps); + + OffRamp.SourceChainConfigArgs[] memory sourceChainConfigs = new OffRamp.SourceChainConfigArgs[](3); + sourceChainConfigs[0] = OffRamp.SourceChainConfigArgs({ + router: s_destRouter, + sourceChainSelector: SOURCE_CHAIN_SELECTOR_1, + isEnabled: true, + onRamp: ON_RAMP_ADDRESS_1 + }); + sourceChainConfigs[1] = OffRamp.SourceChainConfigArgs({ + router: s_destRouter, + sourceChainSelector: SOURCE_CHAIN_SELECTOR_2, + isEnabled: true, + onRamp: ON_RAMP_ADDRESS_2 + }); + sourceChainConfigs[2] = OffRamp.SourceChainConfigArgs({ + router: s_destRouter, + sourceChainSelector: SOURCE_CHAIN_SELECTOR_3, + isEnabled: true, + onRamp: ON_RAMP_ADDRESS_3 + }); + + _setupMultipleOffRampsFromConfigs(sourceChainConfigs); + + s_offRamp.setVerifyOverrideResult(SOURCE_CHAIN_SELECTOR_1, 1); + s_offRamp.setVerifyOverrideResult(SOURCE_CHAIN_SELECTOR_3, 1); + } + + function test_getInboundNonce_Upgraded() public { + Internal.Any2EVMRampMessage[] memory messages = + _generateSingleBasicMessage(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1); + + vm.recordLogs(); + s_offRamp.executeSingleReport( + _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), new OffRamp.GasLimitOverride[](0) + ); + _assertExecutionStateChangedEventLogs( + SOURCE_CHAIN_SELECTOR_1, + messages[0].header.sequenceNumber, + messages[0].header.messageId, + _hashMessage(messages[0], ON_RAMP_ADDRESS_1), + Internal.MessageExecutionState.SUCCESS, + "" + ); + } + + function test_getInboundNonce_NoPrevOffRampForChain() public { + address[] memory senders = new address[](1); + senders[0] = OWNER; + + uint64 startNonceChain3 = s_inboundNonceManager.getInboundNonce(SOURCE_CHAIN_SELECTOR_3, abi.encode(senders[0])); + s_prevOffRamp.execute(senders); + + // Nonce unchanged for chain 3 + assertEq(startNonceChain3, s_inboundNonceManager.getInboundNonce(SOURCE_CHAIN_SELECTOR_3, abi.encode(senders[0]))); + + Internal.Any2EVMRampMessage[] memory messagesChain3 = + _generateSingleBasicMessage(SOURCE_CHAIN_SELECTOR_3, ON_RAMP_ADDRESS_3); + + vm.recordLogs(); + + s_offRamp.executeSingleReport( + _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_3, messagesChain3), new OffRamp.GasLimitOverride[](0) + ); + _assertExecutionStateChangedEventLogs( + SOURCE_CHAIN_SELECTOR_3, + messagesChain3[0].header.sequenceNumber, + messagesChain3[0].header.messageId, + _hashMessage(messagesChain3[0], ON_RAMP_ADDRESS_3), + Internal.MessageExecutionState.SUCCESS, + "" + ); + + assertEq( + startNonceChain3 + 1, s_inboundNonceManager.getInboundNonce(SOURCE_CHAIN_SELECTOR_3, messagesChain3[0].sender) + ); + } + + function test_getInboundNonce_UpgradedSenderNoncesReadsPreviousRamp() public { + address[] memory senders = new address[](1); + senders[0] = OWNER; + + uint64 startNonce = s_inboundNonceManager.getInboundNonce(SOURCE_CHAIN_SELECTOR_1, abi.encode(senders[0])); + + for (uint64 i = 1; i < 4; ++i) { + s_prevOffRamp.execute(senders); + + assertEq(startNonce + i, s_inboundNonceManager.getInboundNonce(SOURCE_CHAIN_SELECTOR_1, abi.encode(senders[0]))); + } + } + + function test_getInboundNonce_UpgradedNonceStartsAtV1Nonce() public { + address[] memory senders = new address[](1); + senders[0] = OWNER; + + uint64 startNonce = s_inboundNonceManager.getInboundNonce(SOURCE_CHAIN_SELECTOR_1, abi.encode(senders[0])); + s_prevOffRamp.execute(senders); + + assertEq(startNonce + 1, s_inboundNonceManager.getInboundNonce(SOURCE_CHAIN_SELECTOR_1, abi.encode(senders[0]))); + + Internal.Any2EVMRampMessage[] memory messagesMultiRamp = + _generateSingleBasicMessage(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1); + + messagesMultiRamp[0].header.nonce++; + messagesMultiRamp[0].header.messageId = _hashMessage(messagesMultiRamp[0], ON_RAMP_ADDRESS_1); + + vm.recordLogs(); + + s_offRamp.executeSingleReport( + _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messagesMultiRamp), new OffRamp.GasLimitOverride[](0) + ); + + _assertExecutionStateChangedEventLogs( + SOURCE_CHAIN_SELECTOR_1, + messagesMultiRamp[0].header.sequenceNumber, + messagesMultiRamp[0].header.messageId, + _hashMessage(messagesMultiRamp[0], ON_RAMP_ADDRESS_1), + Internal.MessageExecutionState.SUCCESS, + "" + ); + + assertEq( + startNonce + 2, s_inboundNonceManager.getInboundNonce(SOURCE_CHAIN_SELECTOR_1, messagesMultiRamp[0].sender) + ); + + messagesMultiRamp[0].header.nonce++; + messagesMultiRamp[0].header.sequenceNumber++; + messagesMultiRamp[0].header.messageId = _hashMessage(messagesMultiRamp[0], ON_RAMP_ADDRESS_1); + + vm.recordLogs(); + s_offRamp.executeSingleReport( + _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messagesMultiRamp), new OffRamp.GasLimitOverride[](0) + ); + _assertExecutionStateChangedEventLogs( + SOURCE_CHAIN_SELECTOR_1, + messagesMultiRamp[0].header.sequenceNumber, + messagesMultiRamp[0].header.messageId, + _hashMessage(messagesMultiRamp[0], ON_RAMP_ADDRESS_1), + Internal.MessageExecutionState.SUCCESS, + "" + ); + + assertEq( + startNonce + 3, s_inboundNonceManager.getInboundNonce(SOURCE_CHAIN_SELECTOR_1, messagesMultiRamp[0].sender) + ); + } + + function test_getInboundNonce_UpgradedNonceNewSenderStartsAtZero() public { + address[] memory senders = new address[](1); + senders[0] = OWNER; + + s_prevOffRamp.execute(senders); + + Internal.Any2EVMRampMessage[] memory messagesMultiRamp = + _generateSingleBasicMessage(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1); + + bytes memory newSender = abi.encode(address(1234567)); + messagesMultiRamp[0].sender = newSender; + messagesMultiRamp[0].header.messageId = _hashMessage(messagesMultiRamp[0], ON_RAMP_ADDRESS_1); + + // new sender nonce in new offRamp should go from 0 -> 1 + assertEq(s_inboundNonceManager.getInboundNonce(SOURCE_CHAIN_SELECTOR_1, newSender), 0); + vm.recordLogs(); + s_offRamp.executeSingleReport( + _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messagesMultiRamp), new OffRamp.GasLimitOverride[](0) + ); + _assertExecutionStateChangedEventLogs( + SOURCE_CHAIN_SELECTOR_1, + messagesMultiRamp[0].header.sequenceNumber, + messagesMultiRamp[0].header.messageId, + _hashMessage(messagesMultiRamp[0], ON_RAMP_ADDRESS_1), + Internal.MessageExecutionState.SUCCESS, + "" + ); + assertEq(s_inboundNonceManager.getInboundNonce(SOURCE_CHAIN_SELECTOR_1, newSender), 1); + } + + function test_getInboundNonce_UpgradedOffRampNonceSkipsIfMsgInFlight() public { + Internal.Any2EVMRampMessage[] memory messages = + _generateSingleBasicMessage(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1); + + address newSender = address(1234567); + messages[0].sender = abi.encode(newSender); + messages[0].header.nonce = 2; + messages[0].header.messageId = _hashMessage(messages[0], ON_RAMP_ADDRESS_1); + + uint64 startNonce = s_inboundNonceManager.getInboundNonce(SOURCE_CHAIN_SELECTOR_1, messages[0].sender); + + // new offRamp sees msg nonce higher than senderNonce + // it waits for previous offRamp to execute + vm.expectEmit(); + emit NonceManager.SkippedIncorrectNonce(SOURCE_CHAIN_SELECTOR_1, messages[0].header.nonce, messages[0].sender); + s_offRamp.executeSingleReport( + _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), new OffRamp.GasLimitOverride[](0) + ); + assertEq(startNonce, s_inboundNonceManager.getInboundNonce(SOURCE_CHAIN_SELECTOR_1, messages[0].sender)); + + address[] memory senders = new address[](1); + senders[0] = newSender; + + // previous offRamp executes msg and increases nonce + s_prevOffRamp.execute(senders); + assertEq(startNonce + 1, s_inboundNonceManager.getInboundNonce(SOURCE_CHAIN_SELECTOR_1, abi.encode(senders[0]))); + + messages[0].header.nonce = 2; + messages[0].header.messageId = _hashMessage(messages[0], ON_RAMP_ADDRESS_1); + + // new offRamp is able to execute + vm.recordLogs(); + s_offRamp.executeSingleReport( + _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), new OffRamp.GasLimitOverride[](0) + ); + + _assertExecutionStateChangedEventLogs( + SOURCE_CHAIN_SELECTOR_1, + messages[0].header.sequenceNumber, + messages[0].header.messageId, + _hashMessage(messages[0], ON_RAMP_ADDRESS_1), + Internal.MessageExecutionState.SUCCESS, + "" + ); + + assertEq(startNonce + 2, s_inboundNonceManager.getInboundNonce(SOURCE_CHAIN_SELECTOR_1, messages[0].sender)); + } +} diff --git a/contracts/src/v0.8/ccip/test/NonceManager/NonceManager.getIncrementedOutboundNonce.t.sol b/contracts/src/v0.8/ccip/test/NonceManager/NonceManager.getIncrementedOutboundNonce.t.sol new file mode 100644 index 00000000000..1ac25d6c31f --- /dev/null +++ b/contracts/src/v0.8/ccip/test/NonceManager/NonceManager.getIncrementedOutboundNonce.t.sol @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {NonceManager} from "../../NonceManager.sol"; +import {BaseTest} from "../BaseTest.t.sol"; + +contract NonceManager_getIncrementedOutboundNonce is BaseTest { + NonceManager private s_nonceManager; + + function setUp() public override { + address[] memory authorizedCallers = new address[](1); + authorizedCallers[0] = address(this); + s_nonceManager = new NonceManager(authorizedCallers); + } + + function test_getIncrementedOutboundNonce() public { + address sender = address(this); + + assertEq(s_nonceManager.getOutboundNonce(DEST_CHAIN_SELECTOR, sender), 0); + + uint64 outboundNonce = s_nonceManager.getIncrementedOutboundNonce(DEST_CHAIN_SELECTOR, sender); + assertEq(outboundNonce, 1); + } + + function test_incrementInboundNonce() public { + address sender = address(this); + + s_nonceManager.incrementInboundNonce(SOURCE_CHAIN_SELECTOR, 1, abi.encode(sender)); + + assertEq(s_nonceManager.getInboundNonce(SOURCE_CHAIN_SELECTOR, abi.encode(sender)), 1); + } + + function test_incrementInboundNonce_SkippedIncorrectNonce() public { + address sender = address(this); + uint64 expectedNonce = 2; + + vm.expectEmit(); + emit NonceManager.SkippedIncorrectNonce(SOURCE_CHAIN_SELECTOR, expectedNonce, abi.encode(sender)); + + s_nonceManager.incrementInboundNonce(SOURCE_CHAIN_SELECTOR, expectedNonce, abi.encode(sender)); + + assertEq(s_nonceManager.getInboundNonce(SOURCE_CHAIN_SELECTOR, abi.encode(sender)), 0); + } + + function test_incrementNoncesInboundAndOutbound() public { + address sender = address(this); + + assertEq(s_nonceManager.getOutboundNonce(DEST_CHAIN_SELECTOR, sender), 0); + uint64 outboundNonce = s_nonceManager.getIncrementedOutboundNonce(DEST_CHAIN_SELECTOR, sender); + assertEq(outboundNonce, 1); + + // Inbound nonce unchanged + assertEq(s_nonceManager.getInboundNonce(DEST_CHAIN_SELECTOR, abi.encode(sender)), 0); + + s_nonceManager.incrementInboundNonce(DEST_CHAIN_SELECTOR, 1, abi.encode(sender)); + assertEq(s_nonceManager.getInboundNonce(DEST_CHAIN_SELECTOR, abi.encode(sender)), 1); + + // Outbound nonce unchanged + assertEq(s_nonceManager.getOutboundNonce(DEST_CHAIN_SELECTOR, sender), 1); + } +} diff --git a/contracts/src/v0.8/ccip/test/NonceManager/NonceManager.getOutboundNonce.t.sol b/contracts/src/v0.8/ccip/test/NonceManager/NonceManager.getOutboundNonce.t.sol new file mode 100644 index 00000000000..2fe02bfb59d --- /dev/null +++ b/contracts/src/v0.8/ccip/test/NonceManager/NonceManager.getOutboundNonce.t.sol @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {IEVM2AnyOnRamp} from "../../interfaces/IEVM2AnyOnRamp.sol"; + +import {NonceManager} from "../../NonceManager.sol"; +import {Client} from "../../libraries/Client.sol"; +import {OnRamp} from "../../onRamp/OnRamp.sol"; +import {OnRampHelper} from "../helpers/OnRampHelper.sol"; +import {OnRampSetup} from "../onRamp/OnRamp/OnRampSetup.t.sol"; + +contract NonceManager_getOutboundNonce is OnRampSetup { + uint256 internal constant FEE_AMOUNT = 1234567890; + OnRampHelper internal s_prevOnRamp; + + function setUp() public virtual override { + super.setUp(); + + (s_prevOnRamp,) = _deployOnRamp( + SOURCE_CHAIN_SELECTOR, s_sourceRouter, address(s_outboundNonceManager), address(s_tokenAdminRegistry) + ); + + // Since the previous onRamp is not a 1.5 ramp it doesn't have the getSenderNonce function. We mock it to return 0 + vm.mockCall(address(s_prevOnRamp), abi.encodeWithSelector(IEVM2AnyOnRamp.getSenderNonce.selector), abi.encode(0)); + + NonceManager.PreviousRampsArgs[] memory previousRamps = new NonceManager.PreviousRampsArgs[](1); + previousRamps[0] = NonceManager.PreviousRampsArgs({ + remoteChainSelector: DEST_CHAIN_SELECTOR, + prevRamps: NonceManager.PreviousRamps(address(s_prevOnRamp), address(0)), + overrideExistingRamps: false + }); + s_outboundNonceManager.applyPreviousRampsUpdates(previousRamps); + + (s_onRamp, s_metadataHash) = _deployOnRamp( + SOURCE_CHAIN_SELECTOR, s_sourceRouter, address(s_outboundNonceManager), address(s_tokenAdminRegistry) + ); + + vm.startPrank(address(s_sourceRouter)); + } + + function test_getOutboundNonce_Upgrade() public { + Client.EVM2AnyMessage memory message = _generateEmptyMessage(); + + vm.expectEmit(); + emit OnRamp.CCIPMessageSent(DEST_CHAIN_SELECTOR, 1, _messageToEvent(message, 1, 1, FEE_AMOUNT, OWNER)); + + s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, FEE_AMOUNT, OWNER); + } + + function test_getOutboundNonce_UpgradeSenderNoncesReadsPreviousRamp() public { + Client.EVM2AnyMessage memory message = _generateEmptyMessage(); + uint64 startNonce = s_outboundNonceManager.getOutboundNonce(DEST_CHAIN_SELECTOR, OWNER); + uint64 prevRampNextOutboundNonce = IEVM2AnyOnRamp(address(s_prevOnRamp)).getSenderNonce(OWNER); + + assertEq(startNonce, prevRampNextOutboundNonce); + + for (uint64 i = 1; i < 4; ++i) { + s_prevOnRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, 0, OWNER); + + assertEq(startNonce + i, s_outboundNonceManager.getOutboundNonce(DEST_CHAIN_SELECTOR, OWNER)); + } + } + + function test_getOutboundNonce_UpgradeNonceStartsAtV1Nonce() public { + Client.EVM2AnyMessage memory message = _generateEmptyMessage(); + + uint64 startNonce = s_outboundNonceManager.getOutboundNonce(DEST_CHAIN_SELECTOR, OWNER); + + // send 1 message from previous onRamp + s_prevOnRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, FEE_AMOUNT, OWNER); + + assertEq(startNonce + 1, s_outboundNonceManager.getOutboundNonce(DEST_CHAIN_SELECTOR, OWNER)); + + // new onRamp nonce should start from 2, while sequence number start from 1 + vm.expectEmit(); + emit OnRamp.CCIPMessageSent(DEST_CHAIN_SELECTOR, 1, _messageToEvent(message, 1, startNonce + 2, FEE_AMOUNT, OWNER)); + s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, FEE_AMOUNT, OWNER); + + assertEq(startNonce + 2, s_outboundNonceManager.getOutboundNonce(DEST_CHAIN_SELECTOR, OWNER)); + + // after another send, nonce should be 3, and sequence number be 2 + vm.expectEmit(); + emit OnRamp.CCIPMessageSent(DEST_CHAIN_SELECTOR, 2, _messageToEvent(message, 2, startNonce + 3, FEE_AMOUNT, OWNER)); + s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, FEE_AMOUNT, OWNER); + + assertEq(startNonce + 3, s_outboundNonceManager.getOutboundNonce(DEST_CHAIN_SELECTOR, OWNER)); + } + + function test_getOutboundNonce_UpgradeNonceNewSenderStartsAtZero() public { + Client.EVM2AnyMessage memory message = _generateEmptyMessage(); + + // send 1 message from previous onRamp from OWNER + s_prevOnRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, FEE_AMOUNT, OWNER); + + address newSender = address(1234567); + // new onRamp nonce should start from 1 for new sender + vm.expectEmit(); + emit OnRamp.CCIPMessageSent(DEST_CHAIN_SELECTOR, 1, _messageToEvent(message, 1, 1, FEE_AMOUNT, newSender)); + + s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, FEE_AMOUNT, newSender); + } +} diff --git a/contracts/src/v0.8/ccip/test/capability/CCIPHome/CCIPHome.applyChainConfigUpdates.t.sol b/contracts/src/v0.8/ccip/test/capability/CCIPHome/CCIPHome.applyChainConfigUpdates.t.sol index 1d2c3a70895..9bf096b9551 100644 --- a/contracts/src/v0.8/ccip/test/capability/CCIPHome/CCIPHome.applyChainConfigUpdates.t.sol +++ b/contracts/src/v0.8/ccip/test/capability/CCIPHome/CCIPHome.applyChainConfigUpdates.t.sol @@ -109,6 +109,7 @@ contract CCIPHome_applyChainConfigUpdates is CCIPHomeTestSetup { function test_applyChainConfigUpdates_removeChainConfigs_Success() public { bytes32[] memory chainReaders = new bytes32[](1); chainReaders[0] = keccak256(abi.encode(1)); + CCIPHome.ChainConfigArgs[] memory adds = new CCIPHome.ChainConfigArgs[](2); adds[0] = CCIPHome.ChainConfigArgs({ chainSelector: 1, @@ -130,6 +131,7 @@ contract CCIPHome_applyChainConfigUpdates is CCIPHomeTestSetup { workflowDONId: uint32(1), capabilitiesDONIds: new uint256[](0) }); + vm.mockCall( CAPABILITIES_REGISTRY, abi.encodeWithSelector(INodeInfoProvider.getNodesByP2PIds.selector, chainReaders), @@ -140,10 +142,14 @@ contract CCIPHome_applyChainConfigUpdates is CCIPHomeTestSetup { emit CCIPHome.ChainConfigSet(1, adds[0].chainConfig); vm.expectEmit(); emit CCIPHome.ChainConfigSet(2, adds[1].chainConfig); + s_ccipHome.applyChainConfigUpdates(new uint64[](0), adds); assertEq(s_ccipHome.getNumChainConfigurations(), 2, "total chain configs must be 2"); + assertEq(s_ccipHome.getChainConfig(adds[0].chainSelector).config, adds[0].chainConfig.config); + assertEq(s_ccipHome.getChainConfig(adds[1].chainSelector).config, adds[1].chainConfig.config); + uint64[] memory removes = new uint64[](1); removes[0] = uint64(1); diff --git a/contracts/src/v0.8/ccip/test/offRamp/OffRamp/OffRamp.afterOC3ConfigSet.t.sol b/contracts/src/v0.8/ccip/test/offRamp/OffRamp/OffRamp.afterOC3ConfigSet.t.sol index 91694dbcb05..d2edb7a261a 100644 --- a/contracts/src/v0.8/ccip/test/offRamp/OffRamp/OffRamp.afterOC3ConfigSet.t.sol +++ b/contracts/src/v0.8/ccip/test/offRamp/OffRamp/OffRamp.afterOC3ConfigSet.t.sol @@ -12,6 +12,7 @@ contract OffRamp_afterOC3ConfigSet is OffRampSetup { s_offRamp = new OffRampHelper( OffRamp.StaticConfig({ chainSelector: DEST_CHAIN_SELECTOR, + gasForCallExactCheck: GAS_FOR_CALL_EXACT_CHECK, rmnRemote: s_mockRMNRemote, tokenAdminRegistry: address(s_tokenAdminRegistry), nonceManager: address(s_inboundNonceManager) diff --git a/contracts/src/v0.8/ccip/test/offRamp/OffRamp/OffRamp.constructor.t.sol b/contracts/src/v0.8/ccip/test/offRamp/OffRamp/OffRamp.constructor.t.sol index bd7bb94344c..d31b7939d9c 100644 --- a/contracts/src/v0.8/ccip/test/offRamp/OffRamp/OffRamp.constructor.t.sol +++ b/contracts/src/v0.8/ccip/test/offRamp/OffRamp/OffRamp.constructor.t.sol @@ -13,6 +13,7 @@ contract OffRamp_constructor is OffRampSetup { function test_Constructor_Success() public { OffRamp.StaticConfig memory staticConfig = OffRamp.StaticConfig({ chainSelector: DEST_CHAIN_SELECTOR, + gasForCallExactCheck: GAS_FOR_CALL_EXACT_CHECK, rmnRemote: s_mockRMNRemote, tokenAdminRegistry: address(s_tokenAdminRegistry), nonceManager: address(s_inboundNonceManager) @@ -142,6 +143,7 @@ contract OffRamp_constructor is OffRampSetup { s_offRamp = new OffRampHelper( OffRamp.StaticConfig({ chainSelector: DEST_CHAIN_SELECTOR, + gasForCallExactCheck: GAS_FOR_CALL_EXACT_CHECK, rmnRemote: s_mockRMNRemote, tokenAdminRegistry: address(s_tokenAdminRegistry), nonceManager: address(s_inboundNonceManager) @@ -168,6 +170,7 @@ contract OffRamp_constructor is OffRampSetup { s_offRamp = new OffRampHelper( OffRamp.StaticConfig({ chainSelector: DEST_CHAIN_SELECTOR, + gasForCallExactCheck: GAS_FOR_CALL_EXACT_CHECK, rmnRemote: s_mockRMNRemote, tokenAdminRegistry: address(s_tokenAdminRegistry), nonceManager: address(s_inboundNonceManager) @@ -188,6 +191,7 @@ contract OffRamp_constructor is OffRampSetup { s_offRamp = new OffRampHelper( OffRamp.StaticConfig({ chainSelector: DEST_CHAIN_SELECTOR, + gasForCallExactCheck: GAS_FOR_CALL_EXACT_CHECK, rmnRemote: IRMNRemote(address(0)), tokenAdminRegistry: address(s_tokenAdminRegistry), nonceManager: address(s_inboundNonceManager) @@ -208,6 +212,7 @@ contract OffRamp_constructor is OffRampSetup { s_offRamp = new OffRampHelper( OffRamp.StaticConfig({ chainSelector: 0, + gasForCallExactCheck: GAS_FOR_CALL_EXACT_CHECK, rmnRemote: s_mockRMNRemote, tokenAdminRegistry: address(s_tokenAdminRegistry), nonceManager: address(s_inboundNonceManager) @@ -228,6 +233,7 @@ contract OffRamp_constructor is OffRampSetup { s_offRamp = new OffRampHelper( OffRamp.StaticConfig({ chainSelector: DEST_CHAIN_SELECTOR, + gasForCallExactCheck: GAS_FOR_CALL_EXACT_CHECK, rmnRemote: s_mockRMNRemote, tokenAdminRegistry: address(0), nonceManager: address(s_inboundNonceManager) @@ -248,6 +254,7 @@ contract OffRamp_constructor is OffRampSetup { s_offRamp = new OffRampHelper( OffRamp.StaticConfig({ chainSelector: DEST_CHAIN_SELECTOR, + gasForCallExactCheck: GAS_FOR_CALL_EXACT_CHECK, rmnRemote: s_mockRMNRemote, tokenAdminRegistry: address(s_tokenAdminRegistry), nonceManager: address(0) diff --git a/contracts/src/v0.8/ccip/test/offRamp/OffRamp/OffRamp.executeSingleMessage.t.sol b/contracts/src/v0.8/ccip/test/offRamp/OffRamp/OffRamp.executeSingleMessage.t.sol index 727a7c63bb1..20b711f529e 100644 --- a/contracts/src/v0.8/ccip/test/offRamp/OffRamp/OffRamp.executeSingleMessage.t.sol +++ b/contracts/src/v0.8/ccip/test/offRamp/OffRamp/OffRamp.executeSingleMessage.t.sol @@ -36,7 +36,7 @@ contract OffRamp_executeSingleMessage is OffRampSetup { abi.encodeWithSelector( IRouter.routeMessage.selector, expectedAny2EvmMessage, - Internal.GAS_FOR_CALL_EXACT_CHECK, + GAS_FOR_CALL_EXACT_CHECK, message.gasLimit, message.receiver ) diff --git a/contracts/src/v0.8/ccip/test/offRamp/OffRamp/OffRampSetup.t.sol b/contracts/src/v0.8/ccip/test/offRamp/OffRamp/OffRampSetup.t.sol index 858ee9e4a45..f8b70ebf283 100644 --- a/contracts/src/v0.8/ccip/test/offRamp/OffRamp/OffRampSetup.t.sol +++ b/contracts/src/v0.8/ccip/test/offRamp/OffRamp/OffRampSetup.t.sol @@ -70,6 +70,7 @@ contract OffRampSetup is FeeQuoterSetup, MultiOCR3BaseSetup { OffRamp.StaticConfig({ chainSelector: DEST_CHAIN_SELECTOR, rmnRemote: rmnRemote, + gasForCallExactCheck: GAS_FOR_CALL_EXACT_CHECK, tokenAdminRegistry: address(s_tokenAdminRegistry), nonceManager: address(nonceManager) }), @@ -348,6 +349,7 @@ contract OffRampSetup is FeeQuoterSetup, MultiOCR3BaseSetup { s_offRamp = new OffRampHelper( OffRamp.StaticConfig({ chainSelector: DEST_CHAIN_SELECTOR, + gasForCallExactCheck: GAS_FOR_CALL_EXACT_CHECK, rmnRemote: s_mockRMNRemote, tokenAdminRegistry: address(s_tokenAdminRegistry), nonceManager: address(s_inboundNonceManager) diff --git a/contracts/src/v0.8/ccip/test/router/Router/Router.applyRampUpdates.t.sol b/contracts/src/v0.8/ccip/test/router/Router/Router.applyRampUpdates.t.sol index 9b46741f96d..be0999542ea 100644 --- a/contracts/src/v0.8/ccip/test/router/Router/Router.applyRampUpdates.t.sol +++ b/contracts/src/v0.8/ccip/test/router/Router/Router.applyRampUpdates.t.sol @@ -38,7 +38,7 @@ contract Router_applyRampUpdates is RouterSetup { ); } - function testFuzz_OffRampUpdates( + function testFuzz_applyRampUpdates_OffRampUpdates( address[20] memory offRampsInput ) public { Router.OffRamp[] memory offRamps = new Router.OffRamp[](20); @@ -72,7 +72,7 @@ contract Router_applyRampUpdates is RouterSetup { } } - function test_OffRampUpdatesWithRouting() public { + function test_applyRampUpdates_OffRampUpdatesWithRouting() public { // Explicitly construct chain selectors and ramp addresses so we have ramp uniqueness for the various test scenarios. uint256 numberOfSelectors = 10; uint64[] memory sourceChainSelectors = new uint64[](numberOfSelectors); @@ -219,7 +219,7 @@ contract Router_applyRampUpdates is RouterSetup { } } - function testFuzz_OnRampUpdates( + function testFuzz_applyRampUpdates_OnRampUpdates( Router.OnRamp[] memory onRamps ) public { // Test adding onRamps @@ -244,7 +244,7 @@ contract Router_applyRampUpdates is RouterSetup { } } - function test_OnRampDisable() public { + function test_applyRampUpdates_OnRampDisable() public { // Add onRamp Router.OnRamp[] memory onRampUpdates = new Router.OnRamp[](1); Router.OffRamp[] memory offRampUpdates = new Router.OffRamp[](0); @@ -267,7 +267,7 @@ contract Router_applyRampUpdates is RouterSetup { assertTrue(s_sourceRouter.isChainSupported(DEST_CHAIN_SELECTOR)); } - function test_OnlyOwner_Revert() public { + function test_applyRampUpdates_RevertWhen_OnlyOwner() public { vm.stopPrank(); vm.expectRevert("Only callable by owner"); Router.OnRamp[] memory onRampUpdates = new Router.OnRamp[](0); @@ -275,7 +275,7 @@ contract Router_applyRampUpdates is RouterSetup { s_sourceRouter.applyRampUpdates(onRampUpdates, offRampUpdates, offRampUpdates); } - function test_OffRampMismatch_Revert() public { + function test_applyRampUpdates_RevertWhen_OffRampMismatch() public { address offRamp = address(uint160(2)); Router.OnRamp[] memory onRampUpdates = new Router.OnRamp[](0); diff --git a/contracts/src/v0.8/shared/token/ERC20/BurnMintERC20.sol b/contracts/src/v0.8/shared/token/ERC20/BurnMintERC20.sol index 946a6623b49..ea11dc08798 100644 --- a/contracts/src/v0.8/shared/token/ERC20/BurnMintERC20.sol +++ b/contracts/src/v0.8/shared/token/ERC20/BurnMintERC20.sol @@ -13,7 +13,6 @@ import {IERC165} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/ut /// @notice A basic ERC20 compatible token contract with burn and minting roles. /// @dev The total supply can be limited during deployment. -/// @dev This contract has not been audited and is not yet approved for production use. contract BurnMintERC20 is IBurnMintERC20, IGetCCIPAdmin, IERC165, ERC20Burnable, AccessControl { error MaxSupplyExceeded(uint256 supplyAfterMint); error InvalidRecipient(address recipient); @@ -153,7 +152,7 @@ contract BurnMintERC20 is IBurnMintERC20, IGetCCIPAdmin, IERC165, ERC20Burnable, /// @dev only the owner can call this function, NOT the current ccipAdmin, and 1-step ownership transfer is used. /// @param newAdmin The address to transfer the CCIPAdmin role to. Setting to address(0) is a valid way to revoke /// the role - function setCCIPAdmin(address newAdmin) public onlyRole(DEFAULT_ADMIN_ROLE) { + function setCCIPAdmin(address newAdmin) external onlyRole(DEFAULT_ADMIN_ROLE) { address currentAdmin = s_ccipAdmin; s_ccipAdmin = newAdmin; diff --git a/contracts/src/v0.8/workflow/dev/WorkflowRegistry.sol b/contracts/src/v0.8/workflow/dev/WorkflowRegistry.sol index 0e6ae3450ac..2454374b2fb 100644 --- a/contracts/src/v0.8/workflow/dev/WorkflowRegistry.sol +++ b/contracts/src/v0.8/workflow/dev/WorkflowRegistry.sol @@ -43,6 +43,8 @@ contract WorkflowRegistry is Ownable2StepMsgSender, ITypeAndVersion { /// @dev Mapping to track workflows by secretsURL hash (owner + secretsURL). /// This is used to find all workflows that have the same secretsURL when a force secrets update event is requested. mapping(bytes32 secretsURLHash => EnumerableSet.Bytes32Set workflowKeys) private s_secretsHashToWorkflows; + /// @dev Keep track of all workflowIDs to ensure uniqueness. + mapping(bytes32 workflowID => bool inUse) private s_workflowIDs; /// @dev List of all authorized EOAs/contracts allowed to access this contract's state functions. All view functions are open access. EnumerableSet.AddressSet private s_authorizedAddresses; @@ -203,13 +205,15 @@ contract WorkflowRegistry is Ownable2StepMsgSender, ITypeAndVersion { ) external registryNotLocked { _validatePermissions(donID, msg.sender); _validateWorkflowName(bytes(workflowName).length); - _validateWorkflowMetadata(workflowID, bytes(binaryURL).length, bytes(configURL).length, bytes(secretsURL).length); + _validateWorkflowURLs(bytes(binaryURL).length, bytes(configURL).length, bytes(secretsURL).length); bytes32 workflowKey = computeHashKey(msg.sender, workflowName); if (s_workflows[workflowKey].owner != address(0)) { revert WorkflowAlreadyRegistered(); } + _requireUniqueWorkflowID(workflowID); + // Create new workflow entry s_workflows[workflowKey] = WorkflowMetadata({ workflowID: workflowID, @@ -272,7 +276,7 @@ contract WorkflowRegistry is Ownable2StepMsgSender, ITypeAndVersion { string calldata configURL, string calldata secretsURL ) external registryNotLocked { - _validateWorkflowMetadata(newWorkflowID, bytes(binaryURL).length, bytes(configURL).length, bytes(secretsURL).length); + _validateWorkflowURLs(bytes(binaryURL).length, bytes(configURL).length, bytes(secretsURL).length); WorkflowMetadata storage workflow = _getWorkflowFromStorage(msg.sender, workflowKey); @@ -295,6 +299,12 @@ contract WorkflowRegistry is Ownable2StepMsgSender, ITypeAndVersion { revert WorkflowContentNotUpdated(); } + // Ensure the new workflowID is unique + _requireUniqueWorkflowID(newWorkflowID); + + // Free the old workflowID + s_workflowIDs[currentWorkflowID] = false; + // Update all fields that have changed and the relevant sets workflow.workflowID = newWorkflowID; if (!sameBinaryURL) { @@ -387,6 +397,9 @@ contract WorkflowRegistry is Ownable2StepMsgSender, ITypeAndVersion { revert AddressNotAuthorized(msg.sender); } + // Release the workflowID for reuse + s_workflowIDs[workflow.workflowID] = false; + // Remove the workflow from the owner and DON mappings s_ownerWorkflowKeys[msg.sender].remove(workflowKey); s_donWorkflowKeys[workflow.donID].remove(workflowKey); @@ -508,6 +521,20 @@ contract WorkflowRegistry is Ownable2StepMsgSender, ITypeAndVersion { return workflow; } + /// @notice Ensures the given workflowID is unique and marks it as used. + /// @param workflowID The workflowID to validate and consume. + function _requireUniqueWorkflowID( + bytes32 workflowID + ) internal { + if (workflowID == bytes32(0)) revert InvalidWorkflowID(); + + if (s_workflowIDs[workflowID]) { + revert WorkflowIDAlreadyExists(); + } + + s_workflowIDs[workflowID] = true; + } + // ================================================================ // | Workflow Queries | // ================================================================ @@ -636,16 +663,12 @@ contract WorkflowRegistry is Ownable2StepMsgSender, ITypeAndVersion { // | Validation | // ================================================================ - /// @dev Internal function to validate the metadata for a workflow. - /// @param workflowID The unique identifier for the workflow. - function _validateWorkflowMetadata( - bytes32 workflowID, + /// @dev Internal function to validate the urls for a workflow. + function _validateWorkflowURLs( uint256 binaryURLLength, uint256 configURLLength, uint256 secretsURLLength ) internal pure { - if (workflowID == bytes32(0)) revert InvalidWorkflowID(); - if (binaryURLLength > MAX_URL_LENGTH) { revert URLTooLong(binaryURLLength, MAX_URL_LENGTH); } diff --git a/contracts/src/v0.8/workflow/test/WorkflowRegistry/WorkflowRegistry.registerWorkflow.t.sol b/contracts/src/v0.8/workflow/test/WorkflowRegistry/WorkflowRegistry.registerWorkflow.t.sol index 426fbfcc502..859437196cd 100644 --- a/contracts/src/v0.8/workflow/test/WorkflowRegistry/WorkflowRegistry.registerWorkflow.t.sol +++ b/contracts/src/v0.8/workflow/test/WorkflowRegistry/WorkflowRegistry.registerWorkflow.t.sol @@ -138,6 +138,35 @@ contract WorkflowRegistry_registerWorkflow is WorkflowRegistrySetup { ); } + // whenTheCallerIsAnAuthorizedAddress whenTheRegistryIsNotLocked whenTheDonIDIsAllowed + function test_RevertWhen_TheWorkflowIDIsAlreadyInUsedByAnotherWorkflow() external { + vm.startPrank(s_authorizedAddress); + + // Register a valid workflow first + s_registry.registerWorkflow( + s_validWorkflowName, + s_validWorkflowID, + s_allowedDonID, + WorkflowRegistry.WorkflowStatus.ACTIVE, + s_validBinaryURL, + s_validConfigURL, + s_validSecretsURL + ); + + vm.expectRevert(WorkflowRegistry.WorkflowIDAlreadyExists.selector); + s_registry.registerWorkflow( + "ValidWorkflow2", + s_validWorkflowID, + s_allowedDonID, + WorkflowRegistry.WorkflowStatus.ACTIVE, + s_validBinaryURL, + s_validConfigURL, + s_validSecretsURL + ); + + vm.stopPrank(); + } + // whenTheCallerIsAnAuthorizedAddress whenTheRegistryIsNotLocked whenTheDonIDIsAllowed function test_RevertWhen_TheWorkflowNameIsAlreadyUsedByTheOwner() external { vm.startPrank(s_authorizedAddress); diff --git a/contracts/src/v0.8/workflow/test/WorkflowRegistry/WorkflowRegistry.registerWorkflow.tree b/contracts/src/v0.8/workflow/test/WorkflowRegistry/WorkflowRegistry.registerWorkflow.tree index 75cdf940575..eabbf58d464 100644 --- a/contracts/src/v0.8/workflow/test/WorkflowRegistry/WorkflowRegistry.registerWorkflow.tree +++ b/contracts/src/v0.8/workflow/test/WorkflowRegistry/WorkflowRegistry.registerWorkflow.tree @@ -18,6 +18,8 @@ WorkflowRegistry.registerWorkflow │ └── it should revert ├── when the workflowID is invalid │ └── it should revert + ├── when the workflowID is already in used by another workflow + │ └── it should revert ├── when the workflow name is already used by the owner │ └── it should revert └── when the workflow inputs are all valid diff --git a/contracts/src/v0.8/workflow/test/WorkflowRegistry/WorkflowRegistry.updateWorkflow.t.sol b/contracts/src/v0.8/workflow/test/WorkflowRegistry/WorkflowRegistry.updateWorkflow.t.sol index 5058512ba7b..4082874a91e 100644 --- a/contracts/src/v0.8/workflow/test/WorkflowRegistry/WorkflowRegistry.updateWorkflow.t.sol +++ b/contracts/src/v0.8/workflow/test/WorkflowRegistry/WorkflowRegistry.updateWorkflow.t.sol @@ -158,6 +158,32 @@ contract WorkflowRegistry_updateWorkflow is WorkflowRegistrySetup { s_registry.updateWorkflow(s_validWorkflowKey, bytes32(0), s_validBinaryURL, s_validConfigURL, s_newValidSecretsURL); } + // whenTheCallerIsAnAuthorizedAddress whenTheRegistryIsNotLocked whenTheDonIDIsAllowed whenTheCallerIsTheWorkflowOwner + function test_RevertWhen_TheWorkflowIDIsAlreadyInUsedByAnotherWorkflow() external { + // Register a workflow first + _registerValidWorkflow(); + + // Register another workflow with another workflow ID + vm.startPrank(s_authorizedAddress); + s_registry.registerWorkflow( + "ValidWorkflow2", + s_newValidWorkflowID, + s_allowedDonID, + WorkflowRegistry.WorkflowStatus.ACTIVE, + s_validBinaryURL, + s_validConfigURL, + s_validSecretsURL + ); + + // Update the workflow with a workflow ID that is already in use by another workflow. + vm.expectRevert(WorkflowRegistry.WorkflowIDAlreadyExists.selector); + s_registry.updateWorkflow( + s_validWorkflowKey, s_newValidWorkflowID, s_validBinaryURL, s_validConfigURL, s_newValidSecretsURL + ); + + vm.stopPrank(); + } + // whenTheCallerIsAnAuthorizedAddress whenTheRegistryIsNotLocked whenTheDonIDIsAllowed whenTheCallerIsTheWorkflowOwner function test_WhenTheWorkflowInputsAreAllValid() external { // Register a workflow first. diff --git a/contracts/src/v0.8/workflow/test/WorkflowRegistry/WorkflowRegistry.updateWorkflow.tree b/contracts/src/v0.8/workflow/test/WorkflowRegistry/WorkflowRegistry.updateWorkflow.tree index 0d4da7cb32e..9b8243a8672 100644 --- a/contracts/src/v0.8/workflow/test/WorkflowRegistry/WorkflowRegistry.updateWorkflow.tree +++ b/contracts/src/v0.8/workflow/test/WorkflowRegistry/WorkflowRegistry.updateWorkflow.tree @@ -25,6 +25,8 @@ WorkflowRegistry.updateWorkflow │ └── it should revert ├── when the workflowID is invalid │ └── it should revert + ├── when the workflowID is already in used by another workflow + │ └── it should revert └── when the workflow inputs are all valid ├── it should update the existing workflow in s_workflows with the new values ├── it should emit {WorkflowUpdatedV1} diff --git a/core/capabilities/ccip/ccip_integration_tests/integrationhelpers/integration_helpers.go b/core/capabilities/ccip/ccip_integration_tests/integrationhelpers/integration_helpers.go index 13d2b8f4d5c..ea52438edad 100644 --- a/core/capabilities/ccip/ccip_integration_tests/integrationhelpers/integration_helpers.go +++ b/core/capabilities/ccip/ccip_integration_tests/integrationhelpers/integration_helpers.go @@ -8,6 +8,7 @@ import ( "fmt" "math/big" "sort" + "strconv" "testing" "time" @@ -137,7 +138,7 @@ func NewTestUniverse(ctx context.Context, t *testing.T, lggr logger.Logger) Test t.Cleanup(func() { require.NoError(t, lp.Close()) }) cr := NewReader(t, lp, headTracker, cl, ccAddress, configsevm.HomeChainReaderConfigRaw) - hcr := NewHomeChainReader(t, cr, ccAddress) + hcr := NewHomeChainReader(t, cr, ccAddress, strconv.Itoa(chainID)) return TestUniverse{ Transactor: transactor, Backend: backend, @@ -237,11 +238,22 @@ func (t *TestUniverse) AddCapability(p2pIDs [][32]byte) { } } -func NewHomeChainReader(t *testing.T, cr types.ContractReader, ccAddress common.Address) ccipreader.HomeChain { - hcr := ccipreader.NewHomeChainReader(cr, logger.TestLogger(t), 50*time.Millisecond, types.BoundContract{ - Address: ccAddress.String(), - Name: consts.ContractNameCCIPConfig, - }) +func NewHomeChainReader( + t *testing.T, + cr types.ContractReader, + ccAddress common.Address, + chainID string, +) ccipreader.HomeChain { + hcr := ccipreader.NewObservedHomeChainReader( + cr, + logger.TestLogger(t), + 50*time.Millisecond, + types.BoundContract{ + Address: ccAddress.String(), + Name: consts.ContractNameCCIPConfig, + }, + chainID, + ) require.NoError(t, hcr.Start(testutils.Context(t))) t.Cleanup(func() { require.NoError(t, hcr.Close()) }) diff --git a/core/capabilities/ccip/configs/evm/contract_reader.go b/core/capabilities/ccip/configs/evm/contract_reader.go index f4942943ec4..dca590094f8 100644 --- a/core/capabilities/ccip/configs/evm/contract_reader.go +++ b/core/capabilities/ccip/configs/evm/contract_reader.go @@ -89,6 +89,10 @@ var DestReaderConfig = evmrelaytypes.ChainReaderConfig{ ChainSpecificName: mustGetMethodName("getAllSourceChainConfigs", offrampABI), ReadType: evmrelaytypes.Method, }, + consts.MethodNameOffRampLatestConfigDetails: { + ChainSpecificName: mustGetMethodName("latestConfigDetails", offrampABI), + ReadType: evmrelaytypes.Method, + }, consts.EventNameCommitReportAccepted: { ChainSpecificName: mustGetEventName(consts.EventNameCommitReportAccepted, offrampABI), ReadType: evmrelaytypes.Event, diff --git a/core/capabilities/ccip/delegate.go b/core/capabilities/ccip/delegate.go index 09e3769627e..1af4f44cd4a 100644 --- a/core/capabilities/ccip/delegate.go +++ b/core/capabilities/ccip/delegate.go @@ -167,11 +167,12 @@ func (d *Delegate) ServicesForSpec(ctx context.Context, spec job.Job) (services return nil, fmt.Errorf("failed to get home chain contract reader: %w", err) } - hcr := ccipreaderpkg.NewHomeChainReader( + hcr := ccipreaderpkg.NewObservedHomeChainReader( homeChainContractReader, d.lggr.Named("HomeChainReader"), 100*time.Millisecond, ccipConfigBinding, + d.capabilityConfig.ExternalRegistry().ChainID(), ) // get the chain selector for the home chain diff --git a/core/capabilities/compute/cache.go b/core/capabilities/compute/cache.go index 7b7cd78aaab..dbcc42c1606 100644 --- a/core/capabilities/compute/cache.go +++ b/core/capabilities/compute/cache.go @@ -38,8 +38,9 @@ type moduleCache struct { timeout time.Duration evictAfterSize int - clock clockwork.Clock - onReaper chan struct{} + clock clockwork.Clock + reapTicker <-chan time.Time + onReaper chan struct{} } func newModuleCache(clock clockwork.Clock, tick, timeout time.Duration, evictAfterSize int) *moduleCache { @@ -49,6 +50,7 @@ func newModuleCache(clock clockwork.Clock, tick, timeout time.Duration, evictAft timeout: timeout, evictAfterSize: evictAfterSize, clock: clock, + reapTicker: clock.NewTicker(tick).Chan(), stopChan: make(chan struct{}), } } @@ -67,10 +69,9 @@ func (mc *moduleCache) close() { } func (mc *moduleCache) reapLoop() { - ticker := mc.clock.NewTicker(mc.tickInterval) for { select { - case <-ticker.Chan(): + case <-mc.reapTicker: mc.evictOlderThan(mc.timeout) if mc.onReaper != nil { mc.onReaper <- struct{}{} @@ -84,7 +85,7 @@ func (mc *moduleCache) reapLoop() { func (mc *moduleCache) add(id string, mod *module) { mc.mu.Lock() defer mc.mu.Unlock() - mod.lastFetchedAt = time.Now() + mod.lastFetchedAt = mc.clock.Now() mc.m[id] = mod moduleCacheAddition.Inc() } diff --git a/core/capabilities/compute/cache_test.go b/core/capabilities/compute/cache_test.go index 3b38cc23001..ad075f493b5 100644 --- a/core/capabilities/compute/cache_test.go +++ b/core/capabilities/compute/cache_test.go @@ -20,14 +20,17 @@ const ( binaryCmd = "core/capabilities/compute/test/simple/cmd" ) +// Verify that cache evicts an expired module. func TestCache(t *testing.T) { t.Parallel() clock := clockwork.NewFakeClock() tick := 1 * time.Second timeout := 1 * time.Second + reapTicker := make(chan time.Time) cache := newModuleCache(clock, tick, timeout, 0) cache.onReaper = make(chan struct{}, 1) + cache.reapTicker = reapTicker cache.start() defer cache.close() @@ -50,20 +53,24 @@ func TestCache(t *testing.T) { assert.Equal(t, got, mod) clock.Advance(15 * time.Second) + reapTicker <- time.Now() <-cache.onReaper _, ok = cache.get(id) assert.False(t, ok) } +// Verify that an expired module is not evicted because evictAfterSize is 1 func TestCache_EvictAfterSize(t *testing.T) { t.Parallel() ctx := tests.Context(t) clock := clockwork.NewFakeClock() tick := 1 * time.Second timeout := 1 * time.Second + reapTicker := make(chan time.Time) cache := newModuleCache(clock, tick, timeout, 1) cache.onReaper = make(chan struct{}, 1) + cache.reapTicker = reapTicker cache.start() defer cache.close() @@ -79,6 +86,7 @@ func TestCache_EvictAfterSize(t *testing.T) { module: hmod, } cache.add(id, mod) + assert.Len(t, cache.m, 1) got, ok := cache.get(id) assert.True(t, ok) @@ -86,6 +94,7 @@ func TestCache_EvictAfterSize(t *testing.T) { assert.Equal(t, got, mod) clock.Advance(15 * time.Second) + reapTicker <- time.Now() select { case <-ctx.Done(): return diff --git a/core/capabilities/compute/compute.go b/core/capabilities/compute/compute.go index 316e4f00eea..2ba5daefaa6 100644 --- a/core/capabilities/compute/compute.go +++ b/core/capabilities/compute/compute.go @@ -318,18 +318,13 @@ func (c *Compute) createFetcher() func(ctx context.Context, req *wasmpb.FetchReq headersReq[k] = v.String() } - payloadBytes, err := json.Marshal(ghcapabilities.Request{ + resp, err := c.outgoingConnectorHandler.HandleSingleNodeRequest(ctx, messageID, ghcapabilities.Request{ URL: req.Url, Method: req.Method, Headers: headersReq, Body: req.Body, TimeoutMs: req.TimeoutMs, }) - if err != nil { - return nil, fmt.Errorf("failed to marshal fetch request: %w", err) - } - - resp, err := c.outgoingConnectorHandler.HandleSingleNodeRequest(ctx, messageID, payloadBytes) if err != nil { return nil, err } diff --git a/core/capabilities/compute/compute_test.go b/core/capabilities/compute/compute_test.go index c4146b7408e..3e5f501fa61 100644 --- a/core/capabilities/compute/compute_test.go +++ b/core/capabilities/compute/compute_test.go @@ -14,6 +14,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/capabilities" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/wasmtest" "github.com/smartcontractkit/chainlink/v2/core/logger" + "github.com/smartcontractkit/chainlink/v2/core/utils/matches" cappkg "github.com/smartcontractkit/chainlink-common/pkg/capabilities" "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" @@ -188,6 +189,7 @@ func TestComputeFetch(t *testing.T) { th := setup(t, defaultConfig) th.connector.EXPECT().DonID().Return("don-id") + th.connector.EXPECT().AwaitConnection(matches.AnyContext, "gateway1").Return(nil) th.connector.EXPECT().GatewayIDs().Return([]string{"gateway1", "gateway2"}) msgID := strings.Join([]string{ diff --git a/core/capabilities/launcher.go b/core/capabilities/launcher.go index 27c43fe0a53..98318853e2a 100644 --- a/core/capabilities/launcher.go +++ b/core/capabilities/launcher.go @@ -398,7 +398,8 @@ func (w *launcher) addToRegistryAndSetDispatcher(ctx context.Context, capability } var ( - defaultTargetRequestTimeout = time.Minute + // TODO: make this configurable + defaultTargetRequestTimeout = 8 * time.Minute ) func (w *launcher) exposeCapabilities(ctx context.Context, myPeerID p2ptypes.PeerID, don registrysyncer.DON, state *registrysyncer.LocalRegistry, remoteWorkflowDONs []registrysyncer.DON) error { diff --git a/core/capabilities/remote/executable/client.go b/core/capabilities/remote/executable/client.go index 9af32eb5f8e..776ddb692ad 100644 --- a/core/capabilities/remote/executable/client.go +++ b/core/capabilities/remote/executable/client.go @@ -41,6 +41,8 @@ var _ commoncap.ExecutableCapability = &client{} var _ types.Receiver = &client{} var _ services.Service = &client{} +const expiryCheckInterval = 30 * time.Second + func NewClient(remoteCapabilityInfo commoncap.CapabilityInfo, localDonInfo commoncap.DON, dispatcher types.Dispatcher, requestTimeout time.Duration, lggr logger.Logger) *client { return &client{ @@ -98,7 +100,11 @@ func (c *client) checkDispatcherReady() { } func (c *client) checkForExpiredRequests() { - ticker := time.NewTicker(c.requestTimeout) + tickerInterval := expiryCheckInterval + if c.requestTimeout < tickerInterval { + tickerInterval = c.requestTimeout + } + ticker := time.NewTicker(tickerInterval) defer ticker.Stop() for { select { @@ -116,7 +122,7 @@ func (c *client) expireRequests() { for messageID, req := range c.requestIDToCallerRequest { if req.Expired() { - req.Cancel(errors.New("request expired")) + req.Cancel(errors.New("request expired by executable client")) delete(c.requestIDToCallerRequest, messageID) } diff --git a/core/capabilities/remote/executable/client_test.go b/core/capabilities/remote/executable/client_test.go index 5c4da350b9e..f4e6add82b0 100644 --- a/core/capabilities/remote/executable/client_test.go +++ b/core/capabilities/remote/executable/client_test.go @@ -12,7 +12,9 @@ import ( commoncap "github.com/smartcontractkit/chainlink-common/pkg/capabilities" "github.com/smartcontractkit/chainlink-common/pkg/capabilities/pb" "github.com/smartcontractkit/chainlink-common/pkg/services/servicetest" + "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" "github.com/smartcontractkit/chainlink-common/pkg/values" + "github.com/smartcontractkit/chainlink/v2/core/capabilities/remote/executable" remotetypes "github.com/smartcontractkit/chainlink/v2/core/capabilities/remote/types" "github.com/smartcontractkit/chainlink/v2/core/capabilities/transmission" @@ -29,6 +31,7 @@ const ( ) func Test_Client_DonTopologies(t *testing.T) { + tests.SkipFlakey(t, "https://smartcontract-it.atlassian.net/browse/CAPPL-363") ctx := testutils.Context(t) transmissionSchedule, err := values.NewMap(map[string]any{ @@ -87,6 +90,7 @@ func Test_Client_DonTopologies(t *testing.T) { } func Test_Client_TransmissionSchedules(t *testing.T) { + tests.SkipFlakey(t, "https://smartcontract-it.atlassian.net/browse/CAPPL-363") ctx := testutils.Context(t) responseTest := func(t *testing.T, response commoncap.CapabilityResponse, responseError error) { diff --git a/core/capabilities/remote/executable/endtoend_test.go b/core/capabilities/remote/executable/endtoend_test.go index 4e78fead87e..5f445db4235 100644 --- a/core/capabilities/remote/executable/endtoend_test.go +++ b/core/capabilities/remote/executable/endtoend_test.go @@ -78,7 +78,7 @@ func Test_RemoteExecutionCapability_CapabilityError(t *testing.T) { methods = append(methods, func(ctx context.Context, caller commoncap.ExecutableCapability) { executeCapability(ctx, t, caller, transmissionSchedule, func(t *testing.T, responseCh commoncap.CapabilityResponse, responseError error) { - assert.Equal(t, "error executing request: failed to execute capability: an error", responseError.Error()) + assert.Equal(t, "error executing request: failed to execute capability", responseError.Error()) }) }) @@ -102,12 +102,12 @@ func Test_RemoteExecutableCapability_RandomCapabilityError(t *testing.T) { methods = append(methods, func(ctx context.Context, caller commoncap.ExecutableCapability) { executeCapability(ctx, t, caller, transmissionSchedule, func(t *testing.T, responseCh commoncap.CapabilityResponse, responseError error) { - assert.Equal(t, "error executing request: request expired", responseError.Error()) + assert.Equal(t, "error executing request: failed to execute capability", responseError.Error()) }) }) for _, method := range methods { - testRemoteExecutableCapability(ctx, t, capability, 10, 9, 10*time.Millisecond, 10, 9, 10*time.Minute, + testRemoteExecutableCapability(ctx, t, capability, 10, 9, 1*time.Second, 10, 9, 10*time.Minute, method) } } diff --git a/core/capabilities/remote/executable/request/server_request.go b/core/capabilities/remote/executable/request/server_request.go index a4662e93987..629622494a4 100644 --- a/core/capabilities/remote/executable/request/server_request.go +++ b/core/capabilities/remote/executable/request/server_request.go @@ -2,6 +2,7 @@ package request import ( "context" + "errors" "fmt" "sync" "time" @@ -48,6 +49,8 @@ type ServerRequest struct { lggr logger.Logger } +var errExternalErrorMsg = errors.New("failed to execute capability") + func NewServerRequest(capability capabilities.ExecutableCapability, method string, capabilityID string, capabilityDonID uint32, capabilityPeerID p2ptypes.PeerID, callingDon commoncap.DON, requestID string, @@ -228,20 +231,22 @@ func executeCapabilityRequest(ctx context.Context, lggr logger.Logger, capabilit payload []byte) ([]byte, error) { capabilityRequest, err := pb.UnmarshalCapabilityRequest(payload) if err != nil { - return nil, fmt.Errorf("failed to unmarshal capability request: %w", err) + lggr.Errorw("failed to unmarshal capability request", "err", err) + return nil, errExternalErrorMsg } lggr.Debugw("executing capability", "metadata", capabilityRequest.Metadata) capResponse, err := capability.Execute(ctx, capabilityRequest) if err != nil { - lggr.Debugw("received execution error", "workflowExecutionID", capabilityRequest.Metadata.WorkflowExecutionID, "error", err) - return nil, fmt.Errorf("failed to execute capability: %w", err) + lggr.Errorw("received execution error", "workflowExecutionID", capabilityRequest.Metadata.WorkflowExecutionID, "error", err) + return nil, errExternalErrorMsg } responsePayload, err := pb.MarshalCapabilityResponse(capResponse) if err != nil { - return nil, fmt.Errorf("failed to marshal capability response: %w", err) + lggr.Errorw("failed to marshal capability request", "err", err) + return nil, errExternalErrorMsg } lggr.Debugw("received execution results", "workflowExecutionID", capabilityRequest.Metadata.WorkflowExecutionID) diff --git a/core/capabilities/remote/executable/request/server_request_test.go b/core/capabilities/remote/executable/request/server_request_test.go index cbeec833a1f..ce539154d93 100644 --- a/core/capabilities/remote/executable/request/server_request_test.go +++ b/core/capabilities/remote/executable/request/server_request_test.go @@ -136,9 +136,9 @@ func Test_ServerRequest_MessageValidation(t *testing.T) { require.NoError(t, err) assert.Len(t, dispatcher.msgs, 2) assert.Equal(t, types.Error_INTERNAL_ERROR, dispatcher.msgs[0].Error) - assert.Equal(t, "failed to execute capability: an error", dispatcher.msgs[0].ErrorMsg) + assert.Equal(t, "failed to execute capability", dispatcher.msgs[0].ErrorMsg) assert.Equal(t, types.Error_INTERNAL_ERROR, dispatcher.msgs[1].Error) - assert.Equal(t, "failed to execute capability: an error", dispatcher.msgs[1].ErrorMsg) + assert.Equal(t, "failed to execute capability", dispatcher.msgs[1].ErrorMsg) }) t.Run("Execute capability", func(t *testing.T) { diff --git a/core/capabilities/remote/executable/server.go b/core/capabilities/remote/executable/server.go index b767a2d7030..d43c7ab5c41 100644 --- a/core/capabilities/remote/executable/server.go +++ b/core/capabilities/remote/executable/server.go @@ -87,7 +87,11 @@ func (r *server) Start(ctx context.Context) error { r.wg.Add(1) go func() { defer r.wg.Done() - ticker := time.NewTicker(r.requestTimeout) + tickerInterval := expiryCheckInterval + if r.requestTimeout < tickerInterval { + tickerInterval = r.requestTimeout + } + ticker := time.NewTicker(tickerInterval) defer ticker.Stop() r.lggr.Info("executable capability server started") for { @@ -118,7 +122,7 @@ func (r *server) expireRequests() { for requestID, executeReq := range r.requestIDToRequest { if executeReq.request.Expired() { - err := executeReq.request.Cancel(types.Error_TIMEOUT, "request expired") + err := executeReq.request.Cancel(types.Error_TIMEOUT, "request expired by executable server") if err != nil { r.lggr.Errorw("failed to cancel request", "request", executeReq, "err", err) } diff --git a/core/capabilities/triggers/logevent/trigger.go b/core/capabilities/triggers/logevent/trigger.go index 7ee76c6f44a..334c3c3f30e 100644 --- a/core/capabilities/triggers/logevent/trigger.go +++ b/core/capabilities/triggers/logevent/trigger.go @@ -67,6 +67,11 @@ func newLogEventTrigger(ctx context.Context, return nil, nil, err } + err = contractReader.Start(ctx) + if err != nil { + return nil, nil, err + } + // Get current block HEAD/tip of the blockchain to start polling from latestHead, err := relayer.LatestHead(ctx) if err != nil { diff --git a/core/capabilities/webapi/outgoing_connector_handler.go b/core/capabilities/webapi/outgoing_connector_handler.go index d18ee971d1a..a9ff9ee3aae 100644 --- a/core/capabilities/webapi/outgoing_connector_handler.go +++ b/core/capabilities/webapi/outgoing_connector_handler.go @@ -6,6 +6,7 @@ import ( "fmt" "sort" "sync" + "time" "github.com/pkg/errors" @@ -17,6 +18,10 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers/common" ) +const ( + defaultFetchTimeoutMs = 20_000 +) + var _ connector.GatewayConnectorHandler = &OutgoingConnectorHandler{} type OutgoingConnectorHandler struct { @@ -51,8 +56,24 @@ func NewOutgoingConnectorHandler(gc connector.GatewayConnector, config ServiceCo } // HandleSingleNodeRequest sends a request to first available gateway node and blocks until response is received -// TODO: handle retries and timeouts -func (c *OutgoingConnectorHandler) HandleSingleNodeRequest(ctx context.Context, messageID string, payload []byte) (*api.Message, error) { +// TODO: handle retries +func (c *OutgoingConnectorHandler) HandleSingleNodeRequest(ctx context.Context, messageID string, req capabilities.Request) (*api.Message, error) { + // set default timeout if not provided for all outgoing requests + if req.TimeoutMs == 0 { + req.TimeoutMs = defaultFetchTimeoutMs + } + + // Create a subcontext with the timeout plus some margin for the gateway to process the request + timeoutDuration := time.Duration(req.TimeoutMs) * time.Millisecond + margin := 100 * time.Millisecond + ctx, cancel := context.WithTimeout(ctx, timeoutDuration+margin) + defer cancel() + + payload, err := json.Marshal(req) + if err != nil { + return nil, fmt.Errorf("failed to marshal fetch request: %w", err) + } + ch := make(chan *api.Message, 1) c.responseChsMu.Lock() c.responseChs[messageID] = ch @@ -75,8 +96,15 @@ func (c *OutgoingConnectorHandler) HandleSingleNodeRequest(ctx context.Context, } sort.Strings(gatewayIDs) - err := c.gc.SignAndSendToGateway(ctx, gatewayIDs[0], body) - if err != nil { + selectedGateway := gatewayIDs[0] + + l.Infow("selected gateway, awaiting connection", "gatewayID", selectedGateway) + + if err := c.gc.AwaitConnection(ctx, selectedGateway); err != nil { + return nil, errors.Wrap(err, "await connection canceled") + } + + if err := c.gc.SignAndSendToGateway(ctx, selectedGateway, body); err != nil { return nil, errors.Wrap(err, "failed to send request to gateway") } diff --git a/core/capabilities/webapi/outgoing_connector_handler_test.go b/core/capabilities/webapi/outgoing_connector_handler_test.go new file mode 100644 index 00000000000..4a8c425d4f1 --- /dev/null +++ b/core/capabilities/webapi/outgoing_connector_handler_test.go @@ -0,0 +1,135 @@ +package webapi + +import ( + "context" + "encoding/json" + "testing" + + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" + "github.com/smartcontractkit/chainlink/v2/core/logger" + "github.com/smartcontractkit/chainlink/v2/core/utils/matches" + + "github.com/smartcontractkit/chainlink/v2/core/services/gateway/api" + gcmocks "github.com/smartcontractkit/chainlink/v2/core/services/gateway/connector/mocks" + ghcapabilities "github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers/capabilities" + "github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers/common" +) + +func TestHandleSingleNodeRequest(t *testing.T) { + t.Run("OK-timeout_is_not_specify_default_timeout_is_expected", func(t *testing.T) { + ctx := tests.Context(t) + log := logger.TestLogger(t) + connector := gcmocks.NewGatewayConnector(t) + var defaultConfig = ServiceConfig{ + RateLimiter: common.RateLimiterConfig{ + GlobalRPS: 100.0, + GlobalBurst: 100, + PerSenderRPS: 100.0, + PerSenderBurst: 100, + }, + } + connectorHandler, err := NewOutgoingConnectorHandler(connector, defaultConfig, ghcapabilities.MethodComputeAction, log) + require.NoError(t, err) + + msgID := "msgID" + testURL := "http://localhost:8080" + connector.EXPECT().DonID().Return("donID") + connector.EXPECT().AwaitConnection(matches.AnyContext, "gateway1").Return(nil) + connector.EXPECT().GatewayIDs().Return([]string{"gateway1"}) + + // build the expected body with the default timeout + req := ghcapabilities.Request{ + URL: testURL, + TimeoutMs: defaultFetchTimeoutMs, + } + payload, err := json.Marshal(req) + require.NoError(t, err) + + expectedBody := &api.MessageBody{ + MessageId: msgID, + DonId: connector.DonID(), + Method: ghcapabilities.MethodComputeAction, + Payload: payload, + } + + // expect the request body to contain the default timeout + connector.EXPECT().SignAndSendToGateway(mock.Anything, "gateway1", expectedBody).Run(func(ctx context.Context, gatewayID string, msg *api.MessageBody) { + connectorHandler.HandleGatewayMessage(ctx, "gateway1", gatewayResponse(t, msgID)) + }).Return(nil).Times(1) + + _, err = connectorHandler.HandleSingleNodeRequest(ctx, msgID, ghcapabilities.Request{ + URL: testURL, + }) + require.NoError(t, err) + }) + + t.Run("OK-timeout_is_specified", func(t *testing.T) { + ctx := tests.Context(t) + log := logger.TestLogger(t) + connector := gcmocks.NewGatewayConnector(t) + var defaultConfig = ServiceConfig{ + RateLimiter: common.RateLimiterConfig{ + GlobalRPS: 100.0, + GlobalBurst: 100, + PerSenderRPS: 100.0, + PerSenderBurst: 100, + }, + } + connectorHandler, err := NewOutgoingConnectorHandler(connector, defaultConfig, ghcapabilities.MethodComputeAction, log) + require.NoError(t, err) + + msgID := "msgID" + testURL := "http://localhost:8080" + connector.EXPECT().DonID().Return("donID") + connector.EXPECT().AwaitConnection(matches.AnyContext, "gateway1").Return(nil) + connector.EXPECT().GatewayIDs().Return([]string{"gateway1"}) + + // build the expected body with the defined timeout + req := ghcapabilities.Request{ + URL: testURL, + TimeoutMs: 40000, + } + payload, err := json.Marshal(req) + require.NoError(t, err) + + expectedBody := &api.MessageBody{ + MessageId: msgID, + DonId: connector.DonID(), + Method: ghcapabilities.MethodComputeAction, + Payload: payload, + } + + // expect the request body to contain the defined timeout + connector.EXPECT().SignAndSendToGateway(mock.Anything, "gateway1", expectedBody).Run(func(ctx context.Context, gatewayID string, msg *api.MessageBody) { + connectorHandler.HandleGatewayMessage(ctx, "gateway1", gatewayResponse(t, msgID)) + }).Return(nil).Times(1) + + _, err = connectorHandler.HandleSingleNodeRequest(ctx, msgID, ghcapabilities.Request{ + URL: testURL, + TimeoutMs: 40000, + }) + require.NoError(t, err) + }) +} + +func gatewayResponse(t *testing.T, msgID string) *api.Message { + headers := map[string]string{"Content-Type": "application/json"} + body := []byte("response body") + responsePayload, err := json.Marshal(ghcapabilities.Response{ + StatusCode: 200, + Headers: headers, + Body: body, + ExecutionError: false, + }) + require.NoError(t, err) + return &api.Message{ + Body: api.MessageBody{ + MessageId: msgID, + Method: ghcapabilities.MethodWebAPITarget, + Payload: responsePayload, + }, + } +} diff --git a/core/capabilities/webapi/target/target.go b/core/capabilities/webapi/target/target.go index b211e0fe837..4934ab382d5 100644 --- a/core/capabilities/webapi/target/target.go +++ b/core/capabilities/webapi/target/target.go @@ -135,18 +135,13 @@ func (c *Capability) Execute(ctx context.Context, req capabilities.CapabilityReq return capabilities.CapabilityResponse{}, err } - payloadBytes, err := json.Marshal(payload) - if err != nil { - return capabilities.CapabilityResponse{}, err - } - // Default to SingleNode delivery mode deliveryMode := defaultIfNil(workflowCfg.DeliveryMode, webapi.SingleNode) switch deliveryMode { case webapi.SingleNode: // blocking call to handle single node request. waits for response from gateway - resp, err := c.connectorHandler.HandleSingleNodeRequest(ctx, messageID, payloadBytes) + resp, err := c.connectorHandler.HandleSingleNodeRequest(ctx, messageID, payload) if err != nil { return capabilities.CapabilityResponse{}, err } diff --git a/core/capabilities/webapi/target/target_test.go b/core/capabilities/webapi/target/target_test.go index f51cdcd0d70..1af9a107054 100644 --- a/core/capabilities/webapi/target/target_test.go +++ b/core/capabilities/webapi/target/target_test.go @@ -194,7 +194,7 @@ func TestCapability_Execute(t *testing.T) { require.NoError(t, err) gatewayResp := gatewayResponse(t, msgID) - + th.connector.EXPECT().AwaitConnection(mock.Anything, "gateway1").Return(nil) th.connector.On("SignAndSendToGateway", mock.Anything, "gateway1", mock.Anything).Return(nil).Run(func(args mock.Arguments) { th.connectorHandler.HandleGatewayMessage(ctx, "gateway1", gatewayResp) }).Once() diff --git a/core/chains/evm/client/errors.go b/core/chains/evm/client/errors.go index d47d97660b1..bde97185580 100644 --- a/core/chains/evm/client/errors.go +++ b/core/chains/evm/client/errors.go @@ -64,6 +64,7 @@ const ( ServiceUnavailable TerminallyStuck TooManyResults + ServiceTimeout ) type ClientErrors map[int]*regexp.Regexp @@ -160,7 +161,8 @@ var arbitrum = ClientErrors{ Fatal: arbitrumFatal, L2FeeTooLow: regexp.MustCompile(`(: |^)max fee per gas less than block base fee(:|$)`), L2Full: regexp.MustCompile(`(: |^)(queue full|sequencer pending tx pool full, please try again)(:|$)`), - ServiceUnavailable: regexp.MustCompile(`(: |^)502 Bad Gateway: [\s\S]*$|network is unreachable|i/o timeout`), + ServiceUnavailable: regexp.MustCompile(`(: |^)502 Bad Gateway: [\s\S]*$|network is unreachable|i/o timeout|(: |^)503 Service Temporarily Unavailable(:|$)`), + ServiceTimeout: regexp.MustCompile(`(: |^)408 Request Timeout(:|$)`), } // Treasure @@ -240,7 +242,7 @@ var harmony = ClientErrors{ var zkSync = ClientErrors{ NonceTooLow: regexp.MustCompile(`(?:: |^)nonce too low\..+actual: \d*$`), NonceTooHigh: regexp.MustCompile(`(?:: |^)nonce too high\..+actual: \d*$`), - TerminallyUnderpriced: regexp.MustCompile(`(?:: |^)(max fee per gas less than block base fee|virtual machine entered unexpected state. please contact developers and provide transaction details that caused this error. Error description: The operator included transaction with an unacceptable gas price)$`), + TerminallyUnderpriced: regexp.MustCompile(`(?:: |^)(max fee per gas less than block base fee|virtual machine entered unexpected state. (?:P|p)lease contact developers and provide transaction details that caused this error. Error description: (?:The operator included transaction with an unacceptable gas price|Assertion error: Fair pubdata price too high))$`), InsufficientEth: regexp.MustCompile(`(?:: |^)(?:insufficient balance for transfer$|insufficient funds for gas + value)`), TxFeeExceedsCap: regexp.MustCompile(`(?:: |^)max priority fee per gas higher than max fee per gas$`), // intrinsic gas too low - gas limit less than 14700 @@ -398,6 +400,11 @@ func (s *SendError) IsServiceUnavailable(configErrors *ClientErrors) bool { return s.is(ServiceUnavailable, configErrors) || pkgerrors.Is(s.err, commonclient.ErroringNodeError) } +// IsServiceTimeout indicates if the error was caused by a service timeout +func (s *SendError) IsServiceTimeout(configErrors *ClientErrors) bool { + return s.is(ServiceTimeout, configErrors) +} + // IsTerminallyStuck indicates if a transaction was stuck without any chance of inclusion func (s *SendError) IsTerminallyStuckConfigError(configErrors *ClientErrors) bool { return s.is(TerminallyStuck, configErrors) @@ -619,6 +626,10 @@ func ClassifySendError(err error, clientErrors config.ClientErrors, lggr logger. lggr.Errorw(fmt.Sprintf("service unavailable while sending transaction %x", tx.Hash()), "err", sendError, "etx", tx) return commonclient.Retryable } + if sendError.IsServiceTimeout(configErrors) { + lggr.Errorw(fmt.Sprintf("service timed out while sending transaction %x", tx.Hash()), "err", sendError, "etx", tx) + return commonclient.Retryable + } if sendError.IsTimeout() { lggr.Errorw(fmt.Sprintf("timeout while sending transaction %x", tx.Hash()), "err", sendError, "etx", tx) return commonclient.Retryable @@ -666,7 +677,7 @@ var drpc = ClientErrors{ // Linkpool, Blockdaemon, and Chainstack all return "request timed out" if the log results are too large for them to process var defaultClient = ClientErrors{ - TooManyResults: regexp.MustCompile(`request timed out`), + TooManyResults: regexp.MustCompile(`request timed out|408 Request Timed Out`), } // JSON-RPC error codes which can indicate a refusal of the server to process an eth_getLogs request because the result set is too large diff --git a/core/chains/evm/client/errors_test.go b/core/chains/evm/client/errors_test.go index 7bdc87840d0..1f9aaa53365 100644 --- a/core/chains/evm/client/errors_test.go +++ b/core/chains/evm/client/errors_test.go @@ -172,6 +172,7 @@ func Test_Eth_Errors(t *testing.T) { {"intrinsic gas too low", true, "Klaytn"}, {"max fee per gas less than block base fee", true, "zkSync"}, {"virtual machine entered unexpected state. please contact developers and provide transaction details that caused this error. Error description: The operator included transaction with an unacceptable gas price", true, "zkSync"}, + {"failed to validate the transaction. reason: Validation revert: virtual machine entered unexpected state. Please contact developers and provide transaction details that caused this error. Error description: Assertion error: Fair pubdata price too high", true, "zkSync"}, {"client error terminally underpriced", true, "tomlConfig"}, {"gas price less than block base fee", true, "aStar"}, {"[Request ID: e4d09e44-19a4-4eb7-babe-270db4c2ebc9] Gas price '830000000000' is below configured minimum gas price '950000000000'", true, "hedera"}, @@ -244,6 +245,7 @@ func Test_Eth_Errors(t *testing.T) { {"network is unreachable", true, "Arbitrum"}, {"client error service unavailable", true, "tomlConfig"}, {"[Request ID: 825608a8-fd8a-4b5b-aea7-92999509306d] Error invoking RPC: [Request ID: 825608a8-fd8a-4b5b-aea7-92999509306d] Transaction execution returns a null value for transaction", true, "hedera"}, + {"call failed: 503 Service Temporarily Unavailable: \r\n503 Service Temporarily Unavailable\r\n\r\n

503 Service Temporarily Unavailable

\r\n\r\n\r\n", true, "Arbitrum"}, } for _, test := range tests { err = evmclient.NewSendErrorS(test.message) @@ -259,6 +261,20 @@ func Test_Eth_Errors(t *testing.T) { } }) + t.Run("IsServiceTimeout", func(t *testing.T) { + tests := []errorCase{ + {"call failed: 408 Request Timeout: {", true, "Arbitrum"}, + {"408 Request Timeout: {\"id\":303,\"jsonrpc\":\"2.0\",\"error\":{\"code\\\":-32009,\\\"message\\\":\\\"request timeout\\\"}}\",\"errVerbose\":\"408 Request Timeout:\n", true, "Arbitrum"}, + {"request timeout", false, "tomlConfig"}, + } + for _, test := range tests { + err = evmclient.NewSendErrorS(test.message) + assert.Equal(t, err.IsServiceTimeout(clientErrors), test.expect) + err = newSendErrorWrapped(test.message) + assert.Equal(t, err.IsServiceTimeout(clientErrors), test.expect) + } + }) + t.Run("IsTxFeeExceedsCap", func(t *testing.T) { tests := []errorCase{ {"tx fee (1.10 ether) exceeds the configured cap (1.00 ether)", true, "geth"}, diff --git a/core/chains/evm/config/toml/defaults/Ronin_Mainnet.toml b/core/chains/evm/config/toml/defaults/Ronin_Mainnet.toml new file mode 100644 index 00000000000..051c528e0f3 --- /dev/null +++ b/core/chains/evm/config/toml/defaults/Ronin_Mainnet.toml @@ -0,0 +1,11 @@ +ChainID = '2020' +FinalityTagEnabled = true +# Ronin produces blocks every 3 seconds +LogPollInterval = "3s" +NoNewHeadsThreshold = "3m" +LinkContractAddress = '0x3902228D6A3d2Dc44731fD9d45FeE6a61c722D0b' + +[GasEstimator] +# Ronin uses default gas price of 20 gwei https://docs.skymavis.com/mavis/mpc/guides/estimate-gas#overview +Mode = 'FeeHistory' +PriceMax = "1000 gwei" diff --git a/core/chains/evm/config/toml/defaults/Ronin_Saigon.toml b/core/chains/evm/config/toml/defaults/Ronin_Saigon.toml new file mode 100644 index 00000000000..48695a9ec08 --- /dev/null +++ b/core/chains/evm/config/toml/defaults/Ronin_Saigon.toml @@ -0,0 +1,11 @@ +ChainID = '2021' +FinalityTagEnabled = true +# Ronin produces blocks every 3 seconds +LogPollInterval = "3s" +NoNewHeadsThreshold = "3m" +LinkContractAddress = '0x5bB50A6888ee6a67E22afFDFD9513be7740F1c15' + +[GasEstimator] +# Ronin uses default gas price of 20 gwei https://docs.skymavis.com/mavis/mpc/guides/estimate-gas#overview +Mode = 'FeeHistory' +PriceMax = "1000 gwei" diff --git a/core/chains/evm/gas/models.go b/core/chains/evm/gas/models.go index 6cb89818c8f..2d6fe971d9c 100644 --- a/core/chains/evm/gas/models.go +++ b/core/chains/evm/gas/models.go @@ -9,6 +9,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/rpc" pkgerrors "github.com/pkg/errors" + "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink-common/pkg/services" bigmath "github.com/smartcontractkit/chainlink-common/pkg/utils/big_math" diff --git a/core/chains/evm/gas/rollups/l1_oracle.go b/core/chains/evm/gas/rollups/l1_oracle.go index ceecb80c608..d9f12dfa79e 100644 --- a/core/chains/evm/gas/rollups/l1_oracle.go +++ b/core/chains/evm/gas/rollups/l1_oracle.go @@ -10,6 +10,7 @@ import ( "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/rpc" + "github.com/smartcontractkit/chainlink-common/pkg/services" "github.com/smartcontractkit/chainlink-common/pkg/logger" diff --git a/core/chains/evm/logpoller/helper_test.go b/core/chains/evm/logpoller/helper_test.go index 947a839521c..b8d849d7d83 100644 --- a/core/chains/evm/logpoller/helper_test.go +++ b/core/chains/evm/logpoller/helper_test.go @@ -118,11 +118,19 @@ func SetupTH(t testing.TB, opts logpoller.Opts) TestHarness { opts.PollPeriod = 1 * time.Hour } lp := logpoller.NewLogPoller(o, esc, lggr, headTracker, opts) + + pendingNonce, err := backend.Client().PendingNonceAt(testutils.Context(t), owner.From) + require.NoError(t, err) + + owner.Nonce = big.NewInt(0).SetUint64(pendingNonce) emitterAddress1, _, emitter1, err := log_emitter.DeployLogEmitter(owner, backend.Client()) require.NoError(t, err) + + owner.Nonce.Add(owner.Nonce, big.NewInt(1)) // Avoid race where DeployLogEmitter returns before PendingNonce has been incremented emitterAddress2, _, emitter2, err := log_emitter.DeployLogEmitter(owner, backend.Client()) require.NoError(t, err) backend.Commit() + owner.Nonce = nil // Just use pending nonce after this return TestHarness{ Lggr: lggr, diff --git a/core/chains/evm/logpoller/log_poller.go b/core/chains/evm/logpoller/log_poller.go index 6ef4fefecee..725fdbda63c 100644 --- a/core/chains/evm/logpoller/log_poller.go +++ b/core/chains/evm/logpoller/log_poller.go @@ -23,6 +23,8 @@ import ( pkgerrors "github.com/pkg/errors" "golang.org/x/exp/maps" + commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" + "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink-common/pkg/services" "github.com/smartcontractkit/chainlink-common/pkg/timeutil" @@ -91,6 +93,7 @@ type Client interface { } type HeadTracker interface { + services.Service LatestAndFinalizedBlock(ctx context.Context) (latest, finalized *evmtypes.Head, err error) } @@ -99,7 +102,6 @@ var ( ErrReplayRequestAborted = pkgerrors.New("aborted, replay request cancelled") ErrReplayInProgress = pkgerrors.New("replay request cancelled, but replay is already in progress") ErrLogPollerShutdown = pkgerrors.New("replay aborted due to log poller shutdown") - ErrFinalityViolated = pkgerrors.New("finality violated") ) type logPoller struct { @@ -525,7 +527,7 @@ func (lp *logPoller) Close() error { func (lp *logPoller) Healthy() error { if lp.finalityViolated.Load() { - return ErrFinalityViolated + return commontypes.ErrFinalityViolated } return nil } diff --git a/core/chains/evm/logpoller/log_poller_test.go b/core/chains/evm/logpoller/log_poller_test.go index 7114960efdd..df688cd5e5c 100644 --- a/core/chains/evm/logpoller/log_poller_test.go +++ b/core/chains/evm/logpoller/log_poller_test.go @@ -22,10 +22,13 @@ import ( "github.com/stretchr/testify/require" "go.uber.org/zap/zapcore" + commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" + "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink-common/pkg/types/query" "github.com/smartcontractkit/chainlink-common/pkg/types/query/primitives" commonutils "github.com/smartcontractkit/chainlink-common/pkg/utils" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/chaintype" htMocks "github.com/smartcontractkit/chainlink/v2/common/headtracker/mocks" @@ -1106,7 +1109,8 @@ func TestLogPoller_ReorgDeeperThanFinality(t *testing.T) { secondPoll := th.PollAndSaveLogs(testutils.Context(t), firstPoll) assert.Equal(t, firstPoll, secondPoll) - assert.Equal(t, logpoller.ErrFinalityViolated, th.LogPoller.Healthy()) + require.Equal(t, commontypes.ErrFinalityViolated, th.LogPoller.Healthy()) + require.Equal(t, commontypes.ErrFinalityViolated, th.LogPoller.HealthReport()[th.LogPoller.Name()]) // Manually remove re-org'd chain from the log poller to bring it back to life // LogPoller should be healthy again after first poll @@ -1116,7 +1120,8 @@ func TestLogPoller_ReorgDeeperThanFinality(t *testing.T) { // Poll from latest recoveryPoll := th.PollAndSaveLogs(testutils.Context(t), 1) assert.Equal(t, int64(35), recoveryPoll) - assert.NoError(t, th.LogPoller.Healthy()) + require.NoError(t, th.LogPoller.Healthy()) + require.NoError(t, th.LogPoller.HealthReport()[th.LogPoller.Name()]) }) } } diff --git a/core/cmd/app.go b/core/cmd/app.go index ad944f0d0a6..8128d578238 100644 --- a/core/cmd/app.go +++ b/core/cmd/app.go @@ -290,25 +290,14 @@ func NewApp(s *Shell) *cli.App { }, }, { - Name: "chains", - Usage: "Commands for handling chain configuration", - Subcommands: cli.Commands{ - chainCommand("EVM", EVMChainClient(s), cli.Int64Flag{Name: "id", Usage: "chain ID"}), - chainCommand("Cosmos", CosmosChainClient(s), cli.StringFlag{Name: "id", Usage: "chain ID"}), - chainCommand("Solana", SolanaChainClient(s), - cli.StringFlag{Name: "id", Usage: "chain ID, options: [mainnet, testnet, devnet, localnet]"}), - chainCommand("StarkNet", StarkNetChainClient(s), cli.StringFlag{Name: "id", Usage: "chain ID"}), - }, + Name: "chains", + Usage: "Commands for handling chain configuration", + Subcommands: initChainSubCmds(s), }, { - Name: "nodes", - Usage: "Commands for handling node configuration", - Subcommands: cli.Commands{ - initEVMNodeSubCmd(s), - initCosmosNodeSubCmd(s), - initSolanaNodeSubCmd(s), - initStarkNetNodeSubCmd(s), - }, + Name: "nodes", + Usage: "Commands for handling node configuration", + Subcommands: initNodeSubCmds(s), }, { Name: "forwarders", diff --git a/core/cmd/chains_commands.go b/core/cmd/chains_commands.go index 6edb5afc5ba..ffbff8ffdd3 100644 --- a/core/cmd/chains_commands.go +++ b/core/cmd/chains_commands.go @@ -2,9 +2,15 @@ package cmd import ( "fmt" + "maps" + "slices" + "strconv" "strings" "github.com/urfave/cli" + + "github.com/smartcontractkit/chainlink/v2/core/services/relay" + "github.com/smartcontractkit/chainlink/v2/core/web/presenters" ) var chainHeaders = []string{"ID", "Enabled", "Config"} @@ -39,12 +45,12 @@ type chainClient[P TableRenderer] struct { path string } -// newChainClient returns a new ChainClient for a particular type of chains.Config. +// NewChainClient returns a new ChainClient for a particular type of chains.Config. // P is a TableRenderer corresponding to R, and P2 is the slice variant (type P2 []P). -func newChainClient[P TableRenderer](s *Shell, name string) ChainClient { - return &chainClient[P]{ +func NewChainClient(s *Shell, network string) ChainClient { + return &chainClient[ChainPresenters]{ Shell: s, - path: "/v2/chains/" + name, + path: "/v2/chains/" + network, } } @@ -53,3 +59,51 @@ func (cli *chainClient[P]) IndexChains(c *cli.Context) (err error) { var p P return cli.getPage(cli.path, c.Int("page"), &p) } + +// ChainPresenter implements TableRenderer for a ChainResource +type ChainPresenter struct { + presenters.ChainResource +} + +// ToRow presents the ChainResource as a slice of strings. +func (p *ChainPresenter) ToRow() []string { + return []string{p.GetID(), strconv.FormatBool(p.Enabled), p.Config} +} + +// RenderTable implements TableRenderer +// Just renders a single row +func (p ChainPresenter) RenderTable(rt RendererTable) error { + rows := [][]string{} + rows = append(rows, p.ToRow()) + + renderList(chainHeaders, rows, rt.Writer) + + return nil +} + +// ChainPresenters implements TableRenderer for a slice of ChainPresenters. +type ChainPresenters []ChainPresenter + +// RenderTable implements TableRenderer +func (ps ChainPresenters) RenderTable(rt RendererTable) error { + rows := [][]string{} + + for _, p := range ps { + rows = append(rows, p.ToRow()) + } + + renderList(chainHeaders, rows, rt.Writer) + + return nil +} + +func initChainSubCmds(s *Shell) []cli.Command { + cmds := []cli.Command{} + for _, network := range slices.Sorted(maps.Keys(relay.SupportedNetworks)) { + if network == relay.NetworkDummy { + continue + } + cmds = append(cmds, chainCommand(network, NewChainClient(s, network), cli.StringFlag{Name: "id", Usage: "chain ID"})) + } + return cmds +} diff --git a/core/cmd/chains_commands_test.go b/core/cmd/chains_commands_test.go new file mode 100644 index 00000000000..d2c8a2e4744 --- /dev/null +++ b/core/cmd/chains_commands_test.go @@ -0,0 +1,81 @@ +package cmd_test + +import ( + "strconv" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + coscfg "github.com/smartcontractkit/chainlink-cosmos/pkg/cosmos/config" + solcfg "github.com/smartcontractkit/chainlink-solana/pkg/solana/config" + + client2 "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils/big" + "github.com/smartcontractkit/chainlink/v2/core/cmd" + "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/cosmostest" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/solanatest" + "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" +) + +func TestShell_IndexCosmosChains(t *testing.T) { + t.Parallel() + + chainID := cosmostest.RandomChainID() + chain := coscfg.TOMLConfig{ + ChainID: ptr(chainID), + Enabled: ptr(true), + } + app := cosmosStartNewApplication(t, &chain) + client, r := app.NewShellAndRenderer() + + require.NoError(t, cmd.NewChainClient(client, "cosmos").IndexChains(cltest.EmptyCLIContext())) + chains := *r.Renders[0].(*cmd.ChainPresenters) + require.Len(t, chains, 1) + c := chains[0] + assert.Equal(t, chainID, c.ID) + assertTableRenders(t, r) +} + +func newRandChainID() *big.Big { + return big.New(testutils.NewRandomEVMChainID()) +} + +func TestShell_IndexEVMChains(t *testing.T) { + t.Parallel() + + app := startNewApplicationV2(t, func(c *chainlink.Config, s *chainlink.Secrets) { + c.EVM[0].Enabled = ptr(true) + c.EVM[0].NonceAutoSync = ptr(false) + c.EVM[0].BalanceMonitor.Enabled = ptr(false) + }) + client, r := app.NewShellAndRenderer() + + require.NoError(t, cmd.NewChainClient(client, "evm").IndexChains(cltest.EmptyCLIContext())) + chains := *r.Renders[0].(*cmd.ChainPresenters) + require.Len(t, chains, 1) + c := chains[0] + assert.Equal(t, strconv.Itoa(client2.NullClientChainID), c.ID) + assertTableRenders(t, r) +} + +func TestShell_IndexSolanaChains(t *testing.T) { + t.Parallel() + + id := solanatest.RandomChainID() + cfg := solcfg.TOMLConfig{ + ChainID: &id, + Enabled: ptr(true), + } + app := solanaStartNewApplication(t, &cfg) + client, r := app.NewShellAndRenderer() + + require.NoError(t, cmd.NewChainClient(client, "solana").IndexChains(cltest.EmptyCLIContext())) + chains := *r.Renders[0].(*cmd.ChainPresenters) + require.Len(t, chains, 1) + c := chains[0] + assert.Equal(t, id, c.ID) + assertTableRenders(t, r) +} diff --git a/core/cmd/cosmos_chains_commands.go b/core/cmd/cosmos_chains_commands.go deleted file mode 100644 index d58b1baa159..00000000000 --- a/core/cmd/cosmos_chains_commands.go +++ /dev/null @@ -1,48 +0,0 @@ -package cmd - -import ( - "strconv" - - "github.com/smartcontractkit/chainlink/v2/core/web/presenters" -) - -// CosmosChainPresenter implements TableRenderer for a CosmosChainResource -type CosmosChainPresenter struct { - presenters.CosmosChainResource -} - -// ToRow presents the CosmosChainResource as a slice of strings. -func (p *CosmosChainPresenter) ToRow() []string { - return []string{p.GetID(), strconv.FormatBool(p.Enabled), p.Config} -} - -// RenderTable implements TableRenderer -// Just renders a single row -func (p CosmosChainPresenter) RenderTable(rt RendererTable) error { - rows := [][]string{} - rows = append(rows, p.ToRow()) - - renderList(chainHeaders, rows, rt.Writer) - - return nil -} - -// CosmosChainPresenters implements TableRenderer for a slice of CosmosChainPresenters. -type CosmosChainPresenters []CosmosChainPresenter - -// RenderTable implements TableRenderer -func (ps CosmosChainPresenters) RenderTable(rt RendererTable) error { - rows := [][]string{} - - for _, p := range ps { - rows = append(rows, p.ToRow()) - } - - renderList(chainHeaders, rows, rt.Writer) - - return nil -} - -func CosmosChainClient(s *Shell) ChainClient { - return newChainClient[CosmosChainPresenters](s, "cosmos") -} diff --git a/core/cmd/cosmos_chains_commands_test.go b/core/cmd/cosmos_chains_commands_test.go deleted file mode 100644 index a0d2052d836..00000000000 --- a/core/cmd/cosmos_chains_commands_test.go +++ /dev/null @@ -1,33 +0,0 @@ -package cmd_test - -import ( - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - coscfg "github.com/smartcontractkit/chainlink-cosmos/pkg/cosmos/config" - - "github.com/smartcontractkit/chainlink/v2/core/cmd" - "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" - "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/cosmostest" -) - -func TestShell_IndexCosmosChains(t *testing.T) { - t.Parallel() - - chainID := cosmostest.RandomChainID() - chain := coscfg.TOMLConfig{ - ChainID: ptr(chainID), - Enabled: ptr(true), - } - app := cosmosStartNewApplication(t, &chain) - client, r := app.NewShellAndRenderer() - - require.Nil(t, cmd.CosmosChainClient(client).IndexChains(cltest.EmptyCLIContext())) - chains := *r.Renders[0].(*cmd.CosmosChainPresenters) - require.Len(t, chains, 1) - c := chains[0] - assert.Equal(t, chainID, c.ID) - assertTableRenders(t, r) -} diff --git a/core/cmd/cosmos_node_commands.go b/core/cmd/cosmos_node_commands.go deleted file mode 100644 index 760691d9379..00000000000 --- a/core/cmd/cosmos_node_commands.go +++ /dev/null @@ -1,44 +0,0 @@ -package cmd - -import ( - "github.com/smartcontractkit/chainlink/v2/core/web/presenters" -) - -// CosmosNodePresenter implements TableRenderer for a CosmosNodeResource. -type CosmosNodePresenter struct { - presenters.CosmosNodeResource -} - -// ToRow presents the CosmosNodeResource as a slice of strings. -func (p *CosmosNodePresenter) ToRow() []string { - return []string{p.Name, p.ChainID, p.State, p.Config} -} - -// RenderTable implements TableRenderer -func (p CosmosNodePresenter) RenderTable(rt RendererTable) error { - var rows [][]string - rows = append(rows, p.ToRow()) - renderList(nodeHeaders, rows, rt.Writer) - - return nil -} - -// CosmosNodePresenters implements TableRenderer for a slice of CosmosNodePresenter. -type CosmosNodePresenters []CosmosNodePresenter - -// RenderTable implements TableRenderer -func (ps CosmosNodePresenters) RenderTable(rt RendererTable) error { - var rows [][]string - - for _, p := range ps { - rows = append(rows, p.ToRow()) - } - - renderList(nodeHeaders, rows, rt.Writer) - - return nil -} - -func NewCosmosNodeClient(s *Shell) NodeClient { - return newNodeClient[CosmosNodePresenters](s, "cosmos") -} diff --git a/core/cmd/cosmos_node_commands_test.go b/core/cmd/cosmos_node_commands_test.go deleted file mode 100644 index f6fa367440c..00000000000 --- a/core/cmd/cosmos_node_commands_test.go +++ /dev/null @@ -1,71 +0,0 @@ -package cmd_test - -import ( - "bytes" - "strings" - "testing" - - "github.com/pelletier/go-toml/v2" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/smartcontractkit/chainlink-common/pkg/config" - coscfg "github.com/smartcontractkit/chainlink-cosmos/pkg/cosmos/config" - - "github.com/smartcontractkit/chainlink/v2/core/cmd" - "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" - "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/cosmostest" - "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" -) - -func cosmosStartNewApplication(t *testing.T, cfgs ...*coscfg.TOMLConfig) *cltest.TestApplication { - for i := range cfgs { - cfgs[i].SetDefaults() - } - return startNewApplicationV2(t, func(c *chainlink.Config, s *chainlink.Secrets) { - c.Cosmos = cfgs - c.EVM = nil - }) -} - -func TestShell_IndexCosmosNodes(t *testing.T) { - t.Parallel() - - chainID := cosmostest.RandomChainID() - node := coscfg.Node{ - Name: ptr("second"), - TendermintURL: config.MustParseURL("http://tender.mint.test/bombay-12"), - } - chain := coscfg.TOMLConfig{ - ChainID: ptr(chainID), - Enabled: ptr(true), - Nodes: coscfg.Nodes{&node}, - } - app := cosmosStartNewApplication(t, &chain) - client, r := app.NewShellAndRenderer() - require.Nil(t, cmd.NewCosmosNodeClient(client).IndexNodes(cltest.EmptyCLIContext())) - require.NotEmpty(t, r.Renders) - nodes := *r.Renders[0].(*cmd.CosmosNodePresenters) - require.Len(t, nodes, 1) - n := nodes[0] - assert.Equal(t, cltest.FormatWithPrefixedChainID(chainID, *node.Name), n.ID) - assert.Equal(t, chainID, n.ChainID) - assert.Equal(t, *node.Name, n.Name) - wantConfig, err := toml.Marshal(node) - require.NoError(t, err) - assert.Equal(t, string(wantConfig), n.Config) - assertTableRenders(t, r) - - // Render table and check the fields order - b := new(bytes.Buffer) - rt := cmd.RendererTable{b} - require.NoError(t, nodes.RenderTable(rt)) - renderLines := strings.Split(b.String(), "\n") - assert.Equal(t, 10, len(renderLines)) - assert.Contains(t, renderLines[2], "Name") - assert.Contains(t, renderLines[2], n.Name) - assert.Contains(t, renderLines[3], "Chain ID") - assert.Contains(t, renderLines[3], n.ChainID) - assert.Contains(t, renderLines[4], "State") - assert.Contains(t, renderLines[4], n.State) -} diff --git a/core/cmd/evm_chains_commands.go b/core/cmd/evm_chains_commands.go deleted file mode 100644 index d4025cfca53..00000000000 --- a/core/cmd/evm_chains_commands.go +++ /dev/null @@ -1,48 +0,0 @@ -package cmd - -import ( - "strconv" - - "github.com/smartcontractkit/chainlink/v2/core/web/presenters" -) - -// EVMChainPresenter implements TableRenderer for an EVMChainResource. -type EVMChainPresenter struct { - presenters.EVMChainResource -} - -// ToRow presents the EVMChainResource as a slice of strings. -func (p *EVMChainPresenter) ToRow() []string { - return []string{p.GetID(), strconv.FormatBool(p.Enabled), p.Config} -} - -// RenderTable implements TableRenderer -// Just renders a single row -func (p EVMChainPresenter) RenderTable(rt RendererTable) error { - rows := [][]string{} - rows = append(rows, p.ToRow()) - - renderList(chainHeaders, rows, rt.Writer) - - return nil -} - -// EVMChainPresenters implements TableRenderer for a slice of EVMChainPresenters. -type EVMChainPresenters []EVMChainPresenter - -// RenderTable implements TableRenderer -func (ps EVMChainPresenters) RenderTable(rt RendererTable) error { - rows := [][]string{} - - for _, p := range ps { - rows = append(rows, p.ToRow()) - } - - renderList(chainHeaders, rows, rt.Writer) - - return nil -} - -func EVMChainClient(s *Shell) ChainClient { - return newChainClient[EVMChainPresenters](s, "evm") -} diff --git a/core/cmd/evm_chains_commands_test.go b/core/cmd/evm_chains_commands_test.go deleted file mode 100644 index fa6d7bb519c..00000000000 --- a/core/cmd/evm_chains_commands_test.go +++ /dev/null @@ -1,38 +0,0 @@ -package cmd_test - -import ( - "strconv" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - client2 "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils/big" - "github.com/smartcontractkit/chainlink/v2/core/cmd" - "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" - "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" -) - -func newRandChainID() *big.Big { - return big.New(testutils.NewRandomEVMChainID()) -} - -func TestShell_IndexEVMChains(t *testing.T) { - t.Parallel() - - app := startNewApplicationV2(t, func(c *chainlink.Config, s *chainlink.Secrets) { - c.EVM[0].Enabled = ptr(true) - c.EVM[0].NonceAutoSync = ptr(false) - c.EVM[0].BalanceMonitor.Enabled = ptr(false) - }) - client, r := app.NewShellAndRenderer() - - require.Nil(t, cmd.EVMChainClient(client).IndexChains(cltest.EmptyCLIContext())) - chains := *r.Renders[0].(*cmd.EVMChainPresenters) - require.Len(t, chains, 1) - c := chains[0] - assert.Equal(t, strconv.Itoa(client2.NullClientChainID), c.ID) - assertTableRenders(t, r) -} diff --git a/core/cmd/evm_node_commands.go b/core/cmd/evm_node_commands.go deleted file mode 100644 index 515ece18a8e..00000000000 --- a/core/cmd/evm_node_commands.go +++ /dev/null @@ -1,44 +0,0 @@ -package cmd - -import ( - "github.com/smartcontractkit/chainlink/v2/core/web/presenters" -) - -// EVMNodePresenter implements TableRenderer for an EVMNodeResource. -type EVMNodePresenter struct { - presenters.EVMNodeResource -} - -// ToRow presents the EVMNodeResource as a slice of strings. -func (p *EVMNodePresenter) ToRow() []string { - return []string{p.Name, p.ChainID, p.State, p.Config} -} - -// RenderTable implements TableRenderer -func (p EVMNodePresenter) RenderTable(rt RendererTable) error { - var rows [][]string - rows = append(rows, p.ToRow()) - renderList(nodeHeaders, rows, rt.Writer) - - return nil -} - -// EVMNodePresenters implements TableRenderer for a slice of EVMNodePresenter. -type EVMNodePresenters []EVMNodePresenter - -// RenderTable implements TableRenderer -func (ps EVMNodePresenters) RenderTable(rt RendererTable) error { - var rows [][]string - - for _, p := range ps { - rows = append(rows, p.ToRow()) - } - - renderList(nodeHeaders, rows, rt.Writer) - - return nil -} - -func NewEVMNodeClient(s *Shell) NodeClient { - return newNodeClient[EVMNodePresenters](s, "evm") -} diff --git a/core/cmd/evm_node_commands_test.go b/core/cmd/evm_node_commands_test.go deleted file mode 100644 index d743ee28e1b..00000000000 --- a/core/cmd/evm_node_commands_test.go +++ /dev/null @@ -1,94 +0,0 @@ -package cmd_test - -import ( - "bytes" - "strings" - "testing" - - "github.com/pelletier/go-toml/v2" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - commonconfig "github.com/smartcontractkit/chainlink-common/pkg/config" - evmcfg "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/toml" - "github.com/smartcontractkit/chainlink/v2/core/cmd" - "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" - "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" -) - -func assertTableRenders(t *testing.T, r *cltest.RendererMock) { - // Should be no error rendering any of the responses as tables - b := bytes.NewBuffer([]byte{}) - tb := cmd.RendererTable{b} - for _, rn := range r.Renders { - require.NoError(t, tb.Render(rn)) - } -} - -func TestShell_IndexEVMNodes(t *testing.T) { - t.Parallel() - - chainID := newRandChainID() - node1 := evmcfg.Node{ - Name: ptr("Test node 1"), - WSURL: commonconfig.MustParseURL("ws://localhost:8546"), - HTTPURL: commonconfig.MustParseURL("http://localhost:8546"), - SendOnly: ptr(false), - Order: ptr(int32(15)), - } - node2 := evmcfg.Node{ - Name: ptr("Test node 2"), - WSURL: commonconfig.MustParseURL("ws://localhost:8547"), - HTTPURL: commonconfig.MustParseURL("http://localhost:8547"), - SendOnly: ptr(false), - Order: ptr(int32(36)), - } - chain := evmcfg.EVMConfig{ - ChainID: chainID, - Chain: evmcfg.Defaults(chainID), - Nodes: evmcfg.EVMNodes{&node1, &node2}, - } - app := startNewApplicationV2(t, func(c *chainlink.Config, s *chainlink.Secrets) { - c.EVM = evmcfg.EVMConfigs{&chain} - }) - client, r := app.NewShellAndRenderer() - - require.Nil(t, cmd.NewEVMNodeClient(client).IndexNodes(cltest.EmptyCLIContext())) - require.NotEmpty(t, r.Renders) - nodes := *r.Renders[0].(*cmd.EVMNodePresenters) - require.Len(t, nodes, 2) - n1 := nodes[0] - n2 := nodes[1] - assert.Equal(t, chainID.String(), n1.ChainID) - assert.Equal(t, cltest.FormatWithPrefixedChainID(chainID.String(), *node1.Name), n1.ID) - assert.Equal(t, *node1.Name, n1.Name) - wantConfig, err := toml.Marshal(node1) - require.NoError(t, err) - assert.Equal(t, string(wantConfig), n1.Config) - assert.Equal(t, chainID.String(), n2.ChainID) - assert.Equal(t, cltest.FormatWithPrefixedChainID(chainID.String(), *node2.Name), n2.ID) - assert.Equal(t, *node2.Name, n2.Name) - wantConfig2, err := toml.Marshal(node2) - require.NoError(t, err) - assert.Equal(t, string(wantConfig2), n2.Config) - assertTableRenders(t, r) - - // Render table and check the fields order - b := new(bytes.Buffer) - rt := cmd.RendererTable{b} - require.NoError(t, nodes.RenderTable(rt)) - renderLines := strings.Split(b.String(), "\n") - assert.Equal(t, 23, len(renderLines)) - assert.Contains(t, renderLines[2], "Name") - assert.Contains(t, renderLines[2], n1.Name) - assert.Contains(t, renderLines[3], "Chain ID") - assert.Contains(t, renderLines[3], n1.ChainID) - assert.Contains(t, renderLines[4], "State") - assert.Contains(t, renderLines[4], n1.State) - assert.Contains(t, renderLines[12], "Name") - assert.Contains(t, renderLines[12], n2.Name) - assert.Contains(t, renderLines[13], "Chain ID") - assert.Contains(t, renderLines[13], n2.ChainID) - assert.Contains(t, renderLines[14], "State") - assert.Contains(t, renderLines[14], n2.State) -} diff --git a/core/cmd/node_commands_test.go b/core/cmd/node_commands_test.go new file mode 100644 index 00000000000..8a17bad719e --- /dev/null +++ b/core/cmd/node_commands_test.go @@ -0,0 +1,290 @@ +package cmd_test + +import ( + "bytes" + "strings" + "testing" + + "github.com/pelletier/go-toml/v2" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink-common/pkg/config" + coscfg "github.com/smartcontractkit/chainlink-cosmos/pkg/cosmos/config" + solcfg "github.com/smartcontractkit/chainlink-solana/pkg/solana/config" + starkcfg "github.com/smartcontractkit/chainlink-starknet/relayer/pkg/chainlink/config" + evmcfg "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/toml" + + "github.com/smartcontractkit/chainlink/v2/core/cmd" + "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/cosmostest" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/solanatest" + "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" +) + +func assertTableRenders(t *testing.T, r *cltest.RendererMock) { + // Should be no error rendering any of the responses as tables + b := bytes.NewBuffer([]byte{}) + tb := cmd.RendererTable{b} + for _, rn := range r.Renders { + require.NoError(t, tb.Render(rn)) + } +} + +func TestShell_IndexEVMNodes(t *testing.T) { + t.Parallel() + + chainID := newRandChainID() + node1 := evmcfg.Node{ + Name: ptr("Test node 1"), + WSURL: config.MustParseURL("ws://localhost:8546"), + HTTPURL: config.MustParseURL("http://localhost:8546"), + SendOnly: ptr(false), + Order: ptr(int32(15)), + } + node2 := evmcfg.Node{ + Name: ptr("Test node 2"), + WSURL: config.MustParseURL("ws://localhost:8547"), + HTTPURL: config.MustParseURL("http://localhost:8547"), + SendOnly: ptr(false), + Order: ptr(int32(36)), + } + chain := evmcfg.EVMConfig{ + ChainID: chainID, + Chain: evmcfg.Defaults(chainID), + Nodes: evmcfg.EVMNodes{&node1, &node2}, + } + app := startNewApplicationV2(t, func(c *chainlink.Config, s *chainlink.Secrets) { + c.EVM = evmcfg.EVMConfigs{&chain} + }) + client, r := app.NewShellAndRenderer() + + require.NoError(t, cmd.NewNodeClient(client, "evm").IndexNodes(cltest.EmptyCLIContext())) + require.NotEmpty(t, r.Renders) + nodes := *r.Renders[0].(*cmd.NodePresenters) + require.Len(t, nodes, 2) + n1 := nodes[0] + n2 := nodes[1] + assert.Equal(t, chainID.String(), n1.ChainID) + assert.Equal(t, cltest.FormatWithPrefixedChainID(chainID.String(), *node1.Name), n1.ID) + assert.Equal(t, *node1.Name, n1.Name) + wantConfig, err := toml.Marshal(node1) + require.NoError(t, err) + assert.Equal(t, string(wantConfig), n1.Config) + assert.Equal(t, chainID.String(), n2.ChainID) + assert.Equal(t, cltest.FormatWithPrefixedChainID(chainID.String(), *node2.Name), n2.ID) + assert.Equal(t, *node2.Name, n2.Name) + wantConfig2, err := toml.Marshal(node2) + require.NoError(t, err) + assert.Equal(t, string(wantConfig2), n2.Config) + assertTableRenders(t, r) + + // Render table and check the fields order + b := new(bytes.Buffer) + rt := cmd.RendererTable{b} + require.NoError(t, nodes.RenderTable(rt)) + renderLines := strings.Split(b.String(), "\n") + assert.Len(t, renderLines, 23) + assert.Contains(t, renderLines[2], "Name") + assert.Contains(t, renderLines[2], n1.Name) + assert.Contains(t, renderLines[3], "Chain ID") + assert.Contains(t, renderLines[3], n1.ChainID) + assert.Contains(t, renderLines[4], "State") + assert.Contains(t, renderLines[4], n1.State) + assert.Contains(t, renderLines[12], "Name") + assert.Contains(t, renderLines[12], n2.Name) + assert.Contains(t, renderLines[13], "Chain ID") + assert.Contains(t, renderLines[13], n2.ChainID) + assert.Contains(t, renderLines[14], "State") + assert.Contains(t, renderLines[14], n2.State) +} + +func cosmosStartNewApplication(t *testing.T, cfgs ...*coscfg.TOMLConfig) *cltest.TestApplication { + for i := range cfgs { + cfgs[i].SetDefaults() + } + return startNewApplicationV2(t, func(c *chainlink.Config, s *chainlink.Secrets) { + c.Cosmos = cfgs + c.EVM = nil + }) +} + +func TestShell_IndexCosmosNodes(t *testing.T) { + t.Parallel() + + chainID := cosmostest.RandomChainID() + node := coscfg.Node{ + Name: ptr("second"), + TendermintURL: config.MustParseURL("http://tender.mint.test/bombay-12"), + } + chain := coscfg.TOMLConfig{ + ChainID: ptr(chainID), + Enabled: ptr(true), + Nodes: coscfg.Nodes{&node}, + } + app := cosmosStartNewApplication(t, &chain) + client, r := app.NewShellAndRenderer() + require.NoError(t, cmd.NewNodeClient(client, "cosmos").IndexNodes(cltest.EmptyCLIContext())) + require.NotEmpty(t, r.Renders) + nodes := *r.Renders[0].(*cmd.NodePresenters) + require.Len(t, nodes, 1) + n := nodes[0] + assert.Equal(t, cltest.FormatWithPrefixedChainID(chainID, *node.Name), n.ID) + assert.Equal(t, chainID, n.ChainID) + assert.Equal(t, *node.Name, n.Name) + wantConfig, err := toml.Marshal(node) + require.NoError(t, err) + assert.Equal(t, string(wantConfig), n.Config) + assertTableRenders(t, r) + + // Render table and check the fields order + b := new(bytes.Buffer) + rt := cmd.RendererTable{b} + require.NoError(t, nodes.RenderTable(rt)) + renderLines := strings.Split(b.String(), "\n") + assert.Len(t, renderLines, 10) + assert.Contains(t, renderLines[2], "Name") + assert.Contains(t, renderLines[2], n.Name) + assert.Contains(t, renderLines[3], "Chain ID") + assert.Contains(t, renderLines[3], n.ChainID) + assert.Contains(t, renderLines[4], "State") + assert.Contains(t, renderLines[4], n.State) +} +func solanaStartNewApplication(t *testing.T, cfgs ...*solcfg.TOMLConfig) *cltest.TestApplication { + for i := range cfgs { + cfgs[i].Chain.SetDefaults() + } + return startNewApplicationV2(t, func(c *chainlink.Config, s *chainlink.Secrets) { + c.Solana = cfgs + c.EVM = nil + }) +} + +func TestShell_IndexSolanaNodes(t *testing.T) { + t.Parallel() + + id := solanatest.RandomChainID() + node1 := solcfg.Node{ + Name: ptr("first"), + URL: config.MustParseURL("https://solana1.example"), + } + node2 := solcfg.Node{ + Name: ptr("second"), + URL: config.MustParseURL("https://solana2.example"), + } + chain := solcfg.TOMLConfig{ + ChainID: &id, + Nodes: solcfg.Nodes{&node1, &node2}, + } + app := solanaStartNewApplication(t, &chain) + client, r := app.NewShellAndRenderer() + + require.NoError(t, cmd.NewNodeClient(client, "solana").IndexNodes(cltest.EmptyCLIContext())) + require.NotEmpty(t, r.Renders) + nodes := *r.Renders[0].(*cmd.NodePresenters) + require.Len(t, nodes, 2) + n1 := nodes[0] + n2 := nodes[1] + assert.Equal(t, id, n1.ChainID) + assert.Equal(t, cltest.FormatWithPrefixedChainID(id, *node1.Name), n1.ID) + assert.Equal(t, *node1.Name, n1.Name) + wantConfig, err := toml.Marshal(node1) + require.NoError(t, err) + assert.Equal(t, string(wantConfig), n1.Config) + assert.Equal(t, id, n2.ChainID) + assert.Equal(t, cltest.FormatWithPrefixedChainID(id, *node2.Name), n2.ID) + assert.Equal(t, *node2.Name, n2.Name) + wantConfig2, err := toml.Marshal(node2) + require.NoError(t, err) + assert.Equal(t, string(wantConfig2), n2.Config) + assertTableRenders(t, r) + + // Render table and check the fields order + b := new(bytes.Buffer) + rt := cmd.RendererTable{b} + require.NoError(t, nodes.RenderTable(rt)) + renderLines := strings.Split(b.String(), "\n") + assert.Len(t, renderLines, 19) + assert.Contains(t, renderLines[2], "Name") + assert.Contains(t, renderLines[2], n1.Name) + assert.Contains(t, renderLines[3], "Chain ID") + assert.Contains(t, renderLines[3], n1.ChainID) + assert.Contains(t, renderLines[4], "State") + assert.Contains(t, renderLines[4], n1.State) + assert.Contains(t, renderLines[10], "Name") + assert.Contains(t, renderLines[10], n2.Name) + assert.Contains(t, renderLines[11], "Chain ID") + assert.Contains(t, renderLines[11], n2.ChainID) + assert.Contains(t, renderLines[12], "State") + assert.Contains(t, renderLines[12], n2.State) +} + +func starknetStartNewApplication(t *testing.T, cfgs ...*starkcfg.TOMLConfig) *cltest.TestApplication { + for i := range cfgs { + cfgs[i].SetDefaults() + } + return startNewApplicationV2(t, func(c *chainlink.Config, s *chainlink.Secrets) { + c.Starknet = cfgs + c.EVM = nil + c.Solana = nil + }) +} + +func TestShell_IndexStarkNetNodes(t *testing.T) { + t.Parallel() + + id := "starknet chain ID" + node1 := starkcfg.Node{ + Name: ptr("first"), + URL: config.MustParseURL("https://starknet1.example"), + } + node2 := starkcfg.Node{ + Name: ptr("second"), + URL: config.MustParseURL("https://starknet2.example"), + } + chain := starkcfg.TOMLConfig{ + ChainID: &id, + Nodes: starkcfg.Nodes{&node1, &node2}, + } + app := starknetStartNewApplication(t, &chain) + client, r := app.NewShellAndRenderer() + + require.NoError(t, cmd.NewNodeClient(client, "starknet").IndexNodes(cltest.EmptyCLIContext())) + require.NotEmpty(t, r.Renders) + nodes := *r.Renders[0].(*cmd.NodePresenters) + require.Len(t, nodes, 2) + n1 := nodes[0] + n2 := nodes[1] + assert.Equal(t, id, n1.ChainID) + assert.Equal(t, cltest.FormatWithPrefixedChainID(id, *node1.Name), n1.ID) + assert.Equal(t, *node1.Name, n1.Name) + wantConfig, err := toml.Marshal(node1) + require.NoError(t, err) + assert.Equal(t, string(wantConfig), n1.Config) + assert.Equal(t, id, n2.ChainID) + assert.Equal(t, cltest.FormatWithPrefixedChainID(id, *node2.Name), n2.ID) + assert.Equal(t, *node2.Name, n2.Name) + wantConfig2, err := toml.Marshal(node2) + require.NoError(t, err) + assert.Equal(t, string(wantConfig2), n2.Config) + assertTableRenders(t, r) + + // Render table and check the fields order + b := new(bytes.Buffer) + rt := cmd.RendererTable{b} + require.NoError(t, nodes.RenderTable(rt)) + renderLines := strings.Split(b.String(), "\n") + assert.Len(t, renderLines, 17) + assert.Contains(t, renderLines[2], "Name") + assert.Contains(t, renderLines[2], n1.Name) + assert.Contains(t, renderLines[3], "Chain ID") + assert.Contains(t, renderLines[3], n1.ChainID) + assert.Contains(t, renderLines[4], "State") + assert.Contains(t, renderLines[4], n1.State) + assert.Contains(t, renderLines[9], "Name") + assert.Contains(t, renderLines[9], n2.Name) + assert.Contains(t, renderLines[10], "Chain ID") + assert.Contains(t, renderLines[10], n2.ChainID) + assert.Contains(t, renderLines[11], "State") + assert.Contains(t, renderLines[11], n2.State) +} diff --git a/core/cmd/nodes_commands.go b/core/cmd/nodes_commands.go index efee10bb156..2afff31ace0 100644 --- a/core/cmd/nodes_commands.go +++ b/core/cmd/nodes_commands.go @@ -2,25 +2,24 @@ package cmd import ( "fmt" + "maps" + "slices" "strings" + "github.com/smartcontractkit/chainlink/v2/core/services/relay" + "github.com/smartcontractkit/chainlink/v2/core/web/presenters" "github.com/urfave/cli" ) -func initCosmosNodeSubCmd(s *Shell) cli.Command { - return nodeCommand("Cosmos", NewCosmosNodeClient(s)) -} - -func initStarkNetNodeSubCmd(s *Shell) cli.Command { - return nodeCommand("StarkNet", NewStarkNetNodeClient(s)) -} - -func initEVMNodeSubCmd(s *Shell) cli.Command { - return nodeCommand("EVM", NewEVMNodeClient(s)) -} - -func initSolanaNodeSubCmd(s *Shell) cli.Command { - return nodeCommand("Solana", NewSolanaNodeClient(s)) +func initNodeSubCmds(s *Shell) []cli.Command { + cmds := []cli.Command{} + for _, network := range slices.Sorted(maps.Keys(relay.SupportedNetworks)) { + if network == relay.NetworkDummy { + continue + } + cmds = append(cmds, nodeCommand(network, NewNodeClient(s, network))) + } + return cmds } // nodeCommand returns a cli.Command with subcommands for the given NodeClient. @@ -40,6 +39,41 @@ func nodeCommand(typ string, client NodeClient) cli.Command { } } +// EVMNodePresenter implements TableRenderer for an EVMNodeResource. +type NodePresenter struct { + presenters.NodeResource +} + +// ToRow presents the EVMNodeResource as a slice of strings. +func (p *NodePresenter) ToRow() []string { + return []string{p.Name, p.ChainID, p.State, p.Config} +} + +// RenderTable implements TableRenderer +func (p NodePresenter) RenderTable(rt RendererTable) error { + var rows [][]string + rows = append(rows, p.ToRow()) + renderList(nodeHeaders, rows, rt.Writer) + + return nil +} + +// NodePresenters implements TableRenderer for a slice of NodePresenter. +type NodePresenters []NodePresenter + +// RenderTable implements TableRenderer +func (ps NodePresenters) RenderTable(rt RendererTable) error { + rows := [][]string{} + + for _, p := range ps { + rows = append(rows, p.ToRow()) + } + + renderList(nodeHeaders, rows, rt.Writer) + + return nil +} + // NodeClient is a generic client interface for any of node. type NodeClient interface { IndexNodes(c *cli.Context) error @@ -52,10 +86,10 @@ type nodeClient[P TableRenderer] struct { // newNodeClient returns a new NodeClient for a particular type of NodeStatus. // P is a TableRenderer for []types.NodeStatus. -func newNodeClient[P TableRenderer](s *Shell, name string) NodeClient { - return &nodeClient[P]{ +func NewNodeClient(s *Shell, network string) NodeClient { + return &nodeClient[NodePresenters]{ Shell: s, - path: "/v2/nodes/" + name, + path: "/v2/nodes/" + network, } } diff --git a/core/cmd/shell.go b/core/cmd/shell.go index 53ba5e3f82f..94664a3cf3d 100644 --- a/core/cmd/shell.go +++ b/core/cmd/shell.go @@ -399,7 +399,7 @@ func takeBackupIfVersionUpgrade(dbUrl url.URL, rootDir string, cfg periodicbacku } // Because backups can take a long time we must start a "fake" health report to prevent - //node shutdown because of healthcheck fail/timeout + // node shutdown because of healthcheck fail/timeout err = databaseBackup.RunBackup(appv.String()) return err } diff --git a/core/cmd/shell_local.go b/core/cmd/shell_local.go index 412231308b6..1fdc1a46d34 100644 --- a/core/cmd/shell_local.go +++ b/core/cmd/shell_local.go @@ -35,6 +35,8 @@ import ( cutils "github.com/smartcontractkit/chainlink-common/pkg/utils" + pgcommon "github.com/smartcontractkit/chainlink-common/pkg/sqlutil/pg" + "github.com/smartcontractkit/chainlink/v2/core/build" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas" @@ -47,7 +49,6 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/sessions" "github.com/smartcontractkit/chainlink/v2/core/shutdown" "github.com/smartcontractkit/chainlink/v2/core/static" - "github.com/smartcontractkit/chainlink/v2/core/store/dialects" "github.com/smartcontractkit/chainlink/v2/core/store/migrate" "github.com/smartcontractkit/chainlink/v2/core/utils" "github.com/smartcontractkit/chainlink/v2/core/web" @@ -805,7 +806,7 @@ func (s *Shell) PrepareTestDatabase(c *cli.Context) error { // Creating pristine DB copy to speed up FullTestDB dbUrl := cfg.Database().URL() - db, err := sqlx.Open(string(dialects.Postgres), dbUrl.String()) + db, err := sqlx.Open(string(pgcommon.Postgres), dbUrl.String()) if err != nil { return s.errorOut(err) } @@ -1088,7 +1089,7 @@ type dbConfig interface { MaxOpenConns() int MaxIdleConns() int URL() url.URL - Dialect() dialects.DialectName + Dialect() pgcommon.DialectName } func newConnection(ctx context.Context, cfg dbConfig) (*sqlx.DB, error) { @@ -1104,7 +1105,7 @@ func dropAndCreateDB(parsed url.URL, force bool) (err error) { // to a different one. template1 should be present on all postgres installations dbname := parsed.Path[1:] parsed.Path = "/template1" - db, err := sql.Open(string(dialects.Postgres), parsed.String()) + db, err := sql.Open(string(pgcommon.Postgres), parsed.String()) if err != nil { return fmt.Errorf("unable to open postgres database for creating test db: %+v", err) } @@ -1203,7 +1204,7 @@ func checkSchema(dbURL url.URL, prevSchema string) error { } func insertFixtures(dbURL url.URL, pathToFixtures string) (err error) { - db, err := sql.Open(string(dialects.Postgres), dbURL.String()) + db, err := sql.Open(string(pgcommon.Postgres), dbURL.String()) if err != nil { return fmt.Errorf("unable to open postgres database for creating test db: %+v", err) } diff --git a/core/cmd/shell_local_test.go b/core/cmd/shell_local_test.go index 78254c0279e..7cdc8c21840 100644 --- a/core/cmd/shell_local_test.go +++ b/core/cmd/shell_local_test.go @@ -10,6 +10,7 @@ import ( "time" commonconfig "github.com/smartcontractkit/chainlink-common/pkg/config" + pgcommon "github.com/smartcontractkit/chainlink-common/pkg/sqlutil/pg" "github.com/smartcontractkit/chainlink-common/pkg/utils/mailbox" "github.com/smartcontractkit/chainlink/v2/common/client" @@ -29,7 +30,6 @@ import ( chainlinkmocks "github.com/smartcontractkit/chainlink/v2/core/services/chainlink/mocks" evmrelayer "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm" "github.com/smartcontractkit/chainlink/v2/core/sessions/localauth" - "github.com/smartcontractkit/chainlink/v2/core/store/dialects" "github.com/smartcontractkit/chainlink/v2/core/store/models" "github.com/smartcontractkit/chainlink/v2/core/utils" "github.com/smartcontractkit/chainlink/v2/core/utils/testutils/heavyweight" @@ -283,7 +283,7 @@ func TestShell_RebroadcastTransactions_Txm(t *testing.T) { // test multiple connections to the database, and changes made within // the transaction cannot be seen from another connection. config, sqlxDB := heavyweight.FullTestDBV2(t, func(c *chainlink.Config, s *chainlink.Secrets) { - c.Database.Dialect = dialects.Postgres + c.Database.Dialect = pgcommon.Postgres // evm config is used in this test. but if set, it must be pass config validation. // simplest to make it nil c.EVM = nil @@ -363,7 +363,7 @@ func TestShell_RebroadcastTransactions_OutsideRange_Txm(t *testing.T) { // test multiple connections to the database, and changes made within // the transaction cannot be seen from another connection. config, sqlxDB := heavyweight.FullTestDBV2(t, func(c *chainlink.Config, s *chainlink.Secrets) { - c.Database.Dialect = dialects.Postgres + c.Database.Dialect = pgcommon.Postgres // evm config is used in this test. but if set, it must be pass config validation. // simplest to make it nil c.EVM = nil @@ -441,7 +441,7 @@ func TestShell_RebroadcastTransactions_AddressCheck(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { config, sqlxDB := heavyweight.FullTestDBV2(t, func(c *chainlink.Config, s *chainlink.Secrets) { - c.Database.Dialect = dialects.Postgres + c.Database.Dialect = pgcommon.Postgres c.EVM = nil // seems to be needed for config validate @@ -499,7 +499,7 @@ func TestShell_RebroadcastTransactions_AddressCheck(t *testing.T) { func TestShell_CleanupChainTables(t *testing.T) { // Just check if it doesn't error, command itself shouldn't be changed unless major schema changes were made. // It would be really hard to write a test that accounts for schema changes, so this should be enough to alarm us that something broke. - config, _ := heavyweight.FullTestDBV2(t, func(c *chainlink.Config, s *chainlink.Secrets) { c.Database.Dialect = dialects.Postgres }) + config, _ := heavyweight.FullTestDBV2(t, func(c *chainlink.Config, s *chainlink.Secrets) { c.Database.Dialect = pgcommon.Postgres }) client := cmd.Shell{ Config: config, Logger: logger.TestLogger(t), diff --git a/core/cmd/solana_chains_commands.go b/core/cmd/solana_chains_commands.go deleted file mode 100644 index aa2a07c0f8c..00000000000 --- a/core/cmd/solana_chains_commands.go +++ /dev/null @@ -1,48 +0,0 @@ -package cmd - -import ( - "strconv" - - "github.com/smartcontractkit/chainlink/v2/core/web/presenters" -) - -// SolanaChainPresenter implements TableRenderer for a SolanaChainResource -type SolanaChainPresenter struct { - presenters.SolanaChainResource -} - -// ToRow presents the SolanaChainResource as a slice of strings. -func (p *SolanaChainPresenter) ToRow() []string { - return []string{p.GetID(), strconv.FormatBool(p.Enabled), p.Config} -} - -// RenderTable implements TableRenderer -// Just renders a single row -func (p SolanaChainPresenter) RenderTable(rt RendererTable) error { - rows := [][]string{} - rows = append(rows, p.ToRow()) - - renderList(chainHeaders, rows, rt.Writer) - - return nil -} - -// SolanaChainPresenters implements TableRenderer for a slice of SolanaChainPresenters. -type SolanaChainPresenters []SolanaChainPresenter - -// RenderTable implements TableRenderer -func (ps SolanaChainPresenters) RenderTable(rt RendererTable) error { - rows := [][]string{} - - for _, p := range ps { - rows = append(rows, p.ToRow()) - } - - renderList(chainHeaders, rows, rt.Writer) - - return nil -} - -func SolanaChainClient(s *Shell) ChainClient { - return newChainClient[SolanaChainPresenters](s, "solana") -} diff --git a/core/cmd/solana_chains_commands_test.go b/core/cmd/solana_chains_commands_test.go deleted file mode 100644 index e374ba11c65..00000000000 --- a/core/cmd/solana_chains_commands_test.go +++ /dev/null @@ -1,32 +0,0 @@ -package cmd_test - -import ( - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - solcfg "github.com/smartcontractkit/chainlink-solana/pkg/solana/config" - "github.com/smartcontractkit/chainlink/v2/core/cmd" - "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" - "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/solanatest" -) - -func TestShell_IndexSolanaChains(t *testing.T) { - t.Parallel() - - id := solanatest.RandomChainID() - cfg := solcfg.TOMLConfig{ - ChainID: &id, - Enabled: ptr(true), - } - app := solanaStartNewApplication(t, &cfg) - client, r := app.NewShellAndRenderer() - - require.Nil(t, cmd.SolanaChainClient(client).IndexChains(cltest.EmptyCLIContext())) - chains := *r.Renders[0].(*cmd.SolanaChainPresenters) - require.Len(t, chains, 1) - c := chains[0] - assert.Equal(t, id, c.ID) - assertTableRenders(t, r) -} diff --git a/core/cmd/solana_node_commands.go b/core/cmd/solana_node_commands.go deleted file mode 100644 index 6ccf9a5864c..00000000000 --- a/core/cmd/solana_node_commands.go +++ /dev/null @@ -1,44 +0,0 @@ -package cmd - -import ( - "github.com/smartcontractkit/chainlink/v2/core/web/presenters" -) - -// SolanaNodePresenter implements TableRenderer for a SolanaNodeResource. -type SolanaNodePresenter struct { - presenters.SolanaNodeResource -} - -// ToRow presents the SolanaNodeResource as a slice of strings. -func (p *SolanaNodePresenter) ToRow() []string { - return []string{p.Name, p.ChainID, p.State, p.Config} -} - -// RenderTable implements TableRenderer -func (p SolanaNodePresenter) RenderTable(rt RendererTable) error { - var rows [][]string - rows = append(rows, p.ToRow()) - renderList(nodeHeaders, rows, rt.Writer) - - return nil -} - -// SolanaNodePresenters implements TableRenderer for a slice of SolanaNodePresenter. -type SolanaNodePresenters []SolanaNodePresenter - -// RenderTable implements TableRenderer -func (ps SolanaNodePresenters) RenderTable(rt RendererTable) error { - var rows [][]string - - for _, p := range ps { - rows = append(rows, p.ToRow()) - } - - renderList(nodeHeaders, rows, rt.Writer) - - return nil -} - -func NewSolanaNodeClient(s *Shell) NodeClient { - return newNodeClient[SolanaNodePresenters](s, "solana") -} diff --git a/core/cmd/solana_node_commands_test.go b/core/cmd/solana_node_commands_test.go deleted file mode 100644 index f08fcd7aaf0..00000000000 --- a/core/cmd/solana_node_commands_test.go +++ /dev/null @@ -1,88 +0,0 @@ -package cmd_test - -import ( - "bytes" - "strings" - "testing" - - "github.com/pelletier/go-toml/v2" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/smartcontractkit/chainlink-common/pkg/config" - solcfg "github.com/smartcontractkit/chainlink-solana/pkg/solana/config" - - "github.com/smartcontractkit/chainlink/v2/core/cmd" - "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" - "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/solanatest" - "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" -) - -func solanaStartNewApplication(t *testing.T, cfgs ...*solcfg.TOMLConfig) *cltest.TestApplication { - for i := range cfgs { - cfgs[i].Chain.SetDefaults() - } - return startNewApplicationV2(t, func(c *chainlink.Config, s *chainlink.Secrets) { - c.Solana = cfgs - c.EVM = nil - }) -} - -func TestShell_IndexSolanaNodes(t *testing.T) { - t.Parallel() - - id := solanatest.RandomChainID() - node1 := solcfg.Node{ - Name: ptr("first"), - URL: config.MustParseURL("https://solana1.example"), - } - node2 := solcfg.Node{ - Name: ptr("second"), - URL: config.MustParseURL("https://solana2.example"), - } - chain := solcfg.TOMLConfig{ - ChainID: &id, - Nodes: solcfg.Nodes{&node1, &node2}, - } - app := solanaStartNewApplication(t, &chain) - client, r := app.NewShellAndRenderer() - - require.Nil(t, cmd.NewSolanaNodeClient(client).IndexNodes(cltest.EmptyCLIContext())) - require.NotEmpty(t, r.Renders) - nodes := *r.Renders[0].(*cmd.SolanaNodePresenters) - require.Len(t, nodes, 2) - n1 := nodes[0] - n2 := nodes[1] - assert.Equal(t, id, n1.ChainID) - assert.Equal(t, cltest.FormatWithPrefixedChainID(id, *node1.Name), n1.ID) - assert.Equal(t, *node1.Name, n1.Name) - wantConfig, err := toml.Marshal(node1) - require.NoError(t, err) - assert.Equal(t, string(wantConfig), n1.Config) - assert.Equal(t, id, n2.ChainID) - assert.Equal(t, cltest.FormatWithPrefixedChainID(id, *node2.Name), n2.ID) - assert.Equal(t, *node2.Name, n2.Name) - wantConfig2, err := toml.Marshal(node2) - require.NoError(t, err) - assert.Equal(t, string(wantConfig2), n2.Config) - assertTableRenders(t, r) - - // Render table and check the fields order - b := new(bytes.Buffer) - rt := cmd.RendererTable{b} - require.NoError(t, nodes.RenderTable(rt)) - renderLines := strings.Split(b.String(), "\n") - assert.Equal(t, 19, len(renderLines)) - assert.Contains(t, renderLines[2], "Name") - assert.Contains(t, renderLines[2], n1.Name) - assert.Contains(t, renderLines[3], "Chain ID") - assert.Contains(t, renderLines[3], n1.ChainID) - assert.Contains(t, renderLines[4], "State") - assert.Contains(t, renderLines[4], n1.State) - assert.Contains(t, renderLines[10], "Name") - assert.Contains(t, renderLines[10], n2.Name) - assert.Contains(t, renderLines[11], "Chain ID") - assert.Contains(t, renderLines[11], n2.ChainID) - assert.Contains(t, renderLines[12], "State") - assert.Contains(t, renderLines[12], n2.State) -} diff --git a/core/cmd/starknet_chains_commands.go b/core/cmd/starknet_chains_commands.go deleted file mode 100644 index 5b20b37ae22..00000000000 --- a/core/cmd/starknet_chains_commands.go +++ /dev/null @@ -1,48 +0,0 @@ -package cmd - -import ( - "strconv" - - "github.com/smartcontractkit/chainlink/v2/core/web/presenters" -) - -// StarkNetChainPresenter implements TableRenderer for a StarkNetChainResource -type StarkNetChainPresenter struct { - presenters.StarkNetChainResource -} - -// ToRow presents the StarkNetChainResource as a slice of strings. -func (p *StarkNetChainPresenter) ToRow() []string { - return []string{p.GetID(), strconv.FormatBool(p.Enabled), p.Config} -} - -// RenderTable implements TableRenderer -// Just renders a single row -func (p StarkNetChainPresenter) RenderTable(rt RendererTable) error { - rows := [][]string{} - rows = append(rows, p.ToRow()) - - renderList(chainHeaders, rows, rt.Writer) - - return nil -} - -// StarkNetChainPresenters implements TableRenderer for a slice of StarkNetChainPresenters. -type StarkNetChainPresenters []StarkNetChainPresenter - -// RenderTable implements TableRenderer -func (ps StarkNetChainPresenters) RenderTable(rt RendererTable) error { - rows := [][]string{} - - for _, p := range ps { - rows = append(rows, p.ToRow()) - } - - renderList(chainHeaders, rows, rt.Writer) - - return nil -} - -func StarkNetChainClient(s *Shell) ChainClient { - return newChainClient[StarkNetChainPresenters](s, "starknet") -} diff --git a/core/cmd/starknet_node_commands.go b/core/cmd/starknet_node_commands.go deleted file mode 100644 index c26bb031145..00000000000 --- a/core/cmd/starknet_node_commands.go +++ /dev/null @@ -1,44 +0,0 @@ -package cmd - -import ( - "github.com/smartcontractkit/chainlink/v2/core/web/presenters" -) - -// StarkNetNodePresenter implements TableRenderer for a StarkNetNodeResource. -type StarkNetNodePresenter struct { - presenters.StarkNetNodeResource -} - -// ToRow presents the StarkNetNodeResource as a slice of strings. -func (p *StarkNetNodePresenter) ToRow() []string { - return []string{p.Name, p.ChainID, p.State, p.Config} -} - -// RenderTable implements TableRenderer -func (p StarkNetNodePresenter) RenderTable(rt RendererTable) error { - var rows [][]string - rows = append(rows, p.ToRow()) - renderList(nodeHeaders, rows, rt.Writer) - - return nil -} - -// StarkNetNodePresenters implements TableRenderer for a slice of StarkNetNodePresenter. -type StarkNetNodePresenters []StarkNetNodePresenter - -// RenderTable implements TableRenderer -func (ps StarkNetNodePresenters) RenderTable(rt RendererTable) error { - var rows [][]string - - for _, p := range ps { - rows = append(rows, p.ToRow()) - } - - renderList(nodeHeaders, rows, rt.Writer) - - return nil -} - -func NewStarkNetNodeClient(s *Shell) NodeClient { - return newNodeClient[StarkNetNodePresenters](s, "starknet") -} diff --git a/core/cmd/starknet_node_commands_test.go b/core/cmd/starknet_node_commands_test.go deleted file mode 100644 index dae93682085..00000000000 --- a/core/cmd/starknet_node_commands_test.go +++ /dev/null @@ -1,88 +0,0 @@ -package cmd_test - -import ( - "bytes" - "strings" - "testing" - - "github.com/pelletier/go-toml/v2" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - commoncfg "github.com/smartcontractkit/chainlink-common/pkg/config" - "github.com/smartcontractkit/chainlink-starknet/relayer/pkg/chainlink/config" - - "github.com/smartcontractkit/chainlink/v2/core/cmd" - "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" - "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" -) - -func starknetStartNewApplication(t *testing.T, cfgs ...*config.TOMLConfig) *cltest.TestApplication { - for i := range cfgs { - cfgs[i].SetDefaults() - } - return startNewApplicationV2(t, func(c *chainlink.Config, s *chainlink.Secrets) { - c.Starknet = cfgs - c.EVM = nil - c.Solana = nil - }) -} - -func TestShell_IndexStarkNetNodes(t *testing.T) { - t.Parallel() - - id := "starknet chain ID" - node1 := config.Node{ - Name: ptr("first"), - URL: commoncfg.MustParseURL("https://starknet1.example"), - } - node2 := config.Node{ - Name: ptr("second"), - URL: commoncfg.MustParseURL("https://starknet2.example"), - } - chain := config.TOMLConfig{ - ChainID: &id, - Nodes: config.Nodes{&node1, &node2}, - } - app := starknetStartNewApplication(t, &chain) - client, r := app.NewShellAndRenderer() - - require.Nil(t, cmd.NewStarkNetNodeClient(client).IndexNodes(cltest.EmptyCLIContext())) - require.NotEmpty(t, r.Renders) - nodes := *r.Renders[0].(*cmd.StarkNetNodePresenters) - require.Len(t, nodes, 2) - n1 := nodes[0] - n2 := nodes[1] - assert.Equal(t, id, n1.ChainID) - assert.Equal(t, cltest.FormatWithPrefixedChainID(id, *node1.Name), n1.ID) - assert.Equal(t, *node1.Name, n1.Name) - wantConfig, err := toml.Marshal(node1) - require.NoError(t, err) - assert.Equal(t, string(wantConfig), n1.Config) - assert.Equal(t, id, n2.ChainID) - assert.Equal(t, cltest.FormatWithPrefixedChainID(id, *node2.Name), n2.ID) - assert.Equal(t, *node2.Name, n2.Name) - wantConfig2, err := toml.Marshal(node2) - require.NoError(t, err) - assert.Equal(t, string(wantConfig2), n2.Config) - assertTableRenders(t, r) - - // Render table and check the fields order - b := new(bytes.Buffer) - rt := cmd.RendererTable{b} - require.NoError(t, nodes.RenderTable(rt)) - renderLines := strings.Split(b.String(), "\n") - assert.Equal(t, 17, len(renderLines)) - assert.Contains(t, renderLines[2], "Name") - assert.Contains(t, renderLines[2], n1.Name) - assert.Contains(t, renderLines[3], "Chain ID") - assert.Contains(t, renderLines[3], n1.ChainID) - assert.Contains(t, renderLines[4], "State") - assert.Contains(t, renderLines[4], n1.State) - assert.Contains(t, renderLines[9], "Name") - assert.Contains(t, renderLines[9], n2.Name) - assert.Contains(t, renderLines[10], "Chain ID") - assert.Contains(t, renderLines[10], n2.ChainID) - assert.Contains(t, renderLines[11], "State") - assert.Contains(t, renderLines[11], n2.State) -} diff --git a/core/config/database_config.go b/core/config/database_config.go index f1cdffc2f46..56f8f8165d4 100644 --- a/core/config/database_config.go +++ b/core/config/database_config.go @@ -4,7 +4,7 @@ import ( "net/url" "time" - "github.com/smartcontractkit/chainlink/v2/core/store/dialects" + pgcommon "github.com/smartcontractkit/chainlink-common/pkg/sqlutil/pg" ) type Backup interface { @@ -35,7 +35,7 @@ type Database interface { DefaultIdleInTxSessionTimeout() time.Duration DefaultLockTimeout() time.Duration DefaultQueryTimeout() time.Duration - Dialect() dialects.DialectName + Dialect() pgcommon.DialectName LogSQL() bool MaxIdleConns() int MaxOpenConns() int diff --git a/core/config/docs/defaults.go b/core/config/docs/defaults.go index 53e6433a8ef..0d94be1b3cc 100644 --- a/core/config/docs/defaults.go +++ b/core/config/docs/defaults.go @@ -5,9 +5,10 @@ import ( "strings" "github.com/smartcontractkit/chainlink-common/pkg/config" + pgcommon "github.com/smartcontractkit/chainlink-common/pkg/sqlutil/pg" + "github.com/smartcontractkit/chainlink/v2/core/config/toml" "github.com/smartcontractkit/chainlink/v2/core/services/chainlink/cfgtest" - "github.com/smartcontractkit/chainlink/v2/core/store/dialects" ) var ( @@ -22,7 +23,7 @@ func init() { func CoreDefaults() (c toml.Core) { c.SetFrom(&defaults) - c.Database.Dialect = dialects.Postgres // not user visible - overridden for tests only + c.Database.Dialect = pgcommon.Postgres // not user visible - overridden for tests only c.Tracing.Attributes = make(map[string]string) return } diff --git a/core/config/toml/types.go b/core/config/toml/types.go index 475e95d53df..620f7d96eee 100644 --- a/core/config/toml/types.go +++ b/core/config/toml/types.go @@ -16,6 +16,7 @@ import ( ocrcommontypes "github.com/smartcontractkit/libocr/commontypes" commonconfig "github.com/smartcontractkit/chainlink-common/pkg/config" + pgcommon "github.com/smartcontractkit/chainlink-common/pkg/sqlutil/pg" "github.com/smartcontractkit/chainlink/v2/core/build" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" @@ -23,10 +24,8 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/config/parse" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/p2pkey" "github.com/smartcontractkit/chainlink/v2/core/sessions" - "github.com/smartcontractkit/chainlink/v2/core/store/dialects" "github.com/smartcontractkit/chainlink/v2/core/store/models" "github.com/smartcontractkit/chainlink/v2/core/utils" - configutils "github.com/smartcontractkit/chainlink/v2/core/utils/config" ) @@ -339,7 +338,7 @@ type Database struct { DefaultIdleInTxSessionTimeout *commonconfig.Duration DefaultLockTimeout *commonconfig.Duration DefaultQueryTimeout *commonconfig.Duration - Dialect dialects.DialectName `toml:"-"` + Dialect pgcommon.DialectName `toml:"-"` LogQueries *bool MaxIdleConns *int64 MaxOpenConns *int64 diff --git a/core/gethwrappers/ccip/deployment_test/deployment_test.go b/core/gethwrappers/ccip/deployment_test/deployment_test.go index 02f00483651..162c0df1946 100644 --- a/core/gethwrappers/ccip/deployment_test/deployment_test.go +++ b/core/gethwrappers/ccip/deployment_test/deployment_test.go @@ -41,10 +41,11 @@ func TestDeployAllV1_6(t *testing.T) { // offramp _, _, _, err = offramp.DeployOffRamp(owner, chain, offramp.OffRampStaticConfig{ - ChainSelector: 1, - RmnRemote: common.HexToAddress("0x1"), - TokenAdminRegistry: common.HexToAddress("0x2"), - NonceManager: common.HexToAddress("0x3"), + ChainSelector: 1, + GasForCallExactCheck: 5_000, + RmnRemote: common.HexToAddress("0x1"), + TokenAdminRegistry: common.HexToAddress("0x2"), + NonceManager: common.HexToAddress("0x3"), }, offramp.OffRampDynamicConfig{ FeeQuoter: common.HexToAddress("0x4"), PermissionLessExecutionThresholdSeconds: uint32((8 * time.Hour).Seconds()), diff --git a/core/gethwrappers/ccip/generated/ccip_config/ccip_config.go b/core/gethwrappers/ccip/generated/ccip_config/ccip_config.go deleted file mode 100644 index 3c2d44cd302..00000000000 --- a/core/gethwrappers/ccip/generated/ccip_config/ccip_config.go +++ /dev/null @@ -1,1126 +0,0 @@ -// Code generated - DO NOT EDIT. -// This file is a generated binding and any manual changes will be lost. - -package ccip_config - -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 CCIPConfigTypesChainConfig struct { - Readers [][32]byte - FChain uint8 - Config []byte -} - -type CCIPConfigTypesChainConfigInfo struct { - ChainSelector uint64 - ChainConfig CCIPConfigTypesChainConfig -} - -type CCIPConfigTypesOCR3Config struct { - PluginType uint8 - ChainSelector uint64 - F uint8 - OffchainConfigVersion uint64 - OfframpAddress []byte - P2pIds [][32]byte - Signers [][]byte - Transmitters [][]byte - OffchainConfig []byte -} - -type CCIPConfigTypesOCR3ConfigWithMeta struct { - Config CCIPConfigTypesOCR3Config - ConfigCount uint64 - ConfigDigest [32]byte -} - -var CCIPConfigMetaData = &bind.MetaData{ - ABI: "[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"capabilitiesRegistry\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"chainSelector\",\"type\":\"uint64\"}],\"name\":\"ChainSelectorNotFound\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"ChainSelectorNotSet\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"FChainMustBePositive\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"FMustBePositive\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"FTooHigh\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"length\",\"type\":\"uint256\"}],\"name\":\"InvalidConfigLength\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"enumCCIPConfigTypes.ConfigState\",\"name\":\"currentState\",\"type\":\"uint8\"},{\"internalType\":\"enumCCIPConfigTypes.ConfigState\",\"name\":\"proposedState\",\"type\":\"uint8\"}],\"name\":\"InvalidConfigStateTransition\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidPluginType\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"p2pId\",\"type\":\"bytes32\"}],\"name\":\"NodeNotInRegistry\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"NonExistentConfigTransition\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"got\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"minimum\",\"type\":\"uint256\"}],\"name\":\"NotEnoughTransmitters\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"OfframpAddressCannotBeZero\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"OnlyCapabilitiesRegistryCanCall\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"p2pIdsLength\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"signersLength\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"transmittersLength\",\"type\":\"uint256\"}],\"name\":\"P2PIdsLengthNotMatching\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"TooManyOCR3Configs\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"TooManySigners\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"got\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"expected\",\"type\":\"uint64\"}],\"name\":\"WrongConfigCount\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"got\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"expected\",\"type\":\"bytes32\"}],\"name\":\"WrongConfigDigest\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"got\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"expected\",\"type\":\"bytes32\"}],\"name\":\"WrongConfigDigestBlueGreen\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"ZeroAddressNotAllowed\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[],\"name\":\"CapabilityConfigurationSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"chainSelector\",\"type\":\"uint64\"}],\"name\":\"ChainConfigRemoved\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"chainSelector\",\"type\":\"uint64\"},{\"components\":[{\"internalType\":\"bytes32[]\",\"name\":\"readers\",\"type\":\"bytes32[]\"},{\"internalType\":\"uint8\",\"name\":\"fChain\",\"type\":\"uint8\"},{\"internalType\":\"bytes\",\"name\":\"config\",\"type\":\"bytes\"}],\"indexed\":false,\"internalType\":\"structCCIPConfigTypes.ChainConfig\",\"name\":\"chainConfig\",\"type\":\"tuple\"}],\"name\":\"ChainConfigSet\",\"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\":[{\"internalType\":\"uint64[]\",\"name\":\"chainSelectorRemoves\",\"type\":\"uint64[]\"},{\"components\":[{\"internalType\":\"uint64\",\"name\":\"chainSelector\",\"type\":\"uint64\"},{\"components\":[{\"internalType\":\"bytes32[]\",\"name\":\"readers\",\"type\":\"bytes32[]\"},{\"internalType\":\"uint8\",\"name\":\"fChain\",\"type\":\"uint8\"},{\"internalType\":\"bytes\",\"name\":\"config\",\"type\":\"bytes\"}],\"internalType\":\"structCCIPConfigTypes.ChainConfig\",\"name\":\"chainConfig\",\"type\":\"tuple\"}],\"internalType\":\"structCCIPConfigTypes.ChainConfigInfo[]\",\"name\":\"chainConfigAdds\",\"type\":\"tuple[]\"}],\"name\":\"applyChainConfigUpdates\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32[]\",\"name\":\"\",\"type\":\"bytes32[]\"},{\"internalType\":\"bytes\",\"name\":\"config\",\"type\":\"bytes\"},{\"internalType\":\"uint64\",\"name\":\"\",\"type\":\"uint64\"},{\"internalType\":\"uint32\",\"name\":\"donId\",\"type\":\"uint32\"}],\"name\":\"beforeCapabilityConfigSet\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"pageIndex\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"pageSize\",\"type\":\"uint256\"}],\"name\":\"getAllChainConfigs\",\"outputs\":[{\"components\":[{\"internalType\":\"uint64\",\"name\":\"chainSelector\",\"type\":\"uint64\"},{\"components\":[{\"internalType\":\"bytes32[]\",\"name\":\"readers\",\"type\":\"bytes32[]\"},{\"internalType\":\"uint8\",\"name\":\"fChain\",\"type\":\"uint8\"},{\"internalType\":\"bytes\",\"name\":\"config\",\"type\":\"bytes\"}],\"internalType\":\"structCCIPConfigTypes.ChainConfig\",\"name\":\"chainConfig\",\"type\":\"tuple\"}],\"internalType\":\"structCCIPConfigTypes.ChainConfigInfo[]\",\"name\":\"\",\"type\":\"tuple[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"}],\"name\":\"getCapabilityConfiguration\",\"outputs\":[{\"internalType\":\"bytes\",\"name\":\"configuration\",\"type\":\"bytes\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getCapabilityRegistry\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"donId\",\"type\":\"uint32\"},{\"internalType\":\"enumInternal.OCRPluginType\",\"name\":\"pluginType\",\"type\":\"uint8\"}],\"name\":\"getOCRConfig\",\"outputs\":[{\"components\":[{\"components\":[{\"internalType\":\"enumInternal.OCRPluginType\",\"name\":\"pluginType\",\"type\":\"uint8\"},{\"internalType\":\"uint64\",\"name\":\"chainSelector\",\"type\":\"uint64\"},{\"internalType\":\"uint8\",\"name\":\"F\",\"type\":\"uint8\"},{\"internalType\":\"uint64\",\"name\":\"offchainConfigVersion\",\"type\":\"uint64\"},{\"internalType\":\"bytes\",\"name\":\"offrampAddress\",\"type\":\"bytes\"},{\"internalType\":\"bytes32[]\",\"name\":\"p2pIds\",\"type\":\"bytes32[]\"},{\"internalType\":\"bytes[]\",\"name\":\"signers\",\"type\":\"bytes[]\"},{\"internalType\":\"bytes[]\",\"name\":\"transmitters\",\"type\":\"bytes[]\"},{\"internalType\":\"bytes\",\"name\":\"offchainConfig\",\"type\":\"bytes\"}],\"internalType\":\"structCCIPConfigTypes.OCR3Config\",\"name\":\"config\",\"type\":\"tuple\"},{\"internalType\":\"uint64\",\"name\":\"configCount\",\"type\":\"uint64\"},{\"internalType\":\"bytes32\",\"name\":\"configDigest\",\"type\":\"bytes32\"}],\"internalType\":\"structCCIPConfigTypes.OCR3ConfigWithMeta[]\",\"name\":\"\",\"type\":\"tuple[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes4\",\"name\":\"interfaceId\",\"type\":\"bytes4\"}],\"name\":\"supportsInterface\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"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\":\"view\",\"type\":\"function\"}]", - Bin: "0x60a06040523480156200001157600080fd5b5060405162004080380380620040808339810160408190526200003491620001a6565b33806000816200008b5760405162461bcd60e51b815260206004820152601860248201527f43616e6e6f7420736574206f776e657220746f207a65726f000000000000000060448201526064015b60405180910390fd5b600080546001600160a01b0319166001600160a01b0384811691909117909155811615620000be57620000be81620000fb565b5050506001600160a01b038116620000e9576040516342bcdf7f60e11b815260040160405180910390fd5b6001600160a01b0316608052620001d8565b336001600160a01b03821603620001555760405162461bcd60e51b815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c66000000000000000000604482015260640162000082565b600180546001600160a01b0319166001600160a01b0383811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b600060208284031215620001b957600080fd5b81516001600160a01b0381168114620001d157600080fd5b9392505050565b608051613e7f620002016000396000818160f801528181610ea701526111170152613e7f6000f3fe608060405234801561001057600080fd5b50600436106100c95760003560e01c80638318ed5d11610081578063f2fde38b1161005b578063f2fde38b1461020f578063f442c89a14610222578063fba64a7c1461023557600080fd5b80638318ed5d146101b05780638da5cb5b146101d1578063b74b2356146101ef57600080fd5b8063181f5a77116100b2578063181f5a771461013d5780634bd0473f1461018657806379ba5097146101a657600080fd5b806301ffc9a7146100ce578063020330e6146100f6575b600080fd5b6100e16100dc366004612cbd565b610248565b60405190151581526020015b60405180910390f35b7f00000000000000000000000000000000000000000000000000000000000000005b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020016100ed565b6101796040518060400160405280601481526020017f43434950436f6e66696720312e362e302d64657600000000000000000000000081525081565b6040516100ed9190612d63565b610199610194366004612da7565b6102e1565b6040516100ed9190612ec6565b6101ae610759565b005b6101796101be366004613082565b5060408051602081019091526000815290565b60005473ffffffffffffffffffffffffffffffffffffffff16610118565b6102026101fd36600461309f565b61085b565b6040516100ed9190613105565b6101ae61021d366004613195565b610adc565b6101ae610230366004613217565b610af0565b6101ae61024336600461329b565b610e8f565b60007fffffffff0000000000000000000000000000000000000000000000000000000082167f78bea7210000000000000000000000000000000000000000000000000000000014806102db57507fffffffff0000000000000000000000000000000000000000000000000000000082167f01ffc9a700000000000000000000000000000000000000000000000000000000145b92915050565b63ffffffff8216600090815260056020526040812060609183600181111561030b5761030b612ddc565b600181111561031c5761031c612ddc565b8152602001908152602001600020805480602002602001604051908101604052809291908181526020016000905b8282101561074d57600084815260209020604080516101808101909152600884029091018054829060608201908390829060ff16600181111561038f5761038f612ddc565b60018111156103a0576103a0612ddc565b8152815467ffffffffffffffff61010082048116602084015260ff690100000000000000000083041660408401526a01000000000000000000009091041660608201526001820180546080909201916103f890613358565b80601f016020809104026020016040519081016040528092919081815260200182805461042490613358565b80156104715780601f1061044657610100808354040283529160200191610471565b820191906000526020600020905b81548152906001019060200180831161045457829003601f168201915b50505050508152602001600282018054806020026020016040519081016040528092919081815260200182805480156104c957602002820191906000526020600020905b8154815260200190600101908083116104b5575b5050505050815260200160038201805480602002602001604051908101604052809291908181526020016000905b828210156105a357838290600052602060002001805461051690613358565b80601f016020809104026020016040519081016040528092919081815260200182805461054290613358565b801561058f5780601f106105645761010080835404028352916020019161058f565b820191906000526020600020905b81548152906001019060200180831161057257829003601f168201915b5050505050815260200190600101906104f7565b50505050815260200160048201805480602002602001604051908101604052809291908181526020016000905b8282101561067c5783829060005260206000200180546105ef90613358565b80601f016020809104026020016040519081016040528092919081815260200182805461061b90613358565b80156106685780601f1061063d57610100808354040283529160200191610668565b820191906000526020600020905b81548152906001019060200180831161064b57829003601f168201915b5050505050815260200190600101906105d0565b50505050815260200160058201805461069490613358565b80601f01602080910402602001604051908101604052809291908181526020018280546106c090613358565b801561070d5780601f106106e25761010080835404028352916020019161070d565b820191906000526020600020905b8154815290600101906020018083116106f057829003601f168201915b505050919092525050508152600682015467ffffffffffffffff16602080830191909152600790920154604090910152908252600192909201910161034a565b50505050905092915050565b60015473ffffffffffffffffffffffffffffffffffffffff1633146107df576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4d7573742062652070726f706f736564206f776e65720000000000000000000060448201526064015b60405180910390fd5b60008054337fffffffffffffffffffffffff00000000000000000000000000000000000000008083168217845560018054909116905560405173ffffffffffffffffffffffffffffffffffffffff90921692909183917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a350565b606060006108696003610f4a565b9050600061087784866133da565b90508315806108865750818110155b156108c65760408051600080825260208201909252906108bc565b6108a9612a5c565b8152602001906001900390816108a15790505b50925050506102db565b60006108d28583613420565b9050828111156108df5750815b60006108eb8383613433565b67ffffffffffffffff811115610903576109036133f1565b60405190808252806020026020018201604052801561093c57816020015b610929612a5c565b8152602001906001900390816109215790505b509050600061094b6003610f54565b9050835b83811015610acf57600082828151811061096b5761096b613446565b60209081029190910181015160408051808201825267ffffffffffffffff8316808252600090815260028552829020825181546080818802830181019095526060820181815295975092958601949093919284928491908401828280156109f157602002820191906000526020600020905b8154815260200190600101908083116109dd575b5050509183525050600182015460ff166020820152600282018054604090920191610a1b90613358565b80601f0160208091040260200160405190810160405280929190818152602001828054610a4790613358565b8015610a945780601f10610a6957610100808354040283529160200191610a94565b820191906000526020600020905b815481529060010190602001808311610a7757829003601f168201915b50505091909252505050905284610aab8885613433565b81518110610abb57610abb613446565b60209081029190910101525060010161094f565b5090979650505050505050565b610ae4610f68565b610aed81610feb565b50565b610af8610f68565b60005b83811015610cde57610b3f858583818110610b1857610b18613446565b9050602002016020810190610b2d9190613475565b60039067ffffffffffffffff166110e0565b610ba957848482818110610b5557610b55613446565b9050602002016020810190610b6a9190613475565b6040517f1bd4d2d200000000000000000000000000000000000000000000000000000000815267ffffffffffffffff90911660048201526024016107d6565b60026000868684818110610bbf57610bbf613446565b9050602002016020810190610bd49190613475565b67ffffffffffffffff1681526020810191909152604001600090812090610bfb8282612aa4565b6001820180547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00169055610c33600283016000612ac2565b5050610c71858583818110610c4a57610c4a613446565b9050602002016020810190610c5f9190613475565b60039067ffffffffffffffff166110f8565b507f2a680691fef3b2d105196805935232c661ce703e92d464ef0b94a7bc62d714f0858583818110610ca557610ca5613446565b9050602002016020810190610cba9190613475565b60405167ffffffffffffffff909116815260200160405180910390a1600101610afb565b5060005b81811015610e88576000838383818110610cfe57610cfe613446565b9050602002810190610d109190613490565b610d1e9060208101906134ce565b610d27906136d0565b90506000848484818110610d3d57610d3d613446565b9050602002810190610d4f9190613490565b610d5d906020810190613475565b9050610d6c8260000151611104565b816020015160ff16600003610dad576040517fa9b3766e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b67ffffffffffffffff81166000908152600260209081526040909120835180518593610ddd928492910190612afc565b5060208201516001820180547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660ff90921691909117905560408201516002820190610e2a90826137b7565b50610e4491506003905067ffffffffffffffff8316611250565b507f05dd57854af2c291a94ea52e7c43d80bc3be7fa73022f98b735dea86642fa5e08183604051610e769291906138d1565b60405180910390a15050600101610ce2565b5050505050565b3373ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001614610efe576040517fac7a7efd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600080610f15610f108688018861397c565b61125c565b8151919350915015610f2d57610f2d836000846114a7565b805115610f4057610f40836001836114a7565b5050505050505050565b60006102db825490565b60606000610f6183611c09565b9392505050565b60005473ffffffffffffffffffffffffffffffffffffffff163314610fe9576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4f6e6c792063616c6c61626c65206279206f776e65720000000000000000000060448201526064016107d6565b565b3373ffffffffffffffffffffffffffffffffffffffff82160361106a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c6600000000000000000060448201526064016107d6565b600180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b60008181526001830160205260408120541515610f61565b6000610f618383611c65565b60005b815181101561124c5760008019167f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff166350c946fe84848151811061116357611163613446565b60200260200101516040518263ffffffff1660e01b815260040161118991815260200190565b600060405180830381865afa1580156111a6573d6000803e3d6000fd5b505050506040513d6000823e601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01682016040526111ec9190810190613bc7565b60800151036112445781818151811061120757611207613446565b60200260200101516040517f8907a4fa0000000000000000000000000000000000000000000000000000000081526004016107d691815260200190565b600101611107565b5050565b6000610f618383611d5f565b606080600460ff168351111561129e576040517f8854586400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040805160028082526060820190925290816020015b61131b6040805161012081019091528060008152602001600067ffffffffffffffff168152602001600060ff168152602001600067ffffffffffffffff16815260200160608152602001606081526020016060815260200160608152602001606081525090565b8152602001906001900390816112b457505060408051600280825260608201909252919350602082015b6113ac6040805161012081019091528060008152602001600067ffffffffffffffff168152602001600060ff168152602001600067ffffffffffffffff16815260200160608152602001606081526020016060815260200160608152602001606081525090565b81526020019060019003908161134557905050905060008060005b855181101561149a5760008682815181106113e4576113e4613446565b602002602001015160000151600181111561140157611401612ddc565b0361144e5785818151811061141857611418613446565b602002602001015185848151811061143257611432613446565b60200260200101819052508261144790613c9f565b9250611492565b85818151811061146057611460613446565b602002602001015184838151811061147a5761147a613446565b60200260200101819052508161148f90613c9f565b91505b6001016113c7565b5090835281529092909150565b63ffffffff83166000908152600560205260408120818460018111156114cf576114cf612ddc565b60018111156114e0576114e0612ddc565b8152602001908152602001600020805480602002602001604051908101604052809291908181526020016000905b8282101561191157600084815260209020604080516101808101909152600884029091018054829060608201908390829060ff16600181111561155357611553612ddc565b600181111561156457611564612ddc565b8152815467ffffffffffffffff61010082048116602084015260ff690100000000000000000083041660408401526a01000000000000000000009091041660608201526001820180546080909201916115bc90613358565b80601f01602080910402602001604051908101604052809291908181526020018280546115e890613358565b80156116355780601f1061160a57610100808354040283529160200191611635565b820191906000526020600020905b81548152906001019060200180831161161857829003601f168201915b505050505081526020016002820180548060200260200160405190810160405280929190818152602001828054801561168d57602002820191906000526020600020905b815481526020019060010190808311611679575b5050505050815260200160038201805480602002602001604051908101604052809291908181526020016000905b828210156117675783829060005260206000200180546116da90613358565b80601f016020809104026020016040519081016040528092919081815260200182805461170690613358565b80156117535780601f1061172857610100808354040283529160200191611753565b820191906000526020600020905b81548152906001019060200180831161173657829003601f168201915b5050505050815260200190600101906116bb565b50505050815260200160048201805480602002602001604051908101604052809291908181526020016000905b828210156118405783829060005260206000200180546117b390613358565b80601f01602080910402602001604051908101604052809291908181526020018280546117df90613358565b801561182c5780601f106118015761010080835404028352916020019161182c565b820191906000526020600020905b81548152906001019060200180831161180f57829003601f168201915b505050505081526020019060010190611794565b50505050815260200160058201805461185890613358565b80601f016020809104026020016040519081016040528092919081815260200182805461188490613358565b80156118d15780601f106118a6576101008083540402835291602001916118d1565b820191906000526020600020905b8154815290600101906020018083116118b457829003601f168201915b505050919092525050508152600682015467ffffffffffffffff16602080830191909152600790920154604090910152908252600192909201910161150e565b50505050905060006119238251611dae565b905060006119318451611dae565b905061193d8282611e00565b600061194c8785878686611ebc565b905061195884826122a0565b63ffffffff871660009081526005602052604081209087600181111561198057611980612ddc565b600181111561199157611991612ddc565b815260200190815260200160002060006119ab9190612b47565b60005b8151811015610f405763ffffffff88166000908152600560205260408120908860018111156119df576119df612ddc565b60018111156119f0576119f0612ddc565b8152602001908152602001600020828281518110611a1057611a10613446565b6020908102919091018101518254600181810185556000948552929093208151805160089095029091018054929490939192849283917fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0016908381811115611a7a57611a7a612ddc565b021790555060208201518154604084015160608501517fffffffffffffffffffffffffffffffffffffffffffff000000000000000000ff90921661010067ffffffffffffffff948516027fffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffff1617690100000000000000000060ff90921691909102177fffffffffffffffffffffffffffff0000000000000000ffffffffffffffffffff166a0100000000000000000000929091169190910217815560808201516001820190611b4990826137b7565b5060a08201518051611b65916002840191602090910190612afc565b5060c08201518051611b81916003840191602090910190612b68565b5060e08201518051611b9d916004840191602090910190612b68565b506101008201516005820190611bb390826137b7565b50505060208201516006820180547fffffffffffffffffffffffffffffffffffffffffffffffff00000000000000001667ffffffffffffffff9092169190911790556040909101516007909101556001016119ae565b606081600001805480602002602001604051908101604052809291908181526020018280548015611c5957602002820191906000526020600020905b815481526020019060010190808311611c45575b50505050509050919050565b60008181526001830160205260408120548015611d4e576000611c89600183613433565b8554909150600090611c9d90600190613433565b9050808214611d02576000866000018281548110611cbd57611cbd613446565b9060005260206000200154905080876000018481548110611ce057611ce0613446565b6000918252602080832090910192909255918252600188019052604090208390555b8554869080611d1357611d13613cd7565b6001900381819060005260206000200160009055905585600101600086815260200190815260200160002060009055600193505050506102db565b60009150506102db565b5092915050565b6000818152600183016020526040812054611da6575081546001818101845560008481526020808220909301849055845484825282860190935260409020919091556102db565b5060006102db565b60006002821115611dee576040517f3e478526000000000000000000000000000000000000000000000000000000008152600481018390526024016107d6565b8160028111156102db576102db612ddc565b6000826002811115611e1457611e14612ddc565b826002811115611e2657611e26612ddc565b611e309190613d06565b90508060011480611e7c5750807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff148015611e7c57506002836002811115611e7a57611e7a612ddc565b145b15611e8657505050565b82826040517f0a6b675b0000000000000000000000000000000000000000000000000000000081526004016107d6929190613d36565b60606000845167ffffffffffffffff811115611eda57611eda6133f1565b604051908082528060200260200182016040528015611f03578160200160208202803683370190505b5090506000846002811115611f1a57611f1a612ddc565b148015611f3857506001836002811115611f3657611f36612ddc565b145b15611f7957600181600081518110611f5257611f52613446565b602002602001019067ffffffffffffffff16908167ffffffffffffffff16815250506120e1565b6001846002811115611f8d57611f8d612ddc565b148015611fab57506002836002811115611fa957611fa9612ddc565b145b156120425785600081518110611fc357611fc3613446565b60200260200101516020015181600081518110611fe257611fe2613446565b602002602001019067ffffffffffffffff16908167ffffffffffffffff16815250508560008151811061201757612017613446565b602002602001015160200151600161202f9190613d51565b81600181518110611f5257611f52613446565b600284600281111561205657612056612ddc565b1480156120745750600183600281111561207257612072612ddc565b145b156120ab578560018151811061208c5761208c613446565b60200260200101516020015181600081518110611f5257611f52613446565b83836040517f0a6b675b0000000000000000000000000000000000000000000000000000000081526004016107d6929190613d36565b6000855167ffffffffffffffff8111156120fd576120fd6133f1565b6040519080825280602002602001820160405280156121ab57816020015b6040805161018081018252600060608083018281526080840183905260a0840183905260c0840183905260e08401829052610100840182905261012084018290526101408401829052610160840191909152825260208083018290529282015282527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff90920191018161211b5790505b50905060005b8251811015612294576121dc8782815181106121cf576121cf613446565b602002602001015161261f565b60405180606001604052808883815181106121f9576121f9613446565b6020026020010151815260200184838151811061221857612218613446565b602002602001015167ffffffffffffffff16815260200161226c8b86858151811061224557612245613446565b60200260200101518b868151811061225f5761225f613446565b602002602001015161298e565b81525082828151811061228157612281613446565b60209081029190910101526001016121b1565b50979650505050505050565b81518151811580156122b25750806001145b1561235457826000815181106122ca576122ca613446565b60200260200101516020015167ffffffffffffffff1660011461234e57826000815181106122fa576122fa613446565b60209081029190910181015101516040517fc1658eb800000000000000000000000000000000000000000000000000000000815267ffffffffffffffff9091166004820152600160248201526044016107d6565b50505050565b8160011480156123645750806002145b1561251a578360008151811061237c5761237c613446565b6020026020010151604001518360008151811061239b5761239b613446565b6020026020010151604001511461242757826000815181106123bf576123bf613446565b602002602001015160400151846000815181106123de576123de613446565b6020026020010151604001516040517fc7ccdd7f0000000000000000000000000000000000000000000000000000000081526004016107d6929190918252602082015260400190565b8360008151811061243a5761243a613446565b60200260200101516020015160016124529190613d51565b67ffffffffffffffff168360018151811061246f5761246f613446565b60200260200101516020015167ffffffffffffffff161461234e578260018151811061249d5761249d613446565b602002602001015160200151846000815181106124bc576124bc613446565b60200260200101516020015160016124d49190613d51565b6040517fc1658eb800000000000000000000000000000000000000000000000000000000815267ffffffffffffffff9283166004820152911660248201526044016107d6565b81600214801561252a5750806001145b156125ed578360018151811061254257612542613446565b6020026020010151604001518360008151811061256157612561613446565b6020026020010151604001511461234e578260008151811061258557612585613446565b602002602001015160400151846001815181106125a4576125a4613446565b6020026020010151604001516040517f9e9756700000000000000000000000000000000000000000000000000000000081526004016107d6929190918252602082015260400190565b6040517f1f1b2bb600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b806020015167ffffffffffffffff16600003612667576040517f698cf8e000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008151600181111561267c5761267c612ddc565b1415801561269d575060018151600181111561269a5761269a612ddc565b14155b156126d4576040517f3302dbd700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6080810151511580612711575060408051600060208201520160405160208183030381529060405280519060200120816080015180519060200120145b15612748576040517f358c192700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60208101516127639060039067ffffffffffffffff166110e0565b6127ab5760208101516040517f1bd4d2d200000000000000000000000000000000000000000000000000000000815267ffffffffffffffff90911660048201526024016107d6565b60208082015167ffffffffffffffff166000908152600290915260408120600101546127db9060ff166003613d72565b6127e6906001613d8e565b60ff169050808260e0015151101561283b5760e0820151516040517f548dd21f0000000000000000000000000000000000000000000000000000000081526004810191909152602481018290526044016107d6565b60c08201515161010081111561287d576040517f1b925da600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8260a00151518114158061289657508260e00151518114155b156128f05760a08301515160c08401515160e0850151516040517fba900f6d0000000000000000000000000000000000000000000000000000000081526004810193909352602483019190915260448201526064016107d6565b826040015160ff16600003612931576040517f39d1a4d000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040830151612941906003613d72565b60ff16811161297c576040517f4856694e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6129898360a00151611104565b505050565b60008082602001518584600001518560800151878760a001518860c001518960e001518a604001518b606001518c61010001516040516020016129db9b9a99989796959493929190613da7565b604080518083037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe001815291905280516020909101207dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff167e0a000000000000000000000000000000000000000000000000000000000000179150509392505050565b6040518060400160405280600067ffffffffffffffff168152602001612a9f604051806060016040528060608152602001600060ff168152602001606081525090565b905290565b5080546000825590600052602060002090810190610aed9190612bba565b508054612ace90613358565b6000825580601f10612ade575050565b601f016020900490600052602060002090810190610aed9190612bba565b828054828255906000526020600020908101928215612b37579160200282015b82811115612b37578251825591602001919060010190612b1c565b50612b43929150612bba565b5090565b5080546000825560080290600052602060002090810190610aed9190612bcf565b828054828255906000526020600020908101928215612bae579160200282015b82811115612bae5782518290612b9e90826137b7565b5091602001919060010190612b88565b50612b43929150612c82565b5b80821115612b435760008155600101612bbb565b80821115612b435780547fffffffffffffffffffffffffffff00000000000000000000000000000000000016815560008181612c0e6001830182612ac2565b612c1c600283016000612aa4565b612c2a600383016000612c9f565b612c38600483016000612c9f565b612c46600583016000612ac2565b5050506006810180547fffffffffffffffffffffffffffffffffffffffffffffffff000000000000000016905560006007820155600801612bcf565b80821115612b43576000612c968282612ac2565b50600101612c82565b5080546000825590600052602060002090810190610aed9190612c82565b600060208284031215612ccf57600080fd5b81357fffffffff0000000000000000000000000000000000000000000000000000000081168114610f6157600080fd5b6000815180845260005b81811015612d2557602081850181015186830182015201612d09565b5060006020828601015260207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f83011685010191505092915050565b602081526000610f616020830184612cff565b63ffffffff81168114610aed57600080fd5b8035612d9381612d76565b919050565b803560028110612d9357600080fd5b60008060408385031215612dba57600080fd5b8235612dc581612d76565b9150612dd360208401612d98565b90509250929050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b60028110612e1b57612e1b612ddc565b9052565b60008151808452602080850194506020840160005b83811015612e5057815187529582019590820190600101612e34565b509495945050505050565b60008282518085526020808601955060208260051b8401016020860160005b84811015610acf577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0868403018952612eb4838351612cff565b98840198925090830190600101612e7a565b600060208083018184528085518083526040925060408601915060408160051b87010184880160005b83811015613074577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc0898403018552815160608151818652612f348287018251612e0b565b898101516080612f4f8189018367ffffffffffffffff169052565b8a830151915060a0612f65818a018460ff169052565b938301519360c09250612f838984018667ffffffffffffffff169052565b818401519450610120915060e082818b0152612fa36101808b0187612cff565b95508185015191507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0610100818c890301818d0152612fe28885612e1f565b958701518c87038301868e0152959750612ffc8887612e5b565b9750828701519550818c8903016101408d01526130198887612e5b565b975080870151965050808b8803016101608c0152505050505061303c8282612cff565b915050888201516130588a87018267ffffffffffffffff169052565b5090870151938701939093529386019390860190600101612eef565b509098975050505050505050565b60006020828403121561309457600080fd5b8135610f6181612d76565b600080604083850312156130b257600080fd5b50508035926020909101359150565b60008151606084526130d66060850182612e1f565b905060ff6020840151166020850152604083015184820360408601526130fc8282612cff565b95945050505050565b600060208083018184528085518083526040925060408601915060408160051b87010184880160005b83811015613074578883037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc00185528151805167ffffffffffffffff168452870151878401879052613182878501826130c1565b958801959350509086019060010161312e565b6000602082840312156131a757600080fd5b813573ffffffffffffffffffffffffffffffffffffffff81168114610f6157600080fd5b60008083601f8401126131dd57600080fd5b50813567ffffffffffffffff8111156131f557600080fd5b6020830191508360208260051b850101111561321057600080fd5b9250929050565b6000806000806040858703121561322d57600080fd5b843567ffffffffffffffff8082111561324557600080fd5b613251888389016131cb565b9096509450602087013591508082111561326a57600080fd5b50613277878288016131cb565b95989497509550505050565b803567ffffffffffffffff81168114612d9357600080fd5b600080600080600080608087890312156132b457600080fd5b863567ffffffffffffffff808211156132cc57600080fd5b6132d88a838b016131cb565b909850965060208901359150808211156132f157600080fd5b818901915089601f83011261330557600080fd5b81358181111561331457600080fd5b8a602082850101111561332657600080fd5b60208301965080955050505061333e60408801613283565b915061334c60608801612d88565b90509295509295509295565b600181811c9082168061336c57607f821691505b6020821081036133a5577f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b50919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b80820281158282048414176102db576102db6133ab565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b808201808211156102db576102db6133ab565b818103818111156102db576102db6133ab565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b60006020828403121561348757600080fd5b610f6182613283565b600082357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc18336030181126134c457600080fd5b9190910192915050565b600082357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa18336030181126134c457600080fd5b604051610120810167ffffffffffffffff81118282101715613526576135266133f1565b60405290565b60405160e0810167ffffffffffffffff81118282101715613526576135266133f1565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff81118282101715613596576135966133f1565b604052919050565b600067ffffffffffffffff8211156135b8576135b86133f1565b5060051b60200190565b600082601f8301126135d357600080fd5b813560206135e86135e38361359e565b61354f565b8083825260208201915060208460051b87010193508684111561360a57600080fd5b602086015b84811015613626578035835291830191830161360f565b509695505050505050565b803560ff81168114612d9357600080fd5b600082601f83011261365357600080fd5b813567ffffffffffffffff81111561366d5761366d6133f1565b61369e60207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8401160161354f565b8181528460208386010111156136b357600080fd5b816020850160208301376000918101602001919091529392505050565b6000606082360312156136e257600080fd5b6040516060810167ffffffffffffffff8282108183111715613706576137066133f1565b81604052843591508082111561371b57600080fd5b613727368387016135c2565b835261373560208601613631565b6020840152604085013591508082111561374e57600080fd5b5061375b36828601613642565b60408301525092915050565b601f821115612989576000816000526020600020601f850160051c810160208610156137905750805b601f850160051c820191505b818110156137af5782815560010161379c565b505050505050565b815167ffffffffffffffff8111156137d1576137d16133f1565b6137e5816137df8454613358565b84613767565b602080601f83116001811461383857600084156138025750858301515b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600386901b1c1916600185901b1785556137af565b6000858152602081207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08616915b8281101561388557888601518255948401946001909101908401613866565b50858210156138c157878501517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600388901b60f8161c191681555b5050505050600190811b01905550565b67ffffffffffffffff831681526040602082015260006138f460408301846130c1565b949350505050565b600082601f83011261390d57600080fd5b8135602061391d6135e38361359e565b82815260059290921b8401810191818101908684111561393c57600080fd5b8286015b8481101561362657803567ffffffffffffffff8111156139605760008081fd5b61396e8986838b0101613642565b845250918301918301613940565b6000602080838503121561398f57600080fd5b823567ffffffffffffffff808211156139a757600080fd5b818501915085601f8301126139bb57600080fd5b81356139c96135e38261359e565b81815260059190911b830184019084810190888311156139e857600080fd5b8585015b83811015613b5057803585811115613a0357600080fd5b8601610120818c037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe001811315613a3957600080fd5b613a41613502565b613a4c8a8401612d98565b8152613a5a60408401613283565b8a820152613a6a60608401613631565b6040820152613a7b60808401613283565b606082015260a083013588811115613a9257600080fd5b613aa08e8c83870101613642565b60808301525060c083013588811115613ab857600080fd5b613ac68e8c838701016135c2565b60a08301525060e083013588811115613adf5760008081fd5b613aed8e8c838701016138fc565b60c0830152506101008084013589811115613b085760008081fd5b613b168f8d838801016138fc565b60e084015250918301359188831115613b2f5760008081fd5b613b3d8e8c85870101613642565b90820152855250509186019186016139ec565b5098975050505050505050565b8051612d9381612d76565b600082601f830112613b7957600080fd5b81516020613b896135e38361359e565b8083825260208201915060208460051b870101935086841115613bab57600080fd5b602086015b848110156136265780518352918301918301613bb0565b600060208284031215613bd957600080fd5b815167ffffffffffffffff80821115613bf157600080fd5b9083019060e08286031215613c0557600080fd5b613c0d61352c565b613c1683613b5d565b8152613c2460208401613b5d565b6020820152613c3560408401613b5d565b6040820152606083015160608201526080830151608082015260a083015182811115613c6057600080fd5b613c6c87828601613b68565b60a08301525060c083015182811115613c8457600080fd5b613c9087828601613b68565b60c08301525095945050505050565b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203613cd057613cd06133ab565b5060010190565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603160045260246000fd5b8181036000831280158383131683831282161715611d5857611d586133ab565b60038110612e1b57612e1b612ddc565b60408101613d448285613d26565b610f616020830184613d26565b67ffffffffffffffff818116838216019080821115611d5857611d586133ab565b60ff8181168382160290811690818114611d5857611d586133ab565b60ff81811683821601908111156102db576102db6133ab565b600061016067ffffffffffffffff8e16835263ffffffff8d166020840152613dd2604084018d612e0b565b806060840152613de48184018c612cff565b67ffffffffffffffff8b166080850152905082810360a0840152613e08818a612e1f565b905082810360c0840152613e1c8189612e5b565b905082810360e0840152613e308188612e5b565b60ff8716610100850152905067ffffffffffffffff8516610120840152828103610140840152613e608185612cff565b9e9d505050505050505050505050505056fea164736f6c6343000818000a", -} - -var CCIPConfigABI = CCIPConfigMetaData.ABI - -var CCIPConfigBin = CCIPConfigMetaData.Bin - -func DeployCCIPConfig(auth *bind.TransactOpts, backend bind.ContractBackend, capabilitiesRegistry common.Address) (common.Address, *types.Transaction, *CCIPConfig, error) { - parsed, err := CCIPConfigMetaData.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(CCIPConfigBin), backend, capabilitiesRegistry) - if err != nil { - return common.Address{}, nil, nil, err - } - return address, tx, &CCIPConfig{address: address, abi: *parsed, CCIPConfigCaller: CCIPConfigCaller{contract: contract}, CCIPConfigTransactor: CCIPConfigTransactor{contract: contract}, CCIPConfigFilterer: CCIPConfigFilterer{contract: contract}}, nil -} - -type CCIPConfig struct { - address common.Address - abi abi.ABI - CCIPConfigCaller - CCIPConfigTransactor - CCIPConfigFilterer -} - -type CCIPConfigCaller struct { - contract *bind.BoundContract -} - -type CCIPConfigTransactor struct { - contract *bind.BoundContract -} - -type CCIPConfigFilterer struct { - contract *bind.BoundContract -} - -type CCIPConfigSession struct { - Contract *CCIPConfig - CallOpts bind.CallOpts - TransactOpts bind.TransactOpts -} - -type CCIPConfigCallerSession struct { - Contract *CCIPConfigCaller - CallOpts bind.CallOpts -} - -type CCIPConfigTransactorSession struct { - Contract *CCIPConfigTransactor - TransactOpts bind.TransactOpts -} - -type CCIPConfigRaw struct { - Contract *CCIPConfig -} - -type CCIPConfigCallerRaw struct { - Contract *CCIPConfigCaller -} - -type CCIPConfigTransactorRaw struct { - Contract *CCIPConfigTransactor -} - -func NewCCIPConfig(address common.Address, backend bind.ContractBackend) (*CCIPConfig, error) { - abi, err := abi.JSON(strings.NewReader(CCIPConfigABI)) - if err != nil { - return nil, err - } - contract, err := bindCCIPConfig(address, backend, backend, backend) - if err != nil { - return nil, err - } - return &CCIPConfig{address: address, abi: abi, CCIPConfigCaller: CCIPConfigCaller{contract: contract}, CCIPConfigTransactor: CCIPConfigTransactor{contract: contract}, CCIPConfigFilterer: CCIPConfigFilterer{contract: contract}}, nil -} - -func NewCCIPConfigCaller(address common.Address, caller bind.ContractCaller) (*CCIPConfigCaller, error) { - contract, err := bindCCIPConfig(address, caller, nil, nil) - if err != nil { - return nil, err - } - return &CCIPConfigCaller{contract: contract}, nil -} - -func NewCCIPConfigTransactor(address common.Address, transactor bind.ContractTransactor) (*CCIPConfigTransactor, error) { - contract, err := bindCCIPConfig(address, nil, transactor, nil) - if err != nil { - return nil, err - } - return &CCIPConfigTransactor{contract: contract}, nil -} - -func NewCCIPConfigFilterer(address common.Address, filterer bind.ContractFilterer) (*CCIPConfigFilterer, error) { - contract, err := bindCCIPConfig(address, nil, nil, filterer) - if err != nil { - return nil, err - } - return &CCIPConfigFilterer{contract: contract}, nil -} - -func bindCCIPConfig(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { - parsed, err := CCIPConfigMetaData.GetAbi() - if err != nil { - return nil, err - } - return bind.NewBoundContract(address, *parsed, caller, transactor, filterer), nil -} - -func (_CCIPConfig *CCIPConfigRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { - return _CCIPConfig.Contract.CCIPConfigCaller.contract.Call(opts, result, method, params...) -} - -func (_CCIPConfig *CCIPConfigRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { - return _CCIPConfig.Contract.CCIPConfigTransactor.contract.Transfer(opts) -} - -func (_CCIPConfig *CCIPConfigRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { - return _CCIPConfig.Contract.CCIPConfigTransactor.contract.Transact(opts, method, params...) -} - -func (_CCIPConfig *CCIPConfigCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { - return _CCIPConfig.Contract.contract.Call(opts, result, method, params...) -} - -func (_CCIPConfig *CCIPConfigTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { - return _CCIPConfig.Contract.contract.Transfer(opts) -} - -func (_CCIPConfig *CCIPConfigTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { - return _CCIPConfig.Contract.contract.Transact(opts, method, params...) -} - -func (_CCIPConfig *CCIPConfigCaller) GetAllChainConfigs(opts *bind.CallOpts, pageIndex *big.Int, pageSize *big.Int) ([]CCIPConfigTypesChainConfigInfo, error) { - var out []interface{} - err := _CCIPConfig.contract.Call(opts, &out, "getAllChainConfigs", pageIndex, pageSize) - - if err != nil { - return *new([]CCIPConfigTypesChainConfigInfo), err - } - - out0 := *abi.ConvertType(out[0], new([]CCIPConfigTypesChainConfigInfo)).(*[]CCIPConfigTypesChainConfigInfo) - - return out0, err - -} - -func (_CCIPConfig *CCIPConfigSession) GetAllChainConfigs(pageIndex *big.Int, pageSize *big.Int) ([]CCIPConfigTypesChainConfigInfo, error) { - return _CCIPConfig.Contract.GetAllChainConfigs(&_CCIPConfig.CallOpts, pageIndex, pageSize) -} - -func (_CCIPConfig *CCIPConfigCallerSession) GetAllChainConfigs(pageIndex *big.Int, pageSize *big.Int) ([]CCIPConfigTypesChainConfigInfo, error) { - return _CCIPConfig.Contract.GetAllChainConfigs(&_CCIPConfig.CallOpts, pageIndex, pageSize) -} - -func (_CCIPConfig *CCIPConfigCaller) GetCapabilityConfiguration(opts *bind.CallOpts, arg0 uint32) ([]byte, error) { - var out []interface{} - err := _CCIPConfig.contract.Call(opts, &out, "getCapabilityConfiguration", arg0) - - if err != nil { - return *new([]byte), err - } - - out0 := *abi.ConvertType(out[0], new([]byte)).(*[]byte) - - return out0, err - -} - -func (_CCIPConfig *CCIPConfigSession) GetCapabilityConfiguration(arg0 uint32) ([]byte, error) { - return _CCIPConfig.Contract.GetCapabilityConfiguration(&_CCIPConfig.CallOpts, arg0) -} - -func (_CCIPConfig *CCIPConfigCallerSession) GetCapabilityConfiguration(arg0 uint32) ([]byte, error) { - return _CCIPConfig.Contract.GetCapabilityConfiguration(&_CCIPConfig.CallOpts, arg0) -} - -func (_CCIPConfig *CCIPConfigCaller) GetCapabilityRegistry(opts *bind.CallOpts) (common.Address, error) { - var out []interface{} - err := _CCIPConfig.contract.Call(opts, &out, "getCapabilityRegistry") - - if err != nil { - return *new(common.Address), err - } - - out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) - - return out0, err - -} - -func (_CCIPConfig *CCIPConfigSession) GetCapabilityRegistry() (common.Address, error) { - return _CCIPConfig.Contract.GetCapabilityRegistry(&_CCIPConfig.CallOpts) -} - -func (_CCIPConfig *CCIPConfigCallerSession) GetCapabilityRegistry() (common.Address, error) { - return _CCIPConfig.Contract.GetCapabilityRegistry(&_CCIPConfig.CallOpts) -} - -func (_CCIPConfig *CCIPConfigCaller) GetOCRConfig(opts *bind.CallOpts, donId uint32, pluginType uint8) ([]CCIPConfigTypesOCR3ConfigWithMeta, error) { - var out []interface{} - err := _CCIPConfig.contract.Call(opts, &out, "getOCRConfig", donId, pluginType) - - if err != nil { - return *new([]CCIPConfigTypesOCR3ConfigWithMeta), err - } - - out0 := *abi.ConvertType(out[0], new([]CCIPConfigTypesOCR3ConfigWithMeta)).(*[]CCIPConfigTypesOCR3ConfigWithMeta) - - return out0, err - -} - -func (_CCIPConfig *CCIPConfigSession) GetOCRConfig(donId uint32, pluginType uint8) ([]CCIPConfigTypesOCR3ConfigWithMeta, error) { - return _CCIPConfig.Contract.GetOCRConfig(&_CCIPConfig.CallOpts, donId, pluginType) -} - -func (_CCIPConfig *CCIPConfigCallerSession) GetOCRConfig(donId uint32, pluginType uint8) ([]CCIPConfigTypesOCR3ConfigWithMeta, error) { - return _CCIPConfig.Contract.GetOCRConfig(&_CCIPConfig.CallOpts, donId, pluginType) -} - -func (_CCIPConfig *CCIPConfigCaller) Owner(opts *bind.CallOpts) (common.Address, error) { - var out []interface{} - err := _CCIPConfig.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 (_CCIPConfig *CCIPConfigSession) Owner() (common.Address, error) { - return _CCIPConfig.Contract.Owner(&_CCIPConfig.CallOpts) -} - -func (_CCIPConfig *CCIPConfigCallerSession) Owner() (common.Address, error) { - return _CCIPConfig.Contract.Owner(&_CCIPConfig.CallOpts) -} - -func (_CCIPConfig *CCIPConfigCaller) SupportsInterface(opts *bind.CallOpts, interfaceId [4]byte) (bool, error) { - var out []interface{} - err := _CCIPConfig.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 (_CCIPConfig *CCIPConfigSession) SupportsInterface(interfaceId [4]byte) (bool, error) { - return _CCIPConfig.Contract.SupportsInterface(&_CCIPConfig.CallOpts, interfaceId) -} - -func (_CCIPConfig *CCIPConfigCallerSession) SupportsInterface(interfaceId [4]byte) (bool, error) { - return _CCIPConfig.Contract.SupportsInterface(&_CCIPConfig.CallOpts, interfaceId) -} - -func (_CCIPConfig *CCIPConfigCaller) TypeAndVersion(opts *bind.CallOpts) (string, error) { - var out []interface{} - err := _CCIPConfig.contract.Call(opts, &out, "typeAndVersion") - - if err != nil { - return *new(string), err - } - - out0 := *abi.ConvertType(out[0], new(string)).(*string) - - return out0, err - -} - -func (_CCIPConfig *CCIPConfigSession) TypeAndVersion() (string, error) { - return _CCIPConfig.Contract.TypeAndVersion(&_CCIPConfig.CallOpts) -} - -func (_CCIPConfig *CCIPConfigCallerSession) TypeAndVersion() (string, error) { - return _CCIPConfig.Contract.TypeAndVersion(&_CCIPConfig.CallOpts) -} - -func (_CCIPConfig *CCIPConfigTransactor) AcceptOwnership(opts *bind.TransactOpts) (*types.Transaction, error) { - return _CCIPConfig.contract.Transact(opts, "acceptOwnership") -} - -func (_CCIPConfig *CCIPConfigSession) AcceptOwnership() (*types.Transaction, error) { - return _CCIPConfig.Contract.AcceptOwnership(&_CCIPConfig.TransactOpts) -} - -func (_CCIPConfig *CCIPConfigTransactorSession) AcceptOwnership() (*types.Transaction, error) { - return _CCIPConfig.Contract.AcceptOwnership(&_CCIPConfig.TransactOpts) -} - -func (_CCIPConfig *CCIPConfigTransactor) ApplyChainConfigUpdates(opts *bind.TransactOpts, chainSelectorRemoves []uint64, chainConfigAdds []CCIPConfigTypesChainConfigInfo) (*types.Transaction, error) { - return _CCIPConfig.contract.Transact(opts, "applyChainConfigUpdates", chainSelectorRemoves, chainConfigAdds) -} - -func (_CCIPConfig *CCIPConfigSession) ApplyChainConfigUpdates(chainSelectorRemoves []uint64, chainConfigAdds []CCIPConfigTypesChainConfigInfo) (*types.Transaction, error) { - return _CCIPConfig.Contract.ApplyChainConfigUpdates(&_CCIPConfig.TransactOpts, chainSelectorRemoves, chainConfigAdds) -} - -func (_CCIPConfig *CCIPConfigTransactorSession) ApplyChainConfigUpdates(chainSelectorRemoves []uint64, chainConfigAdds []CCIPConfigTypesChainConfigInfo) (*types.Transaction, error) { - return _CCIPConfig.Contract.ApplyChainConfigUpdates(&_CCIPConfig.TransactOpts, chainSelectorRemoves, chainConfigAdds) -} - -func (_CCIPConfig *CCIPConfigTransactor) BeforeCapabilityConfigSet(opts *bind.TransactOpts, arg0 [][32]byte, config []byte, arg2 uint64, donId uint32) (*types.Transaction, error) { - return _CCIPConfig.contract.Transact(opts, "beforeCapabilityConfigSet", arg0, config, arg2, donId) -} - -func (_CCIPConfig *CCIPConfigSession) BeforeCapabilityConfigSet(arg0 [][32]byte, config []byte, arg2 uint64, donId uint32) (*types.Transaction, error) { - return _CCIPConfig.Contract.BeforeCapabilityConfigSet(&_CCIPConfig.TransactOpts, arg0, config, arg2, donId) -} - -func (_CCIPConfig *CCIPConfigTransactorSession) BeforeCapabilityConfigSet(arg0 [][32]byte, config []byte, arg2 uint64, donId uint32) (*types.Transaction, error) { - return _CCIPConfig.Contract.BeforeCapabilityConfigSet(&_CCIPConfig.TransactOpts, arg0, config, arg2, donId) -} - -func (_CCIPConfig *CCIPConfigTransactor) TransferOwnership(opts *bind.TransactOpts, to common.Address) (*types.Transaction, error) { - return _CCIPConfig.contract.Transact(opts, "transferOwnership", to) -} - -func (_CCIPConfig *CCIPConfigSession) TransferOwnership(to common.Address) (*types.Transaction, error) { - return _CCIPConfig.Contract.TransferOwnership(&_CCIPConfig.TransactOpts, to) -} - -func (_CCIPConfig *CCIPConfigTransactorSession) TransferOwnership(to common.Address) (*types.Transaction, error) { - return _CCIPConfig.Contract.TransferOwnership(&_CCIPConfig.TransactOpts, to) -} - -type CCIPConfigCapabilityConfigurationSetIterator struct { - Event *CCIPConfigCapabilityConfigurationSet - - contract *bind.BoundContract - event string - - logs chan types.Log - sub ethereum.Subscription - done bool - fail error -} - -func (it *CCIPConfigCapabilityConfigurationSetIterator) Next() bool { - - if it.fail != nil { - return false - } - - if it.done { - select { - case log := <-it.logs: - it.Event = new(CCIPConfigCapabilityConfigurationSet) - 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(CCIPConfigCapabilityConfigurationSet) - 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 *CCIPConfigCapabilityConfigurationSetIterator) Error() error { - return it.fail -} - -func (it *CCIPConfigCapabilityConfigurationSetIterator) Close() error { - it.sub.Unsubscribe() - return nil -} - -type CCIPConfigCapabilityConfigurationSet struct { - Raw types.Log -} - -func (_CCIPConfig *CCIPConfigFilterer) FilterCapabilityConfigurationSet(opts *bind.FilterOpts) (*CCIPConfigCapabilityConfigurationSetIterator, error) { - - logs, sub, err := _CCIPConfig.contract.FilterLogs(opts, "CapabilityConfigurationSet") - if err != nil { - return nil, err - } - return &CCIPConfigCapabilityConfigurationSetIterator{contract: _CCIPConfig.contract, event: "CapabilityConfigurationSet", logs: logs, sub: sub}, nil -} - -func (_CCIPConfig *CCIPConfigFilterer) WatchCapabilityConfigurationSet(opts *bind.WatchOpts, sink chan<- *CCIPConfigCapabilityConfigurationSet) (event.Subscription, error) { - - logs, sub, err := _CCIPConfig.contract.WatchLogs(opts, "CapabilityConfigurationSet") - if err != nil { - return nil, err - } - return event.NewSubscription(func(quit <-chan struct{}) error { - defer sub.Unsubscribe() - for { - select { - case log := <-logs: - - event := new(CCIPConfigCapabilityConfigurationSet) - if err := _CCIPConfig.contract.UnpackLog(event, "CapabilityConfigurationSet", 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 (_CCIPConfig *CCIPConfigFilterer) ParseCapabilityConfigurationSet(log types.Log) (*CCIPConfigCapabilityConfigurationSet, error) { - event := new(CCIPConfigCapabilityConfigurationSet) - if err := _CCIPConfig.contract.UnpackLog(event, "CapabilityConfigurationSet", log); err != nil { - return nil, err - } - event.Raw = log - return event, nil -} - -type CCIPConfigChainConfigRemovedIterator struct { - Event *CCIPConfigChainConfigRemoved - - contract *bind.BoundContract - event string - - logs chan types.Log - sub ethereum.Subscription - done bool - fail error -} - -func (it *CCIPConfigChainConfigRemovedIterator) Next() bool { - - if it.fail != nil { - return false - } - - if it.done { - select { - case log := <-it.logs: - it.Event = new(CCIPConfigChainConfigRemoved) - 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(CCIPConfigChainConfigRemoved) - 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 *CCIPConfigChainConfigRemovedIterator) Error() error { - return it.fail -} - -func (it *CCIPConfigChainConfigRemovedIterator) Close() error { - it.sub.Unsubscribe() - return nil -} - -type CCIPConfigChainConfigRemoved struct { - ChainSelector uint64 - Raw types.Log -} - -func (_CCIPConfig *CCIPConfigFilterer) FilterChainConfigRemoved(opts *bind.FilterOpts) (*CCIPConfigChainConfigRemovedIterator, error) { - - logs, sub, err := _CCIPConfig.contract.FilterLogs(opts, "ChainConfigRemoved") - if err != nil { - return nil, err - } - return &CCIPConfigChainConfigRemovedIterator{contract: _CCIPConfig.contract, event: "ChainConfigRemoved", logs: logs, sub: sub}, nil -} - -func (_CCIPConfig *CCIPConfigFilterer) WatchChainConfigRemoved(opts *bind.WatchOpts, sink chan<- *CCIPConfigChainConfigRemoved) (event.Subscription, error) { - - logs, sub, err := _CCIPConfig.contract.WatchLogs(opts, "ChainConfigRemoved") - if err != nil { - return nil, err - } - return event.NewSubscription(func(quit <-chan struct{}) error { - defer sub.Unsubscribe() - for { - select { - case log := <-logs: - - event := new(CCIPConfigChainConfigRemoved) - if err := _CCIPConfig.contract.UnpackLog(event, "ChainConfigRemoved", 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 (_CCIPConfig *CCIPConfigFilterer) ParseChainConfigRemoved(log types.Log) (*CCIPConfigChainConfigRemoved, error) { - event := new(CCIPConfigChainConfigRemoved) - if err := _CCIPConfig.contract.UnpackLog(event, "ChainConfigRemoved", log); err != nil { - return nil, err - } - event.Raw = log - return event, nil -} - -type CCIPConfigChainConfigSetIterator struct { - Event *CCIPConfigChainConfigSet - - contract *bind.BoundContract - event string - - logs chan types.Log - sub ethereum.Subscription - done bool - fail error -} - -func (it *CCIPConfigChainConfigSetIterator) Next() bool { - - if it.fail != nil { - return false - } - - if it.done { - select { - case log := <-it.logs: - it.Event = new(CCIPConfigChainConfigSet) - 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(CCIPConfigChainConfigSet) - 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 *CCIPConfigChainConfigSetIterator) Error() error { - return it.fail -} - -func (it *CCIPConfigChainConfigSetIterator) Close() error { - it.sub.Unsubscribe() - return nil -} - -type CCIPConfigChainConfigSet struct { - ChainSelector uint64 - ChainConfig CCIPConfigTypesChainConfig - Raw types.Log -} - -func (_CCIPConfig *CCIPConfigFilterer) FilterChainConfigSet(opts *bind.FilterOpts) (*CCIPConfigChainConfigSetIterator, error) { - - logs, sub, err := _CCIPConfig.contract.FilterLogs(opts, "ChainConfigSet") - if err != nil { - return nil, err - } - return &CCIPConfigChainConfigSetIterator{contract: _CCIPConfig.contract, event: "ChainConfigSet", logs: logs, sub: sub}, nil -} - -func (_CCIPConfig *CCIPConfigFilterer) WatchChainConfigSet(opts *bind.WatchOpts, sink chan<- *CCIPConfigChainConfigSet) (event.Subscription, error) { - - logs, sub, err := _CCIPConfig.contract.WatchLogs(opts, "ChainConfigSet") - if err != nil { - return nil, err - } - return event.NewSubscription(func(quit <-chan struct{}) error { - defer sub.Unsubscribe() - for { - select { - case log := <-logs: - - event := new(CCIPConfigChainConfigSet) - if err := _CCIPConfig.contract.UnpackLog(event, "ChainConfigSet", 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 (_CCIPConfig *CCIPConfigFilterer) ParseChainConfigSet(log types.Log) (*CCIPConfigChainConfigSet, error) { - event := new(CCIPConfigChainConfigSet) - if err := _CCIPConfig.contract.UnpackLog(event, "ChainConfigSet", log); err != nil { - return nil, err - } - event.Raw = log - return event, nil -} - -type CCIPConfigOwnershipTransferRequestedIterator struct { - Event *CCIPConfigOwnershipTransferRequested - - contract *bind.BoundContract - event string - - logs chan types.Log - sub ethereum.Subscription - done bool - fail error -} - -func (it *CCIPConfigOwnershipTransferRequestedIterator) Next() bool { - - if it.fail != nil { - return false - } - - if it.done { - select { - case log := <-it.logs: - it.Event = new(CCIPConfigOwnershipTransferRequested) - 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(CCIPConfigOwnershipTransferRequested) - 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 *CCIPConfigOwnershipTransferRequestedIterator) Error() error { - return it.fail -} - -func (it *CCIPConfigOwnershipTransferRequestedIterator) Close() error { - it.sub.Unsubscribe() - return nil -} - -type CCIPConfigOwnershipTransferRequested struct { - From common.Address - To common.Address - Raw types.Log -} - -func (_CCIPConfig *CCIPConfigFilterer) FilterOwnershipTransferRequested(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*CCIPConfigOwnershipTransferRequestedIterator, 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 := _CCIPConfig.contract.FilterLogs(opts, "OwnershipTransferRequested", fromRule, toRule) - if err != nil { - return nil, err - } - return &CCIPConfigOwnershipTransferRequestedIterator{contract: _CCIPConfig.contract, event: "OwnershipTransferRequested", logs: logs, sub: sub}, nil -} - -func (_CCIPConfig *CCIPConfigFilterer) WatchOwnershipTransferRequested(opts *bind.WatchOpts, sink chan<- *CCIPConfigOwnershipTransferRequested, 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 := _CCIPConfig.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(CCIPConfigOwnershipTransferRequested) - if err := _CCIPConfig.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 (_CCIPConfig *CCIPConfigFilterer) ParseOwnershipTransferRequested(log types.Log) (*CCIPConfigOwnershipTransferRequested, error) { - event := new(CCIPConfigOwnershipTransferRequested) - if err := _CCIPConfig.contract.UnpackLog(event, "OwnershipTransferRequested", log); err != nil { - return nil, err - } - event.Raw = log - return event, nil -} - -type CCIPConfigOwnershipTransferredIterator struct { - Event *CCIPConfigOwnershipTransferred - - contract *bind.BoundContract - event string - - logs chan types.Log - sub ethereum.Subscription - done bool - fail error -} - -func (it *CCIPConfigOwnershipTransferredIterator) Next() bool { - - if it.fail != nil { - return false - } - - if it.done { - select { - case log := <-it.logs: - it.Event = new(CCIPConfigOwnershipTransferred) - 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(CCIPConfigOwnershipTransferred) - 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 *CCIPConfigOwnershipTransferredIterator) Error() error { - return it.fail -} - -func (it *CCIPConfigOwnershipTransferredIterator) Close() error { - it.sub.Unsubscribe() - return nil -} - -type CCIPConfigOwnershipTransferred struct { - From common.Address - To common.Address - Raw types.Log -} - -func (_CCIPConfig *CCIPConfigFilterer) FilterOwnershipTransferred(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*CCIPConfigOwnershipTransferredIterator, 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 := _CCIPConfig.contract.FilterLogs(opts, "OwnershipTransferred", fromRule, toRule) - if err != nil { - return nil, err - } - return &CCIPConfigOwnershipTransferredIterator{contract: _CCIPConfig.contract, event: "OwnershipTransferred", logs: logs, sub: sub}, nil -} - -func (_CCIPConfig *CCIPConfigFilterer) WatchOwnershipTransferred(opts *bind.WatchOpts, sink chan<- *CCIPConfigOwnershipTransferred, 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 := _CCIPConfig.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(CCIPConfigOwnershipTransferred) - if err := _CCIPConfig.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 (_CCIPConfig *CCIPConfigFilterer) ParseOwnershipTransferred(log types.Log) (*CCIPConfigOwnershipTransferred, error) { - event := new(CCIPConfigOwnershipTransferred) - if err := _CCIPConfig.contract.UnpackLog(event, "OwnershipTransferred", log); err != nil { - return nil, err - } - event.Raw = log - return event, nil -} - -func (_CCIPConfig *CCIPConfig) ParseLog(log types.Log) (generated.AbigenLog, error) { - switch log.Topics[0] { - case _CCIPConfig.abi.Events["CapabilityConfigurationSet"].ID: - return _CCIPConfig.ParseCapabilityConfigurationSet(log) - case _CCIPConfig.abi.Events["ChainConfigRemoved"].ID: - return _CCIPConfig.ParseChainConfigRemoved(log) - case _CCIPConfig.abi.Events["ChainConfigSet"].ID: - return _CCIPConfig.ParseChainConfigSet(log) - case _CCIPConfig.abi.Events["OwnershipTransferRequested"].ID: - return _CCIPConfig.ParseOwnershipTransferRequested(log) - case _CCIPConfig.abi.Events["OwnershipTransferred"].ID: - return _CCIPConfig.ParseOwnershipTransferred(log) - - default: - return nil, fmt.Errorf("abigen wrapper received unknown log topic: %v", log.Topics[0]) - } -} - -func (CCIPConfigCapabilityConfigurationSet) Topic() common.Hash { - return common.HexToHash("0x84ad7751b744c9e2ee77da1d902b428aec7f0a343d67a24bbe2142e6f58a8d0f") -} - -func (CCIPConfigChainConfigRemoved) Topic() common.Hash { - return common.HexToHash("0x2a680691fef3b2d105196805935232c661ce703e92d464ef0b94a7bc62d714f0") -} - -func (CCIPConfigChainConfigSet) Topic() common.Hash { - return common.HexToHash("0x05dd57854af2c291a94ea52e7c43d80bc3be7fa73022f98b735dea86642fa5e0") -} - -func (CCIPConfigOwnershipTransferRequested) Topic() common.Hash { - return common.HexToHash("0xed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae1278") -} - -func (CCIPConfigOwnershipTransferred) Topic() common.Hash { - return common.HexToHash("0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0") -} - -func (_CCIPConfig *CCIPConfig) Address() common.Address { - return _CCIPConfig.address -} - -type CCIPConfigInterface interface { - GetAllChainConfigs(opts *bind.CallOpts, pageIndex *big.Int, pageSize *big.Int) ([]CCIPConfigTypesChainConfigInfo, error) - - GetCapabilityConfiguration(opts *bind.CallOpts, arg0 uint32) ([]byte, error) - - GetCapabilityRegistry(opts *bind.CallOpts) (common.Address, error) - - GetOCRConfig(opts *bind.CallOpts, donId uint32, pluginType uint8) ([]CCIPConfigTypesOCR3ConfigWithMeta, 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) - - ApplyChainConfigUpdates(opts *bind.TransactOpts, chainSelectorRemoves []uint64, chainConfigAdds []CCIPConfigTypesChainConfigInfo) (*types.Transaction, error) - - BeforeCapabilityConfigSet(opts *bind.TransactOpts, arg0 [][32]byte, config []byte, arg2 uint64, donId uint32) (*types.Transaction, error) - - TransferOwnership(opts *bind.TransactOpts, to common.Address) (*types.Transaction, error) - - FilterCapabilityConfigurationSet(opts *bind.FilterOpts) (*CCIPConfigCapabilityConfigurationSetIterator, error) - - WatchCapabilityConfigurationSet(opts *bind.WatchOpts, sink chan<- *CCIPConfigCapabilityConfigurationSet) (event.Subscription, error) - - ParseCapabilityConfigurationSet(log types.Log) (*CCIPConfigCapabilityConfigurationSet, error) - - FilterChainConfigRemoved(opts *bind.FilterOpts) (*CCIPConfigChainConfigRemovedIterator, error) - - WatchChainConfigRemoved(opts *bind.WatchOpts, sink chan<- *CCIPConfigChainConfigRemoved) (event.Subscription, error) - - ParseChainConfigRemoved(log types.Log) (*CCIPConfigChainConfigRemoved, error) - - FilterChainConfigSet(opts *bind.FilterOpts) (*CCIPConfigChainConfigSetIterator, error) - - WatchChainConfigSet(opts *bind.WatchOpts, sink chan<- *CCIPConfigChainConfigSet) (event.Subscription, error) - - ParseChainConfigSet(log types.Log) (*CCIPConfigChainConfigSet, error) - - FilterOwnershipTransferRequested(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*CCIPConfigOwnershipTransferRequestedIterator, error) - - WatchOwnershipTransferRequested(opts *bind.WatchOpts, sink chan<- *CCIPConfigOwnershipTransferRequested, from []common.Address, to []common.Address) (event.Subscription, error) - - ParseOwnershipTransferRequested(log types.Log) (*CCIPConfigOwnershipTransferRequested, error) - - FilterOwnershipTransferred(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*CCIPConfigOwnershipTransferredIterator, error) - - WatchOwnershipTransferred(opts *bind.WatchOpts, sink chan<- *CCIPConfigOwnershipTransferred, from []common.Address, to []common.Address) (event.Subscription, error) - - ParseOwnershipTransferred(log types.Log) (*CCIPConfigOwnershipTransferred, error) - - ParseLog(log types.Log) (generated.AbigenLog, error) - - Address() common.Address -} diff --git a/core/gethwrappers/ccip/generated/ccip_home/ccip_home.go b/core/gethwrappers/ccip/generated/ccip_home/ccip_home.go index b44dc9a5f96..7ea7f633147 100644 --- a/core/gethwrappers/ccip/generated/ccip_home/ccip_home.go +++ b/core/gethwrappers/ccip/generated/ccip_home/ccip_home.go @@ -65,8 +65,8 @@ type CCIPHomeVersionedConfig struct { } var CCIPHomeMetaData = &bind.MetaData{ - ABI: "[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"capabilitiesRegistry\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[],\"name\":\"CanOnlySelfCall\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"CannotTransferToSelf\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"chainSelector\",\"type\":\"uint64\"}],\"name\":\"ChainSelectorNotFound\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"ChainSelectorNotSet\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"expectedConfigDigest\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"gotConfigDigest\",\"type\":\"bytes32\"}],\"name\":\"ConfigDigestMismatch\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"callDonId\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"capabilityRegistryDonId\",\"type\":\"uint32\"}],\"name\":\"DONIdMismatch\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"FChainMustBePositive\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"fChain\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"FRoleDON\",\"type\":\"uint256\"}],\"name\":\"FChainTooHigh\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"FTooHigh\",\"type\":\"error\"},{\"inputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"p2pId\",\"type\":\"bytes32\"},{\"internalType\":\"bytes\",\"name\":\"signerKey\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"transmitterKey\",\"type\":\"bytes\"}],\"internalType\":\"structCCIPHome.OCR3Node\",\"name\":\"node\",\"type\":\"tuple\"}],\"name\":\"InvalidNode\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidPluginType\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes4\",\"name\":\"selector\",\"type\":\"bytes4\"}],\"name\":\"InvalidSelector\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"MustBeProposedOwner\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"NoOpStateTransitionNotAllowed\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"got\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"minimum\",\"type\":\"uint256\"}],\"name\":\"NotEnoughTransmitters\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"OfframpAddressCannotBeZero\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"OnlyCallableByOwner\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"OnlyCapabilitiesRegistryCanCall\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"OwnerCannotBeZero\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"RMNHomeAddressCannotBeZero\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"RevokingZeroDigestNotAllowed\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"TooManySigners\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"ZeroAddressNotAllowed\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"configDigest\",\"type\":\"bytes32\"}],\"name\":\"ActiveConfigRevoked\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"configDigest\",\"type\":\"bytes32\"}],\"name\":\"CandidateConfigRevoked\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[],\"name\":\"CapabilityConfigurationSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"chainSelector\",\"type\":\"uint64\"}],\"name\":\"ChainConfigRemoved\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"chainSelector\",\"type\":\"uint64\"},{\"components\":[{\"internalType\":\"bytes32[]\",\"name\":\"readers\",\"type\":\"bytes32[]\"},{\"internalType\":\"uint8\",\"name\":\"fChain\",\"type\":\"uint8\"},{\"internalType\":\"bytes\",\"name\":\"config\",\"type\":\"bytes\"}],\"indexed\":false,\"internalType\":\"structCCIPHome.ChainConfig\",\"name\":\"chainConfig\",\"type\":\"tuple\"}],\"name\":\"ChainConfigSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"configDigest\",\"type\":\"bytes32\"}],\"name\":\"ConfigPromoted\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"configDigest\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"uint32\",\"name\":\"version\",\"type\":\"uint32\"},{\"components\":[{\"internalType\":\"enumInternal.OCRPluginType\",\"name\":\"pluginType\",\"type\":\"uint8\"},{\"internalType\":\"uint64\",\"name\":\"chainSelector\",\"type\":\"uint64\"},{\"internalType\":\"uint8\",\"name\":\"FRoleDON\",\"type\":\"uint8\"},{\"internalType\":\"uint64\",\"name\":\"offchainConfigVersion\",\"type\":\"uint64\"},{\"internalType\":\"bytes\",\"name\":\"offrampAddress\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"rmnHomeAddress\",\"type\":\"bytes\"},{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"p2pId\",\"type\":\"bytes32\"},{\"internalType\":\"bytes\",\"name\":\"signerKey\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"transmitterKey\",\"type\":\"bytes\"}],\"internalType\":\"structCCIPHome.OCR3Node[]\",\"name\":\"nodes\",\"type\":\"tuple[]\"},{\"internalType\":\"bytes\",\"name\":\"offchainConfig\",\"type\":\"bytes\"}],\"indexed\":false,\"internalType\":\"structCCIPHome.OCR3Config\",\"name\":\"config\",\"type\":\"tuple\"}],\"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\":[{\"internalType\":\"uint64[]\",\"name\":\"chainSelectorRemoves\",\"type\":\"uint64[]\"},{\"components\":[{\"internalType\":\"uint64\",\"name\":\"chainSelector\",\"type\":\"uint64\"},{\"components\":[{\"internalType\":\"bytes32[]\",\"name\":\"readers\",\"type\":\"bytes32[]\"},{\"internalType\":\"uint8\",\"name\":\"fChain\",\"type\":\"uint8\"},{\"internalType\":\"bytes\",\"name\":\"config\",\"type\":\"bytes\"}],\"internalType\":\"structCCIPHome.ChainConfig\",\"name\":\"chainConfig\",\"type\":\"tuple\"}],\"internalType\":\"structCCIPHome.ChainConfigArgs[]\",\"name\":\"chainConfigAdds\",\"type\":\"tuple[]\"}],\"name\":\"applyChainConfigUpdates\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32[]\",\"name\":\"\",\"type\":\"bytes32[]\"},{\"internalType\":\"bytes\",\"name\":\"update\",\"type\":\"bytes\"},{\"internalType\":\"uint64\",\"name\":\"\",\"type\":\"uint64\"},{\"internalType\":\"uint32\",\"name\":\"donId\",\"type\":\"uint32\"}],\"name\":\"beforeCapabilityConfigSet\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"donId\",\"type\":\"uint32\"},{\"internalType\":\"enumInternal.OCRPluginType\",\"name\":\"pluginType\",\"type\":\"uint8\"}],\"name\":\"getActiveDigest\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"pageIndex\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"pageSize\",\"type\":\"uint256\"}],\"name\":\"getAllChainConfigs\",\"outputs\":[{\"components\":[{\"internalType\":\"uint64\",\"name\":\"chainSelector\",\"type\":\"uint64\"},{\"components\":[{\"internalType\":\"bytes32[]\",\"name\":\"readers\",\"type\":\"bytes32[]\"},{\"internalType\":\"uint8\",\"name\":\"fChain\",\"type\":\"uint8\"},{\"internalType\":\"bytes\",\"name\":\"config\",\"type\":\"bytes\"}],\"internalType\":\"structCCIPHome.ChainConfig\",\"name\":\"chainConfig\",\"type\":\"tuple\"}],\"internalType\":\"structCCIPHome.ChainConfigArgs[]\",\"name\":\"\",\"type\":\"tuple[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"donId\",\"type\":\"uint32\"},{\"internalType\":\"enumInternal.OCRPluginType\",\"name\":\"pluginType\",\"type\":\"uint8\"}],\"name\":\"getAllConfigs\",\"outputs\":[{\"components\":[{\"internalType\":\"uint32\",\"name\":\"version\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"configDigest\",\"type\":\"bytes32\"},{\"components\":[{\"internalType\":\"enumInternal.OCRPluginType\",\"name\":\"pluginType\",\"type\":\"uint8\"},{\"internalType\":\"uint64\",\"name\":\"chainSelector\",\"type\":\"uint64\"},{\"internalType\":\"uint8\",\"name\":\"FRoleDON\",\"type\":\"uint8\"},{\"internalType\":\"uint64\",\"name\":\"offchainConfigVersion\",\"type\":\"uint64\"},{\"internalType\":\"bytes\",\"name\":\"offrampAddress\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"rmnHomeAddress\",\"type\":\"bytes\"},{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"p2pId\",\"type\":\"bytes32\"},{\"internalType\":\"bytes\",\"name\":\"signerKey\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"transmitterKey\",\"type\":\"bytes\"}],\"internalType\":\"structCCIPHome.OCR3Node[]\",\"name\":\"nodes\",\"type\":\"tuple[]\"},{\"internalType\":\"bytes\",\"name\":\"offchainConfig\",\"type\":\"bytes\"}],\"internalType\":\"structCCIPHome.OCR3Config\",\"name\":\"config\",\"type\":\"tuple\"}],\"internalType\":\"structCCIPHome.VersionedConfig\",\"name\":\"activeConfig\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"uint32\",\"name\":\"version\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"configDigest\",\"type\":\"bytes32\"},{\"components\":[{\"internalType\":\"enumInternal.OCRPluginType\",\"name\":\"pluginType\",\"type\":\"uint8\"},{\"internalType\":\"uint64\",\"name\":\"chainSelector\",\"type\":\"uint64\"},{\"internalType\":\"uint8\",\"name\":\"FRoleDON\",\"type\":\"uint8\"},{\"internalType\":\"uint64\",\"name\":\"offchainConfigVersion\",\"type\":\"uint64\"},{\"internalType\":\"bytes\",\"name\":\"offrampAddress\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"rmnHomeAddress\",\"type\":\"bytes\"},{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"p2pId\",\"type\":\"bytes32\"},{\"internalType\":\"bytes\",\"name\":\"signerKey\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"transmitterKey\",\"type\":\"bytes\"}],\"internalType\":\"structCCIPHome.OCR3Node[]\",\"name\":\"nodes\",\"type\":\"tuple[]\"},{\"internalType\":\"bytes\",\"name\":\"offchainConfig\",\"type\":\"bytes\"}],\"internalType\":\"structCCIPHome.OCR3Config\",\"name\":\"config\",\"type\":\"tuple\"}],\"internalType\":\"structCCIPHome.VersionedConfig\",\"name\":\"candidateConfig\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"donId\",\"type\":\"uint32\"},{\"internalType\":\"enumInternal.OCRPluginType\",\"name\":\"pluginType\",\"type\":\"uint8\"}],\"name\":\"getCandidateDigest\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"}],\"name\":\"getCapabilityConfiguration\",\"outputs\":[{\"internalType\":\"bytes\",\"name\":\"configuration\",\"type\":\"bytes\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getCapabilityRegistry\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"donId\",\"type\":\"uint32\"},{\"internalType\":\"enumInternal.OCRPluginType\",\"name\":\"pluginType\",\"type\":\"uint8\"},{\"internalType\":\"bytes32\",\"name\":\"configDigest\",\"type\":\"bytes32\"}],\"name\":\"getConfig\",\"outputs\":[{\"components\":[{\"internalType\":\"uint32\",\"name\":\"version\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"configDigest\",\"type\":\"bytes32\"},{\"components\":[{\"internalType\":\"enumInternal.OCRPluginType\",\"name\":\"pluginType\",\"type\":\"uint8\"},{\"internalType\":\"uint64\",\"name\":\"chainSelector\",\"type\":\"uint64\"},{\"internalType\":\"uint8\",\"name\":\"FRoleDON\",\"type\":\"uint8\"},{\"internalType\":\"uint64\",\"name\":\"offchainConfigVersion\",\"type\":\"uint64\"},{\"internalType\":\"bytes\",\"name\":\"offrampAddress\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"rmnHomeAddress\",\"type\":\"bytes\"},{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"p2pId\",\"type\":\"bytes32\"},{\"internalType\":\"bytes\",\"name\":\"signerKey\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"transmitterKey\",\"type\":\"bytes\"}],\"internalType\":\"structCCIPHome.OCR3Node[]\",\"name\":\"nodes\",\"type\":\"tuple[]\"},{\"internalType\":\"bytes\",\"name\":\"offchainConfig\",\"type\":\"bytes\"}],\"internalType\":\"structCCIPHome.OCR3Config\",\"name\":\"config\",\"type\":\"tuple\"}],\"internalType\":\"structCCIPHome.VersionedConfig\",\"name\":\"versionedConfig\",\"type\":\"tuple\"},{\"internalType\":\"bool\",\"name\":\"ok\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"donId\",\"type\":\"uint32\"},{\"internalType\":\"enumInternal.OCRPluginType\",\"name\":\"pluginType\",\"type\":\"uint8\"}],\"name\":\"getConfigDigests\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"activeConfigDigest\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"candidateConfigDigest\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getNumChainConfigurations\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"donId\",\"type\":\"uint32\"},{\"internalType\":\"enumInternal.OCRPluginType\",\"name\":\"pluginType\",\"type\":\"uint8\"},{\"internalType\":\"bytes32\",\"name\":\"digestToPromote\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"digestToRevoke\",\"type\":\"bytes32\"}],\"name\":\"promoteCandidateAndRevokeActive\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"donId\",\"type\":\"uint32\"},{\"internalType\":\"enumInternal.OCRPluginType\",\"name\":\"pluginType\",\"type\":\"uint8\"},{\"internalType\":\"bytes32\",\"name\":\"configDigest\",\"type\":\"bytes32\"}],\"name\":\"revokeCandidate\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"donId\",\"type\":\"uint32\"},{\"internalType\":\"enumInternal.OCRPluginType\",\"name\":\"pluginType\",\"type\":\"uint8\"},{\"components\":[{\"internalType\":\"enumInternal.OCRPluginType\",\"name\":\"pluginType\",\"type\":\"uint8\"},{\"internalType\":\"uint64\",\"name\":\"chainSelector\",\"type\":\"uint64\"},{\"internalType\":\"uint8\",\"name\":\"FRoleDON\",\"type\":\"uint8\"},{\"internalType\":\"uint64\",\"name\":\"offchainConfigVersion\",\"type\":\"uint64\"},{\"internalType\":\"bytes\",\"name\":\"offrampAddress\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"rmnHomeAddress\",\"type\":\"bytes\"},{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"p2pId\",\"type\":\"bytes32\"},{\"internalType\":\"bytes\",\"name\":\"signerKey\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"transmitterKey\",\"type\":\"bytes\"}],\"internalType\":\"structCCIPHome.OCR3Node[]\",\"name\":\"nodes\",\"type\":\"tuple[]\"},{\"internalType\":\"bytes\",\"name\":\"offchainConfig\",\"type\":\"bytes\"}],\"internalType\":\"structCCIPHome.OCR3Config\",\"name\":\"config\",\"type\":\"tuple\"},{\"internalType\":\"bytes32\",\"name\":\"digestToOverwrite\",\"type\":\"bytes32\"}],\"name\":\"setCandidate\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"newConfigDigest\",\"type\":\"bytes32\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes4\",\"name\":\"interfaceId\",\"type\":\"bytes4\"}],\"name\":\"supportsInterface\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"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\":\"view\",\"type\":\"function\"}]", - Bin: "0x60a06040526006805463ffffffff191690553480156200001e57600080fd5b5060405162004c7a38038062004c7a83398101604081905262000041916200014c565b336000816200006357604051639b15e16f60e01b815260040160405180910390fd5b600180546001600160a01b0319166001600160a01b038481169190911790915581161562000096576200009681620000d2565b50506001600160a01b038116620000c0576040516342bcdf7f60e11b815260040160405180910390fd5b6001600160a01b03166080526200017e565b336001600160a01b03821603620000fc57604051636d6c4ee560e11b815260040160405180910390fd5b600080546001600160a01b0319166001600160a01b03838116918217835560015460405192939116917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b6000602082840312156200015f57600080fd5b81516001600160a01b03811681146200017757600080fd5b9392505050565b608051614ad2620001a860003960008181610180015281816121eb0152612c450152614ad26000f3fe608060405234801561001057600080fd5b50600436106101515760003560e01c806379ba5097116100cd578063b74b235611610081578063f2fde38b11610066578063f2fde38b14610356578063f442c89a14610369578063fba64a7c1461037c57600080fd5b8063b74b235614610323578063bae4e0fa1461034357600080fd5b80638318ed5d116100b25780638318ed5d146102d15780638da5cb5b146102f2578063922ea4061461031057600080fd5b806379ba5097146102c15780637ac0d41e146102c957600080fd5b80633df45a72116101245780635a837f97116101095780635a837f97146102785780635f1edd9c1461028d5780637524051a146102ae57600080fd5b80633df45a721461022f5780634851d5491461025057600080fd5b806301ffc9a714610156578063020330e61461017e578063181f5a77146101c557806333d9704a1461020e575b600080fd5b610169610164366004612fb2565b61038f565b60405190151581526020015b60405180910390f35b7f00000000000000000000000000000000000000000000000000000000000000005b60405173ffffffffffffffffffffffffffffffffffffffff9091168152602001610175565b6102016040518060400160405280601281526020017f43434950486f6d6520312e362e302d646576000000000000000000000000000081525081565b6040516101759190613044565b61022161021c366004613091565b610428565b6040516101759291906132d8565b61024261023d3660046132fc565b610922565b604051610175929190613335565b61026361025e3660046132fc565b611222565b60408051928352602083019190915201610175565b61028b61028636600461335a565b611318565b005b6102a061029b3660046132fc565b61162b565b604051908152602001610175565b61028b6102bc366004613091565b6116a2565b61028b61188d565b6102a061195b565b6102016102df3660046133a0565b5060408051602081019091526000815290565b60015473ffffffffffffffffffffffffffffffffffffffff166101a0565b6102a061031e3660046132fc565b61196c565b6103366103313660046133bd565b6119bd565b6040516101759190613456565b6102a06103513660046134f4565b611c22565b61028b610364366004613564565b611e20565b61028b6103773660046135df565b611e34565b61028b61038a36600461366c565b6121d3565b60007fffffffff0000000000000000000000000000000000000000000000000000000082167f78bea72100000000000000000000000000000000000000000000000000000000148061042257507fffffffff0000000000000000000000000000000000000000000000000000000082167f01ffc9a700000000000000000000000000000000000000000000000000000000145b92915050565b610430612e3b565b6000805b60028110156109145763ffffffff861660009081526005602052604081208591876001811115610466576104666130d2565b6001811115610477576104776130d2565b8152602001908152602001600020826002811061049657610496613729565b60070201600101541480156104aa57508315155b1561090c5763ffffffff86166000908152600560205260408120908660018111156104d7576104d76130d2565b60018111156104e8576104e86130d2565b8152602001908152602001600020816002811061050757610507613729565b6040805160608101825260079290920292909201805463ffffffff1682526001808201546020840152835161010081018552600283018054939592949386938501929190829060ff1687811115610560576105606130d2565b6001811115610571576105716130d2565b8152815467ffffffffffffffff61010082048116602084015260ff690100000000000000000083041660408401526a01000000000000000000009091041660608201526001820180546080909201916105c990613758565b80601f01602080910402602001604051908101604052809291908181526020018280546105f590613758565b80156106425780601f1061061757610100808354040283529160200191610642565b820191906000526020600020905b81548152906001019060200180831161062557829003601f168201915b5050505050815260200160028201805461065b90613758565b80601f016020809104026020016040519081016040528092919081815260200182805461068790613758565b80156106d45780601f106106a9576101008083540402835291602001916106d4565b820191906000526020600020905b8154815290600101906020018083116106b757829003601f168201915b5050505050815260200160038201805480602002602001604051908101604052809291908181526020016000905b8282101561086257838290600052602060002090600302016040518060600160405290816000820154815260200160018201805461073f90613758565b80601f016020809104026020016040519081016040528092919081815260200182805461076b90613758565b80156107b85780601f1061078d576101008083540402835291602001916107b8565b820191906000526020600020905b81548152906001019060200180831161079b57829003601f168201915b505050505081526020016002820180546107d190613758565b80601f01602080910402602001604051908101604052809291908181526020018280546107fd90613758565b801561084a5780601f1061081f5761010080835404028352916020019161084a565b820191906000526020600020905b81548152906001019060200180831161082d57829003601f168201915b50505050508152505081526020019060010190610702565b50505050815260200160048201805461087a90613758565b80601f01602080910402602001604051908101604052809291908181526020018280546108a690613758565b80156108f35780601f106108c8576101008083540402835291602001916108f3565b820191906000526020600020905b8154815290600101906020018083116108d657829003601f168201915b505050505081525050815250509150925092505061091a565b600101610434565b50600090505b935093915050565b61092a612e3b565b610932612e3b565b63ffffffff841660009081526005602052604081208185600181111561095a5761095a6130d2565b600181111561096b5761096b6130d2565b81526020019081526020016000206109838686612490565b63ffffffff166002811061099957610999613729565b6040805160608101825260079290920292909201805463ffffffff1682526001808201546020840152835161010081018552600283018054949593949386019391929091839160ff909116908111156109f4576109f46130d2565b6001811115610a0557610a056130d2565b8152815467ffffffffffffffff61010082048116602084015260ff690100000000000000000083041660408401526a0100000000000000000000909104166060820152600182018054608090920191610a5d90613758565b80601f0160208091040260200160405190810160405280929190818152602001828054610a8990613758565b8015610ad65780601f10610aab57610100808354040283529160200191610ad6565b820191906000526020600020905b815481529060010190602001808311610ab957829003601f168201915b50505050508152602001600282018054610aef90613758565b80601f0160208091040260200160405190810160405280929190818152602001828054610b1b90613758565b8015610b685780601f10610b3d57610100808354040283529160200191610b68565b820191906000526020600020905b815481529060010190602001808311610b4b57829003601f168201915b5050505050815260200160038201805480602002602001604051908101604052809291908181526020016000905b82821015610cf6578382906000526020600020906003020160405180606001604052908160008201548152602001600182018054610bd390613758565b80601f0160208091040260200160405190810160405280929190818152602001828054610bff90613758565b8015610c4c5780601f10610c2157610100808354040283529160200191610c4c565b820191906000526020600020905b815481529060010190602001808311610c2f57829003601f168201915b50505050508152602001600282018054610c6590613758565b80601f0160208091040260200160405190810160405280929190818152602001828054610c9190613758565b8015610cde5780601f10610cb357610100808354040283529160200191610cde565b820191906000526020600020905b815481529060010190602001808311610cc157829003601f168201915b50505050508152505081526020019060010190610b96565b505050508152602001600482018054610d0e90613758565b80601f0160208091040260200160405190810160405280929190818152602001828054610d3a90613758565b8015610d875780601f10610d5c57610100808354040283529160200191610d87565b820191906000526020600020905b815481529060010190602001808311610d6a57829003601f168201915b50505091909252505050905250602081015190915015610da5578092505b63ffffffff8516600090815260056020526040812081866001811115610dcd57610dcd6130d2565b6001811115610dde57610dde6130d2565b8152602001908152602001600020610df687876124e7565b63ffffffff1660028110610e0c57610e0c613729565b6040805160608101825260079290920292909201805463ffffffff1682526001808201546020840152835161010081018552600283018054949593949386019391929091839160ff90911690811115610e6757610e676130d2565b6001811115610e7857610e786130d2565b8152815467ffffffffffffffff61010082048116602084015260ff690100000000000000000083041660408401526a0100000000000000000000909104166060820152600182018054608090920191610ed090613758565b80601f0160208091040260200160405190810160405280929190818152602001828054610efc90613758565b8015610f495780601f10610f1e57610100808354040283529160200191610f49565b820191906000526020600020905b815481529060010190602001808311610f2c57829003601f168201915b50505050508152602001600282018054610f6290613758565b80601f0160208091040260200160405190810160405280929190818152602001828054610f8e90613758565b8015610fdb5780601f10610fb057610100808354040283529160200191610fdb565b820191906000526020600020905b815481529060010190602001808311610fbe57829003601f168201915b5050505050815260200160038201805480602002602001604051908101604052809291908181526020016000905b8282101561116957838290600052602060002090600302016040518060600160405290816000820154815260200160018201805461104690613758565b80601f016020809104026020016040519081016040528092919081815260200182805461107290613758565b80156110bf5780601f10611094576101008083540402835291602001916110bf565b820191906000526020600020905b8154815290600101906020018083116110a257829003601f168201915b505050505081526020016002820180546110d890613758565b80601f016020809104026020016040519081016040528092919081815260200182805461110490613758565b80156111515780601f1061112657610100808354040283529160200191611151565b820191906000526020600020905b81548152906001019060200180831161113457829003601f168201915b50505050508152505081526020019060010190611009565b50505050815260200160048201805461118190613758565b80601f01602080910402602001604051908101604052809291908181526020018280546111ad90613758565b80156111fa5780601f106111cf576101008083540402835291602001916111fa565b820191906000526020600020905b8154815290600101906020018083116111dd57829003601f168201915b50505091909252505050905250602081015190915015611218578092505b50505b9250929050565b63ffffffff8216600090815260056020526040812081908184600181111561124c5761124c6130d2565b600181111561125d5761125d6130d2565b81526020019081526020016000206112758585612490565b63ffffffff166002811061128b5761128b613729565b6007020160010154600560008663ffffffff1663ffffffff16815260200190815260200160002060008560018111156112c6576112c66130d2565b60018111156112d7576112d76130d2565b81526020019081526020016000206112ef86866124e7565b63ffffffff166002811061130557611305613729565b6007020160010154915091509250929050565b611320612542565b8115801561132c575080155b15611363576040517f7b4d1e4f00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600061136f85856124e7565b63ffffffff86811660009081526005602052604081209290911692508491908660018111156113a0576113a06130d2565b60018111156113b1576113b16130d2565b815260200190815260200160002082600281106113d0576113d0613729565b6007020160010154146114845763ffffffff8516600090815260056020526040812090856001811115611405576114056130d2565b6001811115611416576114166130d2565b8152602001908152602001600020816002811061143557611435613729565b6007020160010154836040517f93df584c00000000000000000000000000000000000000000000000000000000815260040161147b929190918252602082015260400190565b60405180910390fd5b63ffffffff85166000908152600560205260408120818660018111156114ac576114ac6130d2565b60018111156114bd576114bd6130d2565b81526020019081526020016000206114d58787612490565b63ffffffff16600281106114eb576114eb613729565b6007020190508281600101541461153e5760018101546040517f93df584c00000000000000000000000000000000000000000000000000000000815260048101919091526024810184905260440161147b565b6000600180830182905563ffffffff881682526007602052604082209091878381111561156d5761156d6130d2565b600181111561157e5761157e6130d2565b8152602081019190915260400160002080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000811663ffffffff918216939093181691909117905582156115f85760405183907f0b31c0055e2d464bef7781994b98c4ff9ef4ae0d05f59feb6a68c42de5e201b890600090a25b60405184907ffc3e98dbbd47c3fa7c1c05b6ec711caeaf70eca4554192b9ada8fc11a37f298e90600090a2505050505050565b63ffffffff8216600090815260056020526040812081836001811115611653576116536130d2565b6001811115611664576116646130d2565b815260200190815260200160002061167c8484612490565b63ffffffff166002811061169257611692613729565b6007020160010154905092915050565b6116aa612542565b806116e1576040517f0849d8cc00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60006116ed84846124e7565b63ffffffff858116600090815260056020526040812092909116925083919085600181111561171e5761171e6130d2565b600181111561172f5761172f6130d2565b8152602001908152602001600020826002811061174e5761174e613729565b6007020160010154146117f95763ffffffff8416600090815260056020526040812090846001811115611783576117836130d2565b6001811115611794576117946130d2565b815260200190815260200160002081600281106117b3576117b3613729565b6007020160010154826040517f93df584c00000000000000000000000000000000000000000000000000000000815260040161147b929190918252602082015260400190565b60405182907f53f5d9228f0a4173bea6e5931c9b3afe6eeb6692ede1d182952970f152534e3b90600090a263ffffffff841660009081526005602052604081209084600181111561184c5761184c6130d2565b600181111561185d5761185d6130d2565b8152602001908152602001600020816002811061187c5761187c613729565b600702016001016000905550505050565b60005473ffffffffffffffffffffffffffffffffffffffff1633146118de576040517f02b543c600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600180547fffffffffffffffffffffffff00000000000000000000000000000000000000008082163390811790935560008054909116815560405173ffffffffffffffffffffffffffffffffffffffff909216929183917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a350565b6000611967600361257d565b905090565b63ffffffff8216600090815260056020526040812081836001811115611994576119946130d2565b60018111156119a5576119a56130d2565b815260200190815260200160002061167c84846124e7565b606060006119cb600361257d565b905060006119d984866137da565b90508315806119e85750818110155b15611a28576040805160008082526020820190925290611a1e565b611a0b612eb7565b815260200190600190039081611a035790505b5092505050610422565b6000611a348583613820565b905082811115611a415750815b6000611a4d8383613833565b67ffffffffffffffff811115611a6557611a656137f1565b604051908082528060200260200182016040528015611a9e57816020015b611a8b612eb7565b815260200190600190039081611a835790505b509050825b82811015611c17576000611ab8600383612587565b60408051808201825267ffffffffffffffff83168082526000908152600260209081529083902083518154608081850283018101909652606082018181529697509395928601949093919284929091849190840182828015611b3957602002820191906000526020600020905b815481526020019060010190808311611b25575b5050509183525050600182015460ff166020820152600282018054604090920191611b6390613758565b80601f0160208091040260200160405190810160405280929190818152602001828054611b8f90613758565b8015611bdc5780601f10611bb157610100808354040283529160200191611bdc565b820191906000526020600020905b815481529060010190602001808311611bbf57829003601f168201915b50505091909252505050905283611bf38785613833565b81518110611c0357611c03613729565b602090810291909101015250600101611aa3565b509695505050505050565b6000611c2c612542565b611c3d611c3884613a61565b61259a565b6000611c49868661196c565b9050828114611c8e576040517f93df584c000000000000000000000000000000000000000000000000000000008152600481018290526024810184905260440161147b565b8015611cc05760405183907f53f5d9228f0a4173bea6e5931c9b3afe6eeb6692ede1d182952970f152534e3b90600090a25b60068054600091908290611cd99063ffffffff16613b5d565b91906101000a81548163ffffffff021916908363ffffffff16021790559050611d23878787604051602001611d0e9190613e39565b60405160208183030381529060405284612a08565b63ffffffff881660009081526005602052604081209194509081886001811115611d4f57611d4f6130d2565b6001811115611d6057611d606130d2565b8152602001908152602001600020611d7889896124e7565b63ffffffff1660028110611d8e57611d8e613729565b600702016001810185905580547fffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000001663ffffffff841617815590508560028201611dd8828261431f565b905050837f94f085b7c57ec2a270befd0b7b2ec7452580040edee8bb0fb04609c81f0359c68388604051611e0d9291906144e6565b60405180910390a2505050949350505050565b611e28612ac8565b611e3181612b19565b50565b611e3c612ac8565b60005b8381101561202257611e83858583818110611e5c57611e5c613729565b9050602002016020810190611e71919061450d565b60039067ffffffffffffffff16612bdd565b611eed57848482818110611e9957611e99613729565b9050602002016020810190611eae919061450d565b6040517f1bd4d2d200000000000000000000000000000000000000000000000000000000815267ffffffffffffffff909116600482015260240161147b565b60026000868684818110611f0357611f03613729565b9050602002016020810190611f18919061450d565b67ffffffffffffffff1681526020810191909152604001600090812090611f3f8282612efa565b6001820180547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00169055611f77600283016000612f18565b5050611fb5858583818110611f8e57611f8e613729565b9050602002016020810190611fa3919061450d565b60039067ffffffffffffffff16612bf5565b507f2a680691fef3b2d105196805935232c661ce703e92d464ef0b94a7bc62d714f0858583818110611fe957611fe9613729565b9050602002016020810190611ffe919061450d565b60405167ffffffffffffffff909116815260200160405180910390a1600101611e3f565b5060005b818110156121cc57600083838381811061204257612042613729565b9050602002810190612054919061452a565b612062906020810190614082565b61206b9061455e565b9050600084848481811061208157612081613729565b9050602002810190612093919061452a565b6120a190602081019061450d565b90506120b08260000151612c01565b816020015160ff166000036120f1576040517fa9b3766e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b67ffffffffffffffff81166000908152600260209081526040909120835180518593612121928492910190612f52565b5060208201516001820180547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660ff9092169190911790556040820151600282019061216e9082614630565b5061218891506003905067ffffffffffffffff8316612cc3565b507f05dd57854af2c291a94ea52e7c43d80bc3be7fa73022f98b735dea86642fa5e081836040516121ba92919061472c565b60405180910390a15050600101612026565b5050505050565b3373ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001614612242576040517fac7a7efd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000612251600482868861474f565b61225a91614779565b90507fffffffff0000000000000000000000000000000000000000000000000000000081167fbae4e0fa00000000000000000000000000000000000000000000000000000000148015906122f057507fffffffff0000000000000000000000000000000000000000000000000000000081167f7524051a0000000000000000000000000000000000000000000000000000000014155b801561233e57507fffffffff0000000000000000000000000000000000000000000000000000000081167f5a837f970000000000000000000000000000000000000000000000000000000014155b15612399576040517f12ba286f0000000000000000000000000000000000000000000000000000000081527fffffffff000000000000000000000000000000000000000000000000000000008216600482015260240161147b565b60006123a960246004878961474f565b8101906123b691906147c1565b90508263ffffffff168114612407576040517f8a6e4ce800000000000000000000000000000000000000000000000000000000815263ffffffff80831660048301528416602482015260440161147b565b6000803073ffffffffffffffffffffffffffffffffffffffff1688886040516124319291906147da565b6000604051808303816000865af19150503d806000811461246e576040519150601f19603f3d011682016040523d82523d6000602084013e612473565b606091505b509150915081612484573d60208201fd5b50505050505050505050565b63ffffffff82166000908152600760205260408120818360018111156124b8576124b86130d2565b60018111156124c9576124c96130d2565b815260208101919091526040016000205463ffffffff169392505050565b63ffffffff821660009081526007602052604081208183600181111561250f5761250f6130d2565b6001811115612520576125206130d2565b815260208101919091526040016000205463ffffffff16600118905092915050565b33301461257b576040517f371a732800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b565b6000610422825490565b60006125938383612ccf565b9392505050565b806020015167ffffffffffffffff166000036125e2576040517f698cf8e000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000815160018111156125f7576125f76130d2565b141580156126185750600181516001811115612615576126156130d2565b14155b1561264f576040517f3302dbd700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b608081015151158061268c575060408051600060208201520160405160208183030381529060405280519060200120816080015180519060200120145b156126c3576040517f358c192700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60a08101515115806127005750604080516000602082015201604051602081830303815290604052805190602001208160a0015180519060200120145b15612737576040517fdee9857400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60208101516127529060039067ffffffffffffffff16612bdd565b61279a5760208101516040517f1bd4d2d200000000000000000000000000000000000000000000000000000000815267ffffffffffffffff909116600482015260240161147b565b60408082015160208084015167ffffffffffffffff1660009081526002909152919091206001015460ff91821691168181111561280d576040517f2db22040000000000000000000000000000000000000000000000000000000008152600481018290526024810183905260440161147b565b60c08301515161010081111561284f576040517f1b925da600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61285a8360036137da565b8111612892576040517f4856694e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000808267ffffffffffffffff8111156128ae576128ae6137f1565b6040519080825280602002602001820160405280156128d7578160200160208202803683370190505b50905060005b838110156129975760008760c0015182815181106128fd576128fd613729565b60200260200101519050806040015151600014612922578361291e816147ea565b9450505b602081015151158061293357508051155b1561296c57806040517f9fa4031400000000000000000000000000000000000000000000000000000000815260040161147b9190614822565b806000015183838151811061298357612983613729565b6020908102919091010152506001016128dd565b5060006129a58560036137da565b6129b0906001613820565b9050808310156129f6576040517f548dd21f000000000000000000000000000000000000000000000000000000008152600481018490526024810182905260440161147b565b6129ff82612c01565b50505050505050565b6040516000907dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff90612a66907f45564d000000000000000000000000000000000000000000000000000000000090469030908a908a908990602001614835565b60408051601f1981840301815290829052612a8591869060200161488e565b60408051808303601f190181529190528051602090910120167e0a0000000000000000000000000000000000000000000000000000000000001795945050505050565b60015473ffffffffffffffffffffffffffffffffffffffff16331461257b576040517f2b5c74de00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b3373ffffffffffffffffffffffffffffffffffffffff821603612b68576040517fdad89dca00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600080547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff838116918217835560015460405192939116917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b60008181526001830160205260408120541515612593565b60006125938383612cf9565b805115611e31576040517f05a5196600000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016906305a5196690612c7a9084906004016148bd565b600060405180830381865afa158015612c97573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052612cbf919081019061493a565b5050565b60006125938383612dec565b6000826000018281548110612ce657612ce6613729565b9060005260206000200154905092915050565b60008181526001830160205260408120548015612de2576000612d1d600183613833565b8554909150600090612d3190600190613833565b9050808214612d96576000866000018281548110612d5157612d51613729565b9060005260206000200154905080876000018481548110612d7457612d74613729565b6000918252602080832090910192909255918252600188019052604090208390555b8554869080612da757612da7614a96565b600190038181906000526020600020016000905590558560010160008681526020019081526020016000206000905560019350505050610422565b6000915050610422565b6000818152600183016020526040812054612e3357508154600181810184556000848152602080822090930184905584548482528286019093526040902091909155610422565b506000610422565b6040805160608101825260008082526020820152908101612eb26040805161010081019091528060008152602001600067ffffffffffffffff168152602001600060ff168152602001600067ffffffffffffffff168152602001606081526020016060815260200160608152602001606081525090565b905290565b6040518060400160405280600067ffffffffffffffff168152602001612eb2604051806060016040528060608152602001600060ff168152602001606081525090565b5080546000825590600052602060002090810190611e319190612f9d565b508054612f2490613758565b6000825580601f10612f34575050565b601f016020900490600052602060002090810190611e319190612f9d565b828054828255906000526020600020908101928215612f8d579160200282015b82811115612f8d578251825591602001919060010190612f72565b50612f99929150612f9d565b5090565b5b80821115612f995760008155600101612f9e565b600060208284031215612fc457600080fd5b81357fffffffff000000000000000000000000000000000000000000000000000000008116811461259357600080fd5b60005b8381101561300f578181015183820152602001612ff7565b50506000910152565b60008151808452613030816020860160208601612ff4565b601f01601f19169290920160200192915050565b6020815260006125936020830184613018565b63ffffffff81168114611e3157600080fd5b803561307481613057565b919050565b60028110611e3157600080fd5b803561307481613079565b6000806000606084860312156130a657600080fd5b83356130b181613057565b925060208401356130c181613079565b929592945050506040919091013590565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b60028110613138577f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b9052565b80518252600060208201516060602085015261315b6060850182613018565b9050604083015184820360408601526131748282613018565b95945050505050565b60008282518085526020808601955060208260051b8401016020860160005b848110156131ca57601f198684030189526131b883835161313c565b9884019892509083019060010161319c565b5090979650505050505050565b63ffffffff8151168252602081015160208301526000604082015160606040850152613207606085018251613101565b602081015167ffffffffffffffff8116608086015250604081015160ff811660a086015250606081015167ffffffffffffffff811660c08601525060808101516101008060e087015261325e610160870183613018565b915060a08301517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa08088850301838901526132998483613018565b935060c0850151925080888503016101208901526132b7848461317d565b935060e0850151945080888503016101408901525050506131748183613018565b6040815260006132eb60408301856131d7565b905082151560208301529392505050565b6000806040838503121561330f57600080fd5b823561331a81613057565b9150602083013561332a81613079565b809150509250929050565b60408152600061334860408301856131d7565b828103602084015261317481856131d7565b6000806000806080858703121561337057600080fd5b843561337b81613057565b9350602085013561338b81613079565b93969395505050506040820135916060013590565b6000602082840312156133b257600080fd5b813561259381613057565b600080604083850312156133d057600080fd5b50508035926020909101359150565b60008151808452602080850194506020840160005b83811015613410578151875295820195908201906001016133f4565b509495945050505050565b600081516060845261343060608501826133df565b905060ff6020840151166020850152604083015184820360408601526131748282613018565b600060208083018184528085518083526040925060408601915060408160051b87010184880160005b838110156134e6578883037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc00185528151805167ffffffffffffffff1684528701518784018790526134d38785018261341b565b958801959350509086019060010161347f565b509098975050505050505050565b6000806000806080858703121561350a57600080fd5b843561351581613057565b9350602085013561352581613079565b9250604085013567ffffffffffffffff81111561354157600080fd5b8501610100818803121561355457600080fd5b9396929550929360600135925050565b60006020828403121561357657600080fd5b813573ffffffffffffffffffffffffffffffffffffffff8116811461259357600080fd5b60008083601f8401126135ac57600080fd5b50813567ffffffffffffffff8111156135c457600080fd5b6020830191508360208260051b850101111561121b57600080fd5b600080600080604085870312156135f557600080fd5b843567ffffffffffffffff8082111561360d57600080fd5b6136198883890161359a565b9096509450602087013591508082111561363257600080fd5b5061363f8782880161359a565b95989497509550505050565b67ffffffffffffffff81168114611e3157600080fd5b80356130748161364b565b6000806000806000806080878903121561368557600080fd5b863567ffffffffffffffff8082111561369d57600080fd5b6136a98a838b0161359a565b909850965060208901359150808211156136c257600080fd5b818901915089601f8301126136d657600080fd5b8135818111156136e557600080fd5b8a60208285010111156136f757600080fd5b60208301965080955050505061370f60408801613661565b915061371d60608801613069565b90509295509295509295565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b600181811c9082168061376c57607f821691505b6020821081036137a5577f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b50919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b8082028115828204841417610422576104226137ab565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b80820180821115610422576104226137ab565b81810381811115610422576104226137ab565b6040516060810167ffffffffffffffff81118282101715613869576138696137f1565b60405290565b604051610100810167ffffffffffffffff81118282101715613869576138696137f1565b604051601f8201601f1916810167ffffffffffffffff811182821017156138bc576138bc6137f1565b604052919050565b60ff81168114611e3157600080fd5b8035613074816138c4565b600082601f8301126138ef57600080fd5b813567ffffffffffffffff811115613909576139096137f1565b61391c6020601f19601f84011601613893565b81815284602083860101111561393157600080fd5b816020850160208301376000918101602001919091529392505050565b600067ffffffffffffffff821115613968576139686137f1565b5060051b60200190565b600082601f83011261398357600080fd5b813560206139986139938361394e565b613893565b82815260059290921b840181019181810190868411156139b757600080fd5b8286015b84811015611c1757803567ffffffffffffffff808211156139dc5760008081fd5b8189019150606080601f19848d030112156139f75760008081fd5b6139ff613846565b87840135815260408085013584811115613a195760008081fd5b613a278e8b838901016138de565b838b015250918401359183831115613a3f5760008081fd5b613a4d8d8a858801016138de565b9082015286525050509183019183016139bb565b60006101008236031215613a7457600080fd5b613a7c61386f565b613a8583613086565b8152613a9360208401613661565b6020820152613aa4604084016138d3565b6040820152613ab560608401613661565b6060820152608083013567ffffffffffffffff80821115613ad557600080fd5b613ae1368387016138de565b608084015260a0850135915080821115613afa57600080fd5b613b06368387016138de565b60a084015260c0850135915080821115613b1f57600080fd5b613b2b36838701613972565b60c084015260e0850135915080821115613b4457600080fd5b50613b51368286016138de565b60e08301525092915050565b600063ffffffff808316818103613b7657613b766137ab565b6001019392505050565b60008083357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe1843603018112613bb557600080fd5b830160208101925035905067ffffffffffffffff811115613bd557600080fd5b80360382131561121b57600080fd5b818352818160208501375060006020828401015260006020601f19601f840116840101905092915050565b60008083357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe1843603018112613c4457600080fd5b830160208101925035905067ffffffffffffffff811115613c6457600080fd5b8060051b360382131561121b57600080fd5b60008383855260208086019550808560051b830101846000805b88811015613d3457601f19868503018a5282357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa1893603018112613cd2578283fd5b8801803585526060613ce687830183613b80565b8289890152613cf88389018284613be4565b925050506040613d0a81840184613b80565b935087830382890152613d1e838583613be4565b9d89019d97505050938601935050600101613c90565b509198975050505050505050565b6000610100613d5984613d5485613086565b613101565b613d6560208401613661565b67ffffffffffffffff166020850152613d80604084016138d3565b60ff166040850152613d9460608401613661565b67ffffffffffffffff166060850152613db06080840184613b80565b826080870152613dc38387018284613be4565b92505050613dd460a0840184613b80565b85830360a0870152613de7838284613be4565b92505050613df860c0840184613c0f565b85830360c0870152613e0b838284613c76565b92505050613e1c60e0840184613b80565b85830360e0870152613e2f838284613be4565b9695505050505050565b6020815260006125936020830184613d42565b600081356104228161364b565b60008135610422816138c4565b60008083357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe1843603018112613e9b57600080fd5b83018035915067ffffffffffffffff821115613eb657600080fd5b60200191503681900382131561121b57600080fd5b5b81811015612cbf5760008155600101613ecc565b601f821115613f1957806000526020600020601f840160051c81016020851015613f075750805b6121cc601f850160051c830182613ecb565b505050565b67ffffffffffffffff831115613f3657613f366137f1565b613f4a83613f448354613758565b83613ee0565b6000601f841160018114613f9c5760008515613f665750838201355b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600387901b1c1916600186901b1783556121cc565b600083815260209020601f19861690835b82811015613fcd5786850135825560209485019460019092019101613fad565b5086821015614008577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60f88860031b161c19848701351681555b505060018560011b0183555050505050565b60008083357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe184360301811261404f57600080fd5b83018035915067ffffffffffffffff82111561406a57600080fd5b6020019150600581901b360382131561121b57600080fd5b600082357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa18336030181126140b657600080fd5b9190910192915050565b6140ca8154613758565b8015612cbf57601f8111600181146140e457505060009055565b826000526020600020614102601f840160051c820160018301613ecb565b60008085559055505050565b81358155600180820160206141266020860186613e66565b67ffffffffffffffff81111561413e5761413e6137f1565b6141528161414c8654613758565b86613ee0565b6000601f8211600181146141a4576000831561416e5750838201355b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600385901b1c1916600184901b178655614219565b600086815260209020601f19841690835b828110156141d257868501358255938701939089019087016141b5565b508482101561420d577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60f88660031b161c19848701351681555b505060018360011b0186555b5050505050505061422d6040830183613e66565b61423b818360028601613f1e565b50505050565b6801000000000000000083111561425a5761425a6137f1565b8054838255808410156142d95760038160030260038104831461427f5761427f6137ab565b85600302600381048714614295576142956137ab565b6000858152602081209283019291909101905b828210156142d4578082556142bf600183016140c0565b6142cb600283016140c0565b908301906142a8565b505050505b5060008181526020812083915b85811015614317576143016142fb8487614082565b8361410e565b60209290920191600391909101906001016142e6565b505050505050565b813561432a81613079565b60028110614361577f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0082541660ff82168117835550506143d861439e60208401613e4c565b82547fffffffffffffffffffffffffffffffffffffffffffffff0000000000000000ff1660089190911b68ffffffffffffffff0016178255565b6144226143e760408401613e59565b82547fffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffff1660489190911b69ff00000000000000000016178255565b61447461443160608401613e4c565b82547fffffffffffffffffffffffffffff0000000000000000ffffffffffffffffffff1660509190911b71ffffffffffffffff0000000000000000000016178255565b6144816080830183613e66565b61448f818360018601613f1e565b505061449e60a0830183613e66565b6144ac818360028601613f1e565b50506144bb60c083018361401a565b6144c9818360038601614241565b50506144d860e0830183613e66565b61423b818360048601613f1e565b63ffffffff831681526040602082015260006145056040830184613d42565b949350505050565b60006020828403121561451f57600080fd5b81356125938161364b565b600082357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc18336030181126140b657600080fd5b60006060823603121561457057600080fd5b614578613846565b823567ffffffffffffffff8082111561459057600080fd5b9084019036601f8301126145a357600080fd5b813560206145b36139938361394e565b82815260059290921b840181019181810190368411156145d257600080fd5b948201945b838610156145f0578535825294820194908201906145d7565b8652506145fe8782016138d3565b9085015250604085013591508082111561461757600080fd5b50614624368286016138de565b60408301525092915050565b815167ffffffffffffffff81111561464a5761464a6137f1565b61465e816146588454613758565b84613ee0565b602080601f8311600181146146b1576000841561467b5750858301515b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600386901b1c1916600185901b178555614317565b600085815260208120601f198616915b828110156146e0578886015182559484019460019091019084016146c1565b508582101561471c57878501517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600388901b60f8161c191681555b5050505050600190811b01905550565b67ffffffffffffffff83168152604060208201526000614505604083018461341b565b6000808585111561475f57600080fd5b8386111561476c57600080fd5b5050820193919092039150565b7fffffffff0000000000000000000000000000000000000000000000000000000081358181169160048510156147b95780818660040360031b1b83161692505b505092915050565b6000602082840312156147d357600080fd5b5035919050565b8183823760009101908152919050565b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff820361481b5761481b6137ab565b5060010190565b602081526000612593602083018461313c565b8681526020810186905273ffffffffffffffffffffffffffffffffffffffff8516604082015263ffffffff848116606083015260c082019061487a6080840186613101565b80841660a084015250979650505050505050565b600083516148a0818460208801612ff4565b8351908301906148b4818360208801612ff4565b01949350505050565b60208152600061259360208301846133df565b805161307481613057565b600082601f8301126148ec57600080fd5b815160206148fc6139938361394e565b8083825260208201915060208460051b87010193508684111561491e57600080fd5b602086015b84811015611c175780518352918301918301614923565b6000602080838503121561494d57600080fd5b825167ffffffffffffffff8082111561496557600080fd5b818501915085601f83011261497957600080fd5b81516149876139938261394e565b81815260059190911b830184019084810190888311156149a657600080fd5b8585015b83811015614a89578051858111156149c157600080fd5b8601610100818c03601f19018113156149d957600080fd5b6149e161386f565b6149ec8a84016148d0565b81526149fa604084016148d0565b8a820152614a0a606084016148d0565b60408201526080830151606082015260a0830151608082015260c083015160a082015260e08084015189811115614a415760008081fd5b614a4f8f8d838801016148db565b60c084015250918301519188831115614a685760008081fd5b614a768e8c858701016148db565b90820152855250509186019186016149aa565b5098975050505050505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603160045260246000fdfea164736f6c6343000818000a", + ABI: "[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"capabilitiesRegistry\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[],\"name\":\"CanOnlySelfCall\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"CannotTransferToSelf\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"chainSelector\",\"type\":\"uint64\"}],\"name\":\"ChainSelectorNotFound\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"ChainSelectorNotSet\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"expectedConfigDigest\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"gotConfigDigest\",\"type\":\"bytes32\"}],\"name\":\"ConfigDigestMismatch\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"callDonId\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"capabilityRegistryDonId\",\"type\":\"uint32\"}],\"name\":\"DONIdMismatch\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"FChainMustBePositive\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"fChain\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"FRoleDON\",\"type\":\"uint256\"}],\"name\":\"FChainTooHigh\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"FTooHigh\",\"type\":\"error\"},{\"inputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"p2pId\",\"type\":\"bytes32\"},{\"internalType\":\"bytes\",\"name\":\"signerKey\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"transmitterKey\",\"type\":\"bytes\"}],\"internalType\":\"structCCIPHome.OCR3Node\",\"name\":\"node\",\"type\":\"tuple\"}],\"name\":\"InvalidNode\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidPluginType\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes4\",\"name\":\"selector\",\"type\":\"bytes4\"}],\"name\":\"InvalidSelector\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"MustBeProposedOwner\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"NoOpStateTransitionNotAllowed\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"got\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"minimum\",\"type\":\"uint256\"}],\"name\":\"NotEnoughTransmitters\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"OfframpAddressCannotBeZero\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"OnlyCallableByOwner\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"OnlyCapabilitiesRegistryCanCall\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"OwnerCannotBeZero\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"RMNHomeAddressCannotBeZero\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"RevokingZeroDigestNotAllowed\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"TooManySigners\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"ZeroAddressNotAllowed\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"configDigest\",\"type\":\"bytes32\"}],\"name\":\"ActiveConfigRevoked\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"configDigest\",\"type\":\"bytes32\"}],\"name\":\"CandidateConfigRevoked\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[],\"name\":\"CapabilityConfigurationSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"chainSelector\",\"type\":\"uint64\"}],\"name\":\"ChainConfigRemoved\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"chainSelector\",\"type\":\"uint64\"},{\"components\":[{\"internalType\":\"bytes32[]\",\"name\":\"readers\",\"type\":\"bytes32[]\"},{\"internalType\":\"uint8\",\"name\":\"fChain\",\"type\":\"uint8\"},{\"internalType\":\"bytes\",\"name\":\"config\",\"type\":\"bytes\"}],\"indexed\":false,\"internalType\":\"structCCIPHome.ChainConfig\",\"name\":\"chainConfig\",\"type\":\"tuple\"}],\"name\":\"ChainConfigSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"configDigest\",\"type\":\"bytes32\"}],\"name\":\"ConfigPromoted\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"configDigest\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"uint32\",\"name\":\"version\",\"type\":\"uint32\"},{\"components\":[{\"internalType\":\"enumInternal.OCRPluginType\",\"name\":\"pluginType\",\"type\":\"uint8\"},{\"internalType\":\"uint64\",\"name\":\"chainSelector\",\"type\":\"uint64\"},{\"internalType\":\"uint8\",\"name\":\"FRoleDON\",\"type\":\"uint8\"},{\"internalType\":\"uint64\",\"name\":\"offchainConfigVersion\",\"type\":\"uint64\"},{\"internalType\":\"bytes\",\"name\":\"offrampAddress\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"rmnHomeAddress\",\"type\":\"bytes\"},{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"p2pId\",\"type\":\"bytes32\"},{\"internalType\":\"bytes\",\"name\":\"signerKey\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"transmitterKey\",\"type\":\"bytes\"}],\"internalType\":\"structCCIPHome.OCR3Node[]\",\"name\":\"nodes\",\"type\":\"tuple[]\"},{\"internalType\":\"bytes\",\"name\":\"offchainConfig\",\"type\":\"bytes\"}],\"indexed\":false,\"internalType\":\"structCCIPHome.OCR3Config\",\"name\":\"config\",\"type\":\"tuple\"}],\"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\":[{\"internalType\":\"uint64[]\",\"name\":\"chainSelectorRemoves\",\"type\":\"uint64[]\"},{\"components\":[{\"internalType\":\"uint64\",\"name\":\"chainSelector\",\"type\":\"uint64\"},{\"components\":[{\"internalType\":\"bytes32[]\",\"name\":\"readers\",\"type\":\"bytes32[]\"},{\"internalType\":\"uint8\",\"name\":\"fChain\",\"type\":\"uint8\"},{\"internalType\":\"bytes\",\"name\":\"config\",\"type\":\"bytes\"}],\"internalType\":\"structCCIPHome.ChainConfig\",\"name\":\"chainConfig\",\"type\":\"tuple\"}],\"internalType\":\"structCCIPHome.ChainConfigArgs[]\",\"name\":\"chainConfigAdds\",\"type\":\"tuple[]\"}],\"name\":\"applyChainConfigUpdates\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32[]\",\"name\":\"\",\"type\":\"bytes32[]\"},{\"internalType\":\"bytes\",\"name\":\"update\",\"type\":\"bytes\"},{\"internalType\":\"uint64\",\"name\":\"\",\"type\":\"uint64\"},{\"internalType\":\"uint32\",\"name\":\"donId\",\"type\":\"uint32\"}],\"name\":\"beforeCapabilityConfigSet\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"donId\",\"type\":\"uint32\"},{\"internalType\":\"enumInternal.OCRPluginType\",\"name\":\"pluginType\",\"type\":\"uint8\"}],\"name\":\"getActiveDigest\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"pageIndex\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"pageSize\",\"type\":\"uint256\"}],\"name\":\"getAllChainConfigs\",\"outputs\":[{\"components\":[{\"internalType\":\"uint64\",\"name\":\"chainSelector\",\"type\":\"uint64\"},{\"components\":[{\"internalType\":\"bytes32[]\",\"name\":\"readers\",\"type\":\"bytes32[]\"},{\"internalType\":\"uint8\",\"name\":\"fChain\",\"type\":\"uint8\"},{\"internalType\":\"bytes\",\"name\":\"config\",\"type\":\"bytes\"}],\"internalType\":\"structCCIPHome.ChainConfig\",\"name\":\"chainConfig\",\"type\":\"tuple\"}],\"internalType\":\"structCCIPHome.ChainConfigArgs[]\",\"name\":\"\",\"type\":\"tuple[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"donId\",\"type\":\"uint32\"},{\"internalType\":\"enumInternal.OCRPluginType\",\"name\":\"pluginType\",\"type\":\"uint8\"}],\"name\":\"getAllConfigs\",\"outputs\":[{\"components\":[{\"internalType\":\"uint32\",\"name\":\"version\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"configDigest\",\"type\":\"bytes32\"},{\"components\":[{\"internalType\":\"enumInternal.OCRPluginType\",\"name\":\"pluginType\",\"type\":\"uint8\"},{\"internalType\":\"uint64\",\"name\":\"chainSelector\",\"type\":\"uint64\"},{\"internalType\":\"uint8\",\"name\":\"FRoleDON\",\"type\":\"uint8\"},{\"internalType\":\"uint64\",\"name\":\"offchainConfigVersion\",\"type\":\"uint64\"},{\"internalType\":\"bytes\",\"name\":\"offrampAddress\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"rmnHomeAddress\",\"type\":\"bytes\"},{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"p2pId\",\"type\":\"bytes32\"},{\"internalType\":\"bytes\",\"name\":\"signerKey\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"transmitterKey\",\"type\":\"bytes\"}],\"internalType\":\"structCCIPHome.OCR3Node[]\",\"name\":\"nodes\",\"type\":\"tuple[]\"},{\"internalType\":\"bytes\",\"name\":\"offchainConfig\",\"type\":\"bytes\"}],\"internalType\":\"structCCIPHome.OCR3Config\",\"name\":\"config\",\"type\":\"tuple\"}],\"internalType\":\"structCCIPHome.VersionedConfig\",\"name\":\"activeConfig\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"uint32\",\"name\":\"version\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"configDigest\",\"type\":\"bytes32\"},{\"components\":[{\"internalType\":\"enumInternal.OCRPluginType\",\"name\":\"pluginType\",\"type\":\"uint8\"},{\"internalType\":\"uint64\",\"name\":\"chainSelector\",\"type\":\"uint64\"},{\"internalType\":\"uint8\",\"name\":\"FRoleDON\",\"type\":\"uint8\"},{\"internalType\":\"uint64\",\"name\":\"offchainConfigVersion\",\"type\":\"uint64\"},{\"internalType\":\"bytes\",\"name\":\"offrampAddress\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"rmnHomeAddress\",\"type\":\"bytes\"},{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"p2pId\",\"type\":\"bytes32\"},{\"internalType\":\"bytes\",\"name\":\"signerKey\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"transmitterKey\",\"type\":\"bytes\"}],\"internalType\":\"structCCIPHome.OCR3Node[]\",\"name\":\"nodes\",\"type\":\"tuple[]\"},{\"internalType\":\"bytes\",\"name\":\"offchainConfig\",\"type\":\"bytes\"}],\"internalType\":\"structCCIPHome.OCR3Config\",\"name\":\"config\",\"type\":\"tuple\"}],\"internalType\":\"structCCIPHome.VersionedConfig\",\"name\":\"candidateConfig\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"donId\",\"type\":\"uint32\"},{\"internalType\":\"enumInternal.OCRPluginType\",\"name\":\"pluginType\",\"type\":\"uint8\"}],\"name\":\"getCandidateDigest\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"}],\"name\":\"getCapabilityConfiguration\",\"outputs\":[{\"internalType\":\"bytes\",\"name\":\"configuration\",\"type\":\"bytes\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getCapabilityRegistry\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"chainSelector\",\"type\":\"uint64\"}],\"name\":\"getChainConfig\",\"outputs\":[{\"components\":[{\"internalType\":\"bytes32[]\",\"name\":\"readers\",\"type\":\"bytes32[]\"},{\"internalType\":\"uint8\",\"name\":\"fChain\",\"type\":\"uint8\"},{\"internalType\":\"bytes\",\"name\":\"config\",\"type\":\"bytes\"}],\"internalType\":\"structCCIPHome.ChainConfig\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"donId\",\"type\":\"uint32\"},{\"internalType\":\"enumInternal.OCRPluginType\",\"name\":\"pluginType\",\"type\":\"uint8\"},{\"internalType\":\"bytes32\",\"name\":\"configDigest\",\"type\":\"bytes32\"}],\"name\":\"getConfig\",\"outputs\":[{\"components\":[{\"internalType\":\"uint32\",\"name\":\"version\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"configDigest\",\"type\":\"bytes32\"},{\"components\":[{\"internalType\":\"enumInternal.OCRPluginType\",\"name\":\"pluginType\",\"type\":\"uint8\"},{\"internalType\":\"uint64\",\"name\":\"chainSelector\",\"type\":\"uint64\"},{\"internalType\":\"uint8\",\"name\":\"FRoleDON\",\"type\":\"uint8\"},{\"internalType\":\"uint64\",\"name\":\"offchainConfigVersion\",\"type\":\"uint64\"},{\"internalType\":\"bytes\",\"name\":\"offrampAddress\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"rmnHomeAddress\",\"type\":\"bytes\"},{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"p2pId\",\"type\":\"bytes32\"},{\"internalType\":\"bytes\",\"name\":\"signerKey\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"transmitterKey\",\"type\":\"bytes\"}],\"internalType\":\"structCCIPHome.OCR3Node[]\",\"name\":\"nodes\",\"type\":\"tuple[]\"},{\"internalType\":\"bytes\",\"name\":\"offchainConfig\",\"type\":\"bytes\"}],\"internalType\":\"structCCIPHome.OCR3Config\",\"name\":\"config\",\"type\":\"tuple\"}],\"internalType\":\"structCCIPHome.VersionedConfig\",\"name\":\"versionedConfig\",\"type\":\"tuple\"},{\"internalType\":\"bool\",\"name\":\"ok\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"donId\",\"type\":\"uint32\"},{\"internalType\":\"enumInternal.OCRPluginType\",\"name\":\"pluginType\",\"type\":\"uint8\"}],\"name\":\"getConfigDigests\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"activeConfigDigest\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"candidateConfigDigest\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getNumChainConfigurations\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"donId\",\"type\":\"uint32\"},{\"internalType\":\"enumInternal.OCRPluginType\",\"name\":\"pluginType\",\"type\":\"uint8\"},{\"internalType\":\"bytes32\",\"name\":\"digestToPromote\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"digestToRevoke\",\"type\":\"bytes32\"}],\"name\":\"promoteCandidateAndRevokeActive\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"donId\",\"type\":\"uint32\"},{\"internalType\":\"enumInternal.OCRPluginType\",\"name\":\"pluginType\",\"type\":\"uint8\"},{\"internalType\":\"bytes32\",\"name\":\"configDigest\",\"type\":\"bytes32\"}],\"name\":\"revokeCandidate\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"donId\",\"type\":\"uint32\"},{\"internalType\":\"enumInternal.OCRPluginType\",\"name\":\"pluginType\",\"type\":\"uint8\"},{\"components\":[{\"internalType\":\"enumInternal.OCRPluginType\",\"name\":\"pluginType\",\"type\":\"uint8\"},{\"internalType\":\"uint64\",\"name\":\"chainSelector\",\"type\":\"uint64\"},{\"internalType\":\"uint8\",\"name\":\"FRoleDON\",\"type\":\"uint8\"},{\"internalType\":\"uint64\",\"name\":\"offchainConfigVersion\",\"type\":\"uint64\"},{\"internalType\":\"bytes\",\"name\":\"offrampAddress\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"rmnHomeAddress\",\"type\":\"bytes\"},{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"p2pId\",\"type\":\"bytes32\"},{\"internalType\":\"bytes\",\"name\":\"signerKey\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"transmitterKey\",\"type\":\"bytes\"}],\"internalType\":\"structCCIPHome.OCR3Node[]\",\"name\":\"nodes\",\"type\":\"tuple[]\"},{\"internalType\":\"bytes\",\"name\":\"offchainConfig\",\"type\":\"bytes\"}],\"internalType\":\"structCCIPHome.OCR3Config\",\"name\":\"config\",\"type\":\"tuple\"},{\"internalType\":\"bytes32\",\"name\":\"digestToOverwrite\",\"type\":\"bytes32\"}],\"name\":\"setCandidate\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"newConfigDigest\",\"type\":\"bytes32\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes4\",\"name\":\"interfaceId\",\"type\":\"bytes4\"}],\"name\":\"supportsInterface\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"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\":\"view\",\"type\":\"function\"}]", + Bin: "0x60a06040526006805463ffffffff191690553480156200001e57600080fd5b5060405162004e0238038062004e0283398101604081905262000041916200014c565b336000816200006357604051639b15e16f60e01b815260040160405180910390fd5b600180546001600160a01b0319166001600160a01b038481169190911790915581161562000096576200009681620000d2565b50506001600160a01b038116620000c0576040516342bcdf7f60e11b815260040160405180910390fd5b6001600160a01b03166080526200017e565b336001600160a01b03821603620000fc57604051636d6c4ee560e11b815260040160405180910390fd5b600080546001600160a01b0319166001600160a01b03838116918217835560015460405192939116917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b6000602082840312156200015f57600080fd5b81516001600160a01b03811681146200017757600080fd5b9392505050565b608051614c5a620001a86000396000818161019b015281816123600152612dba0152614c5a6000f3fe608060405234801561001057600080fd5b506004361061016c5760003560e01c80637ac0d41e116100cd578063b74b235611610081578063f2fde38b11610066578063f2fde38b14610391578063f442c89a146103a4578063fba64a7c146103b757600080fd5b8063b74b23561461035e578063bae4e0fa1461037e57600080fd5b80638da5cb5b116100b25780638da5cb5b1461030d578063922ea4061461032b578063b149092b1461033e57600080fd5b80637ac0d41e146102e45780638318ed5d146102ec57600080fd5b80634851d549116101245780635f1edd9c116101095780635f1edd9c146102a85780637524051a146102c957806379ba5097146102dc57600080fd5b80634851d5491461026b5780635a837f971461029357600080fd5b8063181f5a7711610155578063181f5a77146101e057806333d9704a146102295780633df45a721461024a57600080fd5b806301ffc9a714610171578063020330e614610199575b600080fd5b61018461017f366004613127565b6103ca565b60405190151581526020015b60405180910390f35b7f00000000000000000000000000000000000000000000000000000000000000005b60405173ffffffffffffffffffffffffffffffffffffffff9091168152602001610190565b61021c6040518060400160405280601281526020017f43434950486f6d6520312e362e302d646576000000000000000000000000000081525081565b60405161019091906131b9565b61023c610237366004613206565b610463565b60405161019092919061344d565b61025d610258366004613471565b61095d565b6040516101909291906134aa565b61027e610279366004613471565b61125d565b60408051928352602083019190915201610190565b6102a66102a13660046134cf565b611353565b005b6102bb6102b6366004613471565b611666565b604051908152602001610190565b6102a66102d7366004613206565b6116dd565b6102a66118c8565b6102bb611996565b61021c6102fa366004613515565b5060408051602081019091526000815290565b60015473ffffffffffffffffffffffffffffffffffffffff166101bb565b6102bb610339366004613471565b6119a7565b61035161034c366004613553565b6119f8565b60405161019091906135e7565b61037161036c3660046135fa565b611b32565b604051610190919061361c565b6102bb61038c3660046136ba565b611d97565b6102a661039f36600461372a565b611f95565b6102a66103b23660046137a5565b611fa9565b6102a66103c5366004613811565b612348565b60007fffffffff0000000000000000000000000000000000000000000000000000000082167f78bea72100000000000000000000000000000000000000000000000000000000148061045d57507fffffffff0000000000000000000000000000000000000000000000000000000082167f01ffc9a700000000000000000000000000000000000000000000000000000000145b92915050565b61046b612fb0565b6000805b600281101561094f5763ffffffff8616600090815260056020526040812085918760018111156104a1576104a1613247565b60018111156104b2576104b2613247565b815260200190815260200160002082600281106104d1576104d16138ce565b60070201600101541480156104e557508315155b156109475763ffffffff861660009081526005602052604081209086600181111561051257610512613247565b600181111561052357610523613247565b81526020019081526020016000208160028110610542576105426138ce565b6040805160608101825260079290920292909201805463ffffffff1682526001808201546020840152835161010081018552600283018054939592949386938501929190829060ff168781111561059b5761059b613247565b60018111156105ac576105ac613247565b8152815467ffffffffffffffff61010082048116602084015260ff690100000000000000000083041660408401526a0100000000000000000000909104166060820152600182018054608090920191610604906138fd565b80601f0160208091040260200160405190810160405280929190818152602001828054610630906138fd565b801561067d5780601f106106525761010080835404028352916020019161067d565b820191906000526020600020905b81548152906001019060200180831161066057829003601f168201915b50505050508152602001600282018054610696906138fd565b80601f01602080910402602001604051908101604052809291908181526020018280546106c2906138fd565b801561070f5780601f106106e45761010080835404028352916020019161070f565b820191906000526020600020905b8154815290600101906020018083116106f257829003601f168201915b5050505050815260200160038201805480602002602001604051908101604052809291908181526020016000905b8282101561089d57838290600052602060002090600302016040518060600160405290816000820154815260200160018201805461077a906138fd565b80601f01602080910402602001604051908101604052809291908181526020018280546107a6906138fd565b80156107f35780601f106107c8576101008083540402835291602001916107f3565b820191906000526020600020905b8154815290600101906020018083116107d657829003601f168201915b5050505050815260200160028201805461080c906138fd565b80601f0160208091040260200160405190810160405280929190818152602001828054610838906138fd565b80156108855780601f1061085a57610100808354040283529160200191610885565b820191906000526020600020905b81548152906001019060200180831161086857829003601f168201915b5050505050815250508152602001906001019061073d565b5050505081526020016004820180546108b5906138fd565b80601f01602080910402602001604051908101604052809291908181526020018280546108e1906138fd565b801561092e5780601f106109035761010080835404028352916020019161092e565b820191906000526020600020905b81548152906001019060200180831161091157829003601f168201915b5050505050815250508152505091509250925050610955565b60010161046f565b50600090505b935093915050565b610965612fb0565b61096d612fb0565b63ffffffff841660009081526005602052604081208185600181111561099557610995613247565b60018111156109a6576109a6613247565b81526020019081526020016000206109be8686612605565b63ffffffff16600281106109d4576109d46138ce565b6040805160608101825260079290920292909201805463ffffffff1682526001808201546020840152835161010081018552600283018054949593949386019391929091839160ff90911690811115610a2f57610a2f613247565b6001811115610a4057610a40613247565b8152815467ffffffffffffffff61010082048116602084015260ff690100000000000000000083041660408401526a0100000000000000000000909104166060820152600182018054608090920191610a98906138fd565b80601f0160208091040260200160405190810160405280929190818152602001828054610ac4906138fd565b8015610b115780601f10610ae657610100808354040283529160200191610b11565b820191906000526020600020905b815481529060010190602001808311610af457829003601f168201915b50505050508152602001600282018054610b2a906138fd565b80601f0160208091040260200160405190810160405280929190818152602001828054610b56906138fd565b8015610ba35780601f10610b7857610100808354040283529160200191610ba3565b820191906000526020600020905b815481529060010190602001808311610b8657829003601f168201915b5050505050815260200160038201805480602002602001604051908101604052809291908181526020016000905b82821015610d31578382906000526020600020906003020160405180606001604052908160008201548152602001600182018054610c0e906138fd565b80601f0160208091040260200160405190810160405280929190818152602001828054610c3a906138fd565b8015610c875780601f10610c5c57610100808354040283529160200191610c87565b820191906000526020600020905b815481529060010190602001808311610c6a57829003601f168201915b50505050508152602001600282018054610ca0906138fd565b80601f0160208091040260200160405190810160405280929190818152602001828054610ccc906138fd565b8015610d195780601f10610cee57610100808354040283529160200191610d19565b820191906000526020600020905b815481529060010190602001808311610cfc57829003601f168201915b50505050508152505081526020019060010190610bd1565b505050508152602001600482018054610d49906138fd565b80601f0160208091040260200160405190810160405280929190818152602001828054610d75906138fd565b8015610dc25780601f10610d9757610100808354040283529160200191610dc2565b820191906000526020600020905b815481529060010190602001808311610da557829003601f168201915b50505091909252505050905250602081015190915015610de0578092505b63ffffffff8516600090815260056020526040812081866001811115610e0857610e08613247565b6001811115610e1957610e19613247565b8152602001908152602001600020610e31878761265c565b63ffffffff1660028110610e4757610e476138ce565b6040805160608101825260079290920292909201805463ffffffff1682526001808201546020840152835161010081018552600283018054949593949386019391929091839160ff90911690811115610ea257610ea2613247565b6001811115610eb357610eb3613247565b8152815467ffffffffffffffff61010082048116602084015260ff690100000000000000000083041660408401526a0100000000000000000000909104166060820152600182018054608090920191610f0b906138fd565b80601f0160208091040260200160405190810160405280929190818152602001828054610f37906138fd565b8015610f845780601f10610f5957610100808354040283529160200191610f84565b820191906000526020600020905b815481529060010190602001808311610f6757829003601f168201915b50505050508152602001600282018054610f9d906138fd565b80601f0160208091040260200160405190810160405280929190818152602001828054610fc9906138fd565b80156110165780601f10610feb57610100808354040283529160200191611016565b820191906000526020600020905b815481529060010190602001808311610ff957829003601f168201915b5050505050815260200160038201805480602002602001604051908101604052809291908181526020016000905b828210156111a4578382906000526020600020906003020160405180606001604052908160008201548152602001600182018054611081906138fd565b80601f01602080910402602001604051908101604052809291908181526020018280546110ad906138fd565b80156110fa5780601f106110cf576101008083540402835291602001916110fa565b820191906000526020600020905b8154815290600101906020018083116110dd57829003601f168201915b50505050508152602001600282018054611113906138fd565b80601f016020809104026020016040519081016040528092919081815260200182805461113f906138fd565b801561118c5780601f106111615761010080835404028352916020019161118c565b820191906000526020600020905b81548152906001019060200180831161116f57829003601f168201915b50505050508152505081526020019060010190611044565b5050505081526020016004820180546111bc906138fd565b80601f01602080910402602001604051908101604052809291908181526020018280546111e8906138fd565b80156112355780601f1061120a57610100808354040283529160200191611235565b820191906000526020600020905b81548152906001019060200180831161121857829003601f168201915b50505091909252505050905250602081015190915015611253578092505b50505b9250929050565b63ffffffff8216600090815260056020526040812081908184600181111561128757611287613247565b600181111561129857611298613247565b81526020019081526020016000206112b08585612605565b63ffffffff16600281106112c6576112c66138ce565b6007020160010154600560008663ffffffff1663ffffffff168152602001908152602001600020600085600181111561130157611301613247565b600181111561131257611312613247565b815260200190815260200160002061132a868661265c565b63ffffffff1660028110611340576113406138ce565b6007020160010154915091509250929050565b61135b6126b7565b81158015611367575080155b1561139e576040517f7b4d1e4f00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60006113aa858561265c565b63ffffffff86811660009081526005602052604081209290911692508491908660018111156113db576113db613247565b60018111156113ec576113ec613247565b8152602001908152602001600020826002811061140b5761140b6138ce565b6007020160010154146114bf5763ffffffff851660009081526005602052604081209085600181111561144057611440613247565b600181111561145157611451613247565b81526020019081526020016000208160028110611470576114706138ce565b6007020160010154836040517f93df584c0000000000000000000000000000000000000000000000000000000081526004016114b6929190918252602082015260400190565b60405180910390fd5b63ffffffff85166000908152600560205260408120818660018111156114e7576114e7613247565b60018111156114f8576114f8613247565b81526020019081526020016000206115108787612605565b63ffffffff1660028110611526576115266138ce565b600702019050828160010154146115795760018101546040517f93df584c0000000000000000000000000000000000000000000000000000000081526004810191909152602481018490526044016114b6565b6000600180830182905563ffffffff88168252600760205260408220909187838111156115a8576115a8613247565b60018111156115b9576115b9613247565b8152602081019190915260400160002080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000811663ffffffff918216939093181691909117905582156116335760405183907f0b31c0055e2d464bef7781994b98c4ff9ef4ae0d05f59feb6a68c42de5e201b890600090a25b60405184907ffc3e98dbbd47c3fa7c1c05b6ec711caeaf70eca4554192b9ada8fc11a37f298e90600090a2505050505050565b63ffffffff821660009081526005602052604081208183600181111561168e5761168e613247565b600181111561169f5761169f613247565b81526020019081526020016000206116b78484612605565b63ffffffff16600281106116cd576116cd6138ce565b6007020160010154905092915050565b6116e56126b7565b8061171c576040517f0849d8cc00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000611728848461265c565b63ffffffff858116600090815260056020526040812092909116925083919085600181111561175957611759613247565b600181111561176a5761176a613247565b81526020019081526020016000208260028110611789576117896138ce565b6007020160010154146118345763ffffffff84166000908152600560205260408120908460018111156117be576117be613247565b60018111156117cf576117cf613247565b815260200190815260200160002081600281106117ee576117ee6138ce565b6007020160010154826040517f93df584c0000000000000000000000000000000000000000000000000000000081526004016114b6929190918252602082015260400190565b60405182907f53f5d9228f0a4173bea6e5931c9b3afe6eeb6692ede1d182952970f152534e3b90600090a263ffffffff841660009081526005602052604081209084600181111561188757611887613247565b600181111561189857611898613247565b815260200190815260200160002081600281106118b7576118b76138ce565b600702016001016000905550505050565b60005473ffffffffffffffffffffffffffffffffffffffff163314611919576040517f02b543c600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600180547fffffffffffffffffffffffff00000000000000000000000000000000000000008082163390811790935560008054909116815560405173ffffffffffffffffffffffffffffffffffffffff909216929183917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a350565b60006119a260036126f2565b905090565b63ffffffff82166000908152600560205260408120818360018111156119cf576119cf613247565b60018111156119e0576119e0613247565b81526020019081526020016000206116b7848461265c565b6040805160608082018352808252600060208301529181019190915267ffffffffffffffff821660009081526002602090815260409182902082518154608093810282018401909452606081018481529093919284928491840182828015611a7f57602002820191906000526020600020905b815481526020019060010190808311611a6b575b5050509183525050600182015460ff166020820152600282018054604090920191611aa9906138fd565b80601f0160208091040260200160405190810160405280929190818152602001828054611ad5906138fd565b8015611b225780601f10611af757610100808354040283529160200191611b22565b820191906000526020600020905b815481529060010190602001808311611b0557829003601f168201915b5050505050815250509050919050565b60606000611b4060036126f2565b90506000611b4e848661397f565b9050831580611b5d5750818110155b15611b9d576040805160008082526020820190925290611b93565b611b8061302c565b815260200190600190039081611b785790505b509250505061045d565b6000611ba985836139c5565b905082811115611bb65750815b6000611bc283836139d8565b67ffffffffffffffff811115611bda57611bda613996565b604051908082528060200260200182016040528015611c1357816020015b611c0061302c565b815260200190600190039081611bf85790505b509050825b82811015611d8c576000611c2d6003836126fc565b60408051808201825267ffffffffffffffff83168082526000908152600260209081529083902083518154608081850283018101909652606082018181529697509395928601949093919284929091849190840182828015611cae57602002820191906000526020600020905b815481526020019060010190808311611c9a575b5050509183525050600182015460ff166020820152600282018054604090920191611cd8906138fd565b80601f0160208091040260200160405190810160405280929190818152602001828054611d04906138fd565b8015611d515780601f10611d2657610100808354040283529160200191611d51565b820191906000526020600020905b815481529060010190602001808311611d3457829003601f168201915b50505091909252505050905283611d6887856139d8565b81518110611d7857611d786138ce565b602090810291909101015250600101611c18565b509695505050505050565b6000611da16126b7565b611db2611dad84613c06565b61270f565b6000611dbe86866119a7565b9050828114611e03576040517f93df584c00000000000000000000000000000000000000000000000000000000815260048101829052602481018490526044016114b6565b8015611e355760405183907f53f5d9228f0a4173bea6e5931c9b3afe6eeb6692ede1d182952970f152534e3b90600090a25b60068054600091908290611e4e9063ffffffff16613d02565b91906101000a81548163ffffffff021916908363ffffffff16021790559050611e98878787604051602001611e839190613fde565b60405160208183030381529060405284612b7d565b63ffffffff881660009081526005602052604081209194509081886001811115611ec457611ec4613247565b6001811115611ed557611ed5613247565b8152602001908152602001600020611eed898961265c565b63ffffffff1660028110611f0357611f036138ce565b600702016001810185905580547fffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000001663ffffffff841617815590508560028201611f4d82826144c4565b905050837f94f085b7c57ec2a270befd0b7b2ec7452580040edee8bb0fb04609c81f0359c68388604051611f8292919061468b565b60405180910390a2505050949350505050565b611f9d612c3d565b611fa681612c8e565b50565b611fb1612c3d565b60005b8381101561219757611ff8858583818110611fd157611fd16138ce565b9050602002016020810190611fe69190613553565b60039067ffffffffffffffff16612d52565b6120625784848281811061200e5761200e6138ce565b90506020020160208101906120239190613553565b6040517f1bd4d2d200000000000000000000000000000000000000000000000000000000815267ffffffffffffffff90911660048201526024016114b6565b60026000868684818110612078576120786138ce565b905060200201602081019061208d9190613553565b67ffffffffffffffff16815260208101919091526040016000908120906120b4828261306f565b6001820180547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001690556120ec60028301600061308d565b505061212a858583818110612103576121036138ce565b90506020020160208101906121189190613553565b60039067ffffffffffffffff16612d6a565b507f2a680691fef3b2d105196805935232c661ce703e92d464ef0b94a7bc62d714f085858381811061215e5761215e6138ce565b90506020020160208101906121739190613553565b60405167ffffffffffffffff909116815260200160405180910390a1600101611fb4565b5060005b818110156123415760008383838181106121b7576121b76138ce565b90506020028101906121c991906146b2565b6121d7906020810190614227565b6121e0906146e6565b905060008484848181106121f6576121f66138ce565b905060200281019061220891906146b2565b612216906020810190613553565b90506122258260000151612d76565b816020015160ff16600003612266576040517fa9b3766e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b67ffffffffffffffff811660009081526002602090815260409091208351805185936122969284929101906130c7565b5060208201516001820180547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660ff909216919091179055604082015160028201906122e390826147b8565b506122fd91506003905067ffffffffffffffff8316612e38565b507f05dd57854af2c291a94ea52e7c43d80bc3be7fa73022f98b735dea86642fa5e0818360405161232f9291906148b4565b60405180910390a1505060010161219b565b5050505050565b3373ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016146123b7576040517fac7a7efd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60006123c660048286886148d7565b6123cf91614901565b90507fffffffff0000000000000000000000000000000000000000000000000000000081167fbae4e0fa000000000000000000000000000000000000000000000000000000001480159061246557507fffffffff0000000000000000000000000000000000000000000000000000000081167f7524051a0000000000000000000000000000000000000000000000000000000014155b80156124b357507fffffffff0000000000000000000000000000000000000000000000000000000081167f5a837f970000000000000000000000000000000000000000000000000000000014155b1561250e576040517f12ba286f0000000000000000000000000000000000000000000000000000000081527fffffffff00000000000000000000000000000000000000000000000000000000821660048201526024016114b6565b600061251e6024600487896148d7565b81019061252b9190614949565b90508263ffffffff16811461257c576040517f8a6e4ce800000000000000000000000000000000000000000000000000000000815263ffffffff8083166004830152841660248201526044016114b6565b6000803073ffffffffffffffffffffffffffffffffffffffff1688886040516125a6929190614962565b6000604051808303816000865af19150503d80600081146125e3576040519150601f19603f3d011682016040523d82523d6000602084013e6125e8565b606091505b5091509150816125f9573d60208201fd5b50505050505050505050565b63ffffffff821660009081526007602052604081208183600181111561262d5761262d613247565b600181111561263e5761263e613247565b815260208101919091526040016000205463ffffffff169392505050565b63ffffffff821660009081526007602052604081208183600181111561268457612684613247565b600181111561269557612695613247565b815260208101919091526040016000205463ffffffff16600118905092915050565b3330146126f0576040517f371a732800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b565b600061045d825490565b60006127088383612e44565b9392505050565b806020015167ffffffffffffffff16600003612757576040517f698cf8e000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008151600181111561276c5761276c613247565b1415801561278d575060018151600181111561278a5761278a613247565b14155b156127c4576040517f3302dbd700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6080810151511580612801575060408051600060208201520160405160208183030381529060405280519060200120816080015180519060200120145b15612838576040517f358c192700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60a08101515115806128755750604080516000602082015201604051602081830303815290604052805190602001208160a0015180519060200120145b156128ac576040517fdee9857400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60208101516128c79060039067ffffffffffffffff16612d52565b61290f5760208101516040517f1bd4d2d200000000000000000000000000000000000000000000000000000000815267ffffffffffffffff90911660048201526024016114b6565b60408082015160208084015167ffffffffffffffff1660009081526002909152919091206001015460ff918216911681811115612982576040517f2db2204000000000000000000000000000000000000000000000000000000000815260048101829052602481018390526044016114b6565b60c0830151516101008111156129c4576040517f1b925da600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6129cf83600361397f565b8111612a07576040517f4856694e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000808267ffffffffffffffff811115612a2357612a23613996565b604051908082528060200260200182016040528015612a4c578160200160208202803683370190505b50905060005b83811015612b0c5760008760c001518281518110612a7257612a726138ce565b60200260200101519050806040015151600014612a975783612a9381614972565b9450505b6020810151511580612aa857508051155b15612ae157806040517f9fa403140000000000000000000000000000000000000000000000000000000081526004016114b691906149aa565b8060000151838381518110612af857612af86138ce565b602090810291909101015250600101612a52565b506000612b1a85600361397f565b612b259060016139c5565b905080831015612b6b576040517f548dd21f00000000000000000000000000000000000000000000000000000000815260048101849052602481018290526044016114b6565b612b7482612d76565b50505050505050565b6040516000907dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff90612bdb907f45564d000000000000000000000000000000000000000000000000000000000090469030908a908a9089906020016149bd565b60408051601f1981840301815290829052612bfa918690602001614a16565b60408051808303601f190181529190528051602090910120167e0a0000000000000000000000000000000000000000000000000000000000001795945050505050565b60015473ffffffffffffffffffffffffffffffffffffffff1633146126f0576040517f2b5c74de00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b3373ffffffffffffffffffffffffffffffffffffffff821603612cdd576040517fdad89dca00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600080547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff838116918217835560015460405192939116917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b60008181526001830160205260408120541515612708565b60006127088383612e6e565b805115611fa6576040517f05a5196600000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016906305a5196690612def908490600401614a45565b600060405180830381865afa158015612e0c573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052612e349190810190614ac2565b5050565b60006127088383612f61565b6000826000018281548110612e5b57612e5b6138ce565b9060005260206000200154905092915050565b60008181526001830160205260408120548015612f57576000612e926001836139d8565b8554909150600090612ea6906001906139d8565b9050808214612f0b576000866000018281548110612ec657612ec66138ce565b9060005260206000200154905080876000018481548110612ee957612ee96138ce565b6000918252602080832090910192909255918252600188019052604090208390555b8554869080612f1c57612f1c614c1e565b60019003818190600052602060002001600090559055856001016000868152602001908152602001600020600090556001935050505061045d565b600091505061045d565b6000818152600183016020526040812054612fa85750815460018181018455600084815260208082209093018490558454848252828601909352604090209190915561045d565b50600061045d565b60408051606081018252600080825260208201529081016130276040805161010081019091528060008152602001600067ffffffffffffffff168152602001600060ff168152602001600067ffffffffffffffff168152602001606081526020016060815260200160608152602001606081525090565b905290565b6040518060400160405280600067ffffffffffffffff168152602001613027604051806060016040528060608152602001600060ff168152602001606081525090565b5080546000825590600052602060002090810190611fa69190613112565b508054613099906138fd565b6000825580601f106130a9575050565b601f016020900490600052602060002090810190611fa69190613112565b828054828255906000526020600020908101928215613102579160200282015b828111156131025782518255916020019190600101906130e7565b5061310e929150613112565b5090565b5b8082111561310e5760008155600101613113565b60006020828403121561313957600080fd5b81357fffffffff000000000000000000000000000000000000000000000000000000008116811461270857600080fd5b60005b8381101561318457818101518382015260200161316c565b50506000910152565b600081518084526131a5816020860160208601613169565b601f01601f19169290920160200192915050565b602081526000612708602083018461318d565b63ffffffff81168114611fa657600080fd5b80356131e9816131cc565b919050565b60028110611fa657600080fd5b80356131e9816131ee565b60008060006060848603121561321b57600080fd5b8335613226816131cc565b92506020840135613236816131ee565b929592945050506040919091013590565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b600281106132ad577f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b9052565b8051825260006020820151606060208501526132d0606085018261318d565b9050604083015184820360408601526132e9828261318d565b95945050505050565b60008282518085526020808601955060208260051b8401016020860160005b8481101561333f57601f1986840301895261332d8383516132b1565b98840198925090830190600101613311565b5090979650505050505050565b63ffffffff815116825260208101516020830152600060408201516060604085015261337c606085018251613276565b602081015167ffffffffffffffff8116608086015250604081015160ff811660a086015250606081015167ffffffffffffffff811660c08601525060808101516101008060e08701526133d361016087018361318d565b915060a08301517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa080888503018389015261340e848361318d565b935060c08501519250808885030161012089015261342c84846132f2565b935060e0850151945080888503016101408901525050506132e9818361318d565b604081526000613460604083018561334c565b905082151560208301529392505050565b6000806040838503121561348457600080fd5b823561348f816131cc565b9150602083013561349f816131ee565b809150509250929050565b6040815260006134bd604083018561334c565b82810360208401526132e9818561334c565b600080600080608085870312156134e557600080fd5b84356134f0816131cc565b93506020850135613500816131ee565b93969395505050506040820135916060013590565b60006020828403121561352757600080fd5b8135612708816131cc565b67ffffffffffffffff81168114611fa657600080fd5b80356131e981613532565b60006020828403121561356557600080fd5b813561270881613532565b60008151808452602080850194506020840160005b838110156135a157815187529582019590820190600101613585565b509495945050505050565b60008151606084526135c16060850182613570565b905060ff6020840151166020850152604083015184820360408601526132e9828261318d565b60208152600061270860208301846135ac565b6000806040838503121561360d57600080fd5b50508035926020909101359150565b600060208083018184528085518083526040925060408601915060408160051b87010184880160005b838110156136ac578883037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc00185528151805167ffffffffffffffff168452870151878401879052613699878501826135ac565b9588019593505090860190600101613645565b509098975050505050505050565b600080600080608085870312156136d057600080fd5b84356136db816131cc565b935060208501356136eb816131ee565b9250604085013567ffffffffffffffff81111561370757600080fd5b8501610100818803121561371a57600080fd5b9396929550929360600135925050565b60006020828403121561373c57600080fd5b813573ffffffffffffffffffffffffffffffffffffffff8116811461270857600080fd5b60008083601f84011261377257600080fd5b50813567ffffffffffffffff81111561378a57600080fd5b6020830191508360208260051b850101111561125657600080fd5b600080600080604085870312156137bb57600080fd5b843567ffffffffffffffff808211156137d357600080fd5b6137df88838901613760565b909650945060208701359150808211156137f857600080fd5b5061380587828801613760565b95989497509550505050565b6000806000806000806080878903121561382a57600080fd5b863567ffffffffffffffff8082111561384257600080fd5b61384e8a838b01613760565b9098509650602089013591508082111561386757600080fd5b818901915089601f83011261387b57600080fd5b81358181111561388a57600080fd5b8a602082850101111561389c57600080fd5b6020830196508095505050506138b460408801613548565b91506138c2606088016131de565b90509295509295509295565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b600181811c9082168061391157607f821691505b60208210810361394a577f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b50919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b808202811582820484141761045d5761045d613950565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b8082018082111561045d5761045d613950565b8181038181111561045d5761045d613950565b6040516060810167ffffffffffffffff81118282101715613a0e57613a0e613996565b60405290565b604051610100810167ffffffffffffffff81118282101715613a0e57613a0e613996565b604051601f8201601f1916810167ffffffffffffffff81118282101715613a6157613a61613996565b604052919050565b60ff81168114611fa657600080fd5b80356131e981613a69565b600082601f830112613a9457600080fd5b813567ffffffffffffffff811115613aae57613aae613996565b613ac16020601f19601f84011601613a38565b818152846020838601011115613ad657600080fd5b816020850160208301376000918101602001919091529392505050565b600067ffffffffffffffff821115613b0d57613b0d613996565b5060051b60200190565b600082601f830112613b2857600080fd5b81356020613b3d613b3883613af3565b613a38565b82815260059290921b84018101918181019086841115613b5c57600080fd5b8286015b84811015611d8c57803567ffffffffffffffff80821115613b815760008081fd5b8189019150606080601f19848d03011215613b9c5760008081fd5b613ba46139eb565b87840135815260408085013584811115613bbe5760008081fd5b613bcc8e8b83890101613a83565b838b015250918401359183831115613be45760008081fd5b613bf28d8a85880101613a83565b908201528652505050918301918301613b60565b60006101008236031215613c1957600080fd5b613c21613a14565b613c2a836131fb565b8152613c3860208401613548565b6020820152613c4960408401613a78565b6040820152613c5a60608401613548565b6060820152608083013567ffffffffffffffff80821115613c7a57600080fd5b613c8636838701613a83565b608084015260a0850135915080821115613c9f57600080fd5b613cab36838701613a83565b60a084015260c0850135915080821115613cc457600080fd5b613cd036838701613b17565b60c084015260e0850135915080821115613ce957600080fd5b50613cf636828601613a83565b60e08301525092915050565b600063ffffffff808316818103613d1b57613d1b613950565b6001019392505050565b60008083357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe1843603018112613d5a57600080fd5b830160208101925035905067ffffffffffffffff811115613d7a57600080fd5b80360382131561125657600080fd5b818352818160208501375060006020828401015260006020601f19601f840116840101905092915050565b60008083357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe1843603018112613de957600080fd5b830160208101925035905067ffffffffffffffff811115613e0957600080fd5b8060051b360382131561125657600080fd5b60008383855260208086019550808560051b830101846000805b88811015613ed957601f19868503018a5282357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa1893603018112613e77578283fd5b8801803585526060613e8b87830183613d25565b8289890152613e9d8389018284613d89565b925050506040613eaf81840184613d25565b935087830382890152613ec3838583613d89565b9d89019d97505050938601935050600101613e35565b509198975050505050505050565b6000610100613efe84613ef9856131fb565b613276565b613f0a60208401613548565b67ffffffffffffffff166020850152613f2560408401613a78565b60ff166040850152613f3960608401613548565b67ffffffffffffffff166060850152613f556080840184613d25565b826080870152613f688387018284613d89565b92505050613f7960a0840184613d25565b85830360a0870152613f8c838284613d89565b92505050613f9d60c0840184613db4565b85830360c0870152613fb0838284613e1b565b92505050613fc160e0840184613d25565b85830360e0870152613fd4838284613d89565b9695505050505050565b6020815260006127086020830184613ee7565b6000813561045d81613532565b6000813561045d81613a69565b60008083357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe184360301811261404057600080fd5b83018035915067ffffffffffffffff82111561405b57600080fd5b60200191503681900382131561125657600080fd5b5b81811015612e345760008155600101614071565b601f8211156140be57806000526020600020601f840160051c810160208510156140ac5750805b612341601f850160051c830182614070565b505050565b67ffffffffffffffff8311156140db576140db613996565b6140ef836140e983546138fd565b83614085565b6000601f841160018114614141576000851561410b5750838201355b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600387901b1c1916600186901b178355612341565b600083815260209020601f19861690835b828110156141725786850135825560209485019460019092019101614152565b50868210156141ad577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60f88860031b161c19848701351681555b505060018560011b0183555050505050565b60008083357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe18436030181126141f457600080fd5b83018035915067ffffffffffffffff82111561420f57600080fd5b6020019150600581901b360382131561125657600080fd5b600082357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa183360301811261425b57600080fd5b9190910192915050565b61426f81546138fd565b8015612e3457601f81116001811461428957505060009055565b8260005260206000206142a7601f840160051c820160018301614070565b60008085559055505050565b81358155600180820160206142cb602086018661400b565b67ffffffffffffffff8111156142e3576142e3613996565b6142f7816142f186546138fd565b86614085565b6000601f82116001811461434957600083156143135750838201355b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600385901b1c1916600184901b1786556143be565b600086815260209020601f19841690835b82811015614377578685013582559387019390890190870161435a565b50848210156143b2577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60f88660031b161c19848701351681555b505060018360011b0186555b505050505050506143d2604083018361400b565b6143e08183600286016140c3565b50505050565b680100000000000000008311156143ff576143ff613996565b80548382558084101561447e5760038160030260038104831461442457614424613950565b8560030260038104871461443a5761443a613950565b6000858152602081209283019291909101905b828210156144795780825561446460018301614265565b61447060028301614265565b9083019061444d565b505050505b5060008181526020812083915b858110156144bc576144a66144a08487614227565b836142b3565b602092909201916003919091019060010161448b565b505050505050565b81356144cf816131ee565b60028110614506577f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0082541660ff821681178355505061457d61454360208401613ff1565b82547fffffffffffffffffffffffffffffffffffffffffffffff0000000000000000ff1660089190911b68ffffffffffffffff0016178255565b6145c761458c60408401613ffe565b82547fffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffff1660489190911b69ff00000000000000000016178255565b6146196145d660608401613ff1565b82547fffffffffffffffffffffffffffff0000000000000000ffffffffffffffffffff1660509190911b71ffffffffffffffff0000000000000000000016178255565b614626608083018361400b565b6146348183600186016140c3565b505061464360a083018361400b565b6146518183600286016140c3565b505061466060c08301836141bf565b61466e8183600386016143e6565b505061467d60e083018361400b565b6143e08183600486016140c3565b63ffffffff831681526040602082015260006146aa6040830184613ee7565b949350505050565b600082357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc183360301811261425b57600080fd5b6000606082360312156146f857600080fd5b6147006139eb565b823567ffffffffffffffff8082111561471857600080fd5b9084019036601f83011261472b57600080fd5b8135602061473b613b3883613af3565b82815260059290921b8401810191818101903684111561475a57600080fd5b948201945b838610156147785785358252948201949082019061475f565b865250614786878201613a78565b9085015250604085013591508082111561479f57600080fd5b506147ac36828601613a83565b60408301525092915050565b815167ffffffffffffffff8111156147d2576147d2613996565b6147e6816147e084546138fd565b84614085565b602080601f83116001811461483957600084156148035750858301515b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600386901b1c1916600185901b1785556144bc565b600085815260208120601f198616915b8281101561486857888601518255948401946001909101908401614849565b50858210156148a457878501517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600388901b60f8161c191681555b5050505050600190811b01905550565b67ffffffffffffffff831681526040602082015260006146aa60408301846135ac565b600080858511156148e757600080fd5b838611156148f457600080fd5b5050820193919092039150565b7fffffffff0000000000000000000000000000000000000000000000000000000081358181169160048510156149415780818660040360031b1b83161692505b505092915050565b60006020828403121561495b57600080fd5b5035919050565b8183823760009101908152919050565b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82036149a3576149a3613950565b5060010190565b60208152600061270860208301846132b1565b8681526020810186905273ffffffffffffffffffffffffffffffffffffffff8516604082015263ffffffff848116606083015260c0820190614a026080840186613276565b80841660a084015250979650505050505050565b60008351614a28818460208801613169565b835190830190614a3c818360208801613169565b01949350505050565b6020815260006127086020830184613570565b80516131e9816131cc565b600082601f830112614a7457600080fd5b81516020614a84613b3883613af3565b8083825260208201915060208460051b870101935086841115614aa657600080fd5b602086015b84811015611d8c5780518352918301918301614aab565b60006020808385031215614ad557600080fd5b825167ffffffffffffffff80821115614aed57600080fd5b818501915085601f830112614b0157600080fd5b8151614b0f613b3882613af3565b81815260059190911b83018401908481019088831115614b2e57600080fd5b8585015b83811015614c1157805185811115614b4957600080fd5b8601610100818c03601f1901811315614b6157600080fd5b614b69613a14565b614b748a8401614a58565b8152614b8260408401614a58565b8a820152614b9260608401614a58565b60408201526080830151606082015260a0830151608082015260c083015160a082015260e08084015189811115614bc95760008081fd5b614bd78f8d83880101614a63565b60c084015250918301519188831115614bf05760008081fd5b614bfe8e8c85870101614a63565b9082015285525050918601918601614b32565b5098975050505050505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603160045260246000fdfea164736f6c6343000818000a", } var CCIPHomeABI = CCIPHomeMetaData.ABI @@ -345,6 +345,28 @@ func (_CCIPHome *CCIPHomeCallerSession) GetCapabilityRegistry() (common.Address, return _CCIPHome.Contract.GetCapabilityRegistry(&_CCIPHome.CallOpts) } +func (_CCIPHome *CCIPHomeCaller) GetChainConfig(opts *bind.CallOpts, chainSelector uint64) (CCIPHomeChainConfig, error) { + var out []interface{} + err := _CCIPHome.contract.Call(opts, &out, "getChainConfig", chainSelector) + + if err != nil { + return *new(CCIPHomeChainConfig), err + } + + out0 := *abi.ConvertType(out[0], new(CCIPHomeChainConfig)).(*CCIPHomeChainConfig) + + return out0, err + +} + +func (_CCIPHome *CCIPHomeSession) GetChainConfig(chainSelector uint64) (CCIPHomeChainConfig, error) { + return _CCIPHome.Contract.GetChainConfig(&_CCIPHome.CallOpts, chainSelector) +} + +func (_CCIPHome *CCIPHomeCallerSession) GetChainConfig(chainSelector uint64) (CCIPHomeChainConfig, error) { + return _CCIPHome.Contract.GetChainConfig(&_CCIPHome.CallOpts, chainSelector) +} + func (_CCIPHome *CCIPHomeCaller) GetConfig(opts *bind.CallOpts, donId uint32, pluginType uint8, configDigest [32]byte) (GetConfig, error) { @@ -1804,6 +1826,8 @@ type CCIPHomeInterface interface { GetCapabilityRegistry(opts *bind.CallOpts) (common.Address, error) + GetChainConfig(opts *bind.CallOpts, chainSelector uint64) (CCIPHomeChainConfig, error) + GetConfig(opts *bind.CallOpts, donId uint32, pluginType uint8, configDigest [32]byte) (GetConfig, error) diff --git a/core/gethwrappers/ccip/generated/offramp/offramp.go b/core/gethwrappers/ccip/generated/offramp/offramp.go index b582b60cff6..830dcaf7bf4 100644 --- a/core/gethwrappers/ccip/generated/offramp/offramp.go +++ b/core/gethwrappers/ccip/generated/offramp/offramp.go @@ -148,15 +148,16 @@ type OffRampSourceChainConfigArgs struct { } type OffRampStaticConfig struct { - ChainSelector uint64 - RmnRemote common.Address - TokenAdminRegistry common.Address - NonceManager common.Address + ChainSelector uint64 + GasForCallExactCheck uint16 + RmnRemote common.Address + TokenAdminRegistry common.Address + NonceManager common.Address } var OffRampMetaData = &bind.MetaData{ - ABI: "[{\"inputs\":[{\"components\":[{\"internalType\":\"uint64\",\"name\":\"chainSelector\",\"type\":\"uint64\"},{\"internalType\":\"contractIRMNRemote\",\"name\":\"rmnRemote\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"tokenAdminRegistry\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"nonceManager\",\"type\":\"address\"}],\"internalType\":\"structOffRamp.StaticConfig\",\"name\":\"staticConfig\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"feeQuoter\",\"type\":\"address\"},{\"internalType\":\"uint32\",\"name\":\"permissionLessExecutionThresholdSeconds\",\"type\":\"uint32\"},{\"internalType\":\"bool\",\"name\":\"isRMNVerificationDisabled\",\"type\":\"bool\"},{\"internalType\":\"address\",\"name\":\"messageInterceptor\",\"type\":\"address\"}],\"internalType\":\"structOffRamp.DynamicConfig\",\"name\":\"dynamicConfig\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"contractIRouter\",\"name\":\"router\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"sourceChainSelector\",\"type\":\"uint64\"},{\"internalType\":\"bool\",\"name\":\"isEnabled\",\"type\":\"bool\"},{\"internalType\":\"bytes\",\"name\":\"onRamp\",\"type\":\"bytes\"}],\"internalType\":\"structOffRamp.SourceChainConfigArgs[]\",\"name\":\"sourceChainConfigs\",\"type\":\"tuple[]\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[],\"name\":\"CanOnlySelfCall\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"CannotTransferToSelf\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"reportOnRamp\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"configOnRamp\",\"type\":\"bytes\"}],\"name\":\"CommitOnRampMismatch\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"expected\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"actual\",\"type\":\"bytes32\"}],\"name\":\"ConfigDigestMismatch\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"sourceChainSelector\",\"type\":\"uint64\"}],\"name\":\"CursedByRMN\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"EmptyBatch\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"sourceChainSelector\",\"type\":\"uint64\"}],\"name\":\"EmptyReport\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"messageId\",\"type\":\"bytes32\"},{\"internalType\":\"bytes\",\"name\":\"err\",\"type\":\"bytes\"}],\"name\":\"ExecutionError\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"expected\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"actual\",\"type\":\"uint256\"}],\"name\":\"ForkedChain\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"enumMultiOCR3Base.InvalidConfigErrorType\",\"name\":\"errorType\",\"type\":\"uint8\"}],\"name\":\"InvalidConfig\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"expected\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"got\",\"type\":\"uint256\"}],\"name\":\"InvalidDataLength\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"sourceChainSelector\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"min\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"max\",\"type\":\"uint64\"}],\"name\":\"InvalidInterval\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"sourceChainSelector\",\"type\":\"uint64\"},{\"internalType\":\"bytes32\",\"name\":\"messageId\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"newLimit\",\"type\":\"uint256\"}],\"name\":\"InvalidManualExecutionGasLimit\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"messageId\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"tokenIndex\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"oldLimit\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"tokenGasOverride\",\"type\":\"uint256\"}],\"name\":\"InvalidManualExecutionTokenGasOverride\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"messageDestChainSelector\",\"type\":\"uint64\"}],\"name\":\"InvalidMessageDestChainSelector\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"sourceChainSelector\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"sequenceNumber\",\"type\":\"uint64\"},{\"internalType\":\"enumInternal.MessageExecutionState\",\"name\":\"newState\",\"type\":\"uint8\"}],\"name\":\"InvalidNewState\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"sourceChainSelector\",\"type\":\"uint64\"}],\"name\":\"InvalidOnRampUpdate\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidProof\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidRoot\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"LeavesCannotBeEmpty\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"messageId\",\"type\":\"bytes32\"},{\"internalType\":\"uint64\",\"name\":\"sequenceNumber\",\"type\":\"uint64\"}],\"name\":\"ManualExecutionGasAmountCountMismatch\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"ManualExecutionGasLimitMismatch\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"sourceChainSelector\",\"type\":\"uint64\"}],\"name\":\"ManualExecutionNotYetEnabled\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"errorReason\",\"type\":\"bytes\"}],\"name\":\"MessageValidationError\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"MustBeProposedOwner\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"NonUniqueSignatures\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"notPool\",\"type\":\"address\"}],\"name\":\"NotACompatiblePool\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"OnlyCallableByOwner\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"OracleCannotBeZeroAddress\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"OwnerCannotBeZero\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"err\",\"type\":\"bytes\"}],\"name\":\"ReceiverError\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"amountReleased\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"balancePre\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"balancePost\",\"type\":\"uint256\"}],\"name\":\"ReleaseOrMintBalanceMismatch\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"sourceChainSelector\",\"type\":\"uint64\"},{\"internalType\":\"bytes32\",\"name\":\"merkleRoot\",\"type\":\"bytes32\"}],\"name\":\"RootAlreadyCommitted\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"sourceChainSelector\",\"type\":\"uint64\"}],\"name\":\"RootNotCommitted\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"SignatureVerificationNotAllowedInExecutionPlugin\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"SignatureVerificationRequiredInCommitPlugin\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"SignaturesOutOfRegistration\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"sourceChainSelector\",\"type\":\"uint64\"}],\"name\":\"SourceChainNotEnabled\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"reportSourceChainSelector\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"messageSourceChainSelector\",\"type\":\"uint64\"}],\"name\":\"SourceChainSelectorMismatch\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"StaleCommitReport\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint8\",\"name\":\"ocrPluginType\",\"type\":\"uint8\"}],\"name\":\"StaticConfigCannotBeChanged\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"sourceChainSelector\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"sequenceNumber\",\"type\":\"uint64\"}],\"name\":\"TokenDataMismatch\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"target\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"err\",\"type\":\"bytes\"}],\"name\":\"TokenHandlingError\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"UnauthorizedSigner\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"UnauthorizedTransmitter\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"UnexpectedTokenData\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"expected\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"actual\",\"type\":\"uint256\"}],\"name\":\"WrongMessageLength\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"WrongNumberOfSignatures\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"ZeroAddressNotAllowed\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"ZeroChainSelectorNotAllowed\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"sourceChainSelector\",\"type\":\"uint64\"},{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"sequenceNumber\",\"type\":\"uint64\"}],\"name\":\"AlreadyAttempted\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"components\":[{\"internalType\":\"uint64\",\"name\":\"sourceChainSelector\",\"type\":\"uint64\"},{\"internalType\":\"bytes\",\"name\":\"onRampAddress\",\"type\":\"bytes\"},{\"internalType\":\"uint64\",\"name\":\"minSeqNr\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"maxSeqNr\",\"type\":\"uint64\"},{\"internalType\":\"bytes32\",\"name\":\"merkleRoot\",\"type\":\"bytes32\"}],\"indexed\":false,\"internalType\":\"structInternal.MerkleRoot[]\",\"name\":\"merkleRoots\",\"type\":\"tuple[]\"},{\"components\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"sourceToken\",\"type\":\"address\"},{\"internalType\":\"uint224\",\"name\":\"usdPerToken\",\"type\":\"uint224\"}],\"internalType\":\"structInternal.TokenPriceUpdate[]\",\"name\":\"tokenPriceUpdates\",\"type\":\"tuple[]\"},{\"components\":[{\"internalType\":\"uint64\",\"name\":\"destChainSelector\",\"type\":\"uint64\"},{\"internalType\":\"uint224\",\"name\":\"usdPerUnitGas\",\"type\":\"uint224\"}],\"internalType\":\"structInternal.GasPriceUpdate[]\",\"name\":\"gasPriceUpdates\",\"type\":\"tuple[]\"}],\"indexed\":false,\"internalType\":\"structInternal.PriceUpdates\",\"name\":\"priceUpdates\",\"type\":\"tuple\"}],\"name\":\"CommitReportAccepted\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"ocrPluginType\",\"type\":\"uint8\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"configDigest\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"address[]\",\"name\":\"signers\",\"type\":\"address[]\"},{\"indexed\":false,\"internalType\":\"address[]\",\"name\":\"transmitters\",\"type\":\"address[]\"},{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"F\",\"type\":\"uint8\"}],\"name\":\"ConfigSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"feeQuoter\",\"type\":\"address\"},{\"internalType\":\"uint32\",\"name\":\"permissionLessExecutionThresholdSeconds\",\"type\":\"uint32\"},{\"internalType\":\"bool\",\"name\":\"isRMNVerificationDisabled\",\"type\":\"bool\"},{\"internalType\":\"address\",\"name\":\"messageInterceptor\",\"type\":\"address\"}],\"indexed\":false,\"internalType\":\"structOffRamp.DynamicConfig\",\"name\":\"dynamicConfig\",\"type\":\"tuple\"}],\"name\":\"DynamicConfigSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint64\",\"name\":\"sourceChainSelector\",\"type\":\"uint64\"},{\"indexed\":true,\"internalType\":\"uint64\",\"name\":\"sequenceNumber\",\"type\":\"uint64\"},{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"messageId\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"messageHash\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"enumInternal.MessageExecutionState\",\"name\":\"state\",\"type\":\"uint8\"},{\"indexed\":false,\"internalType\":\"bytes\",\"name\":\"returnData\",\"type\":\"bytes\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"gasUsed\",\"type\":\"uint256\"}],\"name\":\"ExecutionStateChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"OwnershipTransferRequested\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"OwnershipTransferred\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"root\",\"type\":\"bytes32\"}],\"name\":\"RootRemoved\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"sourceChainSelector\",\"type\":\"uint64\"},{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"sequenceNumber\",\"type\":\"uint64\"}],\"name\":\"SkippedAlreadyExecutedMessage\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"sourceChainSelector\",\"type\":\"uint64\"}],\"name\":\"SkippedReportExecution\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint64\",\"name\":\"sourceChainSelector\",\"type\":\"uint64\"},{\"components\":[{\"internalType\":\"contractIRouter\",\"name\":\"router\",\"type\":\"address\"},{\"internalType\":\"bool\",\"name\":\"isEnabled\",\"type\":\"bool\"},{\"internalType\":\"uint64\",\"name\":\"minSeqNr\",\"type\":\"uint64\"},{\"internalType\":\"bytes\",\"name\":\"onRamp\",\"type\":\"bytes\"}],\"indexed\":false,\"internalType\":\"structOffRamp.SourceChainConfig\",\"name\":\"sourceConfig\",\"type\":\"tuple\"}],\"name\":\"SourceChainConfigSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"sourceChainSelector\",\"type\":\"uint64\"}],\"name\":\"SourceChainSelectorAdded\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"components\":[{\"internalType\":\"uint64\",\"name\":\"chainSelector\",\"type\":\"uint64\"},{\"internalType\":\"contractIRMNRemote\",\"name\":\"rmnRemote\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"tokenAdminRegistry\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"nonceManager\",\"type\":\"address\"}],\"indexed\":false,\"internalType\":\"structOffRamp.StaticConfig\",\"name\":\"staticConfig\",\"type\":\"tuple\"}],\"name\":\"StaticConfigSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint8\",\"name\":\"ocrPluginType\",\"type\":\"uint8\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"configDigest\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"sequenceNumber\",\"type\":\"uint64\"}],\"name\":\"Transmitted\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"acceptOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"contractIRouter\",\"name\":\"router\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"sourceChainSelector\",\"type\":\"uint64\"},{\"internalType\":\"bool\",\"name\":\"isEnabled\",\"type\":\"bool\"},{\"internalType\":\"bytes\",\"name\":\"onRamp\",\"type\":\"bytes\"}],\"internalType\":\"structOffRamp.SourceChainConfigArgs[]\",\"name\":\"sourceChainConfigUpdates\",\"type\":\"tuple[]\"}],\"name\":\"applySourceChainConfigUpdates\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"messageId\",\"type\":\"bytes32\"},{\"internalType\":\"uint64\",\"name\":\"sourceChainSelector\",\"type\":\"uint64\"},{\"internalType\":\"bytes\",\"name\":\"sender\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"internalType\":\"structClient.EVMTokenAmount[]\",\"name\":\"destTokenAmounts\",\"type\":\"tuple[]\"}],\"internalType\":\"structClient.Any2EVMMessage\",\"name\":\"\",\"type\":\"tuple\"}],\"name\":\"ccipReceive\",\"outputs\":[],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32[2]\",\"name\":\"reportContext\",\"type\":\"bytes32[2]\"},{\"internalType\":\"bytes\",\"name\":\"report\",\"type\":\"bytes\"},{\"internalType\":\"bytes32[]\",\"name\":\"rs\",\"type\":\"bytes32[]\"},{\"internalType\":\"bytes32[]\",\"name\":\"ss\",\"type\":\"bytes32[]\"},{\"internalType\":\"bytes32\",\"name\":\"rawVs\",\"type\":\"bytes32\"}],\"name\":\"commit\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32[2]\",\"name\":\"reportContext\",\"type\":\"bytes32[2]\"},{\"internalType\":\"bytes\",\"name\":\"report\",\"type\":\"bytes\"}],\"name\":\"execute\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"messageId\",\"type\":\"bytes32\"},{\"internalType\":\"uint64\",\"name\":\"sourceChainSelector\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"destChainSelector\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"sequenceNumber\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"nonce\",\"type\":\"uint64\"}],\"internalType\":\"structInternal.RampMessageHeader\",\"name\":\"header\",\"type\":\"tuple\"},{\"internalType\":\"bytes\",\"name\":\"sender\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"},{\"internalType\":\"address\",\"name\":\"receiver\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"gasLimit\",\"type\":\"uint256\"},{\"components\":[{\"internalType\":\"bytes\",\"name\":\"sourcePoolAddress\",\"type\":\"bytes\"},{\"internalType\":\"address\",\"name\":\"destTokenAddress\",\"type\":\"address\"},{\"internalType\":\"uint32\",\"name\":\"destGasAmount\",\"type\":\"uint32\"},{\"internalType\":\"bytes\",\"name\":\"extraData\",\"type\":\"bytes\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"internalType\":\"structInternal.Any2EVMTokenTransfer[]\",\"name\":\"tokenAmounts\",\"type\":\"tuple[]\"}],\"internalType\":\"structInternal.Any2EVMRampMessage\",\"name\":\"message\",\"type\":\"tuple\"},{\"internalType\":\"bytes[]\",\"name\":\"offchainTokenData\",\"type\":\"bytes[]\"},{\"internalType\":\"uint32[]\",\"name\":\"tokenGasOverrides\",\"type\":\"uint32[]\"}],\"name\":\"executeSingleMessage\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getAllSourceChainConfigs\",\"outputs\":[{\"internalType\":\"uint64[]\",\"name\":\"\",\"type\":\"uint64[]\"},{\"components\":[{\"internalType\":\"contractIRouter\",\"name\":\"router\",\"type\":\"address\"},{\"internalType\":\"bool\",\"name\":\"isEnabled\",\"type\":\"bool\"},{\"internalType\":\"uint64\",\"name\":\"minSeqNr\",\"type\":\"uint64\"},{\"internalType\":\"bytes\",\"name\":\"onRamp\",\"type\":\"bytes\"}],\"internalType\":\"structOffRamp.SourceChainConfig[]\",\"name\":\"\",\"type\":\"tuple[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getDynamicConfig\",\"outputs\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"feeQuoter\",\"type\":\"address\"},{\"internalType\":\"uint32\",\"name\":\"permissionLessExecutionThresholdSeconds\",\"type\":\"uint32\"},{\"internalType\":\"bool\",\"name\":\"isRMNVerificationDisabled\",\"type\":\"bool\"},{\"internalType\":\"address\",\"name\":\"messageInterceptor\",\"type\":\"address\"}],\"internalType\":\"structOffRamp.DynamicConfig\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"sourceChainSelector\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"sequenceNumber\",\"type\":\"uint64\"}],\"name\":\"getExecutionState\",\"outputs\":[{\"internalType\":\"enumInternal.MessageExecutionState\",\"name\":\"\",\"type\":\"uint8\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getLatestPriceSequenceNumber\",\"outputs\":[{\"internalType\":\"uint64\",\"name\":\"\",\"type\":\"uint64\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"sourceChainSelector\",\"type\":\"uint64\"},{\"internalType\":\"bytes32\",\"name\":\"root\",\"type\":\"bytes32\"}],\"name\":\"getMerkleRoot\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"sourceChainSelector\",\"type\":\"uint64\"}],\"name\":\"getSourceChainConfig\",\"outputs\":[{\"components\":[{\"internalType\":\"contractIRouter\",\"name\":\"router\",\"type\":\"address\"},{\"internalType\":\"bool\",\"name\":\"isEnabled\",\"type\":\"bool\"},{\"internalType\":\"uint64\",\"name\":\"minSeqNr\",\"type\":\"uint64\"},{\"internalType\":\"bytes\",\"name\":\"onRamp\",\"type\":\"bytes\"}],\"internalType\":\"structOffRamp.SourceChainConfig\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getStaticConfig\",\"outputs\":[{\"components\":[{\"internalType\":\"uint64\",\"name\":\"chainSelector\",\"type\":\"uint64\"},{\"internalType\":\"contractIRMNRemote\",\"name\":\"rmnRemote\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"tokenAdminRegistry\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"nonceManager\",\"type\":\"address\"}],\"internalType\":\"structOffRamp.StaticConfig\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint8\",\"name\":\"ocrPluginType\",\"type\":\"uint8\"}],\"name\":\"latestConfigDetails\",\"outputs\":[{\"components\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"configDigest\",\"type\":\"bytes32\"},{\"internalType\":\"uint8\",\"name\":\"F\",\"type\":\"uint8\"},{\"internalType\":\"uint8\",\"name\":\"n\",\"type\":\"uint8\"},{\"internalType\":\"bool\",\"name\":\"isSignatureVerificationEnabled\",\"type\":\"bool\"}],\"internalType\":\"structMultiOCR3Base.ConfigInfo\",\"name\":\"configInfo\",\"type\":\"tuple\"},{\"internalType\":\"address[]\",\"name\":\"signers\",\"type\":\"address[]\"},{\"internalType\":\"address[]\",\"name\":\"transmitters\",\"type\":\"address[]\"}],\"internalType\":\"structMultiOCR3Base.OCRConfig\",\"name\":\"ocrConfig\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"uint64\",\"name\":\"sourceChainSelector\",\"type\":\"uint64\"},{\"components\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"messageId\",\"type\":\"bytes32\"},{\"internalType\":\"uint64\",\"name\":\"sourceChainSelector\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"destChainSelector\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"sequenceNumber\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"nonce\",\"type\":\"uint64\"}],\"internalType\":\"structInternal.RampMessageHeader\",\"name\":\"header\",\"type\":\"tuple\"},{\"internalType\":\"bytes\",\"name\":\"sender\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"},{\"internalType\":\"address\",\"name\":\"receiver\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"gasLimit\",\"type\":\"uint256\"},{\"components\":[{\"internalType\":\"bytes\",\"name\":\"sourcePoolAddress\",\"type\":\"bytes\"},{\"internalType\":\"address\",\"name\":\"destTokenAddress\",\"type\":\"address\"},{\"internalType\":\"uint32\",\"name\":\"destGasAmount\",\"type\":\"uint32\"},{\"internalType\":\"bytes\",\"name\":\"extraData\",\"type\":\"bytes\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"internalType\":\"structInternal.Any2EVMTokenTransfer[]\",\"name\":\"tokenAmounts\",\"type\":\"tuple[]\"}],\"internalType\":\"structInternal.Any2EVMRampMessage[]\",\"name\":\"messages\",\"type\":\"tuple[]\"},{\"internalType\":\"bytes[][]\",\"name\":\"offchainTokenData\",\"type\":\"bytes[][]\"},{\"internalType\":\"bytes32[]\",\"name\":\"proofs\",\"type\":\"bytes32[]\"},{\"internalType\":\"uint256\",\"name\":\"proofFlagBits\",\"type\":\"uint256\"}],\"internalType\":\"structInternal.ExecutionReport[]\",\"name\":\"reports\",\"type\":\"tuple[]\"},{\"components\":[{\"internalType\":\"uint256\",\"name\":\"receiverExecutionGasLimit\",\"type\":\"uint256\"},{\"internalType\":\"uint32[]\",\"name\":\"tokenGasOverrides\",\"type\":\"uint32[]\"}],\"internalType\":\"structOffRamp.GasLimitOverride[][]\",\"name\":\"gasLimitOverrides\",\"type\":\"tuple[][]\"}],\"name\":\"manuallyExecute\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"feeQuoter\",\"type\":\"address\"},{\"internalType\":\"uint32\",\"name\":\"permissionLessExecutionThresholdSeconds\",\"type\":\"uint32\"},{\"internalType\":\"bool\",\"name\":\"isRMNVerificationDisabled\",\"type\":\"bool\"},{\"internalType\":\"address\",\"name\":\"messageInterceptor\",\"type\":\"address\"}],\"internalType\":\"structOffRamp.DynamicConfig\",\"name\":\"dynamicConfig\",\"type\":\"tuple\"}],\"name\":\"setDynamicConfig\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"configDigest\",\"type\":\"bytes32\"},{\"internalType\":\"uint8\",\"name\":\"ocrPluginType\",\"type\":\"uint8\"},{\"internalType\":\"uint8\",\"name\":\"F\",\"type\":\"uint8\"},{\"internalType\":\"bool\",\"name\":\"isSignatureVerificationEnabled\",\"type\":\"bool\"},{\"internalType\":\"address[]\",\"name\":\"signers\",\"type\":\"address[]\"},{\"internalType\":\"address[]\",\"name\":\"transmitters\",\"type\":\"address[]\"}],\"internalType\":\"structMultiOCR3Base.OCRConfigArgs[]\",\"name\":\"ocrConfigArgs\",\"type\":\"tuple[]\"}],\"name\":\"setOCR3Configs\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"typeAndVersion\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"}]", - Bin: "0x6101206040523480156200001257600080fd5b5060405162006be738038062006be7833981016040819052620000359162000880565b336000816200005757604051639b15e16f60e01b815260040160405180910390fd5b600180546001600160a01b0319166001600160a01b03848116919091179091558116156200008a576200008a81620001c4565b50504660805260208301516001600160a01b03161580620000b6575060408301516001600160a01b0316155b80620000cd575060608301516001600160a01b0316155b15620000ec576040516342bcdf7f60e11b815260040160405180910390fd5b82516001600160401b0316600003620001185760405163c656089560e01b815260040160405180910390fd5b82516001600160401b0390811660a052602080850180516001600160a01b0390811660c05260408088018051831660e0526060808a01805185166101005283518b519098168852945184169587019590955251821690850152905116908201527f683eb52ee924eb817377cfa8f41f238f4bb7a877da5267869dfffbad85f564d89060800160405180910390a1620001b0826200023e565b620001bb816200032c565b50505062000c72565b336001600160a01b03821603620001ee57604051636d6c4ee560e11b815260040160405180910390fd5b600080546001600160a01b0319166001600160a01b03838116918217835560015460405192939116917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b80516001600160a01b031662000267576040516342bcdf7f60e11b815260040160405180910390fd5b80516004805460208085018051604080880180516001600160a01b039889166001600160c01b03199097168717600160a01b63ffffffff958616021760ff60c01b1916600160c01b911515919091021790965560608089018051600580546001600160a01b031916918b169190911790558251968752935190921693850193909352935115159183019190915251909216908201527fcbb53bda7106a610de67df506ac86b65c44d5afac0fd2b11070dc2d61a6f2dee9060800160405180910390a150565b60005b8151811015620005c1576000828281518110620003505762000350620009aa565b60200260200101519050600081602001519050806001600160401b03166000036200038e5760405163c656089560e01b815260040160405180910390fd5b81516001600160a01b0316620003b7576040516342bcdf7f60e11b815260040160405180910390fd5b6001600160401b03811660009081526008602052604090206060830151600182018054620003e590620009c0565b905060000362000448578154600160a81b600160e81b031916600160a81b1782556040516001600160401b03841681527ff4c1390c70e5c0f491ae1ccbc06f9117cbbadf2767b247b3bc203280f24c0fb99060200160405180910390a1620004b9565b8154600160a81b90046001600160401b03166001148015906200048b57508051602082012060405162000480906001850190620009fc565b604051809103902014155b15620004b957604051632105803760e11b81526001600160401b038416600482015260240160405180910390fd5b80511580620004ef5750604080516000602082015201604051602081830303815290604052805190602001208180519060200120145b156200050e576040516342bcdf7f60e11b815260040160405180910390fd5b600182016200051e828262000acf565b506040840151825485516001600160a01b03166001600160a01b0319921515600160a01b02929092166001600160a81b0319909116171782556200056d60066001600160401b038516620005c5565b50826001600160401b03167f49f51971edd25182e97182d6ea372a0488ce2ab639f6a3a7ab4df0d2636fe56b83604051620005a9919062000b9b565b60405180910390a2505050508060010190506200032f565b5050565b6000620005d38383620005dc565b90505b92915050565b60008181526001830160205260408120546200062557508154600181810184556000848152602080822090930184905584548482528286019093526040902091909155620005d6565b506000620005d6565b634e487b7160e01b600052604160045260246000fd5b604051608081016001600160401b03811182821017156200066957620006696200062e565b60405290565b604051601f8201601f191681016001600160401b03811182821017156200069a576200069a6200062e565b604052919050565b80516001600160401b0381168114620006ba57600080fd5b919050565b6001600160a01b0381168114620006d557600080fd5b50565b80518015158114620006ba57600080fd5b6000601f83601f840112620006fd57600080fd5b825160206001600160401b03808311156200071c576200071c6200062e565b8260051b6200072d8382016200066f565b93845286810183019383810190898611156200074857600080fd5b84890192505b858310156200087357825184811115620007685760008081fd5b89016080601f19828d038101821315620007825760008081fd5b6200078c62000644565b888401516200079b81620006bf565b81526040620007ac858201620006a2565b8a8301526060620007bf818701620006d8565b83830152938501519389851115620007d75760008081fd5b84860195508f603f870112620007ef57600094508485fd5b8a8601519450898511156200080857620008086200062e565b620008198b858f880116016200066f565b93508484528f82868801011115620008315760008081fd5b60005b8581101562000851578681018301518582018d01528b0162000834565b5060009484018b0194909452509182015283525091840191908401906200074e565b9998505050505050505050565b60008060008385036101208112156200089857600080fd5b6080811215620008a757600080fd5b620008b162000644565b620008bc86620006a2565b81526020860151620008ce81620006bf565b60208201526040860151620008e381620006bf565b60408201526060860151620008f881620006bf565b606082015293506080607f19820112156200091257600080fd5b506200091d62000644565b60808501516200092d81620006bf565b815260a085015163ffffffff811681146200094757600080fd5b60208201526200095a60c08601620006d8565b604082015260e08501516200096f81620006bf565b60608201526101008501519092506001600160401b038111156200099257600080fd5b620009a086828701620006e9565b9150509250925092565b634e487b7160e01b600052603260045260246000fd5b600181811c90821680620009d557607f821691505b602082108103620009f657634e487b7160e01b600052602260045260246000fd5b50919050565b600080835462000a0c81620009c0565b6001828116801562000a27576001811462000a3d5762000a6e565b60ff198416875282151583028701945062000a6e565b8760005260208060002060005b8581101562000a655781548a82015290840190820162000a4a565b50505082870194505b50929695505050505050565b601f82111562000aca576000816000526020600020601f850160051c8101602086101562000aa55750805b601f850160051c820191505b8181101562000ac65782815560010162000ab1565b5050505b505050565b81516001600160401b0381111562000aeb5762000aeb6200062e565b62000b038162000afc8454620009c0565b8462000a7a565b602080601f83116001811462000b3b576000841562000b225750858301515b600019600386901b1c1916600185901b17855562000ac6565b600085815260208120601f198616915b8281101562000b6c5788860151825594840194600190910190840162000b4b565b508582101562000b8b5787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b602080825282546001600160a01b0381168383015260a081901c60ff161515604084015260a81c6001600160401b0316606083015260808083015260018084018054600093929190849062000bf081620009c0565b8060a089015260c0600183166000811462000c14576001811462000c315762000c63565b60ff19841660c08b015260c083151560051b8b0101945062000c63565b85600052602060002060005b8481101562000c5a5781548c820185015290880190890162000c3d565b8b0160c0019550505b50929998505050505050505050565b60805160a05160c05160e05161010051615ef862000cef600039600081816102070152612a4a0152600081816101d80152612cf20152600081816101a901528181610f7501528181611125015261244a01526000818161017a015281816125f501526126ac01526000818161190401526119370152615ef86000f3fe608060405234801561001057600080fd5b506004361061012c5760003560e01c80637edf52f4116100ad578063de5e0b9a11610071578063de5e0b9a146104b2578063e9d68a8e146104c5578063f2fde38b146104e5578063f58e03fc146104f8578063f716f99f1461050b57600080fd5b80637edf52f41461041257806385572ffb146104255780638da5cb5b14610433578063c673e5841461044e578063ccd37ba31461046e57600080fd5b80635e36480c116100f45780635e36480c146103075780635e7bb0081461032757806360987c201461033a5780637437ff9f1461034d57806379ba50971461040a57600080fd5b806304666f9c1461013157806306285c6914610146578063181f5a771461028d5780633f4b04aa146102d65780635215505b146102f1575b600080fd5b61014461013f366004613e1c565b61051e565b005b61023760408051608081018252600080825260208201819052918101829052606081019190915260405180608001604052807f00000000000000000000000000000000000000000000000000000000000000006001600160401b031681526020017f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031681526020017f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031681526020017f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316815250905090565b604051610284919081516001600160401b031681526020808301516001600160a01b0390811691830191909152604080840151821690830152606092830151169181019190915260800190565b60405180910390f35b6102c96040518060400160405280601181526020017f4f666652616d7020312e362e302d64657600000000000000000000000000000081525081565b6040516102849190613f8a565b600b546040516001600160401b039091168152602001610284565b6102f9610532565b604051610284929190613fe4565b61031a610315366004614085565b61078d565b60405161028491906140e2565b61014461033536600461464b565b6107e2565b6101446103483660046148da565b610a76565b6103c360408051608081018252600080825260208201819052918101829052606081019190915250604080516080810182526004546001600160a01b038082168352600160a01b820463ffffffff166020840152600160c01b90910460ff16151592820192909252600554909116606082015290565b604051610284919081516001600160a01b03908116825260208084015163ffffffff1690830152604080840151151590830152606092830151169181019190915260800190565b610144610d33565b61014461042036600461496e565b610db6565b61014461012c3660046149d3565b6001546040516001600160a01b039091168152602001610284565b61046161045c366004614a1e565b610dc7565b6040516102849190614a7e565b6104a461047c366004614af3565b6001600160401b03919091166000908152600a60209081526040808320938352929052205490565b604051908152602001610284565b6101446104c0366004614b6f565b610f25565b6104d86104d3366004614c21565b611428565b6040516102849190614c3c565b6101446104f3366004614c4f565b611534565b610144610506366004614c6c565b611545565b610144610519366004614d27565b6115ae565b6105266115f0565b61052f8161161d565b50565b606080600061054160066118a6565b6001600160401b0381111561055857610558613c3c565b6040519080825280602002602001820160405280156105a957816020015b60408051608081018252600080825260208083018290529282015260608082015282526000199092019101816105765790505b50905060006105b860066118a6565b6001600160401b038111156105cf576105cf613c3c565b6040519080825280602002602001820160405280156105f8578160200160208202803683370190505b50905060005b61060860066118a6565b8110156107845761061a6006826118b0565b82828151811061062c5761062c614e64565b60200260200101906001600160401b031690816001600160401b0316815250506008600083838151811061066257610662614e64565b6020908102919091018101516001600160401b039081168352828201939093526040918201600020825160808101845281546001600160a01b038116825260ff600160a01b820416151593820193909352600160a81b909204909316918101919091526001820180549192916060840191906106dd90614e7a565b80601f016020809104026020016040519081016040528092919081815260200182805461070990614e7a565b80156107565780601f1061072b57610100808354040283529160200191610756565b820191906000526020600020905b81548152906001019060200180831161073957829003601f168201915b50505050508152505083828151811061077157610771614e64565b60209081029190910101526001016105fe565b50939092509050565b600061079b60016004614eca565b60026107a8608085614ef3565b6001600160401b03166107bb9190614f19565b6107c585856118bc565b901c1660038111156107d9576107d96140b8565b90505b92915050565b6107ea611901565b81518151811461080d576040516320f8fd5960e21b815260040160405180910390fd5b60005b81811015610a6657600084828151811061082c5761082c614e64565b6020026020010151905060008160200151519050600085848151811061085457610854614e64565b602002602001015190508051821461087f576040516320f8fd5960e21b815260040160405180910390fd5b60005b82811015610a5757600082828151811061089e5761089e614e64565b60200260200101516000015190506000856020015183815181106108c4576108c4614e64565b602002602001015190508160001461091d57806080015182101561091d578551815151604051633a98d46360e11b81526001600160401b0390921660048301526024820152604481018390526064015b60405180910390fd5b83838151811061092f5761092f614e64565b602002602001015160200151518160a00151511461097c57805180516060909101516040516370a193fd60e01b815260048101929092526001600160401b03166024820152604401610914565b60005b8160a0015151811015610a495760008585815181106109a0576109a0614e64565b60200260200101516020015182815181106109bd576109bd614e64565b602002602001015163ffffffff16905080600014610a405760008360a0015183815181106109ed576109ed614e64565b60200260200101516040015163ffffffff16905080821015610a3e578351516040516348e617b360e01b81526004810191909152602481018490526044810182905260648101839052608401610914565b505b5060010161097f565b505050806001019050610882565b50505050806001019050610810565b50610a718383611969565b505050565b333014610a96576040516306e34e6560e31b815260040160405180910390fd5b6040805160008082526020820190925281610ad3565b6040805180820190915260008082526020820152815260200190600190039081610aac5790505b5060a08701515190915015610b0957610b068660a001518760200151886060015189600001516020015189898989611a2c565b90505b6040805160a081018252875151815287516020908101516001600160401b03168183015288015181830152908701516060820152608081018290526005546001600160a01b03168015610bfc576040516308d450a160e01b81526001600160a01b038216906308d450a190610b82908590600401614fdd565b600060405180830381600087803b158015610b9c57600080fd5b505af1925050508015610bad575060015b610bfc573d808015610bdb576040519150601f19603f3d011682016040523d82523d6000602084013e610be0565b606091505b50806040516309c2532560e01b81526004016109149190613f8a565b604088015151158015610c1157506080880151155b80610c28575060608801516001600160a01b03163b155b80610c4f57506060880151610c4d906001600160a01b03166385572ffb60e01b611bdd565b155b15610c5c57505050610d2c565b87516020908101516001600160401b03166000908152600890915260408082205460808b015160608c01519251633cf9798360e01b815284936001600160a01b0390931692633cf9798392610cba9289926113889291600401614ff0565b6000604051808303816000875af1158015610cd9573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052610d01919081019061502c565b509150915081610d2657806040516302a35ba360e21b81526004016109149190613f8a565b50505050505b5050505050565b6000546001600160a01b03163314610d5e5760405163015aa1e360e11b815260040160405180910390fd5b600180546001600160a01b0319808216339081179093556000805490911681556040516001600160a01b03909216929183917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a350565b610dbe6115f0565b61052f81611bf9565b610e0a6040805160e081019091526000606082018181526080830182905260a0830182905260c08301919091528190815260200160608152602001606081525090565b60ff808316600090815260026020818152604092839020835160e081018552815460608201908152600183015480881660808401526101008104881660a0840152620100009004909616151560c082015294855291820180548451818402810184019095528085529293858301939092830182828015610eb357602002820191906000526020600020905b81546001600160a01b03168152600190910190602001808311610e95575b5050505050815260200160038201805480602002602001604051908101604052809291908181526020018280548015610f1557602002820191906000526020600020905b81546001600160a01b03168152600190910190602001808311610ef7575b5050505050815250509050919050565b6000610f33878901896152d9565b6004805491925090600160c01b900460ff16610fdd5760208201515115610fdd5760208201516040808401519051633854844f60e11b81526001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016926370a9089e92610fac9230929190600401615501565b60006040518083038186803b158015610fc457600080fd5b505afa158015610fd8573d6000803e3d6000fd5b505050505b81515151151580610ff357508151602001515115155b156110be57600b5460208b0135906001600160401b038083169116101561109657600b805467ffffffffffffffff19166001600160401b03831617905581548351604051633937306f60e01b81526001600160a01b0390921691633937306f9161105f91600401615614565b600060405180830381600087803b15801561107957600080fd5b505af115801561108d573d6000803e3d6000fd5b505050506110bc565b8260200151516000036110bc57604051632261116760e01b815260040160405180910390fd5b505b60005b826020015151811015611374576000836020015182815181106110e6576110e6614e64565b60209081029190910101518051604051632cbc26bb60e01b815267ffffffffffffffff60801b608083901b166004820152919250906001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001690632cbc26bb90602401602060405180830381865afa15801561116c573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906111909190615627565b156111b957604051637edeb53960e11b81526001600160401b0382166004820152602401610914565b60006111c482611cfe565b9050806001016040516111d79190615644565b6040518091039020836020015180519060200120146112145782602001518160010160405163b80d8fa960e01b8152600401610914929190615737565b60408301518154600160a81b90046001600160401b039081169116141580611255575082606001516001600160401b031683604001516001600160401b0316115b1561129a57825160408085015160608601519151636af0786b60e11b81526001600160401b039384166004820152908316602482015291166044820152606401610914565b6080830151806112bd5760405163504570e360e01b815260040160405180910390fd5b83516001600160401b03166000908152600a60209081526040808320848452909152902054156113155783516040516332cf0cbf60e01b81526001600160401b03909116600482015260248101829052604401610914565b606084015161132590600161575c565b825467ffffffffffffffff60a81b1916600160a81b6001600160401b0392831602179092559251166000908152600a6020908152604080832094835293905291909120429055506001016110c1565b50602082015182516040517f35c02761bcd3ef995c6a601a1981f4ed3934dcbe5041e24e286c89f5531d17e4926113ac929091615783565b60405180910390a1610d2660008b8b8b8b8b8080602002602001604051908101604052809392919081815260200183836020028082843760009201919091525050604080516020808f0282810182019093528e82529093508e92508d9182918501908490808284376000920191909152508c9250611d4a915050565b60408051608080820183526000808352602080840182905283850182905260608085018190526001600160401b03878116845260088352928690208651948501875280546001600160a01b0381168652600160a01b810460ff16151593860193909352600160a81b9092049092169483019490945260018401805493949293918401916114b490614e7a565b80601f01602080910402602001604051908101604052809291908181526020018280546114e090614e7a565b8015610f155780601f1061150257610100808354040283529160200191610f15565b820191906000526020600020905b81548152906001019060200180831161151057505050919092525091949350505050565b61153c6115f0565b61052f81612043565b611585611554828401846157a8565b604080516000808252602082019092529061157f565b606081526020019060019003908161156a5790505b50611969565b6040805160008082526020820190925290506115a8600185858585866000611d4a565b50505050565b6115b66115f0565b60005b81518110156115ec576115e48282815181106115d7576115d7614e64565b60200260200101516120bc565b6001016115b9565b5050565b6001546001600160a01b0316331461161b576040516315ae3a6f60e11b815260040160405180910390fd5b565b60005b81518110156115ec57600082828151811061163d5761163d614e64565b60200260200101519050600081602001519050806001600160401b031660000361167a5760405163c656089560e01b815260040160405180910390fd5b81516001600160a01b03166116a2576040516342bcdf7f60e11b815260040160405180910390fd5b6001600160401b038116600090815260086020526040902060608301516001820180546116ce90614e7a565b905060000361173057815467ffffffffffffffff60a81b1916600160a81b1782556040516001600160401b03841681527ff4c1390c70e5c0f491ae1ccbc06f9117cbbadf2767b247b3bc203280f24c0fb99060200160405180910390a1611799565b8154600160a81b90046001600160401b0316600114801590611770575080516020820120604051611765906001850190615644565b604051809103902014155b1561179957604051632105803760e11b81526001600160401b0384166004820152602401610914565b805115806117ce5750604080516000602082015201604051602081830303815290604052805190602001208180519060200120145b156117ec576040516342bcdf7f60e11b815260040160405180910390fd5b600182016117fa828261582c565b506040840151825485516001600160a01b03166001600160a01b0319921515600160a01b029290921674ffffffffffffffffffffffffffffffffffffffffff199091161717825561185560066001600160401b0385166123e6565b50826001600160401b03167f49f51971edd25182e97182d6ea372a0488ce2ab639f6a3a7ab4df0d2636fe56b8360405161188f91906158eb565b60405180910390a250505050806001019050611620565b60006107dc825490565b60006107d983836123f2565b6001600160401b0382166000908152600960205260408120816118e0608085615939565b6001600160401b031681526020810191909152604001600020549392505050565b467f00000000000000000000000000000000000000000000000000000000000000001461161b57604051630f01ce8560e01b81527f00000000000000000000000000000000000000000000000000000000000000006004820152466024820152604401610914565b815160000361198b5760405163c2e5347d60e01b815260040160405180910390fd5b805160408051600080825260208201909252911591816119ce565b6040805180820190915260008152606060208201528152602001906001900390816119a65790505b50905060005b8451811015610d2c57611a248582815181106119f2576119f2614e64565b602002602001015184611a1e57858381518110611a1157611a11614e64565b602002602001015161241c565b8361241c565b6001016119d4565b606088516001600160401b03811115611a4757611a47613c3c565b604051908082528060200260200182016040528015611a8c57816020015b6040805180820190915260008082526020820152815260200190600190039081611a655790505b509050811560005b8a51811015611bcf5781611b2c57848482818110611ab457611ab4614e64565b9050602002016020810190611ac9919061595f565b63ffffffff1615611b2c57848482818110611ae657611ae6614e64565b9050602002016020810190611afb919061595f565b8b8281518110611b0d57611b0d614e64565b60200260200101516040019063ffffffff16908163ffffffff16815250505b611baa8b8281518110611b4157611b41614e64565b60200260200101518b8b8b8b8b87818110611b5e57611b5e614e64565b9050602002810190611b70919061597a565b8080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250612cb792505050565b838281518110611bbc57611bbc614e64565b6020908102919091010152600101611a94565b505098975050505050505050565b6000611be883612f99565b80156107d957506107d98383612fcc565b80516001600160a01b0316611c21576040516342bcdf7f60e11b815260040160405180910390fd5b80516004805460208085018051604080880180516001600160a01b039889167fffffffffffffffff0000000000000000000000000000000000000000000000009097168717600160a01b63ffffffff958616021760ff60c01b1916600160c01b911515919091021790965560608089018051600580546001600160a01b031916918b169190911790558251968752935190921693850193909352935115159183019190915251909216908201527fcbb53bda7106a610de67df506ac86b65c44d5afac0fd2b11070dc2d61a6f2dee9060800160405180910390a150565b6001600160401b03811660009081526008602052604081208054600160a01b900460ff166107dc5760405163ed053c5960e01b81526001600160401b0384166004820152602401610914565b60ff87811660009081526002602090815260408083208151608081018352815481526001909101548086169382019390935261010083048516918101919091526201000090910490921615156060830152873590611da98760846159c0565b9050826060015115611df1578451611dc2906020614f19565b8651611dcf906020614f19565b611dda9060a06159c0565b611de491906159c0565b611dee90826159c0565b90505b368114611e1a57604051638e1192e160e01b815260048101829052366024820152604401610914565b5081518114611e495781516040516324f7d61360e21b8152600481019190915260248101829052604401610914565b611e51611901565b60ff808a1660009081526003602090815260408083203384528252808320815180830190925280548086168352939491939092840191610100909104166002811115611e9f57611e9f6140b8565b6002811115611eb057611eb06140b8565b9052509050600281602001516002811115611ecd57611ecd6140b8565b148015611f215750600260008b60ff1660ff168152602001908152602001600020600301816000015160ff1681548110611f0957611f09614e64565b6000918252602090912001546001600160a01b031633145b611f3e57604051631b41e11d60e31b815260040160405180910390fd5b50816060015115611fee576020820151611f599060016159d3565b60ff16855114611f7c576040516371253a2560e01b815260040160405180910390fd5b8351855114611f9e5760405163a75d88af60e01b815260040160405180910390fd5b60008787604051611fb09291906159ec565b604051908190038120611fc7918b906020016159fc565b604051602081830303815290604052805190602001209050611fec8a82888888613056565b505b6040805182815260208a8101356001600160401b03169082015260ff8b16917f198d6990ef96613a9026203077e422916918b03ff47f0be6bee7b02d8e139ef0910160405180910390a2505050505050505050565b336001600160a01b0382160361206c57604051636d6c4ee560e11b815260040160405180910390fd5b600080546001600160a01b0319166001600160a01b03838116918217835560015460405192939116917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b806040015160ff166000036120e7576000604051631b3fab5160e11b81526004016109149190615a10565b60208082015160ff80821660009081526002909352604083206001810154929390928392169003612138576060840151600182018054911515620100000262ff000019909216919091179055612174565b6060840151600182015460ff6201000090910416151590151514612174576040516321fd80df60e21b815260ff84166004820152602401610914565b60a0840151805161010010156121a0576001604051631b3fab5160e11b81526004016109149190615a10565b80516000036121c5576005604051631b3fab5160e11b81526004016109149190615a10565b61222b848460030180548060200260200160405190810160405280929190818152602001828054801561222157602002820191906000526020600020905b81546001600160a01b03168152600190910190602001808311612203575b5050505050613209565b84606001511561235b576122998484600201805480602002602001604051908101604052809291908181526020018280548015612221576020028201919060005260206000209081546001600160a01b03168152600190910190602001808311612203575050505050613209565b6080850151805161010010156122c5576002604051631b3fab5160e11b81526004016109149190615a10565b60408601516122d5906003615a2a565b60ff168151116122fb576003604051631b3fab5160e11b81526004016109149190615a10565b815181511015612321576001604051631b3fab5160e11b81526004016109149190615a10565b805160018401805461ff00191661010060ff84160217905561234c9060028601906020840190613bc2565b5061235985826001613272565b505b61236784826002613272565b805161237c9060038501906020840190613bc2565b5060408581015160018401805460ff191660ff8316179055865180855560a088015192517fab8b1b57514019638d7b5ce9c638fe71366fe8e2be1c40a7a80f1733d0e9f547936123d59389939260028a01929190615a46565b60405180910390a1610d2c846133cd565b60006107d98383613450565b600082600001828154811061240957612409614e64565b9060005260206000200154905092915050565b81518151604051632cbc26bb60e01b8152608083901b67ffffffffffffffff60801b166004820152901515907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031690632cbc26bb90602401602060405180830381865afa158015612499573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906124bd9190615627565b1561252e5780156124ec57604051637edeb53960e11b81526001600160401b0383166004820152602401610914565b6040516001600160401b03831681527faab522ed53d887e56ed53dd37398a01aeef6a58e0fa77c2173beb9512d8949339060200160405180910390a150505050565b602084015151600081900361256457845160405163676cf24b60e11b81526001600160401b039091166004820152602401610914565b8460400151518114612589576040516357e0e08360e01b815260040160405180910390fd5b6000816001600160401b038111156125a3576125a3613c3c565b6040519080825280602002602001820160405280156125cc578160200160208202803683370190505b50905060007f2425b0b9f9054c76ff151b0a175b18f37a4a4e82013a72e9f15c9caa095ed21f857f000000000000000000000000000000000000000000000000000000000000000061261d88611cfe565b60010160405161262d9190615644565b604051908190038120612665949392916020019384526001600160401b03928316602085015291166040830152606082015260800190565b60405160208183030381529060405280519060200120905060005b8381101561279b576000886020015182815181106126a0576126a0614e64565b602002602001015190507f00000000000000000000000000000000000000000000000000000000000000006001600160401b03168160000151604001516001600160401b0316146127175780516040908101519051631c21951160e11b81526001600160401b039091166004820152602401610914565b866001600160401b03168160000151602001516001600160401b03161461276b57805160200151604051636c95f1eb60e01b81526001600160401b03808a1660048301529091166024820152604401610914565b612775818461349f565b84838151811061278757612787614e64565b602090810291909101015250600101612680565b505060006127b3858389606001518a608001516135a7565b9050806000036127e157604051633ee8bd3f60e11b81526001600160401b0386166004820152602401610914565b60005b83811015612cad5760005a905060008960200151838151811061280957612809614e64565b6020026020010151905060006128278983600001516060015161078d565b9050600081600381111561283d5761283d6140b8565b148061285a57506003816003811115612858576128586140b8565b145b6128b057815160600151604080516001600160401b03808d16825290921660208301527f3b575419319662b2a6f5e2467d84521517a3382b908eb3d557bb3fdb0c50e23c910160405180910390a1505050612ca5565b6060881561298f578a85815181106128ca576128ca614e64565b6020908102919091018101510151600454909150600090600160a01b900463ffffffff166128f88842614eca565b119050808061291857506003836003811115612916576129166140b8565b145b612940576040516354e7e43160e11b81526001600160401b038c166004820152602401610914565b8b868151811061295257612952614e64565b602002602001015160000151600014612989578b868151811061297757612977614e64565b60209081029190910101515160808501525b506129fb565b60008260038111156129a3576129a36140b8565b146129fb57825160600151604080516001600160401b03808e16825290921660208301527f3ef2a99c550a751d4b0b261268f05a803dfb049ab43616a1ffb388f61fe65120910160405180910390a150505050612ca5565b8251608001516001600160401b031615612ad1576000826003811115612a2357612a236140b8565b03612ad15782516080015160208401516040516370701e5760e11b81526001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000169263e0e03cae92612a81928f929190600401615af8565b6020604051808303816000875af1158015612aa0573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612ac49190615627565b612ad15750505050612ca5565b60008c604001518681518110612ae957612ae9614e64565b6020026020010151905080518460a001515114612b3357835160600151604051631cfe6d8b60e01b81526001600160401b03808e1660048301529091166024820152604401610914565b612b478b85600001516060015160016135e4565b600080612b55868486613689565b91509150612b6c8d876000015160600151846135e4565b8b15612bc3576003826003811115612b8657612b866140b8565b03612bc3576000856003811115612b9f57612b9f6140b8565b14612bc357855151604051632b11b8d960e01b815261091491908390600401615b24565b6002826003811115612bd757612bd76140b8565b14612c18576003826003811115612bf057612bf06140b8565b14612c18578551606001516040516349362d1f60e11b8152610914918f918590600401615b3d565b8560000151600001518660000151606001516001600160401b03168e6001600160401b03167f05665fe9ad095383d018353f4cbcba77e84db27dd215081bbf7cdf9ae6fbe48b8d8c81518110612c7057612c70614e64565b602002602001015186865a612c85908f614eca565b604051612c959493929190615b62565b60405180910390a4505050505050505b6001016127e4565b5050505050505050565b6040805180820190915260008082526020820152602086015160405163bbe4f6db60e01b81526001600160a01b0380831660048301526000917f00000000000000000000000000000000000000000000000000000000000000009091169063bbe4f6db90602401602060405180830381865afa158015612d3b573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612d5f9190615b99565b90506001600160a01b0381161580612d8e5750612d8c6001600160a01b03821663aff2afbf60e01b611bdd565b155b15612db75760405163ae9b4ce960e01b81526001600160a01b0382166004820152602401610914565b600080612dcf88858c6040015163ffffffff1661373d565b915091506000806000612e826040518061010001604052808e81526020018c6001600160401b031681526020018d6001600160a01b031681526020018f608001518152602001896001600160a01b031681526020018f6000015181526020018f6060015181526020018b815250604051602401612e4c9190615bb6565b60408051601f198184030181529190526020810180516001600160e01b0316633907753760e01b17905287866113886084613822565b92509250925082612eaa578582604051634ff17cad60e11b8152600401610914929190615c82565b8151602014612ed9578151604051631e3be00960e21b8152602060048201526024810191909152604401610914565b600082806020019051810190612eef9190615ca4565b9050866001600160a01b03168c6001600160a01b031614612f6b576000612f208d8a612f1b868a614eca565b61373d565b50905086811080612f3a575081612f378883614eca565b14155b15612f695760405163a966e21f60e01b8152600481018390526024810188905260448101829052606401610914565b505b604080518082019091526001600160a01b039098168852602088015250949550505050505095945050505050565b6000612fac826301ffc9a760e01b612fcc565b80156107dc5750612fc5826001600160e01b0319612fcc565b1592915050565b6040516001600160e01b031982166024820152600090819060440160408051601f19818403018152919052602080820180516001600160e01b03166301ffc9a760e01b178152825192935060009283928392909183918a617530fa92503d9150600051905082801561303f575060208210155b801561304b5750600081115b979650505050505050565b8251600090815b81811015612cad57600060018886846020811061307c5761307c614e64565b61308991901a601b6159d3565b89858151811061309b5761309b614e64565b60200260200101518986815181106130b5576130b5614e64565b6020026020010151604051600081526020016040526040516130f3949392919093845260ff9290921660208401526040830152606082015260800190565b6020604051602081039080840390855afa158015613115573d6000803e3d6000fd5b505060408051601f1981015160ff808e166000908152600360209081528582206001600160a01b03851683528152858220858701909652855480841686529397509095509293928401916101009004166002811115613176576131766140b8565b6002811115613187576131876140b8565b90525090506001816020015160028111156131a4576131a46140b8565b146131c257604051636518c33d60e11b815260040160405180910390fd5b8051600160ff9091161b8516156131ec57604051633d9ef1f160e21b815260040160405180910390fd5b806000015160ff166001901b85179450505080600101905061305d565b60005b8151811015610a715760ff83166000908152600360205260408120835190919084908490811061323e5761323e614e64565b6020908102919091018101516001600160a01b03168252810191909152604001600020805461ffff1916905560010161320c565b60005b82518110156115a857600083828151811061329257613292614e64565b60200260200101519050600060028111156132af576132af6140b8565b60ff80871660009081526003602090815260408083206001600160a01b038716845290915290205461010090041660028111156132ee576132ee6140b8565b1461330f576004604051631b3fab5160e11b81526004016109149190615a10565b6001600160a01b0381166133365760405163d6c62c9b60e01b815260040160405180910390fd5b60405180604001604052808360ff16815260200184600281111561335c5761335c6140b8565b905260ff80871660009081526003602090815260408083206001600160a01b0387168452825290912083518154931660ff198416811782559184015190929091839161ffff1916176101008360028111156133b9576133b96140b8565b021790555090505050806001019050613275565b60ff818116600081815260026020526040902060010154620100009004909116906134255780613410576040516317bd8dd160e11b815260040160405180910390fd5b600b805467ffffffffffffffff191690555050565b60001960ff8316016115ec5780156115ec576040516307b8c74d60e51b815260040160405180910390fd5b6000818152600183016020526040812054613497575081546001818101845560008481526020808220909301849055845484825282860190935260409020919091556107dc565b5060006107dc565b81518051606080850151908301516080808701519401516040516000958695889561350395919490939192916020019485526001600160a01b039390931660208501526001600160401b039182166040850152606084015216608082015260a00190565b604051602081830303815290604052805190602001208560200151805190602001208660400151805190602001208760a001516040516020016135469190615d5e565b60408051601f198184030181528282528051602091820120908301979097528101949094526060840192909252608083015260a082015260c081019190915260e0015b60405160208183030381529060405280519060200120905092915050565b6000806135b58585856138fc565b6001600160401b0387166000908152600a6020908152604080832093835292905220549150505b949350505050565b600060026135f3608085614ef3565b6001600160401b03166136069190614f19565b9050600061361485856118bc565b90508161362360016004614eca565b901b19168183600381111561363a5761363a6140b8565b6001600160401b03871660009081526009602052604081209190921b92909217918291613668608088615939565b6001600160401b031681526020810191909152604001600020555050505050565b604051630304c3e160e51b815260009060609030906360987c20906136b690889088908890600401615df5565b600060405180830381600087803b1580156136d057600080fd5b505af19250505080156136e1575060015b613720573d80801561370f576040519150601f19603f3d011682016040523d82523d6000602084013e613714565b606091505b50600392509050613735565b50506040805160208101909152600081526002905b935093915050565b600080600080600061379e8860405160240161376891906001600160a01b0391909116815260200190565b60408051601f198184030181529190526020810180516001600160e01b03166370a0823160e01b17905288886113886084613822565b925092509250826137c6578682604051634ff17cad60e11b8152600401610914929190615c82565b60208251146137f5578151604051631e3be00960e21b8152602060048201526024810191909152604401610914565b818060200190518101906138099190615ca4565b6138138288614eca565b94509450505050935093915050565b6000606060008361ffff166001600160401b0381111561384457613844613c3c565b6040519080825280601f01601f19166020018201604052801561386e576020820181803683370190505b509150863b6138885763030ed58f60e21b60005260046000fd5b5a858110156138a257632be8ca8b60e21b60005260046000fd5b85900360408104810387106138c2576337c3be2960e01b60005260046000fd5b505a6000808a5160208c0160008c8cf193505a900390503d848111156138e55750835b808352806000602085013e50955095509592505050565b825182516000919081830361392457604051630469ac9960e21b815260040160405180910390fd5b610101821180159061393857506101018111155b613955576040516309bde33960e01b815260040160405180910390fd5b6000198282010161010081111561397f576040516309bde33960e01b815260040160405180910390fd5b806000036139ac578660008151811061399a5761399a614e64565b60200260200101519350505050613b7a565b6000816001600160401b038111156139c6576139c6613c3c565b6040519080825280602002602001820160405280156139ef578160200160208202803683370190505b50905060008080805b85811015613b195760006001821b8b811603613a535788851015613a3c578c5160018601958e918110613a2d57613a2d614e64565b60200260200101519050613a75565b8551600185019487918110613a2d57613a2d614e64565b8b5160018401938d918110613a6a57613a6a614e64565b602002602001015190505b600089861015613aa5578d5160018701968f918110613a9657613a96614e64565b60200260200101519050613ac7565b8651600186019588918110613abc57613abc614e64565b602002602001015190505b82851115613ae8576040516309bde33960e01b815260040160405180910390fd5b613af28282613b81565b878481518110613b0457613b04614e64565b602090810291909101015250506001016139f8565b506001850382148015613b2b57508683145b8015613b3657508581145b613b53576040516309bde33960e01b815260040160405180910390fd5b836001860381518110613b6857613b68614e64565b60200260200101519750505050505050505b9392505050565b6000818310613b9957613b948284613b9f565b6107d9565b6107d983835b604080516001602082015290810183905260608101829052600090608001613589565b828054828255906000526020600020908101928215613c17579160200282015b82811115613c1757825182546001600160a01b0319166001600160a01b03909116178255602090920191600190910190613be2565b50613c23929150613c27565b5090565b5b80821115613c235760008155600101613c28565b634e487b7160e01b600052604160045260246000fd5b604051608081016001600160401b0381118282101715613c7457613c74613c3c565b60405290565b60405160a081016001600160401b0381118282101715613c7457613c74613c3c565b60405160c081016001600160401b0381118282101715613c7457613c74613c3c565b604080519081016001600160401b0381118282101715613c7457613c74613c3c565b604051606081016001600160401b0381118282101715613c7457613c74613c3c565b604051601f8201601f191681016001600160401b0381118282101715613d2a57613d2a613c3c565b604052919050565b60006001600160401b03821115613d4b57613d4b613c3c565b5060051b60200190565b6001600160a01b038116811461052f57600080fd5b80356001600160401b0381168114613d8157600080fd5b919050565b801515811461052f57600080fd5b8035613d8181613d86565b60006001600160401b03821115613db857613db8613c3c565b50601f01601f191660200190565b600082601f830112613dd757600080fd5b8135613dea613de582613d9f565b613d02565b818152846020838601011115613dff57600080fd5b816020850160208301376000918101602001919091529392505050565b60006020808385031215613e2f57600080fd5b82356001600160401b0380821115613e4657600080fd5b818501915085601f830112613e5a57600080fd5b8135613e68613de582613d32565b81815260059190911b83018401908481019088831115613e8757600080fd5b8585015b83811015613f2d57803585811115613ea35760008081fd5b86016080818c03601f1901811315613ebb5760008081fd5b613ec3613c52565b89830135613ed081613d55565b81526040613edf848201613d6a565b8b830152606080850135613ef281613d86565b83830152928401359289841115613f0b57600091508182fd5b613f198f8d86880101613dc6565b908301525085525050918601918601613e8b565b5098975050505050505050565b60005b83811015613f55578181015183820152602001613f3d565b50506000910152565b60008151808452613f76816020860160208601613f3a565b601f01601f19169290920160200192915050565b6020815260006107d96020830184613f5e565b6001600160a01b0381511682526020810151151560208301526001600160401b03604082015116604083015260006060820151608060608501526135dc6080850182613f5e565b604080825283519082018190526000906020906060840190828701845b828110156140265781516001600160401b031684529284019290840190600101614001565b50505083810382850152845180825282820190600581901b8301840187850160005b8381101561407657601f19868403018552614064838351613f9d565b94870194925090860190600101614048565b50909998505050505050505050565b6000806040838503121561409857600080fd5b6140a183613d6a565b91506140af60208401613d6a565b90509250929050565b634e487b7160e01b600052602160045260246000fd5b600481106140de576140de6140b8565b9052565b602081016107dc82846140ce565b600060a0828403121561410257600080fd5b61410a613c7a565b90508135815261411c60208301613d6a565b602082015261412d60408301613d6a565b604082015261413e60608301613d6a565b606082015261414f60808301613d6a565b608082015292915050565b8035613d8181613d55565b803563ffffffff81168114613d8157600080fd5b600082601f83011261418a57600080fd5b8135602061419a613de583613d32565b82815260059290921b840181019181810190868411156141b957600080fd5b8286015b848110156142895780356001600160401b03808211156141dd5760008081fd5b9088019060a0828b03601f19018113156141f75760008081fd5b6141ff613c7a565b87840135838111156142115760008081fd5b61421f8d8a83880101613dc6565b82525060408085013561423181613d55565b828a01526060614242868201614165565b8284015260809150818601358581111561425c5760008081fd5b61426a8f8c838a0101613dc6565b91840191909152509190930135908301525083529183019183016141bd565b509695505050505050565b600061014082840312156142a757600080fd5b6142af613c9c565b90506142bb83836140f0565b815260a08201356001600160401b03808211156142d757600080fd5b6142e385838601613dc6565b602084015260c08401359150808211156142fc57600080fd5b61430885838601613dc6565b604084015261431960e0850161415a565b6060840152610100840135608084015261012084013591508082111561433e57600080fd5b5061434b84828501614179565b60a08301525092915050565b600082601f83011261436857600080fd5b81356020614378613de583613d32565b82815260059290921b8401810191818101908684111561439757600080fd5b8286015b848110156142895780356001600160401b038111156143ba5760008081fd5b6143c88986838b0101614294565b84525091830191830161439b565b600082601f8301126143e757600080fd5b813560206143f7613de583613d32565b82815260059290921b8401810191818101908684111561441657600080fd5b8286015b848110156142895780356001600160401b038082111561443957600080fd5b818901915089603f83011261444d57600080fd5b8582013561445d613de582613d32565b81815260059190911b830160400190878101908c83111561447d57600080fd5b604085015b838110156144b65780358581111561449957600080fd5b6144a88f6040838a0101613dc6565b845250918901918901614482565b5087525050509284019250830161441a565b600082601f8301126144d957600080fd5b813560206144e9613de583613d32565b8083825260208201915060208460051b87010193508684111561450b57600080fd5b602086015b848110156142895780358352918301918301614510565b600082601f83011261453857600080fd5b81356020614548613de583613d32565b82815260059290921b8401810191818101908684111561456757600080fd5b8286015b848110156142895780356001600160401b038082111561458b5760008081fd5b9088019060a0828b03601f19018113156145a55760008081fd5b6145ad613c7a565b6145b8888501613d6a565b8152604080850135848111156145ce5760008081fd5b6145dc8e8b83890101614357565b8a84015250606080860135858111156145f55760008081fd5b6146038f8c838a01016143d6565b838501525060809150818601358581111561461e5760008081fd5b61462c8f8c838a01016144c8565b918401919091525091909301359083015250835291830191830161456b565b6000806040838503121561465e57600080fd5b6001600160401b038335111561467357600080fd5b6146808484358501614527565b91506001600160401b036020840135111561469a57600080fd5b6020830135830184601f8201126146b057600080fd5b6146bd613de58235613d32565b81358082526020808301929160051b8401018710156146db57600080fd5b602083015b6020843560051b850101811015614881576001600160401b038135111561470657600080fd5b87603f82358601011261471857600080fd5b61472b613de56020833587010135613d32565b81358501602081810135808452908301929160059190911b016040018a101561475357600080fd5b604083358701015b83358701602081013560051b01604001811015614871576001600160401b038135111561478757600080fd5b833587018135016040818d03603f190112156147a257600080fd5b6147aa613cbe565b604082013581526001600160401b03606083013511156147c957600080fd5b8c605f6060840135840101126147de57600080fd5b60406060830135830101356147f5613de582613d32565b808282526020820191508f60608460051b606088013588010101111561481a57600080fd5b6060808601358601015b60608460051b6060880135880101018110156148515761484381614165565b835260209283019201614824565b50806020850152505050808552505060208301925060208101905061475b565b50845250602092830192016146e0565b508093505050509250929050565b60008083601f8401126148a157600080fd5b5081356001600160401b038111156148b857600080fd5b6020830191508360208260051b85010111156148d357600080fd5b9250929050565b6000806000806000606086880312156148f257600080fd5b85356001600160401b038082111561490957600080fd5b61491589838a01614294565b9650602088013591508082111561492b57600080fd5b61493789838a0161488f565b9096509450604088013591508082111561495057600080fd5b5061495d8882890161488f565b969995985093965092949392505050565b60006080828403121561498057600080fd5b614988613c52565b823561499381613d55565b81526149a160208401614165565b602082015260408301356149b481613d86565b604082015260608301356149c781613d55565b60608201529392505050565b6000602082840312156149e557600080fd5b81356001600160401b038111156149fb57600080fd5b820160a08185031215613b7a57600080fd5b803560ff81168114613d8157600080fd5b600060208284031215614a3057600080fd5b6107d982614a0d565b60008151808452602080850194506020840160005b83811015614a735781516001600160a01b031687529582019590820190600101614a4e565b509495945050505050565b60208152600082518051602084015260ff602082015116604084015260ff604082015116606084015260608101511515608084015250602083015160c060a0840152614acd60e0840182614a39565b90506040840151601f198483030160c0850152614aea8282614a39565b95945050505050565b60008060408385031215614b0657600080fd5b614b0f83613d6a565b946020939093013593505050565b80604081018310156107dc57600080fd5b60008083601f840112614b4057600080fd5b5081356001600160401b03811115614b5757600080fd5b6020830191508360208285010111156148d357600080fd5b60008060008060008060008060c0898b031215614b8b57600080fd5b614b958a8a614b1d565b975060408901356001600160401b0380821115614bb157600080fd5b614bbd8c838d01614b2e565b909950975060608b0135915080821115614bd657600080fd5b614be28c838d0161488f565b909750955060808b0135915080821115614bfb57600080fd5b50614c088b828c0161488f565b999c989b50969995989497949560a00135949350505050565b600060208284031215614c3357600080fd5b6107d982613d6a565b6020815260006107d96020830184613f9d565b600060208284031215614c6157600080fd5b8135613b7a81613d55565b600080600060608486031215614c8157600080fd5b614c8b8585614b1d565b925060408401356001600160401b03811115614ca657600080fd5b614cb286828701614b2e565b9497909650939450505050565b600082601f830112614cd057600080fd5b81356020614ce0613de583613d32565b8083825260208201915060208460051b870101935086841115614d0257600080fd5b602086015b84811015614289578035614d1a81613d55565b8352918301918301614d07565b60006020808385031215614d3a57600080fd5b82356001600160401b0380821115614d5157600080fd5b818501915085601f830112614d6557600080fd5b8135614d73613de582613d32565b81815260059190911b83018401908481019088831115614d9257600080fd5b8585015b83811015613f2d57803585811115614dad57600080fd5b860160c0818c03601f19011215614dc45760008081fd5b614dcc613c9c565b8882013581526040614ddf818401614a0d565b8a8301526060614df0818501614a0d565b8284015260809150614e03828501613d94565b9083015260a08381013589811115614e1b5760008081fd5b614e298f8d83880101614cbf565b838501525060c0840135915088821115614e435760008081fd5b614e518e8c84870101614cbf565b9083015250845250918601918601614d96565b634e487b7160e01b600052603260045260246000fd5b600181811c90821680614e8e57607f821691505b602082108103614eae57634e487b7160e01b600052602260045260246000fd5b50919050565b634e487b7160e01b600052601160045260246000fd5b818103818111156107dc576107dc614eb4565b634e487b7160e01b600052601260045260246000fd5b60006001600160401b0380841680614f0d57614f0d614edd565b92169190910692915050565b80820281158282048414176107dc576107dc614eb4565b80518252600060206001600160401b0381840151168185015260408084015160a06040870152614f6360a0870182613f5e565b905060608501518682036060880152614f7c8282613f5e565b608087810151898303918a01919091528051808352908601935060009250908501905b80831015614fd157835180516001600160a01b0316835286015186830152928501926001929092019190840190614f9f565b50979650505050505050565b6020815260006107d96020830184614f30565b6080815260006150036080830187614f30565b61ffff9590951660208301525060408101929092526001600160a01b0316606090910152919050565b60008060006060848603121561504157600080fd5b835161504c81613d86565b60208501519093506001600160401b0381111561506857600080fd5b8401601f8101861361507957600080fd5b8051615087613de582613d9f565b81815287602083850101111561509c57600080fd5b6150ad826020830160208601613f3a565b809450505050604084015190509250925092565b80356001600160e01b0381168114613d8157600080fd5b600082601f8301126150e957600080fd5b813560206150f9613de583613d32565b82815260069290921b8401810191818101908684111561511857600080fd5b8286015b8481101561428957604081890312156151355760008081fd5b61513d613cbe565b61514682613d6a565b81526151538583016150c1565b8186015283529183019160400161511c565b600082601f83011261517657600080fd5b81356020615186613de583613d32565b82815260059290921b840181019181810190868411156151a557600080fd5b8286015b848110156142895780356001600160401b03808211156151c95760008081fd5b9088019060a0828b03601f19018113156151e35760008081fd5b6151eb613c7a565b6151f6888501613d6a565b81526040808501358481111561520c5760008081fd5b61521a8e8b83890101613dc6565b8a840152506060935061522e848601613d6a565b90820152608061523f858201613d6a565b938201939093529201359082015283529183019183016151a9565b600082601f83011261526b57600080fd5b8135602061527b613de583613d32565b82815260069290921b8401810191818101908684111561529a57600080fd5b8286015b8481101561428957604081890312156152b75760008081fd5b6152bf613cbe565b81358152848201358582015283529183019160400161529e565b600060208083850312156152ec57600080fd5b82356001600160401b038082111561530357600080fd5b908401906060828703121561531757600080fd5b61531f613ce0565b82358281111561532e57600080fd5b8301604081890381131561534157600080fd5b615349613cbe565b82358581111561535857600080fd5b8301601f81018b1361536957600080fd5b8035615377613de582613d32565b81815260069190911b8201890190898101908d83111561539657600080fd5b928a01925b828410156153e65785848f0312156153b35760008081fd5b6153bb613cbe565b84356153c681613d55565b81526153d3858d016150c1565b818d0152825292850192908a019061539b565b8452505050828701359150848211156153fe57600080fd5b61540a8a8385016150d8565b8188015283525050828401358281111561542357600080fd5b61542f88828601615165565b8583015250604083013593508184111561544857600080fd5b6154548785850161525a565b60408201529695505050505050565b600082825180855260208086019550808260051b84010181860160005b848110156154f457601f19868403018952815160a06001600160401b038083511686528683015182888801526154b883880182613f5e565b60408581015184169089015260608086015190931692880192909252506080928301519290950191909152509783019790830190600101615480565b5090979650505050505050565b6001600160a01b0384168152600060206060818401526155246060840186615463565b83810360408581019190915285518083528387019284019060005b818110156140765784518051845286015186840152938501939183019160010161553f565b805160408084528151848201819052600092602091908201906060870190855b818110156155bb57835180516001600160a01b031684528501516001600160e01b0316858401529284019291850191600101615584565b50508583015187820388850152805180835290840192506000918401905b80831015614fd157835180516001600160401b031683528501516001600160e01b0316858301529284019260019290920191908501906155d9565b6020815260006107d96020830184615564565b60006020828403121561563957600080fd5b8151613b7a81613d86565b600080835461565281614e7a565b6001828116801561566a576001811461567f576156ae565b60ff19841687528215158302870194506156ae565b8760005260208060002060005b858110156156a55781548a82015290840190820161568c565b50505082870194505b50929695505050505050565b600081546156c781614e7a565b8085526020600183811680156156e457600181146156fe5761572c565b60ff1985168884015283151560051b88018301955061572c565b866000528260002060005b858110156157245781548a8201860152908301908401615709565b890184019650505b505050505092915050565b60408152600061574a6040830185613f5e565b8281036020840152614aea81856156ba565b6001600160401b0381811683821601908082111561577c5761577c614eb4565b5092915050565b6040815260006157966040830185615463565b8281036020840152614aea8185615564565b6000602082840312156157ba57600080fd5b81356001600160401b038111156157d057600080fd5b6135dc84828501614527565b601f821115610a71576000816000526020600020601f850160051c810160208610156158055750805b601f850160051c820191505b8181101561582457828155600101615811565b505050505050565b81516001600160401b0381111561584557615845613c3c565b615859816158538454614e7a565b846157dc565b602080601f83116001811461588e57600084156158765750858301515b600019600386901b1c1916600185901b178555615824565b600085815260208120601f198616915b828110156158bd5788860151825594840194600190910190840161589e565b50858210156158db5787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b60208152600082546001600160a01b038116602084015260ff8160a01c16151560408401526001600160401b038160a81c166060840152506080808301526107d960a08301600185016156ba565b60006001600160401b038084168061595357615953614edd565b92169190910492915050565b60006020828403121561597157600080fd5b6107d982614165565b6000808335601e1984360301811261599157600080fd5b8301803591506001600160401b038211156159ab57600080fd5b6020019150368190038213156148d357600080fd5b808201808211156107dc576107dc614eb4565b60ff81811683821601908111156107dc576107dc614eb4565b8183823760009101908152919050565b828152604082602083013760600192915050565b6020810160068310615a2457615a246140b8565b91905290565b60ff818116838216029081169081811461577c5761577c614eb4565b600060a0820160ff881683526020878185015260a0604085015281875480845260c0860191508860005282600020935060005b81811015615a9e5784546001600160a01b031683526001948501949284019201615a79565b50508481036060860152865180825290820192508187019060005b81811015615ade5782516001600160a01b031685529383019391830191600101615ab9565b50505060ff851660808501525090505b9695505050505050565b60006001600160401b03808616835280851660208401525060606040830152614aea6060830184613f5e565b8281526040602082015260006135dc6040830184613f5e565b6001600160401b03848116825283166020820152606081016135dc60408301846140ce565b848152615b7260208201856140ce565b608060408201526000615b886080830185613f5e565b905082606083015295945050505050565b600060208284031215615bab57600080fd5b8151613b7a81613d55565b6020815260008251610100806020850152615bd5610120850183613f5e565b91506020850151615bf160408601826001600160401b03169052565b5060408501516001600160a01b038116606086015250606085015160808501526080850151615c2b60a08601826001600160a01b03169052565b5060a0850151601f19808685030160c0870152615c488483613f5e565b935060c08701519150808685030160e0870152615c658483613f5e565b935060e0870151915080868503018387015250615aee8382613f5e565b6001600160a01b03831681526040602082015260006135dc6040830184613f5e565b600060208284031215615cb657600080fd5b5051919050565b600082825180855260208086019550808260051b84010181860160005b848110156154f457601f19868403018952815160a08151818652615d0082870182613f5e565b9150506001600160a01b03868301511686860152604063ffffffff8184015116818701525060608083015186830382880152615d3c8382613f5e565b6080948501519790940196909652505098840198925090830190600101615cda565b6020815260006107d96020830184615cbd565b60008282518085526020808601955060208260051b8401016020860160005b848110156154f457601f19868403018952615dac838351613f5e565b98840198925090830190600101615d90565b60008151808452602080850194506020840160005b83811015614a7357815163ffffffff1687529582019590820190600101615dd3565b60608152600084518051606084015260208101516001600160401b0380821660808601528060408401511660a08601528060608401511660c08601528060808401511660e0860152505050602085015161014080610100850152615e5d6101a0850183613f5e565b91506040870151605f198086850301610120870152615e7c8483613f5e565b935060608901519150615e99838701836001600160a01b03169052565b608089015161016087015260a0890151925080868503016101808701525050615ec28282615cbd565b9150508281036020840152615ed78186615d71565b90508281036040840152615aee8185615dbe56fea164736f6c6343000818000a", + ABI: "[{\"inputs\":[{\"components\":[{\"internalType\":\"uint64\",\"name\":\"chainSelector\",\"type\":\"uint64\"},{\"internalType\":\"uint16\",\"name\":\"gasForCallExactCheck\",\"type\":\"uint16\"},{\"internalType\":\"contractIRMNRemote\",\"name\":\"rmnRemote\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"tokenAdminRegistry\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"nonceManager\",\"type\":\"address\"}],\"internalType\":\"structOffRamp.StaticConfig\",\"name\":\"staticConfig\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"feeQuoter\",\"type\":\"address\"},{\"internalType\":\"uint32\",\"name\":\"permissionLessExecutionThresholdSeconds\",\"type\":\"uint32\"},{\"internalType\":\"bool\",\"name\":\"isRMNVerificationDisabled\",\"type\":\"bool\"},{\"internalType\":\"address\",\"name\":\"messageInterceptor\",\"type\":\"address\"}],\"internalType\":\"structOffRamp.DynamicConfig\",\"name\":\"dynamicConfig\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"contractIRouter\",\"name\":\"router\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"sourceChainSelector\",\"type\":\"uint64\"},{\"internalType\":\"bool\",\"name\":\"isEnabled\",\"type\":\"bool\"},{\"internalType\":\"bytes\",\"name\":\"onRamp\",\"type\":\"bytes\"}],\"internalType\":\"structOffRamp.SourceChainConfigArgs[]\",\"name\":\"sourceChainConfigs\",\"type\":\"tuple[]\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[],\"name\":\"CanOnlySelfCall\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"CannotTransferToSelf\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"reportOnRamp\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"configOnRamp\",\"type\":\"bytes\"}],\"name\":\"CommitOnRampMismatch\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"expected\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"actual\",\"type\":\"bytes32\"}],\"name\":\"ConfigDigestMismatch\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"sourceChainSelector\",\"type\":\"uint64\"}],\"name\":\"CursedByRMN\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"EmptyBatch\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"sourceChainSelector\",\"type\":\"uint64\"}],\"name\":\"EmptyReport\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"messageId\",\"type\":\"bytes32\"},{\"internalType\":\"bytes\",\"name\":\"err\",\"type\":\"bytes\"}],\"name\":\"ExecutionError\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"expected\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"actual\",\"type\":\"uint256\"}],\"name\":\"ForkedChain\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"enumMultiOCR3Base.InvalidConfigErrorType\",\"name\":\"errorType\",\"type\":\"uint8\"}],\"name\":\"InvalidConfig\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"expected\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"got\",\"type\":\"uint256\"}],\"name\":\"InvalidDataLength\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"sourceChainSelector\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"min\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"max\",\"type\":\"uint64\"}],\"name\":\"InvalidInterval\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"sourceChainSelector\",\"type\":\"uint64\"},{\"internalType\":\"bytes32\",\"name\":\"messageId\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"newLimit\",\"type\":\"uint256\"}],\"name\":\"InvalidManualExecutionGasLimit\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"messageId\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"tokenIndex\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"oldLimit\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"tokenGasOverride\",\"type\":\"uint256\"}],\"name\":\"InvalidManualExecutionTokenGasOverride\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"messageDestChainSelector\",\"type\":\"uint64\"}],\"name\":\"InvalidMessageDestChainSelector\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"sourceChainSelector\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"sequenceNumber\",\"type\":\"uint64\"},{\"internalType\":\"enumInternal.MessageExecutionState\",\"name\":\"newState\",\"type\":\"uint8\"}],\"name\":\"InvalidNewState\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"sourceChainSelector\",\"type\":\"uint64\"}],\"name\":\"InvalidOnRampUpdate\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidProof\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidRoot\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"LeavesCannotBeEmpty\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"messageId\",\"type\":\"bytes32\"},{\"internalType\":\"uint64\",\"name\":\"sequenceNumber\",\"type\":\"uint64\"}],\"name\":\"ManualExecutionGasAmountCountMismatch\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"ManualExecutionGasLimitMismatch\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"sourceChainSelector\",\"type\":\"uint64\"}],\"name\":\"ManualExecutionNotYetEnabled\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"errorReason\",\"type\":\"bytes\"}],\"name\":\"MessageValidationError\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"MustBeProposedOwner\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"NonUniqueSignatures\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"notPool\",\"type\":\"address\"}],\"name\":\"NotACompatiblePool\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"OnlyCallableByOwner\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"OracleCannotBeZeroAddress\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"OwnerCannotBeZero\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"err\",\"type\":\"bytes\"}],\"name\":\"ReceiverError\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"amountReleased\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"balancePre\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"balancePost\",\"type\":\"uint256\"}],\"name\":\"ReleaseOrMintBalanceMismatch\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"sourceChainSelector\",\"type\":\"uint64\"},{\"internalType\":\"bytes32\",\"name\":\"merkleRoot\",\"type\":\"bytes32\"}],\"name\":\"RootAlreadyCommitted\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"sourceChainSelector\",\"type\":\"uint64\"}],\"name\":\"RootNotCommitted\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"SignatureVerificationNotAllowedInExecutionPlugin\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"SignatureVerificationRequiredInCommitPlugin\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"SignaturesOutOfRegistration\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"sourceChainSelector\",\"type\":\"uint64\"}],\"name\":\"SourceChainNotEnabled\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"reportSourceChainSelector\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"messageSourceChainSelector\",\"type\":\"uint64\"}],\"name\":\"SourceChainSelectorMismatch\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"StaleCommitReport\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint8\",\"name\":\"ocrPluginType\",\"type\":\"uint8\"}],\"name\":\"StaticConfigCannotBeChanged\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"sourceChainSelector\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"sequenceNumber\",\"type\":\"uint64\"}],\"name\":\"TokenDataMismatch\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"target\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"err\",\"type\":\"bytes\"}],\"name\":\"TokenHandlingError\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"UnauthorizedSigner\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"UnauthorizedTransmitter\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"UnexpectedTokenData\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"expected\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"actual\",\"type\":\"uint256\"}],\"name\":\"WrongMessageLength\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"WrongNumberOfSignatures\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"ZeroAddressNotAllowed\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"ZeroChainSelectorNotAllowed\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"sourceChainSelector\",\"type\":\"uint64\"},{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"sequenceNumber\",\"type\":\"uint64\"}],\"name\":\"AlreadyAttempted\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"components\":[{\"internalType\":\"uint64\",\"name\":\"sourceChainSelector\",\"type\":\"uint64\"},{\"internalType\":\"bytes\",\"name\":\"onRampAddress\",\"type\":\"bytes\"},{\"internalType\":\"uint64\",\"name\":\"minSeqNr\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"maxSeqNr\",\"type\":\"uint64\"},{\"internalType\":\"bytes32\",\"name\":\"merkleRoot\",\"type\":\"bytes32\"}],\"indexed\":false,\"internalType\":\"structInternal.MerkleRoot[]\",\"name\":\"merkleRoots\",\"type\":\"tuple[]\"},{\"components\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"sourceToken\",\"type\":\"address\"},{\"internalType\":\"uint224\",\"name\":\"usdPerToken\",\"type\":\"uint224\"}],\"internalType\":\"structInternal.TokenPriceUpdate[]\",\"name\":\"tokenPriceUpdates\",\"type\":\"tuple[]\"},{\"components\":[{\"internalType\":\"uint64\",\"name\":\"destChainSelector\",\"type\":\"uint64\"},{\"internalType\":\"uint224\",\"name\":\"usdPerUnitGas\",\"type\":\"uint224\"}],\"internalType\":\"structInternal.GasPriceUpdate[]\",\"name\":\"gasPriceUpdates\",\"type\":\"tuple[]\"}],\"indexed\":false,\"internalType\":\"structInternal.PriceUpdates\",\"name\":\"priceUpdates\",\"type\":\"tuple\"}],\"name\":\"CommitReportAccepted\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"ocrPluginType\",\"type\":\"uint8\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"configDigest\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"address[]\",\"name\":\"signers\",\"type\":\"address[]\"},{\"indexed\":false,\"internalType\":\"address[]\",\"name\":\"transmitters\",\"type\":\"address[]\"},{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"F\",\"type\":\"uint8\"}],\"name\":\"ConfigSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"feeQuoter\",\"type\":\"address\"},{\"internalType\":\"uint32\",\"name\":\"permissionLessExecutionThresholdSeconds\",\"type\":\"uint32\"},{\"internalType\":\"bool\",\"name\":\"isRMNVerificationDisabled\",\"type\":\"bool\"},{\"internalType\":\"address\",\"name\":\"messageInterceptor\",\"type\":\"address\"}],\"indexed\":false,\"internalType\":\"structOffRamp.DynamicConfig\",\"name\":\"dynamicConfig\",\"type\":\"tuple\"}],\"name\":\"DynamicConfigSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint64\",\"name\":\"sourceChainSelector\",\"type\":\"uint64\"},{\"indexed\":true,\"internalType\":\"uint64\",\"name\":\"sequenceNumber\",\"type\":\"uint64\"},{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"messageId\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"messageHash\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"enumInternal.MessageExecutionState\",\"name\":\"state\",\"type\":\"uint8\"},{\"indexed\":false,\"internalType\":\"bytes\",\"name\":\"returnData\",\"type\":\"bytes\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"gasUsed\",\"type\":\"uint256\"}],\"name\":\"ExecutionStateChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"OwnershipTransferRequested\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"OwnershipTransferred\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"root\",\"type\":\"bytes32\"}],\"name\":\"RootRemoved\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"sourceChainSelector\",\"type\":\"uint64\"},{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"sequenceNumber\",\"type\":\"uint64\"}],\"name\":\"SkippedAlreadyExecutedMessage\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"sourceChainSelector\",\"type\":\"uint64\"}],\"name\":\"SkippedReportExecution\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint64\",\"name\":\"sourceChainSelector\",\"type\":\"uint64\"},{\"components\":[{\"internalType\":\"contractIRouter\",\"name\":\"router\",\"type\":\"address\"},{\"internalType\":\"bool\",\"name\":\"isEnabled\",\"type\":\"bool\"},{\"internalType\":\"uint64\",\"name\":\"minSeqNr\",\"type\":\"uint64\"},{\"internalType\":\"bytes\",\"name\":\"onRamp\",\"type\":\"bytes\"}],\"indexed\":false,\"internalType\":\"structOffRamp.SourceChainConfig\",\"name\":\"sourceConfig\",\"type\":\"tuple\"}],\"name\":\"SourceChainConfigSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"sourceChainSelector\",\"type\":\"uint64\"}],\"name\":\"SourceChainSelectorAdded\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"components\":[{\"internalType\":\"uint64\",\"name\":\"chainSelector\",\"type\":\"uint64\"},{\"internalType\":\"uint16\",\"name\":\"gasForCallExactCheck\",\"type\":\"uint16\"},{\"internalType\":\"contractIRMNRemote\",\"name\":\"rmnRemote\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"tokenAdminRegistry\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"nonceManager\",\"type\":\"address\"}],\"indexed\":false,\"internalType\":\"structOffRamp.StaticConfig\",\"name\":\"staticConfig\",\"type\":\"tuple\"}],\"name\":\"StaticConfigSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint8\",\"name\":\"ocrPluginType\",\"type\":\"uint8\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"configDigest\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"sequenceNumber\",\"type\":\"uint64\"}],\"name\":\"Transmitted\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"acceptOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"contractIRouter\",\"name\":\"router\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"sourceChainSelector\",\"type\":\"uint64\"},{\"internalType\":\"bool\",\"name\":\"isEnabled\",\"type\":\"bool\"},{\"internalType\":\"bytes\",\"name\":\"onRamp\",\"type\":\"bytes\"}],\"internalType\":\"structOffRamp.SourceChainConfigArgs[]\",\"name\":\"sourceChainConfigUpdates\",\"type\":\"tuple[]\"}],\"name\":\"applySourceChainConfigUpdates\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"messageId\",\"type\":\"bytes32\"},{\"internalType\":\"uint64\",\"name\":\"sourceChainSelector\",\"type\":\"uint64\"},{\"internalType\":\"bytes\",\"name\":\"sender\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"internalType\":\"structClient.EVMTokenAmount[]\",\"name\":\"destTokenAmounts\",\"type\":\"tuple[]\"}],\"internalType\":\"structClient.Any2EVMMessage\",\"name\":\"\",\"type\":\"tuple\"}],\"name\":\"ccipReceive\",\"outputs\":[],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32[2]\",\"name\":\"reportContext\",\"type\":\"bytes32[2]\"},{\"internalType\":\"bytes\",\"name\":\"report\",\"type\":\"bytes\"},{\"internalType\":\"bytes32[]\",\"name\":\"rs\",\"type\":\"bytes32[]\"},{\"internalType\":\"bytes32[]\",\"name\":\"ss\",\"type\":\"bytes32[]\"},{\"internalType\":\"bytes32\",\"name\":\"rawVs\",\"type\":\"bytes32\"}],\"name\":\"commit\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32[2]\",\"name\":\"reportContext\",\"type\":\"bytes32[2]\"},{\"internalType\":\"bytes\",\"name\":\"report\",\"type\":\"bytes\"}],\"name\":\"execute\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"messageId\",\"type\":\"bytes32\"},{\"internalType\":\"uint64\",\"name\":\"sourceChainSelector\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"destChainSelector\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"sequenceNumber\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"nonce\",\"type\":\"uint64\"}],\"internalType\":\"structInternal.RampMessageHeader\",\"name\":\"header\",\"type\":\"tuple\"},{\"internalType\":\"bytes\",\"name\":\"sender\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"},{\"internalType\":\"address\",\"name\":\"receiver\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"gasLimit\",\"type\":\"uint256\"},{\"components\":[{\"internalType\":\"bytes\",\"name\":\"sourcePoolAddress\",\"type\":\"bytes\"},{\"internalType\":\"address\",\"name\":\"destTokenAddress\",\"type\":\"address\"},{\"internalType\":\"uint32\",\"name\":\"destGasAmount\",\"type\":\"uint32\"},{\"internalType\":\"bytes\",\"name\":\"extraData\",\"type\":\"bytes\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"internalType\":\"structInternal.Any2EVMTokenTransfer[]\",\"name\":\"tokenAmounts\",\"type\":\"tuple[]\"}],\"internalType\":\"structInternal.Any2EVMRampMessage\",\"name\":\"message\",\"type\":\"tuple\"},{\"internalType\":\"bytes[]\",\"name\":\"offchainTokenData\",\"type\":\"bytes[]\"},{\"internalType\":\"uint32[]\",\"name\":\"tokenGasOverrides\",\"type\":\"uint32[]\"}],\"name\":\"executeSingleMessage\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getAllSourceChainConfigs\",\"outputs\":[{\"internalType\":\"uint64[]\",\"name\":\"\",\"type\":\"uint64[]\"},{\"components\":[{\"internalType\":\"contractIRouter\",\"name\":\"router\",\"type\":\"address\"},{\"internalType\":\"bool\",\"name\":\"isEnabled\",\"type\":\"bool\"},{\"internalType\":\"uint64\",\"name\":\"minSeqNr\",\"type\":\"uint64\"},{\"internalType\":\"bytes\",\"name\":\"onRamp\",\"type\":\"bytes\"}],\"internalType\":\"structOffRamp.SourceChainConfig[]\",\"name\":\"\",\"type\":\"tuple[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getDynamicConfig\",\"outputs\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"feeQuoter\",\"type\":\"address\"},{\"internalType\":\"uint32\",\"name\":\"permissionLessExecutionThresholdSeconds\",\"type\":\"uint32\"},{\"internalType\":\"bool\",\"name\":\"isRMNVerificationDisabled\",\"type\":\"bool\"},{\"internalType\":\"address\",\"name\":\"messageInterceptor\",\"type\":\"address\"}],\"internalType\":\"structOffRamp.DynamicConfig\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"sourceChainSelector\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"sequenceNumber\",\"type\":\"uint64\"}],\"name\":\"getExecutionState\",\"outputs\":[{\"internalType\":\"enumInternal.MessageExecutionState\",\"name\":\"\",\"type\":\"uint8\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getLatestPriceSequenceNumber\",\"outputs\":[{\"internalType\":\"uint64\",\"name\":\"\",\"type\":\"uint64\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"sourceChainSelector\",\"type\":\"uint64\"},{\"internalType\":\"bytes32\",\"name\":\"root\",\"type\":\"bytes32\"}],\"name\":\"getMerkleRoot\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"sourceChainSelector\",\"type\":\"uint64\"}],\"name\":\"getSourceChainConfig\",\"outputs\":[{\"components\":[{\"internalType\":\"contractIRouter\",\"name\":\"router\",\"type\":\"address\"},{\"internalType\":\"bool\",\"name\":\"isEnabled\",\"type\":\"bool\"},{\"internalType\":\"uint64\",\"name\":\"minSeqNr\",\"type\":\"uint64\"},{\"internalType\":\"bytes\",\"name\":\"onRamp\",\"type\":\"bytes\"}],\"internalType\":\"structOffRamp.SourceChainConfig\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getStaticConfig\",\"outputs\":[{\"components\":[{\"internalType\":\"uint64\",\"name\":\"chainSelector\",\"type\":\"uint64\"},{\"internalType\":\"uint16\",\"name\":\"gasForCallExactCheck\",\"type\":\"uint16\"},{\"internalType\":\"contractIRMNRemote\",\"name\":\"rmnRemote\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"tokenAdminRegistry\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"nonceManager\",\"type\":\"address\"}],\"internalType\":\"structOffRamp.StaticConfig\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint8\",\"name\":\"ocrPluginType\",\"type\":\"uint8\"}],\"name\":\"latestConfigDetails\",\"outputs\":[{\"components\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"configDigest\",\"type\":\"bytes32\"},{\"internalType\":\"uint8\",\"name\":\"F\",\"type\":\"uint8\"},{\"internalType\":\"uint8\",\"name\":\"n\",\"type\":\"uint8\"},{\"internalType\":\"bool\",\"name\":\"isSignatureVerificationEnabled\",\"type\":\"bool\"}],\"internalType\":\"structMultiOCR3Base.ConfigInfo\",\"name\":\"configInfo\",\"type\":\"tuple\"},{\"internalType\":\"address[]\",\"name\":\"signers\",\"type\":\"address[]\"},{\"internalType\":\"address[]\",\"name\":\"transmitters\",\"type\":\"address[]\"}],\"internalType\":\"structMultiOCR3Base.OCRConfig\",\"name\":\"ocrConfig\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"uint64\",\"name\":\"sourceChainSelector\",\"type\":\"uint64\"},{\"components\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"messageId\",\"type\":\"bytes32\"},{\"internalType\":\"uint64\",\"name\":\"sourceChainSelector\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"destChainSelector\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"sequenceNumber\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"nonce\",\"type\":\"uint64\"}],\"internalType\":\"structInternal.RampMessageHeader\",\"name\":\"header\",\"type\":\"tuple\"},{\"internalType\":\"bytes\",\"name\":\"sender\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"},{\"internalType\":\"address\",\"name\":\"receiver\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"gasLimit\",\"type\":\"uint256\"},{\"components\":[{\"internalType\":\"bytes\",\"name\":\"sourcePoolAddress\",\"type\":\"bytes\"},{\"internalType\":\"address\",\"name\":\"destTokenAddress\",\"type\":\"address\"},{\"internalType\":\"uint32\",\"name\":\"destGasAmount\",\"type\":\"uint32\"},{\"internalType\":\"bytes\",\"name\":\"extraData\",\"type\":\"bytes\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"internalType\":\"structInternal.Any2EVMTokenTransfer[]\",\"name\":\"tokenAmounts\",\"type\":\"tuple[]\"}],\"internalType\":\"structInternal.Any2EVMRampMessage[]\",\"name\":\"messages\",\"type\":\"tuple[]\"},{\"internalType\":\"bytes[][]\",\"name\":\"offchainTokenData\",\"type\":\"bytes[][]\"},{\"internalType\":\"bytes32[]\",\"name\":\"proofs\",\"type\":\"bytes32[]\"},{\"internalType\":\"uint256\",\"name\":\"proofFlagBits\",\"type\":\"uint256\"}],\"internalType\":\"structInternal.ExecutionReport[]\",\"name\":\"reports\",\"type\":\"tuple[]\"},{\"components\":[{\"internalType\":\"uint256\",\"name\":\"receiverExecutionGasLimit\",\"type\":\"uint256\"},{\"internalType\":\"uint32[]\",\"name\":\"tokenGasOverrides\",\"type\":\"uint32[]\"}],\"internalType\":\"structOffRamp.GasLimitOverride[][]\",\"name\":\"gasLimitOverrides\",\"type\":\"tuple[][]\"}],\"name\":\"manuallyExecute\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"feeQuoter\",\"type\":\"address\"},{\"internalType\":\"uint32\",\"name\":\"permissionLessExecutionThresholdSeconds\",\"type\":\"uint32\"},{\"internalType\":\"bool\",\"name\":\"isRMNVerificationDisabled\",\"type\":\"bool\"},{\"internalType\":\"address\",\"name\":\"messageInterceptor\",\"type\":\"address\"}],\"internalType\":\"structOffRamp.DynamicConfig\",\"name\":\"dynamicConfig\",\"type\":\"tuple\"}],\"name\":\"setDynamicConfig\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"configDigest\",\"type\":\"bytes32\"},{\"internalType\":\"uint8\",\"name\":\"ocrPluginType\",\"type\":\"uint8\"},{\"internalType\":\"uint8\",\"name\":\"F\",\"type\":\"uint8\"},{\"internalType\":\"bool\",\"name\":\"isSignatureVerificationEnabled\",\"type\":\"bool\"},{\"internalType\":\"address[]\",\"name\":\"signers\",\"type\":\"address[]\"},{\"internalType\":\"address[]\",\"name\":\"transmitters\",\"type\":\"address[]\"}],\"internalType\":\"structMultiOCR3Base.OCRConfigArgs[]\",\"name\":\"ocrConfigArgs\",\"type\":\"tuple[]\"}],\"name\":\"setOCR3Configs\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"typeAndVersion\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"}]", + Bin: "0x6101406040523480156200001257600080fd5b5060405162006d0638038062006d06833981016040819052620000359162000936565b336000816200005757604051639b15e16f60e01b815260040160405180910390fd5b600180546001600160a01b0319166001600160a01b03848116919091179091558116156200008a576200008a81620001dc565b50504660805260408301516001600160a01b03161580620000b6575060608301516001600160a01b0316155b80620000cd575060808301516001600160a01b0316155b15620000ec576040516342bcdf7f60e11b815260040160405180910390fd5b82516001600160401b0316600003620001185760405163c656089560e01b815260040160405180910390fd5b82516001600160401b0390811660a0908152604080860180516001600160a01b0390811660c05260608089018051831660e0526080808b0180518516610100526020808d01805161ffff9081166101205289518f51909c168c52905116908a0152945184169588019590955251821690860152905116908301527fb0fa1fb01508c5097c502ad056fd77018870c9be9a86d9e56b6b471862d7c5b7910160405180910390a1620001c88262000256565b620001d38162000344565b50505062000cde565b336001600160a01b038216036200020657604051636d6c4ee560e11b815260040160405180910390fd5b600080546001600160a01b0319166001600160a01b03838116918217835560015460405192939116917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b80516001600160a01b03166200027f576040516342bcdf7f60e11b815260040160405180910390fd5b80516004805460208085018051604080880180516001600160a01b039889166001600160c01b03199097168717600160a01b63ffffffff958616021760ff60c01b1916600160c01b911515919091021790965560608089018051600580546001600160a01b031916918b169190911790558251968752935190921693850193909352935115159183019190915251909216908201527fcbb53bda7106a610de67df506ac86b65c44d5afac0fd2b11070dc2d61a6f2dee9060800160405180910390a150565b60005b8151811015620005d957600082828151811062000368576200036862000a16565b60200260200101519050600081602001519050806001600160401b0316600003620003a65760405163c656089560e01b815260040160405180910390fd5b81516001600160a01b0316620003cf576040516342bcdf7f60e11b815260040160405180910390fd5b6001600160401b03811660009081526008602052604090206060830151600182018054620003fd9062000a2c565b905060000362000460578154600160a81b600160e81b031916600160a81b1782556040516001600160401b03841681527ff4c1390c70e5c0f491ae1ccbc06f9117cbbadf2767b247b3bc203280f24c0fb99060200160405180910390a1620004d1565b8154600160a81b90046001600160401b0316600114801590620004a35750805160208201206040516200049890600185019062000a68565b604051809103902014155b15620004d157604051632105803760e11b81526001600160401b038416600482015260240160405180910390fd5b80511580620005075750604080516000602082015201604051602081830303815290604052805190602001208180519060200120145b1562000526576040516342bcdf7f60e11b815260040160405180910390fd5b6001820162000536828262000b3b565b506040840151825485516001600160a01b03166001600160a01b0319921515600160a01b02929092166001600160a81b0319909116171782556200058560066001600160401b038516620005dd565b50826001600160401b03167f49f51971edd25182e97182d6ea372a0488ce2ab639f6a3a7ab4df0d2636fe56b83604051620005c1919062000c07565b60405180910390a25050505080600101905062000347565b5050565b6000620005eb8383620005f4565b90505b92915050565b60008181526001830160205260408120546200063d57508154600181810184556000848152602080822090930184905584548482528286019093526040902091909155620005ee565b506000620005ee565b634e487b7160e01b600052604160045260246000fd5b604051608081016001600160401b038111828210171562000681576200068162000646565b60405290565b60405160a081016001600160401b038111828210171562000681576200068162000646565b604051601f8201601f191681016001600160401b0381118282101715620006d757620006d762000646565b604052919050565b80516001600160401b0381168114620006f757600080fd5b919050565b6001600160a01b03811681146200071257600080fd5b50565b80518015158114620006f757600080fd5b6000608082840312156200073957600080fd5b620007436200065c565b905081516200075281620006fc565b8152602082015163ffffffff811681146200076c57600080fd5b60208201526200077f6040830162000715565b604082015260608201516200079481620006fc565b606082015292915050565b6000601f83601f840112620007b357600080fd5b825160206001600160401b0380831115620007d257620007d262000646565b8260051b620007e3838201620006ac565b9384528681018301938381019089861115620007fe57600080fd5b84890192505b8583101562000929578251848111156200081e5760008081fd5b89016080601f19828d038101821315620008385760008081fd5b620008426200065c565b888401516200085181620006fc565b8152604062000862858201620006df565b8a83015260606200087581870162000715565b838301529385015193898511156200088d5760008081fd5b84860195508f603f870112620008a557600094508485fd5b8a860151945089851115620008be57620008be62000646565b620008cf8b858f88011601620006ac565b93508484528f82868801011115620008e75760008081fd5b60005b8581101562000907578681018301518582018d01528b01620008ea565b5060009484018b01949094525091820152835250918401919084019062000804565b9998505050505050505050565b60008060008385036101408112156200094e57600080fd5b60a08112156200095d57600080fd5b506200096862000687565b6200097385620006df565b8152602085015161ffff811681146200098b57600080fd5b60208201526040850151620009a081620006fc565b60408201526060850151620009b581620006fc565b60608201526080850151620009ca81620006fc565b60808201529250620009e08560a0860162000726565b6101208501519092506001600160401b03811115620009fe57600080fd5b62000a0c868287016200079f565b9150509250925092565b634e487b7160e01b600052603260045260246000fd5b600181811c9082168062000a4157607f821691505b60208210810362000a6257634e487b7160e01b600052602260045260246000fd5b50919050565b600080835462000a788162000a2c565b6001828116801562000a93576001811462000aa95762000ada565b60ff198416875282151583028701945062000ada565b8760005260208060002060005b8581101562000ad15781548a82015290840190820162000ab6565b50505082870194505b50929695505050505050565b601f82111562000b36576000816000526020600020601f850160051c8101602086101562000b115750805b601f850160051c820191505b8181101562000b325782815560010162000b1d565b5050505b505050565b81516001600160401b0381111562000b575762000b5762000646565b62000b6f8162000b68845462000a2c565b8462000ae6565b602080601f83116001811462000ba7576000841562000b8e5750858301515b600019600386901b1c1916600185901b17855562000b32565b600085815260208120601f198616915b8281101562000bd85788860151825594840194600190910190840162000bb7565b508582101562000bf75787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b602080825282546001600160a01b0381168383015260a081901c60ff161515604084015260a81c6001600160401b0316606083015260808083015260018084018054600093929190849062000c5c8162000a2c565b8060a089015260c0600183166000811462000c80576001811462000c9d5762000ccf565b60ff19841660c08b015260c083151560051b8b0101945062000ccf565b85600052602060002060005b8481101562000cc65781548c820185015290880190890162000ca9565b8b0160c0019550505b50929998505050505050505050565b60805160a05160c05160e0516101005161012051615f8b62000d7b600039600081816101b001528181610ce801528181612ed1015261380b0152600081816102380152612aa10152600081816102090152612d490152600081816101da01528181610fcc0152818161117c01526124a10152600081816101810152818161264c015261270301526000818161195b015261198e0152615f8b6000f3fe608060405234801561001057600080fd5b506004361061012c5760003560e01c80637edf52f4116100ad578063de5e0b9a11610071578063de5e0b9a146104eb578063e9d68a8e146104fe578063f2fde38b1461051e578063f58e03fc14610531578063f716f99f1461054457600080fd5b80637edf52f41461044b57806385572ffb1461045e5780638da5cb5b1461046c578063c673e58414610487578063ccd37ba3146104a757600080fd5b80635e36480c116100f45780635e36480c146103405780635e7bb0081461036057806360987c20146103735780637437ff9f1461038657806379ba50971461044357600080fd5b806304666f9c1461013157806306285c6914610146578063181f5a77146102c65780633f4b04aa1461030f5780635215505b1461032a575b600080fd5b61014461013f366004613eaf565b610557565b005b6102686040805160a0810182526000808252602082018190529181018290526060810182905260808101919091526040518060a001604052807f00000000000000000000000000000000000000000000000000000000000000006001600160401b031681526020017f000000000000000000000000000000000000000000000000000000000000000061ffff1681526020017f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031681526020017f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031681526020017f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316815250905090565b6040805182516001600160401b0316815260208084015161ffff1690820152828201516001600160a01b03908116928201929092526060808401518316908201526080928301519091169181019190915260a0015b60405180910390f35b6103026040518060400160405280601181526020017f4f666652616d7020312e362e302d64657600000000000000000000000000000081525081565b6040516102bd919061401d565b600b546040516001600160401b0390911681526020016102bd565b61033261056b565b6040516102bd929190614077565b61035361034e366004614118565b6107c6565b6040516102bd9190614175565b61014461036e3660046146de565b61081b565b61014461038136600461496d565b610aaf565b6103fc60408051608081018252600080825260208201819052918101829052606081019190915250604080516080810182526004546001600160a01b038082168352600160a01b820463ffffffff166020840152600160c01b90910460ff16151592820192909252600554909116606082015290565b6040516102bd919081516001600160a01b03908116825260208084015163ffffffff1690830152604080840151151590830152606092830151169181019190915260800190565b610144610d8a565b610144610459366004614a01565b610e0d565b61014461012c366004614a66565b6001546040516001600160a01b0390911681526020016102bd565b61049a610495366004614ab1565b610e1e565b6040516102bd9190614b11565b6104dd6104b5366004614b86565b6001600160401b03919091166000908152600a60209081526040808320938352929052205490565b6040519081526020016102bd565b6101446104f9366004614c02565b610f7c565b61051161050c366004614cb4565b61147f565b6040516102bd9190614ccf565b61014461052c366004614ce2565b61158b565b61014461053f366004614cff565b61159c565b610144610552366004614dba565b611605565b61055f611647565b61056881611674565b50565b606080600061057a60066118fd565b6001600160401b0381111561059157610591613ccf565b6040519080825280602002602001820160405280156105e257816020015b60408051608081018252600080825260208083018290529282015260608082015282526000199092019101816105af5790505b50905060006105f160066118fd565b6001600160401b0381111561060857610608613ccf565b604051908082528060200260200182016040528015610631578160200160208202803683370190505b50905060005b61064160066118fd565b8110156107bd57610653600682611907565b82828151811061066557610665614ef7565b60200260200101906001600160401b031690816001600160401b0316815250506008600083838151811061069b5761069b614ef7565b6020908102919091018101516001600160401b039081168352828201939093526040918201600020825160808101845281546001600160a01b038116825260ff600160a01b820416151593820193909352600160a81b9092049093169181019190915260018201805491929160608401919061071690614f0d565b80601f016020809104026020016040519081016040528092919081815260200182805461074290614f0d565b801561078f5780601f106107645761010080835404028352916020019161078f565b820191906000526020600020905b81548152906001019060200180831161077257829003601f168201915b5050505050815250508382815181106107aa576107aa614ef7565b6020908102919091010152600101610637565b50939092509050565b60006107d460016004614f5d565b60026107e1608085614f86565b6001600160401b03166107f49190614fac565b6107fe8585611913565b901c1660038111156108125761081261414b565b90505b92915050565b610823611958565b815181518114610846576040516320f8fd5960e21b815260040160405180910390fd5b60005b81811015610a9f57600084828151811061086557610865614ef7565b6020026020010151905060008160200151519050600085848151811061088d5761088d614ef7565b60200260200101519050805182146108b8576040516320f8fd5960e21b815260040160405180910390fd5b60005b82811015610a905760008282815181106108d7576108d7614ef7565b60200260200101516000015190506000856020015183815181106108fd576108fd614ef7565b6020026020010151905081600014610956578060800151821015610956578551815151604051633a98d46360e11b81526001600160401b0390921660048301526024820152604481018390526064015b60405180910390fd5b83838151811061096857610968614ef7565b602002602001015160200151518160a0015151146109b557805180516060909101516040516370a193fd60e01b815260048101929092526001600160401b0316602482015260440161094d565b60005b8160a0015151811015610a825760008585815181106109d9576109d9614ef7565b60200260200101516020015182815181106109f6576109f6614ef7565b602002602001015163ffffffff16905080600014610a795760008360a001518381518110610a2657610a26614ef7565b60200260200101516040015163ffffffff16905080821015610a77578351516040516348e617b360e01b8152600481019190915260248101849052604481018290526064810183905260840161094d565b505b506001016109b8565b5050508060010190506108bb565b50505050806001019050610849565b50610aaa83836119c0565b505050565b333014610acf576040516306e34e6560e31b815260040160405180910390fd5b6040805160008082526020820190925281610b0c565b6040805180820190915260008082526020820152815260200190600190039081610ae55790505b5060a08701515190915015610b4257610b3f8660a001518760200151886060015189600001516020015189898989611a83565b90505b6040805160a081018252875151815287516020908101516001600160401b03168183015288015181830152908701516060820152608081018290526005546001600160a01b03168015610c35576040516308d450a160e01b81526001600160a01b038216906308d450a190610bbb908590600401615070565b600060405180830381600087803b158015610bd557600080fd5b505af1925050508015610be6575060015b610c35573d808015610c14576040519150601f19603f3d011682016040523d82523d6000602084013e610c19565b606091505b50806040516309c2532560e01b815260040161094d919061401d565b604088015151158015610c4a57506080880151155b80610c61575060608801516001600160a01b03163b155b80610c8857506060880151610c86906001600160a01b03166385572ffb60e01b611c34565b155b15610c9557505050610d83565b87516020908101516001600160401b03166000908152600890915260408082205460808b015160608c01519251633cf9798360e01b815284936001600160a01b0390931692633cf9798392610d119289927f00000000000000000000000000000000000000000000000000000000000000009291600401615083565b6000604051808303816000875af1158015610d30573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052610d5891908101906150bf565b509150915081610d7d57806040516302a35ba360e21b815260040161094d919061401d565b50505050505b5050505050565b6000546001600160a01b03163314610db55760405163015aa1e360e11b815260040160405180910390fd5b600180546001600160a01b0319808216339081179093556000805490911681556040516001600160a01b03909216929183917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a350565b610e15611647565b61056881611c50565b610e616040805160e081019091526000606082018181526080830182905260a0830182905260c08301919091528190815260200160608152602001606081525090565b60ff808316600090815260026020818152604092839020835160e081018552815460608201908152600183015480881660808401526101008104881660a0840152620100009004909616151560c082015294855291820180548451818402810184019095528085529293858301939092830182828015610f0a57602002820191906000526020600020905b81546001600160a01b03168152600190910190602001808311610eec575b5050505050815260200160038201805480602002602001604051908101604052809291908181526020018280548015610f6c57602002820191906000526020600020905b81546001600160a01b03168152600190910190602001808311610f4e575b5050505050815250509050919050565b6000610f8a8789018961536c565b6004805491925090600160c01b900460ff1661103457602082015151156110345760208201516040808401519051633854844f60e11b81526001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016926370a9089e926110039230929190600401615594565b60006040518083038186803b15801561101b57600080fd5b505afa15801561102f573d6000803e3d6000fd5b505050505b8151515115158061104a57508151602001515115155b1561111557600b5460208b0135906001600160401b03808316911610156110ed57600b805467ffffffffffffffff19166001600160401b03831617905581548351604051633937306f60e01b81526001600160a01b0390921691633937306f916110b6916004016156a7565b600060405180830381600087803b1580156110d057600080fd5b505af11580156110e4573d6000803e3d6000fd5b50505050611113565b82602001515160000361111357604051632261116760e01b815260040160405180910390fd5b505b60005b8260200151518110156113cb5760008360200151828151811061113d5761113d614ef7565b60209081029190910101518051604051632cbc26bb60e01b815267ffffffffffffffff60801b608083901b166004820152919250906001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001690632cbc26bb90602401602060405180830381865afa1580156111c3573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906111e791906156ba565b1561121057604051637edeb53960e11b81526001600160401b038216600482015260240161094d565b600061121b82611d55565b90508060010160405161122e91906156d7565b60405180910390208360200151805190602001201461126b5782602001518160010160405163b80d8fa960e01b815260040161094d9291906157ca565b60408301518154600160a81b90046001600160401b0390811691161415806112ac575082606001516001600160401b031683604001516001600160401b0316115b156112f157825160408085015160608601519151636af0786b60e11b81526001600160401b03938416600482015290831660248201529116604482015260640161094d565b6080830151806113145760405163504570e360e01b815260040160405180910390fd5b83516001600160401b03166000908152600a602090815260408083208484529091529020541561136c5783516040516332cf0cbf60e01b81526001600160401b0390911660048201526024810182905260440161094d565b606084015161137c9060016157ef565b825467ffffffffffffffff60a81b1916600160a81b6001600160401b0392831602179092559251166000908152600a602090815260408083209483529390529190912042905550600101611118565b50602082015182516040517f35c02761bcd3ef995c6a601a1981f4ed3934dcbe5041e24e286c89f5531d17e492611403929091615816565b60405180910390a1610d7d60008b8b8b8b8b8080602002602001604051908101604052809392919081815260200183836020028082843760009201919091525050604080516020808f0282810182019093528e82529093508e92508d9182918501908490808284376000920191909152508c9250611da1915050565b60408051608080820183526000808352602080840182905283850182905260608085018190526001600160401b03878116845260088352928690208651948501875280546001600160a01b0381168652600160a01b810460ff16151593860193909352600160a81b90920490921694830194909452600184018054939492939184019161150b90614f0d565b80601f016020809104026020016040519081016040528092919081815260200182805461153790614f0d565b8015610f6c5780601f1061155957610100808354040283529160200191610f6c565b820191906000526020600020905b81548152906001019060200180831161156757505050919092525091949350505050565b611593611647565b6105688161209a565b6115dc6115ab8284018461583b565b60408051600080825260208201909252906115d6565b60608152602001906001900390816115c15790505b506119c0565b6040805160008082526020820190925290506115ff600185858585866000611da1565b50505050565b61160d611647565b60005b81518110156116435761163b82828151811061162e5761162e614ef7565b6020026020010151612113565b600101611610565b5050565b6001546001600160a01b03163314611672576040516315ae3a6f60e11b815260040160405180910390fd5b565b60005b815181101561164357600082828151811061169457611694614ef7565b60200260200101519050600081602001519050806001600160401b03166000036116d15760405163c656089560e01b815260040160405180910390fd5b81516001600160a01b03166116f9576040516342bcdf7f60e11b815260040160405180910390fd5b6001600160401b0381166000908152600860205260409020606083015160018201805461172590614f0d565b905060000361178757815467ffffffffffffffff60a81b1916600160a81b1782556040516001600160401b03841681527ff4c1390c70e5c0f491ae1ccbc06f9117cbbadf2767b247b3bc203280f24c0fb99060200160405180910390a16117f0565b8154600160a81b90046001600160401b03166001148015906117c75750805160208201206040516117bc9060018501906156d7565b604051809103902014155b156117f057604051632105803760e11b81526001600160401b038416600482015260240161094d565b805115806118255750604080516000602082015201604051602081830303815290604052805190602001208180519060200120145b15611843576040516342bcdf7f60e11b815260040160405180910390fd5b6001820161185182826158bf565b506040840151825485516001600160a01b03166001600160a01b0319921515600160a01b029290921674ffffffffffffffffffffffffffffffffffffffffff19909116171782556118ac60066001600160401b03851661243d565b50826001600160401b03167f49f51971edd25182e97182d6ea372a0488ce2ab639f6a3a7ab4df0d2636fe56b836040516118e6919061597e565b60405180910390a250505050806001019050611677565b6000610815825490565b60006108128383612449565b6001600160401b0382166000908152600960205260408120816119376080856159cc565b6001600160401b031681526020810191909152604001600020549392505050565b467f00000000000000000000000000000000000000000000000000000000000000001461167257604051630f01ce8560e01b81527f0000000000000000000000000000000000000000000000000000000000000000600482015246602482015260440161094d565b81516000036119e25760405163c2e5347d60e01b815260040160405180910390fd5b80516040805160008082526020820190925291159181611a25565b6040805180820190915260008152606060208201528152602001906001900390816119fd5790505b50905060005b8451811015610d8357611a7b858281518110611a4957611a49614ef7565b602002602001015184611a7557858381518110611a6857611a68614ef7565b6020026020010151612473565b83612473565b600101611a2b565b606088516001600160401b03811115611a9e57611a9e613ccf565b604051908082528060200260200182016040528015611ae357816020015b6040805180820190915260008082526020820152815260200190600190039081611abc5790505b509050811560005b8a51811015611c265781611b8357848482818110611b0b57611b0b614ef7565b9050602002016020810190611b2091906159f2565b63ffffffff1615611b8357848482818110611b3d57611b3d614ef7565b9050602002016020810190611b5291906159f2565b8b8281518110611b6457611b64614ef7565b60200260200101516040019063ffffffff16908163ffffffff16815250505b611c018b8281518110611b9857611b98614ef7565b60200260200101518b8b8b8b8b87818110611bb557611bb5614ef7565b9050602002810190611bc79190615a0d565b8080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250612d0e92505050565b838281518110611c1357611c13614ef7565b6020908102919091010152600101611aeb565b505098975050505050505050565b6000611c3f8361300e565b801561081257506108128383613041565b80516001600160a01b0316611c78576040516342bcdf7f60e11b815260040160405180910390fd5b80516004805460208085018051604080880180516001600160a01b039889167fffffffffffffffff0000000000000000000000000000000000000000000000009097168717600160a01b63ffffffff958616021760ff60c01b1916600160c01b911515919091021790965560608089018051600580546001600160a01b031916918b169190911790558251968752935190921693850193909352935115159183019190915251909216908201527fcbb53bda7106a610de67df506ac86b65c44d5afac0fd2b11070dc2d61a6f2dee9060800160405180910390a150565b6001600160401b03811660009081526008602052604081208054600160a01b900460ff166108155760405163ed053c5960e01b81526001600160401b038416600482015260240161094d565b60ff87811660009081526002602090815260408083208151608081018352815481526001909101548086169382019390935261010083048516918101919091526201000090910490921615156060830152873590611e00876084615a53565b9050826060015115611e48578451611e19906020614fac565b8651611e26906020614fac565b611e319060a0615a53565b611e3b9190615a53565b611e459082615a53565b90505b368114611e7157604051638e1192e160e01b81526004810182905236602482015260440161094d565b5081518114611ea05781516040516324f7d61360e21b815260048101919091526024810182905260440161094d565b611ea8611958565b60ff808a1660009081526003602090815260408083203384528252808320815180830190925280548086168352939491939092840191610100909104166002811115611ef657611ef661414b565b6002811115611f0757611f0761414b565b9052509050600281602001516002811115611f2457611f2461414b565b148015611f785750600260008b60ff1660ff168152602001908152602001600020600301816000015160ff1681548110611f6057611f60614ef7565b6000918252602090912001546001600160a01b031633145b611f9557604051631b41e11d60e31b815260040160405180910390fd5b50816060015115612045576020820151611fb0906001615a66565b60ff16855114611fd3576040516371253a2560e01b815260040160405180910390fd5b8351855114611ff55760405163a75d88af60e01b815260040160405180910390fd5b60008787604051612007929190615a7f565b60405190819003812061201e918b90602001615a8f565b6040516020818303038152906040528051906020012090506120438a828888886130cb565b505b6040805182815260208a8101356001600160401b03169082015260ff8b16917f198d6990ef96613a9026203077e422916918b03ff47f0be6bee7b02d8e139ef0910160405180910390a2505050505050505050565b336001600160a01b038216036120c357604051636d6c4ee560e11b815260040160405180910390fd5b600080546001600160a01b0319166001600160a01b03838116918217835560015460405192939116917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b806040015160ff1660000361213e576000604051631b3fab5160e11b815260040161094d9190615aa3565b60208082015160ff8082166000908152600290935260408320600181015492939092839216900361218f576060840151600182018054911515620100000262ff0000199092169190911790556121cb565b6060840151600182015460ff62010000909104161515901515146121cb576040516321fd80df60e21b815260ff8416600482015260240161094d565b60a0840151805161010010156121f7576001604051631b3fab5160e11b815260040161094d9190615aa3565b805160000361221c576005604051631b3fab5160e11b815260040161094d9190615aa3565b612282848460030180548060200260200160405190810160405280929190818152602001828054801561227857602002820191906000526020600020905b81546001600160a01b0316815260019091019060200180831161225a575b505050505061327e565b8460600151156123b2576122f08484600201805480602002602001604051908101604052809291908181526020018280548015612278576020028201919060005260206000209081546001600160a01b0316815260019091019060200180831161225a57505050505061327e565b60808501518051610100101561231c576002604051631b3fab5160e11b815260040161094d9190615aa3565b604086015161232c906003615abd565b60ff16815111612352576003604051631b3fab5160e11b815260040161094d9190615aa3565b815181511015612378576001604051631b3fab5160e11b815260040161094d9190615aa3565b805160018401805461ff00191661010060ff8416021790556123a39060028601906020840190613c55565b506123b0858260016132e7565b505b6123be848260026132e7565b80516123d39060038501906020840190613c55565b5060408581015160018401805460ff191660ff8316179055865180855560a088015192517fab8b1b57514019638d7b5ce9c638fe71366fe8e2be1c40a7a80f1733d0e9f5479361242c9389939260028a01929190615ad9565b60405180910390a1610d8384613442565b600061081283836134c5565b600082600001828154811061246057612460614ef7565b9060005260206000200154905092915050565b81518151604051632cbc26bb60e01b8152608083901b67ffffffffffffffff60801b166004820152901515907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031690632cbc26bb90602401602060405180830381865afa1580156124f0573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061251491906156ba565b1561258557801561254357604051637edeb53960e11b81526001600160401b038316600482015260240161094d565b6040516001600160401b03831681527faab522ed53d887e56ed53dd37398a01aeef6a58e0fa77c2173beb9512d8949339060200160405180910390a150505050565b60208401515160008190036125bb57845160405163676cf24b60e11b81526001600160401b03909116600482015260240161094d565b84604001515181146125e0576040516357e0e08360e01b815260040160405180910390fd5b6000816001600160401b038111156125fa576125fa613ccf565b604051908082528060200260200182016040528015612623578160200160208202803683370190505b50905060007f2425b0b9f9054c76ff151b0a175b18f37a4a4e82013a72e9f15c9caa095ed21f857f000000000000000000000000000000000000000000000000000000000000000061267488611d55565b60010160405161268491906156d7565b6040519081900381206126bc949392916020019384526001600160401b03928316602085015291166040830152606082015260800190565b60405160208183030381529060405280519060200120905060005b838110156127f2576000886020015182815181106126f7576126f7614ef7565b602002602001015190507f00000000000000000000000000000000000000000000000000000000000000006001600160401b03168160000151604001516001600160401b03161461276e5780516040908101519051631c21951160e11b81526001600160401b03909116600482015260240161094d565b866001600160401b03168160000151602001516001600160401b0316146127c257805160200151604051636c95f1eb60e01b81526001600160401b03808a166004830152909116602482015260440161094d565b6127cc8184613514565b8483815181106127de576127de614ef7565b6020908102919091010152506001016126d7565b5050600061280a858389606001518a6080015161361c565b90508060000361283857604051633ee8bd3f60e11b81526001600160401b038616600482015260240161094d565b60005b83811015612d045760005a905060008960200151838151811061286057612860614ef7565b60200260200101519050600061287e898360000151606001516107c6565b905060008160038111156128945761289461414b565b14806128b1575060038160038111156128af576128af61414b565b145b61290757815160600151604080516001600160401b03808d16825290921660208301527f3b575419319662b2a6f5e2467d84521517a3382b908eb3d557bb3fdb0c50e23c910160405180910390a1505050612cfc565b606088156129e6578a858151811061292157612921614ef7565b6020908102919091018101510151600454909150600090600160a01b900463ffffffff1661294f8842614f5d565b119050808061296f5750600383600381111561296d5761296d61414b565b145b612997576040516354e7e43160e11b81526001600160401b038c16600482015260240161094d565b8b86815181106129a9576129a9614ef7565b6020026020010151600001516000146129e0578b86815181106129ce576129ce614ef7565b60209081029190910101515160808501525b50612a52565b60008260038111156129fa576129fa61414b565b14612a5257825160600151604080516001600160401b03808e16825290921660208301527f3ef2a99c550a751d4b0b261268f05a803dfb049ab43616a1ffb388f61fe65120910160405180910390a150505050612cfc565b8251608001516001600160401b031615612b28576000826003811115612a7a57612a7a61414b565b03612b285782516080015160208401516040516370701e5760e11b81526001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000169263e0e03cae92612ad8928f929190600401615b8b565b6020604051808303816000875af1158015612af7573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612b1b91906156ba565b612b285750505050612cfc565b60008c604001518681518110612b4057612b40614ef7565b6020026020010151905080518460a001515114612b8a57835160600151604051631cfe6d8b60e01b81526001600160401b03808e166004830152909116602482015260440161094d565b612b9e8b8560000151606001516001613659565b600080612bac8684866136fe565b91509150612bc38d87600001516060015184613659565b8b15612c1a576003826003811115612bdd57612bdd61414b565b03612c1a576000856003811115612bf657612bf661414b565b14612c1a57855151604051632b11b8d960e01b815261094d91908390600401615bb7565b6002826003811115612c2e57612c2e61414b565b14612c6f576003826003811115612c4757612c4761414b565b14612c6f578551606001516040516349362d1f60e11b815261094d918f918590600401615bd0565b8560000151600001518660000151606001516001600160401b03168e6001600160401b03167f05665fe9ad095383d018353f4cbcba77e84db27dd215081bbf7cdf9ae6fbe48b8d8c81518110612cc757612cc7614ef7565b602002602001015186865a612cdc908f614f5d565b604051612cec9493929190615bf5565b60405180910390a4505050505050505b60010161283b565b5050505050505050565b6040805180820190915260008082526020820152602086015160405163bbe4f6db60e01b81526001600160a01b0380831660048301526000917f00000000000000000000000000000000000000000000000000000000000000009091169063bbe4f6db90602401602060405180830381865afa158015612d92573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612db69190615c2c565b90506001600160a01b0381161580612de55750612de36001600160a01b03821663aff2afbf60e01b611c34565b155b15612e0e5760405163ae9b4ce960e01b81526001600160a01b038216600482015260240161094d565b600080612e2688858c6040015163ffffffff166137b2565b915091506000806000612ef76040518061010001604052808e81526020018c6001600160401b031681526020018d6001600160a01b031681526020018f608001518152602001896001600160a01b031681526020018f6000015181526020018f6060015181526020018b815250604051602401612ea39190615c49565b60408051601f198184030181529190526020810180516001600160e01b0316633907753760e01b17905287867f000000000000000000000000000000000000000000000000000000000000000060846138b5565b92509250925082612f1f578582604051634ff17cad60e11b815260040161094d929190615d15565b8151602014612f4e578151604051631e3be00960e21b815260206004820152602481019190915260440161094d565b600082806020019051810190612f649190615d37565b9050866001600160a01b03168c6001600160a01b031614612fe0576000612f958d8a612f90868a614f5d565b6137b2565b50905086811080612faf575081612fac8883614f5d565b14155b15612fde5760405163a966e21f60e01b815260048101839052602481018890526044810182905260640161094d565b505b604080518082019091526001600160a01b039098168852602088015250949550505050505095945050505050565b6000613021826301ffc9a760e01b613041565b8015610815575061303a826001600160e01b0319613041565b1592915050565b6040516001600160e01b031982166024820152600090819060440160408051601f19818403018152919052602080820180516001600160e01b03166301ffc9a760e01b178152825192935060009283928392909183918a617530fa92503d915060005190508280156130b4575060208210155b80156130c05750600081115b979650505050505050565b8251600090815b81811015612d045760006001888684602081106130f1576130f1614ef7565b6130fe91901a601b615a66565b89858151811061311057613110614ef7565b602002602001015189868151811061312a5761312a614ef7565b602002602001015160405160008152602001604052604051613168949392919093845260ff9290921660208401526040830152606082015260800190565b6020604051602081039080840390855afa15801561318a573d6000803e3d6000fd5b505060408051601f1981015160ff808e166000908152600360209081528582206001600160a01b038516835281528582208587019096528554808416865293975090955092939284019161010090041660028111156131eb576131eb61414b565b60028111156131fc576131fc61414b565b90525090506001816020015160028111156132195761321961414b565b1461323757604051636518c33d60e11b815260040160405180910390fd5b8051600160ff9091161b85161561326157604051633d9ef1f160e21b815260040160405180910390fd5b806000015160ff166001901b8517945050508060010190506130d2565b60005b8151811015610aaa5760ff8316600090815260036020526040812083519091908490849081106132b3576132b3614ef7565b6020908102919091018101516001600160a01b03168252810191909152604001600020805461ffff19169055600101613281565b60005b82518110156115ff57600083828151811061330757613307614ef7565b60200260200101519050600060028111156133245761332461414b565b60ff80871660009081526003602090815260408083206001600160a01b038716845290915290205461010090041660028111156133635761336361414b565b14613384576004604051631b3fab5160e11b815260040161094d9190615aa3565b6001600160a01b0381166133ab5760405163d6c62c9b60e01b815260040160405180910390fd5b60405180604001604052808360ff1681526020018460028111156133d1576133d161414b565b905260ff80871660009081526003602090815260408083206001600160a01b0387168452825290912083518154931660ff198416811782559184015190929091839161ffff19161761010083600281111561342e5761342e61414b565b0217905550905050508060010190506132ea565b60ff8181166000818152600260205260409020600101546201000090049091169061349a5780613485576040516317bd8dd160e11b815260040160405180910390fd5b600b805467ffffffffffffffff191690555050565b60001960ff831601611643578015611643576040516307b8c74d60e51b815260040160405180910390fd5b600081815260018301602052604081205461350c57508154600181810184556000848152602080822090930184905584548482528286019093526040902091909155610815565b506000610815565b81518051606080850151908301516080808701519401516040516000958695889561357895919490939192916020019485526001600160a01b039390931660208501526001600160401b039182166040850152606084015216608082015260a00190565b604051602081830303815290604052805190602001208560200151805190602001208660400151805190602001208760a001516040516020016135bb9190615df1565b60408051601f198184030181528282528051602091820120908301979097528101949094526060840192909252608083015260a082015260c081019190915260e0015b60405160208183030381529060405280519060200120905092915050565b60008061362a85858561398f565b6001600160401b0387166000908152600a6020908152604080832093835292905220549150505b949350505050565b60006002613668608085614f86565b6001600160401b031661367b9190614fac565b905060006136898585611913565b90508161369860016004614f5d565b901b1916818360038111156136af576136af61414b565b6001600160401b03871660009081526009602052604081209190921b929092179182916136dd6080886159cc565b6001600160401b031681526020810191909152604001600020555050505050565b604051630304c3e160e51b815260009060609030906360987c209061372b90889088908890600401615e88565b600060405180830381600087803b15801561374557600080fd5b505af1925050508015613756575060015b613795573d808015613784576040519150601f19603f3d011682016040523d82523d6000602084013e613789565b606091505b506003925090506137aa565b50506040805160208101909152600081526002905b935093915050565b6000806000806000613831886040516024016137dd91906001600160a01b0391909116815260200190565b60408051601f198184030181529190526020810180516001600160e01b03166370a0823160e01b17905288887f000000000000000000000000000000000000000000000000000000000000000060846138b5565b92509250925082613859578682604051634ff17cad60e11b815260040161094d929190615d15565b6020825114613888578151604051631e3be00960e21b815260206004820152602481019190915260440161094d565b8180602001905181019061389c9190615d37565b6138a68288614f5d565b94509450505050935093915050565b6000606060008361ffff166001600160401b038111156138d7576138d7613ccf565b6040519080825280601f01601f191660200182016040528015613901576020820181803683370190505b509150863b61391b5763030ed58f60e21b60005260046000fd5b5a8581101561393557632be8ca8b60e21b60005260046000fd5b8590036040810481038710613955576337c3be2960e01b60005260046000fd5b505a6000808a5160208c0160008c8cf193505a900390503d848111156139785750835b808352806000602085013e50955095509592505050565b82518251600091908183036139b757604051630469ac9960e21b815260040160405180910390fd5b61010182118015906139cb57506101018111155b6139e8576040516309bde33960e01b815260040160405180910390fd5b60001982820101610100811115613a12576040516309bde33960e01b815260040160405180910390fd5b80600003613a3f5786600081518110613a2d57613a2d614ef7565b60200260200101519350505050613c0d565b6000816001600160401b03811115613a5957613a59613ccf565b604051908082528060200260200182016040528015613a82578160200160208202803683370190505b50905060008080805b85811015613bac5760006001821b8b811603613ae65788851015613acf578c5160018601958e918110613ac057613ac0614ef7565b60200260200101519050613b08565b8551600185019487918110613ac057613ac0614ef7565b8b5160018401938d918110613afd57613afd614ef7565b602002602001015190505b600089861015613b38578d5160018701968f918110613b2957613b29614ef7565b60200260200101519050613b5a565b8651600186019588918110613b4f57613b4f614ef7565b602002602001015190505b82851115613b7b576040516309bde33960e01b815260040160405180910390fd5b613b858282613c14565b878481518110613b9757613b97614ef7565b60209081029190910101525050600101613a8b565b506001850382148015613bbe57508683145b8015613bc957508581145b613be6576040516309bde33960e01b815260040160405180910390fd5b836001860381518110613bfb57613bfb614ef7565b60200260200101519750505050505050505b9392505050565b6000818310613c2c57613c278284613c32565b610812565b61081283835b6040805160016020820152908101839052606081018290526000906080016135fe565b828054828255906000526020600020908101928215613caa579160200282015b82811115613caa57825182546001600160a01b0319166001600160a01b03909116178255602090920191600190910190613c75565b50613cb6929150613cba565b5090565b5b80821115613cb65760008155600101613cbb565b634e487b7160e01b600052604160045260246000fd5b604051608081016001600160401b0381118282101715613d0757613d07613ccf565b60405290565b60405160a081016001600160401b0381118282101715613d0757613d07613ccf565b60405160c081016001600160401b0381118282101715613d0757613d07613ccf565b604080519081016001600160401b0381118282101715613d0757613d07613ccf565b604051606081016001600160401b0381118282101715613d0757613d07613ccf565b604051601f8201601f191681016001600160401b0381118282101715613dbd57613dbd613ccf565b604052919050565b60006001600160401b03821115613dde57613dde613ccf565b5060051b60200190565b6001600160a01b038116811461056857600080fd5b80356001600160401b0381168114613e1457600080fd5b919050565b801515811461056857600080fd5b8035613e1481613e19565b60006001600160401b03821115613e4b57613e4b613ccf565b50601f01601f191660200190565b600082601f830112613e6a57600080fd5b8135613e7d613e7882613e32565b613d95565b818152846020838601011115613e9257600080fd5b816020850160208301376000918101602001919091529392505050565b60006020808385031215613ec257600080fd5b82356001600160401b0380821115613ed957600080fd5b818501915085601f830112613eed57600080fd5b8135613efb613e7882613dc5565b81815260059190911b83018401908481019088831115613f1a57600080fd5b8585015b83811015613fc057803585811115613f365760008081fd5b86016080818c03601f1901811315613f4e5760008081fd5b613f56613ce5565b89830135613f6381613de8565b81526040613f72848201613dfd565b8b830152606080850135613f8581613e19565b83830152928401359289841115613f9e57600091508182fd5b613fac8f8d86880101613e59565b908301525085525050918601918601613f1e565b5098975050505050505050565b60005b83811015613fe8578181015183820152602001613fd0565b50506000910152565b60008151808452614009816020860160208601613fcd565b601f01601f19169290920160200192915050565b6020815260006108126020830184613ff1565b6001600160a01b0381511682526020810151151560208301526001600160401b03604082015116604083015260006060820151608060608501526136516080850182613ff1565b604080825283519082018190526000906020906060840190828701845b828110156140b95781516001600160401b031684529284019290840190600101614094565b50505083810382850152845180825282820190600581901b8301840187850160005b8381101561410957601f198684030185526140f7838351614030565b948701949250908601906001016140db565b50909998505050505050505050565b6000806040838503121561412b57600080fd5b61413483613dfd565b915061414260208401613dfd565b90509250929050565b634e487b7160e01b600052602160045260246000fd5b600481106141715761417161414b565b9052565b602081016108158284614161565b600060a0828403121561419557600080fd5b61419d613d0d565b9050813581526141af60208301613dfd565b60208201526141c060408301613dfd565b60408201526141d160608301613dfd565b60608201526141e260808301613dfd565b608082015292915050565b8035613e1481613de8565b803563ffffffff81168114613e1457600080fd5b600082601f83011261421d57600080fd5b8135602061422d613e7883613dc5565b82815260059290921b8401810191818101908684111561424c57600080fd5b8286015b8481101561431c5780356001600160401b03808211156142705760008081fd5b9088019060a0828b03601f190181131561428a5760008081fd5b614292613d0d565b87840135838111156142a45760008081fd5b6142b28d8a83880101613e59565b8252506040808501356142c481613de8565b828a015260606142d58682016141f8565b828401526080915081860135858111156142ef5760008081fd5b6142fd8f8c838a0101613e59565b9184019190915250919093013590830152508352918301918301614250565b509695505050505050565b6000610140828403121561433a57600080fd5b614342613d2f565b905061434e8383614183565b815260a08201356001600160401b038082111561436a57600080fd5b61437685838601613e59565b602084015260c084013591508082111561438f57600080fd5b61439b85838601613e59565b60408401526143ac60e085016141ed565b606084015261010084013560808401526101208401359150808211156143d157600080fd5b506143de8482850161420c565b60a08301525092915050565b600082601f8301126143fb57600080fd5b8135602061440b613e7883613dc5565b82815260059290921b8401810191818101908684111561442a57600080fd5b8286015b8481101561431c5780356001600160401b0381111561444d5760008081fd5b61445b8986838b0101614327565b84525091830191830161442e565b600082601f83011261447a57600080fd5b8135602061448a613e7883613dc5565b82815260059290921b840181019181810190868411156144a957600080fd5b8286015b8481101561431c5780356001600160401b03808211156144cc57600080fd5b818901915089603f8301126144e057600080fd5b858201356144f0613e7882613dc5565b81815260059190911b830160400190878101908c83111561451057600080fd5b604085015b838110156145495780358581111561452c57600080fd5b61453b8f6040838a0101613e59565b845250918901918901614515565b508752505050928401925083016144ad565b600082601f83011261456c57600080fd5b8135602061457c613e7883613dc5565b8083825260208201915060208460051b87010193508684111561459e57600080fd5b602086015b8481101561431c57803583529183019183016145a3565b600082601f8301126145cb57600080fd5b813560206145db613e7883613dc5565b82815260059290921b840181019181810190868411156145fa57600080fd5b8286015b8481101561431c5780356001600160401b038082111561461e5760008081fd5b9088019060a0828b03601f19018113156146385760008081fd5b614640613d0d565b61464b888501613dfd565b8152604080850135848111156146615760008081fd5b61466f8e8b838901016143ea565b8a84015250606080860135858111156146885760008081fd5b6146968f8c838a0101614469565b83850152506080915081860135858111156146b15760008081fd5b6146bf8f8c838a010161455b565b91840191909152509190930135908301525083529183019183016145fe565b600080604083850312156146f157600080fd5b6001600160401b038335111561470657600080fd5b61471384843585016145ba565b91506001600160401b036020840135111561472d57600080fd5b6020830135830184601f82011261474357600080fd5b614750613e788235613dc5565b81358082526020808301929160051b84010187101561476e57600080fd5b602083015b6020843560051b850101811015614914576001600160401b038135111561479957600080fd5b87603f8235860101126147ab57600080fd5b6147be613e786020833587010135613dc5565b81358501602081810135808452908301929160059190911b016040018a10156147e657600080fd5b604083358701015b83358701602081013560051b01604001811015614904576001600160401b038135111561481a57600080fd5b833587018135016040818d03603f1901121561483557600080fd5b61483d613d51565b604082013581526001600160401b036060830135111561485c57600080fd5b8c605f60608401358401011261487157600080fd5b6040606083013583010135614888613e7882613dc5565b808282526020820191508f60608460051b60608801358801010111156148ad57600080fd5b6060808601358601015b60608460051b6060880135880101018110156148e4576148d6816141f8565b8352602092830192016148b7565b5080602085015250505080855250506020830192506020810190506147ee565b5084525060209283019201614773565b508093505050509250929050565b60008083601f84011261493457600080fd5b5081356001600160401b0381111561494b57600080fd5b6020830191508360208260051b850101111561496657600080fd5b9250929050565b60008060008060006060868803121561498557600080fd5b85356001600160401b038082111561499c57600080fd5b6149a889838a01614327565b965060208801359150808211156149be57600080fd5b6149ca89838a01614922565b909650945060408801359150808211156149e357600080fd5b506149f088828901614922565b969995985093965092949392505050565b600060808284031215614a1357600080fd5b614a1b613ce5565b8235614a2681613de8565b8152614a34602084016141f8565b60208201526040830135614a4781613e19565b60408201526060830135614a5a81613de8565b60608201529392505050565b600060208284031215614a7857600080fd5b81356001600160401b03811115614a8e57600080fd5b820160a08185031215613c0d57600080fd5b803560ff81168114613e1457600080fd5b600060208284031215614ac357600080fd5b61081282614aa0565b60008151808452602080850194506020840160005b83811015614b065781516001600160a01b031687529582019590820190600101614ae1565b509495945050505050565b60208152600082518051602084015260ff602082015116604084015260ff604082015116606084015260608101511515608084015250602083015160c060a0840152614b6060e0840182614acc565b90506040840151601f198483030160c0850152614b7d8282614acc565b95945050505050565b60008060408385031215614b9957600080fd5b614ba283613dfd565b946020939093013593505050565b806040810183101561081557600080fd5b60008083601f840112614bd357600080fd5b5081356001600160401b03811115614bea57600080fd5b60208301915083602082850101111561496657600080fd5b60008060008060008060008060c0898b031215614c1e57600080fd5b614c288a8a614bb0565b975060408901356001600160401b0380821115614c4457600080fd5b614c508c838d01614bc1565b909950975060608b0135915080821115614c6957600080fd5b614c758c838d01614922565b909750955060808b0135915080821115614c8e57600080fd5b50614c9b8b828c01614922565b999c989b50969995989497949560a00135949350505050565b600060208284031215614cc657600080fd5b61081282613dfd565b6020815260006108126020830184614030565b600060208284031215614cf457600080fd5b8135613c0d81613de8565b600080600060608486031215614d1457600080fd5b614d1e8585614bb0565b925060408401356001600160401b03811115614d3957600080fd5b614d4586828701614bc1565b9497909650939450505050565b600082601f830112614d6357600080fd5b81356020614d73613e7883613dc5565b8083825260208201915060208460051b870101935086841115614d9557600080fd5b602086015b8481101561431c578035614dad81613de8565b8352918301918301614d9a565b60006020808385031215614dcd57600080fd5b82356001600160401b0380821115614de457600080fd5b818501915085601f830112614df857600080fd5b8135614e06613e7882613dc5565b81815260059190911b83018401908481019088831115614e2557600080fd5b8585015b83811015613fc057803585811115614e4057600080fd5b860160c0818c03601f19011215614e575760008081fd5b614e5f613d2f565b8882013581526040614e72818401614aa0565b8a8301526060614e83818501614aa0565b8284015260809150614e96828501613e27565b9083015260a08381013589811115614eae5760008081fd5b614ebc8f8d83880101614d52565b838501525060c0840135915088821115614ed65760008081fd5b614ee48e8c84870101614d52565b9083015250845250918601918601614e29565b634e487b7160e01b600052603260045260246000fd5b600181811c90821680614f2157607f821691505b602082108103614f4157634e487b7160e01b600052602260045260246000fd5b50919050565b634e487b7160e01b600052601160045260246000fd5b8181038181111561081557610815614f47565b634e487b7160e01b600052601260045260246000fd5b60006001600160401b0380841680614fa057614fa0614f70565b92169190910692915050565b808202811582820484141761081557610815614f47565b80518252600060206001600160401b0381840151168185015260408084015160a06040870152614ff660a0870182613ff1565b90506060850151868203606088015261500f8282613ff1565b608087810151898303918a01919091528051808352908601935060009250908501905b8083101561506457835180516001600160a01b0316835286015186830152928501926001929092019190840190615032565b50979650505050505050565b6020815260006108126020830184614fc3565b6080815260006150966080830187614fc3565b61ffff9590951660208301525060408101929092526001600160a01b0316606090910152919050565b6000806000606084860312156150d457600080fd5b83516150df81613e19565b60208501519093506001600160401b038111156150fb57600080fd5b8401601f8101861361510c57600080fd5b805161511a613e7882613e32565b81815287602083850101111561512f57600080fd5b615140826020830160208601613fcd565b809450505050604084015190509250925092565b80356001600160e01b0381168114613e1457600080fd5b600082601f83011261517c57600080fd5b8135602061518c613e7883613dc5565b82815260069290921b840181019181810190868411156151ab57600080fd5b8286015b8481101561431c57604081890312156151c85760008081fd5b6151d0613d51565b6151d982613dfd565b81526151e6858301615154565b818601528352918301916040016151af565b600082601f83011261520957600080fd5b81356020615219613e7883613dc5565b82815260059290921b8401810191818101908684111561523857600080fd5b8286015b8481101561431c5780356001600160401b038082111561525c5760008081fd5b9088019060a0828b03601f19018113156152765760008081fd5b61527e613d0d565b615289888501613dfd565b81526040808501358481111561529f5760008081fd5b6152ad8e8b83890101613e59565b8a84015250606093506152c1848601613dfd565b9082015260806152d2858201613dfd565b9382019390935292013590820152835291830191830161523c565b600082601f8301126152fe57600080fd5b8135602061530e613e7883613dc5565b82815260069290921b8401810191818101908684111561532d57600080fd5b8286015b8481101561431c576040818903121561534a5760008081fd5b615352613d51565b813581528482013585820152835291830191604001615331565b6000602080838503121561537f57600080fd5b82356001600160401b038082111561539657600080fd5b90840190606082870312156153aa57600080fd5b6153b2613d73565b8235828111156153c157600080fd5b830160408189038113156153d457600080fd5b6153dc613d51565b8235858111156153eb57600080fd5b8301601f81018b136153fc57600080fd5b803561540a613e7882613dc5565b81815260069190911b8201890190898101908d83111561542957600080fd5b928a01925b828410156154795785848f0312156154465760008081fd5b61544e613d51565b843561545981613de8565b8152615466858d01615154565b818d0152825292850192908a019061542e565b84525050508287013591508482111561549157600080fd5b61549d8a83850161516b565b818801528352505082840135828111156154b657600080fd5b6154c2888286016151f8565b858301525060408301359350818411156154db57600080fd5b6154e7878585016152ed565b60408201529695505050505050565b600082825180855260208086019550808260051b84010181860160005b8481101561558757601f19868403018952815160a06001600160401b0380835116865286830151828888015261554b83880182613ff1565b60408581015184169089015260608086015190931692880192909252506080928301519290950191909152509783019790830190600101615513565b5090979650505050505050565b6001600160a01b0384168152600060206060818401526155b760608401866154f6565b83810360408581019190915285518083528387019284019060005b81811015614109578451805184528601518684015293850193918301916001016155d2565b805160408084528151848201819052600092602091908201906060870190855b8181101561564e57835180516001600160a01b031684528501516001600160e01b0316858401529284019291850191600101615617565b50508583015187820388850152805180835290840192506000918401905b8083101561506457835180516001600160401b031683528501516001600160e01b03168583015292840192600192909201919085019061566c565b60208152600061081260208301846155f7565b6000602082840312156156cc57600080fd5b8151613c0d81613e19565b60008083546156e581614f0d565b600182811680156156fd576001811461571257615741565b60ff1984168752821515830287019450615741565b8760005260208060002060005b858110156157385781548a82015290840190820161571f565b50505082870194505b50929695505050505050565b6000815461575a81614f0d565b8085526020600183811680156157775760018114615791576157bf565b60ff1985168884015283151560051b8801830195506157bf565b866000528260002060005b858110156157b75781548a820186015290830190840161579c565b890184019650505b505050505092915050565b6040815260006157dd6040830185613ff1565b8281036020840152614b7d818561574d565b6001600160401b0381811683821601908082111561580f5761580f614f47565b5092915050565b60408152600061582960408301856154f6565b8281036020840152614b7d81856155f7565b60006020828403121561584d57600080fd5b81356001600160401b0381111561586357600080fd5b613651848285016145ba565b601f821115610aaa576000816000526020600020601f850160051c810160208610156158985750805b601f850160051c820191505b818110156158b7578281556001016158a4565b505050505050565b81516001600160401b038111156158d8576158d8613ccf565b6158ec816158e68454614f0d565b8461586f565b602080601f83116001811461592157600084156159095750858301515b600019600386901b1c1916600185901b1785556158b7565b600085815260208120601f198616915b8281101561595057888601518255948401946001909101908401615931565b508582101561596e5787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b60208152600082546001600160a01b038116602084015260ff8160a01c16151560408401526001600160401b038160a81c1660608401525060808083015261081260a083016001850161574d565b60006001600160401b03808416806159e6576159e6614f70565b92169190910492915050565b600060208284031215615a0457600080fd5b610812826141f8565b6000808335601e19843603018112615a2457600080fd5b8301803591506001600160401b03821115615a3e57600080fd5b60200191503681900382131561496657600080fd5b8082018082111561081557610815614f47565b60ff818116838216019081111561081557610815614f47565b8183823760009101908152919050565b828152604082602083013760600192915050565b6020810160068310615ab757615ab761414b565b91905290565b60ff818116838216029081169081811461580f5761580f614f47565b600060a0820160ff881683526020878185015260a0604085015281875480845260c0860191508860005282600020935060005b81811015615b315784546001600160a01b031683526001948501949284019201615b0c565b50508481036060860152865180825290820192508187019060005b81811015615b715782516001600160a01b031685529383019391830191600101615b4c565b50505060ff851660808501525090505b9695505050505050565b60006001600160401b03808616835280851660208401525060606040830152614b7d6060830184613ff1565b8281526040602082015260006136516040830184613ff1565b6001600160401b03848116825283166020820152606081016136516040830184614161565b848152615c056020820185614161565b608060408201526000615c1b6080830185613ff1565b905082606083015295945050505050565b600060208284031215615c3e57600080fd5b8151613c0d81613de8565b6020815260008251610100806020850152615c68610120850183613ff1565b91506020850151615c8460408601826001600160401b03169052565b5060408501516001600160a01b038116606086015250606085015160808501526080850151615cbe60a08601826001600160a01b03169052565b5060a0850151601f19808685030160c0870152615cdb8483613ff1565b935060c08701519150808685030160e0870152615cf88483613ff1565b935060e0870151915080868503018387015250615b818382613ff1565b6001600160a01b03831681526040602082015260006136516040830184613ff1565b600060208284031215615d4957600080fd5b5051919050565b600082825180855260208086019550808260051b84010181860160005b8481101561558757601f19868403018952815160a08151818652615d9382870182613ff1565b9150506001600160a01b03868301511686860152604063ffffffff8184015116818701525060608083015186830382880152615dcf8382613ff1565b6080948501519790940196909652505098840198925090830190600101615d6d565b6020815260006108126020830184615d50565b60008282518085526020808601955060208260051b8401016020860160005b8481101561558757601f19868403018952615e3f838351613ff1565b98840198925090830190600101615e23565b60008151808452602080850194506020840160005b83811015614b0657815163ffffffff1687529582019590820190600101615e66565b60608152600084518051606084015260208101516001600160401b0380821660808601528060408401511660a08601528060608401511660c08601528060808401511660e0860152505050602085015161014080610100850152615ef06101a0850183613ff1565b91506040870151605f198086850301610120870152615f0f8483613ff1565b935060608901519150615f2c838701836001600160a01b03169052565b608089015161016087015260a0890151925080868503016101808701525050615f558282615d50565b9150508281036020840152615f6a8186615e04565b90508281036040840152615b818185615e5156fea164736f6c6343000818000a", } var OffRampABI = OffRampMetaData.ABI @@ -2467,7 +2468,7 @@ func (OffRampSourceChainSelectorAdded) Topic() common.Hash { } func (OffRampStaticConfigSet) Topic() common.Hash { - return common.HexToHash("0x683eb52ee924eb817377cfa8f41f238f4bb7a877da5267869dfffbad85f564d8") + return common.HexToHash("0xb0fa1fb01508c5097c502ad056fd77018870c9be9a86d9e56b6b471862d7c5b7") } func (OffRampTransmitted) Topic() common.Hash { diff --git a/core/gethwrappers/ccip/generation/generated-wrapper-dependency-versions-do-not-edit.txt b/core/gethwrappers/ccip/generation/generated-wrapper-dependency-versions-do-not-edit.txt index 3d084f43606..f35104e20df 100644 --- a/core/gethwrappers/ccip/generation/generated-wrapper-dependency-versions-do-not-edit.txt +++ b/core/gethwrappers/ccip/generation/generated-wrapper-dependency-versions-do-not-edit.txt @@ -3,7 +3,7 @@ burn_from_mint_token_pool: ../../../contracts/solc/v0.8.24/BurnFromMintTokenPool burn_mint_token_pool: ../../../contracts/solc/v0.8.24/BurnMintTokenPool/BurnMintTokenPool.abi ../../../contracts/solc/v0.8.24/BurnMintTokenPool/BurnMintTokenPool.bin 1c78cd3118b3c9ca82f8cb77ffc1137619ea4e8e503c460f2dafb659d0dd766b burn_with_from_mint_token_pool: ../../../contracts/solc/v0.8.24/BurnWithFromMintTokenPool/BurnWithFromMintTokenPool.abi ../../../contracts/solc/v0.8.24/BurnWithFromMintTokenPool/BurnWithFromMintTokenPool.bin eab9c19ef27b245e5ef0216ab1080c9dd89c96013b7dc978bf610288d5e82b00 ccip_encoding_utils: ../../../contracts/solc/v0.8.24/ICCIPEncodingUtils/ICCIPEncodingUtils.abi ../../../contracts/solc/v0.8.24/ICCIPEncodingUtils/ICCIPEncodingUtils.bin 9971fc93c34442a0989570d3dab90a125de31e6e60754ad972807ce6ad4dfba0 -ccip_home: ../../../contracts/solc/v0.8.24/CCIPHome/CCIPHome.abi ../../../contracts/solc/v0.8.24/CCIPHome/CCIPHome.bin 02cb75b4274a5be7f4006cf2b72cc09e77eb6dba4c1a9c720af86668ff8ea1df +ccip_home: ../../../contracts/solc/v0.8.24/CCIPHome/CCIPHome.abi ../../../contracts/solc/v0.8.24/CCIPHome/CCIPHome.bin 865bc25c54cf346e5f519dc3fb625260a12c80983b5ba2dcea63519a7befc660 ccip_reader_tester: ../../../contracts/solc/v0.8.24/CCIPReaderTester/CCIPReaderTester.abi ../../../contracts/solc/v0.8.24/CCIPReaderTester/CCIPReaderTester.bin b368699ae7dbee7c21d049a641642837f18ce2cc8d4ece69509f205de673108e ether_sender_receiver: ../../../contracts/solc/v0.8.24/EtherSenderReceiver/EtherSenderReceiver.abi ../../../contracts/solc/v0.8.24/EtherSenderReceiver/EtherSenderReceiver.bin 09510a3f773f108a3c231e8d202835c845ded862d071ec54c4f89c12d868b8de fee_quoter: ../../../contracts/solc/v0.8.24/FeeQuoter/FeeQuoter.abi ../../../contracts/solc/v0.8.24/FeeQuoter/FeeQuoter.bin aee49c9246d5903e68b175516b3cdfdec5df23e25d53d604cd382b6bc0bf34f7 @@ -16,7 +16,7 @@ mock_v3_aggregator_contract: ../../../contracts/solc/v0.8.24/MockV3Aggregator/Mo multi_aggregate_rate_limiter: ../../../contracts/solc/v0.8.24/MultiAggregateRateLimiter/MultiAggregateRateLimiter.abi ../../../contracts/solc/v0.8.24/MultiAggregateRateLimiter/MultiAggregateRateLimiter.bin c3cac2010c2815b484055bf981363a2bd04e7fbe7bb502dc8fd29a16165d221c multi_ocr3_helper: ../../../contracts/solc/v0.8.24/MultiOCR3Helper/MultiOCR3Helper.abi ../../../contracts/solc/v0.8.24/MultiOCR3Helper/MultiOCR3Helper.bin a523e11ea4c069d7d61b309c156951cc6834aff0f352bd1ac37c3a838ff2588f nonce_manager: ../../../contracts/solc/v0.8.24/NonceManager/NonceManager.abi ../../../contracts/solc/v0.8.24/NonceManager/NonceManager.bin e6008490d916826cefd1903612db39621d51617300fc9bb42b68c6c117958198 -offramp: ../../../contracts/solc/v0.8.24/OffRamp/OffRamp.abi ../../../contracts/solc/v0.8.24/OffRamp/OffRamp.bin 7c65e586181c5099a6ecb5353f60043bb6add9ebad941ddf7ef9998c7ee008ea +offramp: ../../../contracts/solc/v0.8.24/OffRamp/OffRamp.abi ../../../contracts/solc/v0.8.24/OffRamp/OffRamp.bin 067fdfbf7cae1557fc03ca16d9c38737ee4595655792a1b8bc4846c45caa0c74 onramp: ../../../contracts/solc/v0.8.24/OnRamp/OnRamp.abi ../../../contracts/solc/v0.8.24/OnRamp/OnRamp.bin 2bf74188a997218502031f177cb2df505b272d66b25fd341a741289e77380c59 ping_pong_demo: ../../../contracts/solc/v0.8.24/PingPongDemo/PingPongDemo.abi ../../../contracts/solc/v0.8.24/PingPongDemo/PingPongDemo.bin 24b4415a883a470d65c484be0fa20714a46b1c9262db205f1c958017820307b2 registry_module_owner_custom: ../../../contracts/solc/v0.8.24/RegistryModuleOwnerCustom/RegistryModuleOwnerCustom.abi ../../../contracts/solc/v0.8.24/RegistryModuleOwnerCustom/RegistryModuleOwnerCustom.bin 0fc277a0b512db4e20b5a32a775b94ed2c0d342d8237511de78c94f7dacad428 diff --git a/core/gethwrappers/go_generate_test.go b/core/gethwrappers/go_generate_test.go index a6253cb1a66..1066149278f 100644 --- a/core/gethwrappers/go_generate_test.go +++ b/core/gethwrappers/go_generate_test.go @@ -16,7 +16,8 @@ import ( "github.com/fatih/color" cutils "github.com/smartcontractkit/chainlink-common/pkg/utils" - "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" + "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" + "github.com/smartcontractkit/chainlink/v2/core/utils" "github.com/stretchr/testify/assert" @@ -29,7 +30,7 @@ const compileCommand = "../../contracts/scripts/native_solc_compile_all" // contract artifacts in contracts/solc with the abi and bytecode stored in the // contract wrapper func TestCheckContractHashesFromLastGoGenerate(t *testing.T) { - testutils.SkipShort(t, "requires compiled artifacts") + tests.SkipShort(t, "requires compiled artifacts") versions, err := ReadVersionsDB() require.NoError(t, err) require.NotEmpty(t, versions.GethVersion, `version DB should have a "GETH_VERSION:" line`) diff --git a/core/gethwrappers/workflow/generated/workflow_registry_wrapper/workflow_registry_wrapper.go b/core/gethwrappers/workflow/generated/workflow_registry_wrapper/workflow_registry_wrapper.go index c87f59c0e7b..a81d69c343e 100644 --- a/core/gethwrappers/workflow/generated/workflow_registry_wrapper/workflow_registry_wrapper.go +++ b/core/gethwrappers/workflow/generated/workflow_registry_wrapper/workflow_registry_wrapper.go @@ -43,7 +43,7 @@ type WorkflowRegistryWorkflowMetadata struct { var WorkflowRegistryMetaData = &bind.MetaData{ ABI: "[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"caller\",\"type\":\"address\"}],\"name\":\"AddressNotAuthorized\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"caller\",\"type\":\"address\"}],\"name\":\"CallerIsNotWorkflowOwner\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"CannotTransferToSelf\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"donID\",\"type\":\"uint32\"}],\"name\":\"DONNotAllowed\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidWorkflowID\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"MustBeProposedOwner\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"OnlyCallableByOwner\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"OwnerCannotBeZero\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"RegistryLocked\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"providedLength\",\"type\":\"uint256\"},{\"internalType\":\"uint8\",\"name\":\"maxAllowedLength\",\"type\":\"uint8\"}],\"name\":\"URLTooLong\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"WorkflowAlreadyInDesiredStatus\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"WorkflowAlreadyRegistered\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"WorkflowContentNotUpdated\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"WorkflowDoesNotExist\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"WorkflowIDAlreadyExists\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"WorkflowIDNotUpdated\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"providedLength\",\"type\":\"uint256\"},{\"internalType\":\"uint8\",\"name\":\"maxAllowedLength\",\"type\":\"uint8\"}],\"name\":\"WorkflowNameTooLong\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint32[]\",\"name\":\"donIDs\",\"type\":\"uint32[]\"},{\"indexed\":false,\"internalType\":\"bool\",\"name\":\"allowed\",\"type\":\"bool\"}],\"name\":\"AllowedDONsUpdatedV1\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address[]\",\"name\":\"addresses\",\"type\":\"address[]\"},{\"indexed\":false,\"internalType\":\"bool\",\"name\":\"allowed\",\"type\":\"bool\"}],\"name\":\"AuthorizedAddressesUpdatedV1\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"OwnershipTransferRequested\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"OwnershipTransferred\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"lockedBy\",\"type\":\"address\"}],\"name\":\"RegistryLockedV1\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"unlockedBy\",\"type\":\"address\"}],\"name\":\"RegistryUnlockedV1\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"workflowID\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"workflowOwner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"uint32\",\"name\":\"donID\",\"type\":\"uint32\"},{\"indexed\":false,\"internalType\":\"string\",\"name\":\"workflowName\",\"type\":\"string\"}],\"name\":\"WorkflowActivatedV1\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"workflowID\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"workflowOwner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"uint32\",\"name\":\"donID\",\"type\":\"uint32\"},{\"indexed\":false,\"internalType\":\"string\",\"name\":\"workflowName\",\"type\":\"string\"}],\"name\":\"WorkflowDeletedV1\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"secretsURLHash\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"string\",\"name\":\"workflowName\",\"type\":\"string\"}],\"name\":\"WorkflowForceUpdateSecretsRequestedV1\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"workflowID\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"workflowOwner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"uint32\",\"name\":\"donID\",\"type\":\"uint32\"},{\"indexed\":false,\"internalType\":\"string\",\"name\":\"workflowName\",\"type\":\"string\"}],\"name\":\"WorkflowPausedV1\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"workflowID\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"workflowOwner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"uint32\",\"name\":\"donID\",\"type\":\"uint32\"},{\"indexed\":false,\"internalType\":\"enumWorkflowRegistry.WorkflowStatus\",\"name\":\"status\",\"type\":\"uint8\"},{\"indexed\":false,\"internalType\":\"string\",\"name\":\"workflowName\",\"type\":\"string\"},{\"indexed\":false,\"internalType\":\"string\",\"name\":\"binaryURL\",\"type\":\"string\"},{\"indexed\":false,\"internalType\":\"string\",\"name\":\"configURL\",\"type\":\"string\"},{\"indexed\":false,\"internalType\":\"string\",\"name\":\"secretsURL\",\"type\":\"string\"}],\"name\":\"WorkflowRegisteredV1\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"oldWorkflowID\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"workflowOwner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"uint32\",\"name\":\"donID\",\"type\":\"uint32\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"newWorkflowID\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"string\",\"name\":\"workflowName\",\"type\":\"string\"},{\"indexed\":false,\"internalType\":\"string\",\"name\":\"binaryURL\",\"type\":\"string\"},{\"indexed\":false,\"internalType\":\"string\",\"name\":\"configURL\",\"type\":\"string\"},{\"indexed\":false,\"internalType\":\"string\",\"name\":\"secretsURL\",\"type\":\"string\"}],\"name\":\"WorkflowUpdatedV1\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"acceptOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"workflowKey\",\"type\":\"bytes32\"}],\"name\":\"activateWorkflow\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"internalType\":\"string\",\"name\":\"field\",\"type\":\"string\"}],\"name\":\"computeHashKey\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"workflowKey\",\"type\":\"bytes32\"}],\"name\":\"deleteWorkflow\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getAllAllowedDONs\",\"outputs\":[{\"internalType\":\"uint32[]\",\"name\":\"allowedDONs\",\"type\":\"uint32[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getAllAuthorizedAddresses\",\"outputs\":[{\"internalType\":\"address[]\",\"name\":\"authorizedAddresses\",\"type\":\"address[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"workflowOwner\",\"type\":\"address\"},{\"internalType\":\"string\",\"name\":\"workflowName\",\"type\":\"string\"}],\"name\":\"getWorkflowMetadata\",\"outputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"workflowID\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"internalType\":\"uint32\",\"name\":\"donID\",\"type\":\"uint32\"},{\"internalType\":\"enumWorkflowRegistry.WorkflowStatus\",\"name\":\"status\",\"type\":\"uint8\"},{\"internalType\":\"string\",\"name\":\"workflowName\",\"type\":\"string\"},{\"internalType\":\"string\",\"name\":\"binaryURL\",\"type\":\"string\"},{\"internalType\":\"string\",\"name\":\"configURL\",\"type\":\"string\"},{\"internalType\":\"string\",\"name\":\"secretsURL\",\"type\":\"string\"}],\"internalType\":\"structWorkflowRegistry.WorkflowMetadata\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"donID\",\"type\":\"uint32\"},{\"internalType\":\"uint256\",\"name\":\"start\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"limit\",\"type\":\"uint256\"}],\"name\":\"getWorkflowMetadataListByDON\",\"outputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"workflowID\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"internalType\":\"uint32\",\"name\":\"donID\",\"type\":\"uint32\"},{\"internalType\":\"enumWorkflowRegistry.WorkflowStatus\",\"name\":\"status\",\"type\":\"uint8\"},{\"internalType\":\"string\",\"name\":\"workflowName\",\"type\":\"string\"},{\"internalType\":\"string\",\"name\":\"binaryURL\",\"type\":\"string\"},{\"internalType\":\"string\",\"name\":\"configURL\",\"type\":\"string\"},{\"internalType\":\"string\",\"name\":\"secretsURL\",\"type\":\"string\"}],\"internalType\":\"structWorkflowRegistry.WorkflowMetadata[]\",\"name\":\"workflowMetadataList\",\"type\":\"tuple[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"workflowOwner\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"start\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"limit\",\"type\":\"uint256\"}],\"name\":\"getWorkflowMetadataListByOwner\",\"outputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"workflowID\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"internalType\":\"uint32\",\"name\":\"donID\",\"type\":\"uint32\"},{\"internalType\":\"enumWorkflowRegistry.WorkflowStatus\",\"name\":\"status\",\"type\":\"uint8\"},{\"internalType\":\"string\",\"name\":\"workflowName\",\"type\":\"string\"},{\"internalType\":\"string\",\"name\":\"binaryURL\",\"type\":\"string\"},{\"internalType\":\"string\",\"name\":\"configURL\",\"type\":\"string\"},{\"internalType\":\"string\",\"name\":\"secretsURL\",\"type\":\"string\"}],\"internalType\":\"structWorkflowRegistry.WorkflowMetadata[]\",\"name\":\"workflowMetadataList\",\"type\":\"tuple[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"isRegistryLocked\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"lockRegistry\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"workflowKey\",\"type\":\"bytes32\"}],\"name\":\"pauseWorkflow\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"string\",\"name\":\"workflowName\",\"type\":\"string\"},{\"internalType\":\"bytes32\",\"name\":\"workflowID\",\"type\":\"bytes32\"},{\"internalType\":\"uint32\",\"name\":\"donID\",\"type\":\"uint32\"},{\"internalType\":\"enumWorkflowRegistry.WorkflowStatus\",\"name\":\"status\",\"type\":\"uint8\"},{\"internalType\":\"string\",\"name\":\"binaryURL\",\"type\":\"string\"},{\"internalType\":\"string\",\"name\":\"configURL\",\"type\":\"string\"},{\"internalType\":\"string\",\"name\":\"secretsURL\",\"type\":\"string\"}],\"name\":\"registerWorkflow\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"string\",\"name\":\"secretsURL\",\"type\":\"string\"}],\"name\":\"requestForceUpdateSecrets\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"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\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"unlockRegistry\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32[]\",\"name\":\"donIDs\",\"type\":\"uint32[]\"},{\"internalType\":\"bool\",\"name\":\"allowed\",\"type\":\"bool\"}],\"name\":\"updateAllowedDONs\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address[]\",\"name\":\"addresses\",\"type\":\"address[]\"},{\"internalType\":\"bool\",\"name\":\"allowed\",\"type\":\"bool\"}],\"name\":\"updateAuthorizedAddresses\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"workflowKey\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"newWorkflowID\",\"type\":\"bytes32\"},{\"internalType\":\"string\",\"name\":\"binaryURL\",\"type\":\"string\"},{\"internalType\":\"string\",\"name\":\"configURL\",\"type\":\"string\"},{\"internalType\":\"string\",\"name\":\"secretsURL\",\"type\":\"string\"}],\"name\":\"updateWorkflow\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]", - Bin: "0x6080806040523461004a57331561003b57600180546001600160a01b03191633179055600a805460ff191690556040516133f390816100508239f35b639b15e16f60e01b8152600490fd5b600080fdfe6080604052600436101561001257600080fd5b60003560e01c806308e7f63a14612096578063181f5a77146120075780632303348a14611eca5780632b596f6d14611e3c5780633ccd14ff14611502578063695e13401461135a5780636f35177114611281578063724c13dd1461118a5780637497066b1461106f57806379ba509714610f995780637ec0846d14610f0e5780638da5cb5b14610ebc5780639f4cb53414610e9b578063b87a019414610e45578063d4b89c7414610698578063db800092146105fd578063e3dce080146104d6578063e690f33214610362578063f2fde38b14610284578063f794bdeb146101495763f99ecb6b1461010357600080fd5b346101445760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261014457602060ff600a54166040519015158152f35b600080fd5b346101445760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610144576006805461018581612410565b6101926040519182612297565b81815261019e82612410565b916020937fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe060208401940136853760005b82811061023257505050906040519283926020840190602085525180915260408401929160005b82811061020557505050500390f35b835173ffffffffffffffffffffffffffffffffffffffff16855286955093810193928101926001016101f6565b6001908260005273ffffffffffffffffffffffffffffffffffffffff817ff652222313e28459528d920b65115c16c04f3efc82aaedc97be59f3f377c0d3f01541661027d8287612542565b52016101cf565b346101445760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610144576102bb61237e565b6102c3612bdb565b73ffffffffffffffffffffffffffffffffffffffff8091169033821461033857817fffffffffffffffffffffffff00000000000000000000000000000000000000006000541617600055600154167fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae1278600080a3005b60046040517fdad89dca000000000000000000000000000000000000000000000000000000008152fd5b346101445760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101445760ff600a54166104ac576103a760043533612dc1565b600181019081549160ff8360c01c16600281101561047d576001146104535778010000000000000000000000000000000000000000000000007fffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffff841617905580547f6a0ed88e9cf3cb493ab4028fcb1dc7d18f0130fcdfba096edde0aadbfbf5e99f63ffffffff604051946020865260a01c16938061044e339560026020840191016125e4565b0390a4005b60046040517f6f861db1000000000000000000000000000000000000000000000000000000008152fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b60046040517f78a4e7d9000000000000000000000000000000000000000000000000000000008152fd5b34610144576104e436612306565b916104ed612bdb565b60ff600a54166104ac5760005b828110610589575060405191806040840160408552526060830191906000905b8082106105515785151560208601527f509460cccbb176edde6cac28895a4415a24961b8f3a0bd2617b9bb7b4e166c9b85850386a1005b90919283359073ffffffffffffffffffffffffffffffffffffffff82168092036101445760019181526020809101940192019061051a565b60019084156105cb576105c373ffffffffffffffffffffffffffffffffffffffff6105bd6105b8848888612a7b565b612bba565b16612f9c565b505b016104fa565b6105f773ffffffffffffffffffffffffffffffffffffffff6105f16105b8848888612a7b565b166131cd565b506105c5565b346101445761061d61060e366123a1565b91610617612428565b50612a9c565b6000526004602052604060002073ffffffffffffffffffffffffffffffffffffffff6001820154161561066e5761065661066a91612698565b604051918291602083526020830190612154565b0390f35b60046040517f871e01b2000000000000000000000000000000000000000000000000000000008152fd5b346101445760a07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101445760443567ffffffffffffffff8111610144576106e79036906004016122d8565b9060643567ffffffffffffffff8111610144576107089036906004016122d8565b9160843567ffffffffffffffff8111610144576107299036906004016122d8565b60ff600a94929454166104ac57610744818688602435612cd0565b61075060043533612dc1565b9163ffffffff600184015460a01c169561076a3388612c26565b8354946024358614610e1b576107a56040516107948161078d8160038b016125e4565b0382612297565b61079f368c8561284c565b90612e30565b6107c76040516107bc8161078d8160048c016125e4565b61079f36868861284c565b6107e96040516107de8161078d8160058d016125e4565b61079f36898d61284c565b918080610e14575b80610e0d575b610de357602435885515610c8e575b15610b3d575b15610890575b926108807f41161473ce2ed633d9f902aab9702d16a5531da27ec84e1939abeffe54ad7353959361044e93610872610864978d604051998a996024358b5260a060208c0152600260a08c0191016125e4565b9189830360408b015261290f565b91868303606088015261290f565b908382036080850152339761290f565b61089d6005860154612591565b610ad6575b67ffffffffffffffff8411610aa7576108cb846108c26005880154612591565b600588016128c8565b6000601f85116001146109a757928492610872610880938a9b9c61094f876108649b9a61044e9a7f41161473ce2ed633d9f902aab9702d16a5531da27ec84e1939abeffe54ad73539e9f60009261099c575b50507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8260011b9260031b1c19161790565b60058a01555b8c8780610972575b50509c9b9a9950935050929495509250610812565b61097c9133612a9c565b60005260056020526109946004356040600020612fee565b508c8761095d565b013590508f8061091d565b9860058601600052602060002060005b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe087168110610a8f5750926108726108809361044e969388968c7f41161473ce2ed633d9f902aab9702d16a5531da27ec84e1939abeffe54ad73539c9d9e9f897fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe06108649e9d1610610a57575b505050600187811b0160058a0155610955565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60f88b60031b161c199101351690558e8d81610a44565b898c0135825560209b8c019b600190920191016109b7565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6040516020810190610b1c81610af060058a01338661294e565b037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08101835282612297565b5190206000526005602052610b376004356040600020613294565b506108a2565b67ffffffffffffffff8311610aa757610b6683610b5d6004890154612591565b600489016128c8565b600083601f8111600114610bc75780610bb292600091610bbc575b507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8260011b9260031b1c19161790565b600487015561080c565b90508601358d610b81565b506004870160005260206000209060005b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe086168110610c765750847fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0811610610c3e575b5050600183811b01600487015561080c565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60f88660031b161c19908601351690558a80610c2c565b9091602060018192858a013581550193019101610bd8565b67ffffffffffffffff8b11610aa757610cb78b610cae60038a0154612591565b60038a016128c8565b60008b601f8111600114610d175780610d0292600091610d0c57507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8260011b9260031b1c19161790565b6003880155610806565b90508501358e610b81565b506003880160005260206000209060005b8d7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081168210610dca578091507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0811610610d91575b905060018092501b016003880155610806565b60f87fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9160031b161c19908501351690558b808c610d7e565b5085820135835560019092019160209182019101610d28565b60046040517f6b4a810d000000000000000000000000000000000000000000000000000000008152fd5b50826107f7565b50816107f1565b60046040517f95406722000000000000000000000000000000000000000000000000000000008152fd5b346101445760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101445761066a610e8f610e8261237e565b6044359060243590612af7565b604051918291826121f8565b34610144576020610eb4610eae366123a1565b91612a9c565b604051908152f35b346101445760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261014457602073ffffffffffffffffffffffffffffffffffffffff60015416604051908152f35b346101445760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261014457610f45612bdb565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00600a5416600a557f11a03e25ee25bf1459f9e1cb293ea03707d84917f54a65e32c9a7be2f2edd68a6020604051338152a1005b346101445760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101445760005473ffffffffffffffffffffffffffffffffffffffff808216330361104557600154917fffffffffffffffffffffffff0000000000000000000000000000000000000000903382851617600155166000553391167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0600080a3005b60046040517f02b543c6000000000000000000000000000000000000000000000000000000008152fd5b346101445760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261014457600880546110ab81612410565b6110b86040519182612297565b8181526110c482612410565b916020937fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe060208401940136853760005b82811061114857505050906040519283926020840190602085525180915260408401929160005b82811061112b57505050500390f35b835163ffffffff168552869550938101939281019260010161111c565b6001908260005263ffffffff817ff3f7a9fe364faab93b216da50a3214154f22a0a2b415b23a84c8169e8b636ee30154166111838287612542565b52016110f5565b346101445761119836612306565b916111a1612bdb565b60ff600a54166104ac5760005b82811061122d575060405191806040840160408552526060830191906000905b8082106112055785151560208601527fcab63bf31d1e656baa23cebef64e12033ea0ffbd44b1278c3747beec2d2f618c85850386a1005b90919283359063ffffffff8216809203610144576001918152602080910194019201906111ce565b600190841561125f5761125763ffffffff61125161124c848888612a7b565b612a8b565b16612ee3565b505b016111ae565b61127b63ffffffff61127561124c848888612a7b565b1661307a565b50611259565b346101445760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101445760ff600a54166104ac576112c660043533612dc1565b600181019081549163ffffffff8360a01c169260ff8160c01c16600281101561047d5715610453577fffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffff9061131a3386612c26565b16905580547f17b2d730bb5e064df3fbc6165c8aceb3b0d62c524c196c0bc1012209280bc9a6604051602081528061044e339560026020840191016125e4565b34610144576020807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610144576004359060ff600a54166104ac576113a28233612dc1565b916113ba336000526007602052604060002054151590565b156114d25760049233600052600283526113d8826040600020613294565b50600181019063ffffffff80835460a01c16600052600385526113ff846040600020613294565b506005820161140e8154612591565b61149e575b508154925460a01c16917f76ee2dfcae10cb8522e62e713e62660e09ecfaab08db15d9404de1914132257160405186815280611456339560028a840191016125e4565b0390a46000525261149c60056040600020600081556000600182015561147e60028201612a32565b61148a60038201612a32565b61149660048201612a32565b01612a32565b005b6040516114b381610af089820194338661294e565b519020600052600585526114cb846040600020613294565b5086611413565b60246040517f85982a00000000000000000000000000000000000000000000000000000000008152336004820152fd5b346101445760e07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101445760043567ffffffffffffffff8111610144576115519036906004016122d8565b6044359163ffffffff8316830361014457600260643510156101445760843567ffffffffffffffff81116101445761158d9036906004016122d8565b91909260a43567ffffffffffffffff8111610144576115b09036906004016122d8565b60c43567ffffffffffffffff8111610144576115d09036906004016122d8565b96909560ff600a54166104ac576115e7338a612c26565b60408511611e04576115fd888483602435612cd0565b611608858733612a9c565b80600052600460205273ffffffffffffffffffffffffffffffffffffffff60016040600020015416611dda57604051906116418261227a565b602435825233602083015263ffffffff8b16604083015261166760643560608401612585565b61167236888a61284c565b608083015261168236848661284c565b60a083015261169236868861284c565b60c08301526116a2368b8b61284c565b60e0830152806000526004602052604060002091805183556001830173ffffffffffffffffffffffffffffffffffffffff60208301511681549077ffffffff0000000000000000000000000000000000000000604085015160a01b16906060850151600281101561047d5778ff0000000000000000000000000000000000000000000000007fffffffffffffff000000000000000000000000000000000000000000000000009160c01b1693161717179055608081015180519067ffffffffffffffff8211610aa7576117858261177c6002880154612591565b600288016128c8565b602090601f8311600114611d0e576117d2929160009183611c375750507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8260011b9260031b1c19161790565b60028401555b60a081015180519067ffffffffffffffff8211610aa757611809826118006003880154612591565b600388016128c8565b602090601f8311600114611c4257611856929160009183611c375750507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8260011b9260031b1c19161790565b60038401555b60c081015180519067ffffffffffffffff8211610aa75761188d826118846004880154612591565b600488016128c8565b602090601f8311600114611b6a5791806118de9260e09594600092611a455750507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8260011b9260031b1c19161790565b60048501555b015180519267ffffffffffffffff8411610aa757838d926119168e9661190d6005860154612591565b600586016128c8565b602090601f8311600114611a50579463ffffffff61087295819a957fc4399022965bad9b2b468bbd8c758a7e80cdde36ff3088ddbb7f93bdfb5623cb9f9e9d99946119a28761044e9f9b98600593611a069f9a600092611a455750507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8260011b9260031b1c19161790565b9101555b3360005260026020526119bd836040600020612fee565b501660005260036020526119d5816040600020612fee565b508d82611a1c575b5050506108646040519a8b9a6119f58c6064356120e9565b60a060208d015260a08c019161290f565b978389036080850152169633966024359661290f565b611a3c92611a2a9133612a9c565b60005260056020526040600020612fee565b508c8f8d6119dd565b01519050388061091d565b906005840160005260206000209160005b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe085168110611b4057506108729563ffffffff9a957fc4399022965bad9b2b468bbd8c758a7e80cdde36ff3088ddbb7f93bdfb5623cb9f9e9d999460018761044e9f9b96928f9693611a069f9a94837fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe06005971610611b09575b505050811b019101556119a6565b01517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60f88460031b161c19169055388080611afb565b939550918194969750600160209291839285015181550194019201918f9492918f97969492611a61565b906004860160005260206000209160005b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe085168110611c1f5750918391600193837fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe060e098971610611be8575b505050811b0160048501556118e4565b01517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60f88460031b161c191690558f8080611bd8565b91926020600181928685015181550194019201611b7b565b015190508f8061091d565b9190600386016000526020600020906000935b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe084168510611cf35760019450837fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0811610611cbc575b505050811b01600384015561185c565b01517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60f88460031b161c191690558e8080611cac565b81810151835560209485019460019093019290910190611c55565b9190600286016000526020600020906000935b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe084168510611dbf5760019450837fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0811610611d88575b505050811b0160028401556117d8565b01517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60f88460031b161c191690558e8080611d78565b81810151835560209485019460019093019290910190611d21565b60046040517fa0677dd0000000000000000000000000000000000000000000000000000000008152fd5b604485604051907f36a7c503000000000000000000000000000000000000000000000000000000008252600482015260406024820152fd5b346101445760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261014457611e73612bdb565b60017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00600a541617600a557f2789711f6fd67d131ad68378617b5d1d21a2c92b34d7c3745d70b3957c08096c6020604051338152a1005b34610144576020807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101445760043567ffffffffffffffff811161014457611f1a9036906004016122d8565b60ff600a54166104ac57611f2e9133612a9c565b90816000526005602052604060002091825491821561066e5760005b838110611f5357005b80611f6060019287612ecb565b90549060031b1c60005260048352604060002063ffffffff8382015460a01c1660005260098452604060002054151580611fea575b611fa1575b5001611f4a565b7f95d94f817db4971aa99ba35d0fe019bd8cc39866fbe02b6d47b5f0f3727fb67360405186815260408682015280611fe1339460026040840191016125e4565b0390a286611f9a565b50612002336000526007602052604060002054151590565b611f95565b346101445760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261014457604051604081019080821067ffffffffffffffff831117610aa75761066a91604052601a81527f576f726b666c6f77526567697374727920312e302e302d64657600000000000060208201526040519182916020835260208301906120f6565b346101445760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101445760043563ffffffff8116810361014457610e8f61066a916044359060243590612757565b90600282101561047d5752565b919082519283825260005b8481106121405750507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8460006020809697860101520116010190565b602081830181015184830182015201612101565b6121f59160e06121e46121d26121c06101008651865273ffffffffffffffffffffffffffffffffffffffff602088015116602087015263ffffffff60408801511660408701526121ac606088015160608801906120e9565b6080870151908060808801528601906120f6565b60a086015185820360a08701526120f6565b60c085015184820360c08601526120f6565b9201519060e08184039101526120f6565b90565b6020808201906020835283518092526040830192602060408460051b8301019501936000915b84831061222e5750505050505090565b909192939495848061226a837fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc086600196030187528a51612154565b980193019301919493929061221e565b610100810190811067ffffffffffffffff821117610aa757604052565b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff821117610aa757604052565b9181601f840112156101445782359167ffffffffffffffff8311610144576020838186019501011161014457565b9060407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc8301126101445760043567ffffffffffffffff9283821161014457806023830112156101445781600401359384116101445760248460051b8301011161014457602401919060243580151581036101445790565b6004359073ffffffffffffffffffffffffffffffffffffffff8216820361014457565b9060407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc8301126101445760043573ffffffffffffffffffffffffffffffffffffffff8116810361014457916024359067ffffffffffffffff82116101445761240c916004016122d8565b9091565b67ffffffffffffffff8111610aa75760051b60200190565b604051906124358261227a565b606060e0836000815260006020820152600060408201526000838201528260808201528260a08201528260c08201520152565b6040516020810181811067ffffffffffffffff821117610aa7576040526000815290565b9061249682612410565b6124a36040519182612297565b8281527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe06124d18294612410565b019060005b8281106124e257505050565b6020906124ed612428565b828285010152016124d6565b9190820180921161250657565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b9190820391821161250657565b80518210156125565760209160051b010190565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b600282101561047d5752565b90600182811c921680156125da575b60208310146125ab57565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b91607f16916125a0565b8054600093926125f382612591565b9182825260209360019160018116908160001461265b575060011461261a575b5050505050565b90939495506000929192528360002092846000945b83861061264757505050500101903880808080612613565b80548587018301529401938590820161262f565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00168685015250505090151560051b010191503880808080612613565b90600560e06040936127538551916126af8361227a565b61274c8397825485526126f960ff600185015473ffffffffffffffffffffffffffffffffffffffff8116602089015263ffffffff8160a01c168489015260c01c1660608701612585565b805161270c8161078d81600288016125e4565b608086015280516127248161078d81600388016125e4565b60a0860152805161273c8161078d81600488016125e4565b60c08601525180968193016125e4565b0384612297565b0152565b63ffffffff16916000838152600360209060036020526040936040842054908187101561283c576127ab918160648993118015612834575b61282c575b8161279f82856124f9565b111561281c5750612535565b946127b58661248c565b96845b8781106127ca57505050505050505090565b6001908287528486526127e98888206127e383876124f9565b90612ecb565b905490861b1c875260048652612800888820612698565b61280a828c612542565b52612815818b612542565b50016127b8565b6128279150826124f9565b612535565b506064612794565b50801561278f565b50505050505050506121f5612468565b92919267ffffffffffffffff8211610aa7576040519161289460207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8401160184612297565b829481845281830111610144578281602093846000960137010152565b8181106128bc575050565b600081556001016128b1565b9190601f81116128d757505050565b612903926000526020600020906020601f840160051c83019310612905575b601f0160051c01906128b1565b565b90915081906128f6565b601f82602094937fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0938186528686013760008582860101520116010190565b91907fffffffffffffffffffffffffffffffffffffffff0000000000000000000000009060601b16825260149060009281549261298a84612591565b926001946001811690816000146129f157506001146129ac575b505050505090565b9091929395945060005260209460206000206000905b8582106129de57505050506014929350010138808080806129a4565b80548583018501529087019082016129c2565b92505050601494507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00919350168383015280151502010138808080806129a4565b612a3c8154612591565b9081612a46575050565b81601f60009311600114612a58575055565b908083918252612a77601f60208420940160051c8401600185016128b1565b5555565b91908110156125565760051b0190565b3563ffffffff811681036101445790565b91906034612af191836040519485927fffffffffffffffffffffffffffffffffffffffff000000000000000000000000602085019860601b168852848401378101600083820152036014810184520182612297565b51902090565b73ffffffffffffffffffffffffffffffffffffffff1691600083815260029260209060026020526040936040842054908183101561283c57612b4e9181606485931180156128345761282c578161279f82856124f9565b94612b588661248c565b96845b878110612b6d57505050505050505090565b600190828752838652612b868888206127e383886124f9565b90549060031b1c875260048652612b9e888820612698565b612ba8828c612542565b52612bb3818b612542565b5001612b5b565b3573ffffffffffffffffffffffffffffffffffffffff811681036101445790565b73ffffffffffffffffffffffffffffffffffffffff600154163303612bfc57565b60046040517f2b5c74de000000000000000000000000000000000000000000000000000000008152fd5b63ffffffff1680600052600960205260406000205415612c9f575073ffffffffffffffffffffffffffffffffffffffff1680600052600760205260406000205415612c6e5750565b602490604051907f85982a000000000000000000000000000000000000000000000000000000000082526004820152fd5b602490604051907f8fe6d7e10000000000000000000000000000000000000000000000000000000082526004820152fd5b91909115612d975760c891828111612d615750818111612d2c5750808211612cf6575050565b60449250604051917ecd56a800000000000000000000000000000000000000000000000000000000835260048301526024820152fd5b604491604051917ecd56a800000000000000000000000000000000000000000000000000000000835260048301526024820152fd5b60449083604051917ecd56a800000000000000000000000000000000000000000000000000000000835260048301526024820152fd5b60046040517f7dc2f4e1000000000000000000000000000000000000000000000000000000008152fd5b90600052600460205260406000209073ffffffffffffffffffffffffffffffffffffffff8060018401541691821561066e5716809103612dff575090565b602490604051907f31ee6dc70000000000000000000000000000000000000000000000000000000082526004820152fd5b9081518151908181149384612e47575b5050505090565b6020929394508201209201201438808080612e40565b6008548110156125565760086000527ff3f7a9fe364faab93b216da50a3214154f22a0a2b415b23a84c8169e8b636ee30190600090565b6006548110156125565760066000527ff652222313e28459528d920b65115c16c04f3efc82aaedc97be59f3f377c0d3f0190600090565b80548210156125565760005260206000200190600090565b600081815260096020526040812054612f975760085468010000000000000000811015612f6a579082612f56612f2184600160409601600855612e5d565b81939154907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9060031b92831b921b19161790565b905560085492815260096020522055600190565b6024827f4e487b710000000000000000000000000000000000000000000000000000000081526041600452fd5b905090565b600081815260076020526040812054612f975760065468010000000000000000811015612f6a579082612fda612f2184600160409601600655612e94565b905560065492815260076020522055600190565b9190600183016000908282528060205260408220541560001461307457845494680100000000000000008610156130475783613037612f21886001604098999a01855584612ecb565b9055549382526020522055600190565b6024837f4e487b710000000000000000000000000000000000000000000000000000000081526041600452fd5b50925050565b60008181526009602052604081205490919080156131c8577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9081810181811161319b576008549083820191821161316e5781810361313a575b505050600854801561310d578101906130ec82612e5d565b909182549160031b1b19169055600855815260096020526040812055600190565b6024847f4e487b710000000000000000000000000000000000000000000000000000000081526031600452fd5b613158613149612f2193612e5d565b90549060031b1c928392612e5d565b90558452600960205260408420553880806130d4565b6024867f4e487b710000000000000000000000000000000000000000000000000000000081526011600452fd5b6024857f4e487b710000000000000000000000000000000000000000000000000000000081526011600452fd5b505090565b60008181526007602052604081205490919080156131c8577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9081810181811161319b576006549083820191821161316e57818103613260575b505050600654801561310d5781019061323f82612e94565b909182549160031b1b19169055600655815260076020526040812055600190565b61327e61326f612f2193612e94565b90549060031b1c928392612e94565b9055845260076020526040842055388080613227565b90600182019060009281845282602052604084205490811515600014612e40577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff918281018181116133b95782549084820191821161338c57818103613357575b5050508054801561332a5782019161330d8383612ecb565b909182549160031b1b191690555582526020526040812055600190565b6024867f4e487b710000000000000000000000000000000000000000000000000000000081526031600452fd5b613377613367612f219386612ecb565b90549060031b1c92839286612ecb565b905586528460205260408620553880806132f5565b6024887f4e487b710000000000000000000000000000000000000000000000000000000081526011600452fd5b6024877f4e487b710000000000000000000000000000000000000000000000000000000081526011600452fdfea164736f6c6343000818000a", + Bin: "0x6080806040523461004a57331561003b57600180546001600160a01b03191633179055600b805460ff191690556040516134e290816100508239f35b639b15e16f60e01b8152600490fd5b600080fdfe6080604052600436101561001257600080fd5b60003560e01c806308e7f63a1461210e578063181f5a771461207f5780632303348a14611f425780632b596f6d14611eb45780633ccd14ff14611572578063695e1340146113965780636f351771146112bd578063724c13dd146111c65780637497066b146110ab57806379ba509714610fd55780637ec0846d14610f4a5780638da5cb5b14610ef85780639f4cb53414610ed7578063b87a019414610e81578063d4b89c7414610698578063db800092146105fd578063e3dce080146104d6578063e690f33214610362578063f2fde38b14610284578063f794bdeb146101495763f99ecb6b1461010357600080fd5b346101445760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261014457602060ff600b54166040519015158152f35b600080fd5b346101445760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610144576007805461018581612488565b610192604051918261230f565b81815261019e82612488565b916020937fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe060208401940136853760005b82811061023257505050906040519283926020840190602085525180915260408401929160005b82811061020557505050500390f35b835173ffffffffffffffffffffffffffffffffffffffff16855286955093810193928101926001016101f6565b6001908260005273ffffffffffffffffffffffffffffffffffffffff817fa66cc928b5edb82af9bd49922954155ab7b0942694bea4ce44661d9a8736c68801541661027d82876125ba565b52016101cf565b346101445760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610144576102bb6123f6565b6102c3612c53565b73ffffffffffffffffffffffffffffffffffffffff8091169033821461033857817fffffffffffffffffffffffff00000000000000000000000000000000000000006000541617600055600154167fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae1278600080a3005b60046040517fdad89dca000000000000000000000000000000000000000000000000000000008152fd5b346101445760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101445760ff600b54166104ac576103a760043533612eb0565b600181019081549160ff8360c01c16600281101561047d576001146104535778010000000000000000000000000000000000000000000000007fffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffff841617905580547f6a0ed88e9cf3cb493ab4028fcb1dc7d18f0130fcdfba096edde0aadbfbf5e99f63ffffffff604051946020865260a01c16938061044e3395600260208401910161265c565b0390a4005b60046040517f6f861db1000000000000000000000000000000000000000000000000000000008152fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b60046040517f78a4e7d9000000000000000000000000000000000000000000000000000000008152fd5b34610144576104e43661237e565b916104ed612c53565b60ff600b54166104ac5760005b828110610589575060405191806040840160408552526060830191906000905b8082106105515785151560208601527f509460cccbb176edde6cac28895a4415a24961b8f3a0bd2617b9bb7b4e166c9b85850386a1005b90919283359073ffffffffffffffffffffffffffffffffffffffff82168092036101445760019181526020809101940192019061051a565b60019084156105cb576105c373ffffffffffffffffffffffffffffffffffffffff6105bd6105b8848888612af3565b612c32565b1661308b565b505b016104fa565b6105f773ffffffffffffffffffffffffffffffffffffffff6105f16105b8848888612af3565b166132bc565b506105c5565b346101445761061d61060e36612419565b916106176124a0565b50612b14565b6000526004602052604060002073ffffffffffffffffffffffffffffffffffffffff6001820154161561066e5761065661066a91612710565b6040519182916020835260208301906121cc565b0390f35b60046040517f871e01b2000000000000000000000000000000000000000000000000000000008152fd5b346101445760a07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101445760443567ffffffffffffffff8111610144576106e7903690600401612350565b9060643567ffffffffffffffff811161014457610708903690600401612350565b9160843567ffffffffffffffff811161014457610729903690600401612350565b60ff600b94929454166104ac57610741818688612d48565b61074d60043533612eb0565b9163ffffffff600184015460a01c16956107673388612c9e565b8354946024358614610e57576107a26040516107918161078a8160038b0161265c565b038261230f565b61079c368c856128c4565b90612f1f565b6107c46040516107b98161078a8160048c0161265c565b61079c3686886128c4565b6107e66040516107db8161078a8160058d0161265c565b61079c36898d6128c4565b918080610e50575b80610e49575b610e1f57610803602435612e08565b88600052600660205260406000207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff008154169055602435885515610cca575b15610b79575b156108cc575b926108bc7f41161473ce2ed633d9f902aab9702d16a5531da27ec84e1939abeffe54ad7353959361044e936108ae6108a0978d604051998a996024358b5260a060208c0152600260a08c01910161265c565b9189830360408b0152612987565b918683036060880152612987565b9083820360808501523397612987565b6108d96005860154612609565b610b12575b67ffffffffffffffff8411610ae357610907846108fe6005880154612609565b60058801612940565b6000601f85116001146109e3579284926108ae6108bc938a9b9c61098b876108a09b9a61044e9a7f41161473ce2ed633d9f902aab9702d16a5531da27ec84e1939abeffe54ad73539e9f6000926109d8575b50507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8260011b9260031b1c19161790565b60058a01555b8c87806109ae575b50509c9b9a995093505092949550925061084e565b6109b89133612b14565b60005260056020526109d060043560406000206130dd565b508c87610999565b013590508f80610959565b9860058601600052602060002060005b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe087168110610acb5750926108ae6108bc9361044e969388968c7f41161473ce2ed633d9f902aab9702d16a5531da27ec84e1939abeffe54ad73539c9d9e9f897fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe06108a09e9d1610610a93575b505050600187811b0160058a0155610991565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60f88b60031b161c199101351690558e8d81610a80565b898c0135825560209b8c019b600190920191016109f3565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6040516020810190610b5881610b2c60058a0133866129c6565b037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0810183528261230f565b5190206000526005602052610b736004356040600020613383565b506108de565b67ffffffffffffffff8311610ae357610ba283610b996004890154612609565b60048901612940565b600083601f8111600114610c035780610bee92600091610bf8575b507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8260011b9260031b1c19161790565b6004870155610848565b90508601358d610bbd565b506004870160005260206000209060005b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe086168110610cb25750847fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0811610610c7a575b5050600183811b016004870155610848565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60f88660031b161c19908601351690558a80610c68565b9091602060018192858a013581550193019101610c14565b67ffffffffffffffff8b11610ae357610cf38b610cea60038a0154612609565b60038a01612940565b60008b601f8111600114610d535780610d3e92600091610d4857507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8260011b9260031b1c19161790565b6003880155610842565b90508501358e610bbd565b506003880160005260206000209060005b8d7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081168210610e06578091507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0811610610dcd575b905060018092501b016003880155610842565b60f87fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9160031b161c19908501351690558b808c610dba565b5085820135835560019092019160209182019101610d64565b60046040517f6b4a810d000000000000000000000000000000000000000000000000000000008152fd5b50826107f4565b50816107ee565b60046040517f95406722000000000000000000000000000000000000000000000000000000008152fd5b346101445760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101445761066a610ecb610ebe6123f6565b6044359060243590612b6f565b60405191829182612270565b34610144576020610ef0610eea36612419565b91612b14565b604051908152f35b346101445760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261014457602073ffffffffffffffffffffffffffffffffffffffff60015416604051908152f35b346101445760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261014457610f81612c53565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00600b5416600b557f11a03e25ee25bf1459f9e1cb293ea03707d84917f54a65e32c9a7be2f2edd68a6020604051338152a1005b346101445760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101445760005473ffffffffffffffffffffffffffffffffffffffff808216330361108157600154917fffffffffffffffffffffffff0000000000000000000000000000000000000000903382851617600155166000553391167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0600080a3005b60046040517f02b543c6000000000000000000000000000000000000000000000000000000008152fd5b346101445760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261014457600980546110e781612488565b6110f4604051918261230f565b81815261110082612488565b916020937fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe060208401940136853760005b82811061118457505050906040519283926020840190602085525180915260408401929160005b82811061116757505050500390f35b835163ffffffff1685528695509381019392810192600101611158565b6001908260005263ffffffff817f6e1540171b6c0c960b71a7020d9f60077f6af931a8bbf590da0223dacf75c7af0154166111bf82876125ba565b5201611131565b34610144576111d43661237e565b916111dd612c53565b60ff600b54166104ac5760005b828110611269575060405191806040840160408552526060830191906000905b8082106112415785151560208601527fcab63bf31d1e656baa23cebef64e12033ea0ffbd44b1278c3747beec2d2f618c85850386a1005b90919283359063ffffffff82168092036101445760019181526020809101940192019061120a565b600190841561129b5761129363ffffffff61128d611288848888612af3565b612b03565b16612fd2565b505b016111ea565b6112b763ffffffff6112b1611288848888612af3565b16613169565b50611295565b346101445760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101445760ff600b54166104ac5761130260043533612eb0565b600181019081549163ffffffff8360a01c169260ff8160c01c16600281101561047d5715610453577fffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffff906113563386612c9e565b16905580547f17b2d730bb5e064df3fbc6165c8aceb3b0d62c524c196c0bc1012209280bc9a6604051602081528061044e3395600260208401910161265c565b34610144576020807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610144576004359060ff600b54166104ac576113de8233612eb0565b916113f6336000526008602052604060002054151590565b156115425782600493546000526006835260406000207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0081541690553360005260028352611448826040600020613383565b50600181019063ffffffff80835460a01c166000526003855261146f846040600020613383565b506005820161147e8154612609565b61150e575b508154925460a01c16917f76ee2dfcae10cb8522e62e713e62660e09ecfaab08db15d9404de19141322571604051868152806114c6339560028a8401910161265c565b0390a46000525261150c6005604060002060008155600060018201556114ee60028201612aaa565b6114fa60038201612aaa565b61150660048201612aaa565b01612aaa565b005b60405161152381610b2c8982019433866129c6565b5190206000526005855261153b846040600020613383565b5086611483565b60246040517f85982a00000000000000000000000000000000000000000000000000000000008152336004820152fd5b346101445760e07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101445760043567ffffffffffffffff8111610144576115c1903690600401612350565b6044359163ffffffff8316830361014457600260643510156101445760843567ffffffffffffffff8111610144576115fd903690600401612350565b91909260a43567ffffffffffffffff811161014457611620903690600401612350565b60c43567ffffffffffffffff811161014457611640903690600401612350565b96909560ff600b54166104ac57611657338a612c9e565b60408511611e7c5761166a888483612d48565b611675858733612b14565b80600052600460205273ffffffffffffffffffffffffffffffffffffffff60016040600020015416611e52576116ac602435612e08565b604051906116b9826122f2565b602435825233602083015263ffffffff8b1660408301526116df606435606084016125fd565b6116ea36888a6128c4565b60808301526116fa3684866128c4565b60a083015261170a3686886128c4565b60c083015261171a368b8b6128c4565b60e0830152806000526004602052604060002091805183556001830173ffffffffffffffffffffffffffffffffffffffff60208301511681549077ffffffff0000000000000000000000000000000000000000604085015160a01b16906060850151600281101561047d5778ff0000000000000000000000000000000000000000000000007fffffffffffffff000000000000000000000000000000000000000000000000009160c01b1693161717179055608081015180519067ffffffffffffffff8211610ae3576117fd826117f46002880154612609565b60028801612940565b602090601f8311600114611d865761184a929160009183611caf5750507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8260011b9260031b1c19161790565b60028401555b60a081015180519067ffffffffffffffff8211610ae357611881826118786003880154612609565b60038801612940565b602090601f8311600114611cba576118ce929160009183611caf5750507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8260011b9260031b1c19161790565b60038401555b60c081015180519067ffffffffffffffff8211610ae357611905826118fc6004880154612609565b60048801612940565b602090601f8311600114611be25791806119569260e09594600092611abd5750507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8260011b9260031b1c19161790565b60048501555b015180519267ffffffffffffffff8411610ae357838d9261198e8e966119856005860154612609565b60058601612940565b602090601f8311600114611ac8579463ffffffff6108ae95819a957fc4399022965bad9b2b468bbd8c758a7e80cdde36ff3088ddbb7f93bdfb5623cb9f9e9d9994611a1a8761044e9f9b98600593611a7e9f9a600092611abd5750507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8260011b9260031b1c19161790565b9101555b336000526002602052611a358360406000206130dd565b50166000526003602052611a4d8160406000206130dd565b508d82611a94575b5050506108a06040519a8b9a611a6d8c606435612161565b60a060208d015260a08c0191612987565b9783890360808501521696339660243596612987565b611ab492611aa29133612b14565b600052600560205260406000206130dd565b508c8f8d611a55565b015190503880610959565b906005840160005260206000209160005b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe085168110611bb857506108ae9563ffffffff9a957fc4399022965bad9b2b468bbd8c758a7e80cdde36ff3088ddbb7f93bdfb5623cb9f9e9d999460018761044e9f9b96928f9693611a7e9f9a94837fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe06005971610611b81575b505050811b01910155611a1e565b01517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60f88460031b161c19169055388080611b73565b939550918194969750600160209291839285015181550194019201918f9492918f97969492611ad9565b906004860160005260206000209160005b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe085168110611c975750918391600193837fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe060e098971610611c60575b505050811b01600485015561195c565b01517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60f88460031b161c191690558f8080611c50565b91926020600181928685015181550194019201611bf3565b015190508f80610959565b9190600386016000526020600020906000935b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe084168510611d6b5760019450837fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0811610611d34575b505050811b0160038401556118d4565b01517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60f88460031b161c191690558e8080611d24565b81810151835560209485019460019093019290910190611ccd565b9190600286016000526020600020906000935b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe084168510611e375760019450837fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0811610611e00575b505050811b016002840155611850565b01517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60f88460031b161c191690558e8080611df0565b81810151835560209485019460019093019290910190611d99565b60046040517fa0677dd0000000000000000000000000000000000000000000000000000000008152fd5b604485604051907f36a7c503000000000000000000000000000000000000000000000000000000008252600482015260406024820152fd5b346101445760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261014457611eeb612c53565b60017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00600b541617600b557f2789711f6fd67d131ad68378617b5d1d21a2c92b34d7c3745d70b3957c08096c6020604051338152a1005b34610144576020807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101445760043567ffffffffffffffff811161014457611f92903690600401612350565b60ff600b54166104ac57611fa69133612b14565b90816000526005602052604060002091825491821561066e5760005b838110611fcb57005b80611fd860019287612fba565b90549060031b1c60005260048352604060002063ffffffff8382015460a01c16600052600a8452604060002054151580612062575b612019575b5001611fc2565b7f95d94f817db4971aa99ba35d0fe019bd8cc39866fbe02b6d47b5f0f3727fb673604051868152604086820152806120593394600260408401910161265c565b0390a286612012565b5061207a336000526008602052604060002054151590565b61200d565b346101445760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261014457604051604081019080821067ffffffffffffffff831117610ae35761066a91604052601a81527f576f726b666c6f77526567697374727920312e302e302d646576000000000000602082015260405191829160208352602083019061216e565b346101445760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101445760043563ffffffff8116810361014457610ecb61066a9160443590602435906127cf565b90600282101561047d5752565b919082519283825260005b8481106121b85750507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8460006020809697860101520116010190565b602081830181015184830182015201612179565b61226d9160e061225c61224a6122386101008651865273ffffffffffffffffffffffffffffffffffffffff602088015116602087015263ffffffff604088015116604087015261222460608801516060880190612161565b60808701519080608088015286019061216e565b60a086015185820360a087015261216e565b60c085015184820360c086015261216e565b9201519060e081840391015261216e565b90565b6020808201906020835283518092526040830192602060408460051b8301019501936000915b8483106122a65750505050505090565b90919293949584806122e2837fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc086600196030187528a516121cc565b9801930193019194939290612296565b610100810190811067ffffffffffffffff821117610ae357604052565b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff821117610ae357604052565b9181601f840112156101445782359167ffffffffffffffff8311610144576020838186019501011161014457565b9060407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc8301126101445760043567ffffffffffffffff9283821161014457806023830112156101445781600401359384116101445760248460051b8301011161014457602401919060243580151581036101445790565b6004359073ffffffffffffffffffffffffffffffffffffffff8216820361014457565b9060407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc8301126101445760043573ffffffffffffffffffffffffffffffffffffffff8116810361014457916024359067ffffffffffffffff82116101445761248491600401612350565b9091565b67ffffffffffffffff8111610ae35760051b60200190565b604051906124ad826122f2565b606060e0836000815260006020820152600060408201526000838201528260808201528260a08201528260c08201520152565b6040516020810181811067ffffffffffffffff821117610ae3576040526000815290565b9061250e82612488565b61251b604051918261230f565b8281527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe06125498294612488565b019060005b82811061255a57505050565b6020906125656124a0565b8282850101520161254e565b9190820180921161257e57565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b9190820391821161257e57565b80518210156125ce5760209160051b010190565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b600282101561047d5752565b90600182811c92168015612652575b602083101461262357565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b91607f1691612618565b80546000939261266b82612609565b918282526020936001916001811690816000146126d35750600114612692575b5050505050565b90939495506000929192528360002092846000945b8386106126bf5750505050010190388080808061268b565b8054858701830152940193859082016126a7565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00168685015250505090151560051b01019150388080808061268b565b90600560e06040936127cb855191612727836122f2565b6127c483978254855261277160ff600185015473ffffffffffffffffffffffffffffffffffffffff8116602089015263ffffffff8160a01c168489015260c01c16606087016125fd565b80516127848161078a816002880161265c565b6080860152805161279c8161078a816003880161265c565b60a086015280516127b48161078a816004880161265c565b60c086015251809681930161265c565b038461230f565b0152565b63ffffffff1691600083815260036020906003602052604093604084205490818710156128b4576128239181606489931180156128ac575b6128a4575b816128178285612571565b111561289457506125ad565b9461282d86612504565b96845b87811061284257505050505050505090565b60019082875284865261286188882061285b8387612571565b90612fba565b905490861b1c875260048652612878888820612710565b612882828c6125ba565b5261288d818b6125ba565b5001612830565b61289f915082612571565b6125ad565b50606461280c565b508015612807565b505050505050505061226d6124e0565b92919267ffffffffffffffff8211610ae3576040519161290c60207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f840116018461230f565b829481845281830111610144578281602093846000960137010152565b818110612934575050565b60008155600101612929565b9190601f811161294f57505050565b61297b926000526020600020906020601f840160051c8301931061297d575b601f0160051c0190612929565b565b909150819061296e565b601f82602094937fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0938186528686013760008582860101520116010190565b91907fffffffffffffffffffffffffffffffffffffffff0000000000000000000000009060601b168252601490600092815492612a0284612609565b92600194600181169081600014612a695750600114612a24575b505050505090565b9091929395945060005260209460206000206000905b858210612a565750505050601492935001013880808080612a1c565b8054858301850152908701908201612a3a565b92505050601494507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0091935016838301528015150201013880808080612a1c565b612ab48154612609565b9081612abe575050565b81601f60009311600114612ad0575055565b908083918252612aef601f60208420940160051c840160018501612929565b5555565b91908110156125ce5760051b0190565b3563ffffffff811681036101445790565b91906034612b6991836040519485927fffffffffffffffffffffffffffffffffffffffff000000000000000000000000602085019860601b16885284840137810160008382015203601481018452018261230f565b51902090565b73ffffffffffffffffffffffffffffffffffffffff169160008381526002926020906002602052604093604084205490818310156128b457612bc69181606485931180156128ac576128a457816128178285612571565b94612bd086612504565b96845b878110612be557505050505050505090565b600190828752838652612bfe88882061285b8388612571565b90549060031b1c875260048652612c16888820612710565b612c20828c6125ba565b52612c2b818b6125ba565b5001612bd3565b3573ffffffffffffffffffffffffffffffffffffffff811681036101445790565b73ffffffffffffffffffffffffffffffffffffffff600154163303612c7457565b60046040517f2b5c74de000000000000000000000000000000000000000000000000000000008152fd5b63ffffffff1680600052600a60205260406000205415612d17575073ffffffffffffffffffffffffffffffffffffffff1680600052600860205260406000205415612ce65750565b602490604051907f85982a000000000000000000000000000000000000000000000000000000000082526004820152fd5b602490604051907f8fe6d7e10000000000000000000000000000000000000000000000000000000082526004820152fd5b9060c891828111612dd25750818111612d9d5750808211612d67575050565b60449250604051917ecd56a800000000000000000000000000000000000000000000000000000000835260048301526024820152fd5b604491604051917ecd56a800000000000000000000000000000000000000000000000000000000835260048301526024820152fd5b60449083604051917ecd56a800000000000000000000000000000000000000000000000000000000835260048301526024820152fd5b8015612e865780600052600660205260ff60406000205416612e5c576000526006602052604060002060017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00825416179055565b60046040517f4cb050e4000000000000000000000000000000000000000000000000000000008152fd5b60046040517f7dc2f4e1000000000000000000000000000000000000000000000000000000008152fd5b90600052600460205260406000209073ffffffffffffffffffffffffffffffffffffffff8060018401541691821561066e5716809103612eee575090565b602490604051907f31ee6dc70000000000000000000000000000000000000000000000000000000082526004820152fd5b9081518151908181149384612f36575b5050505090565b6020929394508201209201201438808080612f2f565b6009548110156125ce5760096000527f6e1540171b6c0c960b71a7020d9f60077f6af931a8bbf590da0223dacf75c7af0190600090565b6007548110156125ce5760076000527fa66cc928b5edb82af9bd49922954155ab7b0942694bea4ce44661d9a8736c6880190600090565b80548210156125ce5760005260206000200190600090565b6000818152600a6020526040812054613086576009546801000000000000000081101561305957908261304561301084600160409601600955612f4c565b81939154907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9060031b92831b921b19161790565b9055600954928152600a6020522055600190565b6024827f4e487b710000000000000000000000000000000000000000000000000000000081526041600452fd5b905090565b60008181526008602052604081205461308657600754680100000000000000008110156130595790826130c961301084600160409601600755612f83565b905560075492815260086020522055600190565b9190600183016000908282528060205260408220541560001461316357845494680100000000000000008610156131365783613126613010886001604098999a01855584612fba565b9055549382526020522055600190565b6024837f4e487b710000000000000000000000000000000000000000000000000000000081526041600452fd5b50925050565b6000818152600a602052604081205490919080156132b7577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9081810181811161328a576009549083820191821161325d57818103613229575b50505060095480156131fc578101906131db82612f4c565b909182549160031b1b191690556009558152600a6020526040812055600190565b6024847f4e487b710000000000000000000000000000000000000000000000000000000081526031600452fd5b61324761323861301093612f4c565b90549060031b1c928392612f4c565b90558452600a60205260408420553880806131c3565b6024867f4e487b710000000000000000000000000000000000000000000000000000000081526011600452fd5b6024857f4e487b710000000000000000000000000000000000000000000000000000000081526011600452fd5b505090565b60008181526008602052604081205490919080156132b7577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9081810181811161328a576007549083820191821161325d5781810361334f575b50505060075480156131fc5781019061332e82612f83565b909182549160031b1b19169055600755815260086020526040812055600190565b61336d61335e61301093612f83565b90549060031b1c928392612f83565b9055845260086020526040842055388080613316565b90600182019060009281845282602052604084205490811515600014612f2f577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff918281018181116134a85782549084820191821161347b57818103613446575b50505080548015613419578201916133fc8383612fba565b909182549160031b1b191690555582526020526040812055600190565b6024867f4e487b710000000000000000000000000000000000000000000000000000000081526031600452fd5b6134666134566130109386612fba565b90549060031b1c92839286612fba565b905586528460205260408620553880806133e4565b6024887f4e487b710000000000000000000000000000000000000000000000000000000081526011600452fd5b6024877f4e487b710000000000000000000000000000000000000000000000000000000081526011600452fdfea164736f6c6343000818000a", } var WorkflowRegistryABI = WorkflowRegistryMetaData.ABI diff --git a/core/gethwrappers/workflow/generation/generated-wrapper-dependency-versions-do-not-edit.txt b/core/gethwrappers/workflow/generation/generated-wrapper-dependency-versions-do-not-edit.txt index 7552f72d164..a908ff2e724 100644 --- a/core/gethwrappers/workflow/generation/generated-wrapper-dependency-versions-do-not-edit.txt +++ b/core/gethwrappers/workflow/generation/generated-wrapper-dependency-versions-do-not-edit.txt @@ -1,2 +1,2 @@ GETH_VERSION: 1.14.11 -workflow_registry_wrapper: ../../../contracts/solc/v0.8.24/WorkflowRegistry/WorkflowRegistry.abi ../../../contracts/solc/v0.8.24/WorkflowRegistry/WorkflowRegistry.bin 910925e0786fbe9efb686646ede620e7fc0536c74acdaeef49e96ac67580ea14 +workflow_registry_wrapper: ../../../contracts/solc/v0.8.24/WorkflowRegistry/WorkflowRegistry.abi ../../../contracts/solc/v0.8.24/WorkflowRegistry/WorkflowRegistry.bin bad48df0196c8a170a8e5486d0334183defd60e74bd89d3885989e00d6f13d23 diff --git a/core/internal/cltest/cltest.go b/core/internal/cltest/cltest.go index 7ade85f4bf7..a55c57cc9a2 100644 --- a/core/internal/cltest/cltest.go +++ b/core/internal/cltest/cltest.go @@ -47,6 +47,7 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/loop" "github.com/smartcontractkit/chainlink-common/pkg/sqlutil" "github.com/smartcontractkit/chainlink-common/pkg/utils/mailbox" + "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" "github.com/smartcontractkit/chainlink/v2/common/client" commonmocks "github.com/smartcontractkit/chainlink/v2/common/types/mocks" @@ -531,7 +532,7 @@ func NewEthMocks(t testing.TB) *evmclimocks.Client { } func NewEthMocksWithStartupAssertions(t testing.TB) *evmclimocks.Client { - testutils.SkipShort(t, "long test") + tests.SkipShort(t, "long test") c := NewEthMocks(t) chHead := make(<-chan *evmtypes.Head) c.On("Dial", mock.Anything).Maybe().Return(nil) @@ -554,7 +555,7 @@ func NewEthMocksWithStartupAssertions(t testing.TB) *evmclimocks.Client { // NewEthMocksWithTransactionsOnBlocksAssertions sets an Eth mock with transactions on blocks func NewEthMocksWithTransactionsOnBlocksAssertions(t testing.TB) *evmclimocks.Client { - testutils.SkipShort(t, "long test") + tests.SkipShort(t, "long test") c := NewEthMocks(t) chHead := make(<-chan *evmtypes.Head) c.On("Dial", mock.Anything).Maybe().Return(nil) diff --git a/core/internal/features/features_test.go b/core/internal/features/features_test.go index 2d0d046857c..88305403f2b 100644 --- a/core/internal/features/features_test.go +++ b/core/internal/features/features_test.go @@ -40,6 +40,7 @@ import ( commonconfig "github.com/smartcontractkit/chainlink-common/pkg/config" "github.com/smartcontractkit/chainlink-common/pkg/services/servicetest" + "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" "github.com/smartcontractkit/chainlink/v2/core/auth" "github.com/smartcontractkit/chainlink/v2/core/bridges" @@ -798,7 +799,7 @@ func setupForwarderEnabledNode(t *testing.T, owner *bind.TransactOpts, portV2 in func TestIntegration_OCR(t *testing.T) { t.Skip("fails after geth upgrade https://github.com/smartcontractkit/chainlink/pull/11809; passes local but fails CI") - testutils.SkipShort(t, "long test") + tests.SkipShort(t, "long test") t.Parallel() tests := []struct { id int @@ -1031,7 +1032,7 @@ observationSource = """ func TestIntegration_OCR_ForwarderFlow(t *testing.T) { t.Skip("fails after geth upgrade https://github.com/smartcontractkit/chainlink/pull/11809") - testutils.SkipShort(t, "long test") + tests.SkipShort(t, "long test") t.Parallel() numOracles := 4 t.Run("ocr_forwarder_flow", func(t *testing.T) { diff --git a/core/internal/features/ocr2/features_ocr2_helper.go b/core/internal/features/ocr2/features_ocr2_helper.go new file mode 100644 index 00000000000..9287d0df5b1 --- /dev/null +++ b/core/internal/features/ocr2/features_ocr2_helper.go @@ -0,0 +1,705 @@ +package ocr2 + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "maps" + "math/big" + "net/http" + "net/http/httptest" + "net/url" + "strconv" + "strings" + "sync" + "testing" + "time" + + "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/eth/ethconfig" + "github.com/ethereum/go-ethereum/ethclient/simulated" + "github.com/hashicorp/consul/sdk/freeport" + "github.com/onsi/gomega" + testoffchainaggregator2 "github.com/smartcontractkit/libocr/gethwrappers2/testocr2aggregator" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/libocr/commontypes" + "github.com/smartcontractkit/libocr/gethwrappers2/ocr2aggregator" + + confighelper2 "github.com/smartcontractkit/libocr/offchainreporting2plus/confighelper" + ocrtypes2 "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + + commonconfig "github.com/smartcontractkit/chainlink-common/pkg/config" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/testhelpers" + "github.com/smartcontractkit/chainlink/v2/core/services/ocrbootstrap" + + "github.com/smartcontractkit/chainlink/v2/core/bridges" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/forwarders" + ubig "github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils/big" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/authorized_forwarder" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/link_token_interface" + "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" + "github.com/smartcontractkit/chainlink/v2/core/logger" + "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" + "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/ocr2key" + "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/p2pkey" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/validate" + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm" + "github.com/smartcontractkit/chainlink/v2/core/store/models" + "github.com/smartcontractkit/chainlink/v2/core/utils/testutils/heavyweight" +) + +type Node struct { + App *cltest.TestApplication + PeerID string + Transmitter common.Address + EffectiveTransmitter common.Address + KeyBundle ocr2key.KeyBundle +} + +func SetupOCR2Contracts(t *testing.T) (*bind.TransactOpts, *simulated.Backend, common.Address, *ocr2aggregator.OCR2Aggregator) { + owner := testutils.MustNewSimTransactor(t) + sb := new(big.Int) + sb, _ = sb.SetString("100000000000000000000", 10) // 1 eth + genesisData := types.GenesisAlloc{owner.From: {Balance: sb}} + gasLimit := ethconfig.Defaults.Miner.GasCeil * 2 + b := simulated.NewBackend(genesisData, simulated.WithBlockGasLimit(gasLimit)) + linkTokenAddress, _, linkContract, err := link_token_interface.DeployLinkToken(owner, b.Client()) + require.NoError(t, err) + b.Commit() + accessAddress, _, _, err := testoffchainaggregator2.DeploySimpleWriteAccessController(owner, b.Client()) + require.NoError(t, err, "failed to deploy test access controller contract") + b.Commit() + + minAnswer, maxAnswer := new(big.Int), new(big.Int) + minAnswer.Exp(big.NewInt(-2), big.NewInt(191), nil) + maxAnswer.Exp(big.NewInt(2), big.NewInt(191), nil) + maxAnswer.Sub(maxAnswer, big.NewInt(1)) + ocrContractAddress, _, ocrContract, err := ocr2aggregator.DeployOCR2Aggregator( + owner, + b.Client(), + linkTokenAddress, // _link common.Address, + minAnswer, // -2**191 + maxAnswer, // 2**191 - 1 + accessAddress, + accessAddress, + 9, + "TEST", + ) + // Ensure we have finality depth worth of blocks to start. + for i := 0; i < 20; i++ { + b.Commit() + } + require.NoError(t, err) + _, err = linkContract.Transfer(owner, ocrContractAddress, big.NewInt(1000)) + require.NoError(t, err) + b.Commit() + return owner, b, ocrContractAddress, ocrContract +} + +func SetupNodeOCR2( + t *testing.T, + owner *bind.TransactOpts, + port int, + useForwarder bool, + b *simulated.Backend, + p2pV2Bootstrappers []commontypes.BootstrapperLocator, +) *Node { + ctx := testutils.Context(t) + p2pKey := p2pkey.MustNewV2XXXTestingOnly(big.NewInt(int64(port))) + config, _ := heavyweight.FullTestDBV2(t, func(c *chainlink.Config, s *chainlink.Secrets) { + c.Insecure.OCRDevelopmentMode = ptr(true) // Disables ocr spec validation so we can have fast polling for the test. + + c.Feature.LogPoller = ptr(true) + + c.OCR.Enabled = ptr(false) + c.OCR2.Enabled = ptr(true) + + c.P2P.PeerID = ptr(p2pKey.PeerID()) + c.P2P.V2.Enabled = ptr(true) + c.P2P.V2.DeltaDial = commonconfig.MustNewDuration(500 * time.Millisecond) + c.P2P.V2.DeltaReconcile = commonconfig.MustNewDuration(5 * time.Second) + c.P2P.V2.ListenAddresses = &[]string{fmt.Sprintf("127.0.0.1:%d", port)} + if len(p2pV2Bootstrappers) > 0 { + c.P2P.V2.DefaultBootstrappers = &p2pV2Bootstrappers + } + + c.EVM[0].LogPollInterval = commonconfig.MustNewDuration(5 * time.Second) + c.EVM[0].Transactions.ForwardersEnabled = &useForwarder + }) + + app := cltest.NewApplicationWithConfigV2AndKeyOnSimulatedBlockchain(t, config, b, p2pKey) + + sendingKeys, err := app.KeyStore.Eth().EnabledKeysForChain(testutils.Context(t), testutils.SimulatedChainID) + require.NoError(t, err) + require.Len(t, sendingKeys, 1) + transmitter := sendingKeys[0].Address + effectiveTransmitter := sendingKeys[0].Address + + // Fund the transmitter address with some ETH + n, err := b.Client().NonceAt(testutils.Context(t), owner.From, nil) + require.NoError(t, err) + + tx := cltest.NewLegacyTransaction( + n, transmitter, + assets.Ether(1).ToInt(), + 21000, + assets.GWei(1).ToInt(), + nil) + signedTx, err := owner.Signer(owner.From, tx) + require.NoError(t, err) + err = b.Client().SendTransaction(testutils.Context(t), signedTx) + require.NoError(t, err) + b.Commit() + + kb, err := app.GetKeyStore().OCR2().Create(ctx, "evm") + require.NoError(t, err) + + if useForwarder { + // deploy a forwarder + faddr, _, authorizedForwarder, err2 := authorized_forwarder.DeployAuthorizedForwarder(owner, b.Client(), common.HexToAddress("0x326C977E6efc84E512bB9C30f76E30c160eD06FB"), owner.From, common.Address{}, []byte{}) + require.NoError(t, err2) + b.Commit() + + // set EOA as an authorized sender for the forwarder + _, err2 = authorizedForwarder.SetAuthorizedSenders(owner, []common.Address{transmitter}) + require.NoError(t, err2) + b.Commit() + + // add forwarder address to be tracked in db + forwarderORM := forwarders.NewORM(app.GetDB()) + chainID, err := b.Client().ChainID(testutils.Context(t)) + require.NoError(t, err) + _, err2 = forwarderORM.CreateForwarder(testutils.Context(t), faddr, ubig.Big(*chainID)) + require.NoError(t, err2) + + effectiveTransmitter = faddr + } + return &Node{ + App: app, + PeerID: p2pKey.PeerID().Raw(), + Transmitter: transmitter, + EffectiveTransmitter: effectiveTransmitter, + KeyBundle: kb, + } +} + +func RunTestIntegrationOCR2(t *testing.T) { + for _, test := range []struct { + name string + chainReaderAndCodec bool + }{ + {"legacy", false}, + {"chain-reader", true}, + } { + test := test + t.Run(test.name, func(t *testing.T) { + owner, b, ocrContractAddress, ocrContract := SetupOCR2Contracts(t) + + lggr := logger.TestLogger(t) + bootstrapNodePort := freeport.GetOne(t) + bootstrapNode := SetupNodeOCR2(t, owner, bootstrapNodePort, false /* useForwarders */, b, nil) + + var ( + oracles []confighelper2.OracleIdentityExtra + transmitters []common.Address + kbs []ocr2key.KeyBundle + apps []*cltest.TestApplication + ) + ports := freeport.GetN(t, 4) + for i := 0; i < 4; i++ { + node := SetupNodeOCR2(t, owner, ports[i], false /* useForwarders */, b, []commontypes.BootstrapperLocator{ + // Supply the bootstrap IP and port as a V2 peer address + {PeerID: bootstrapNode.PeerID, Addrs: []string{fmt.Sprintf("127.0.0.1:%d", bootstrapNodePort)}}, + }) + + kbs = append(kbs, node.KeyBundle) + apps = append(apps, node.App) + transmitters = append(transmitters, node.Transmitter) + + oracles = append(oracles, confighelper2.OracleIdentityExtra{ + OracleIdentity: confighelper2.OracleIdentity{ + OnchainPublicKey: node.KeyBundle.PublicKey(), + TransmitAccount: ocrtypes2.Account(node.Transmitter.String()), + OffchainPublicKey: node.KeyBundle.OffchainPublicKey(), + PeerID: node.PeerID, + }, + ConfigEncryptionPublicKey: node.KeyBundle.ConfigEncryptionPublicKey(), + }) + } + + blockBeforeConfig := InitOCR2(t, lggr, b, ocrContract, owner, bootstrapNode, oracles, transmitters, transmitters, func(blockNum int64) string { + return fmt.Sprintf(` +type = "bootstrap" +name = "bootstrap" +relay = "evm" +schemaVersion = 1 +contractID = "%s" +[relayConfig] +chainID = 1337 +fromBlock = %d +`, ocrContractAddress, blockNum) + }) + + tick := time.NewTicker(1 * time.Second) + defer tick.Stop() + go func() { + for range tick.C { + b.Commit() + } + }() + + var jids []int32 + var servers, slowServers = make([]*httptest.Server, 4), make([]*httptest.Server, 4) + // We expect metadata of: + // latestAnswer:nil // First call + // latestAnswer:0 + // latestAnswer:10 + // latestAnswer:20 + // latestAnswer:30 + var metaLock sync.Mutex + expectedMeta := map[string]struct{}{ + "0": {}, "10": {}, "20": {}, "30": {}, + } + returnData := int(10) + for i := 0; i < 4; i++ { + s := i + require.NoError(t, apps[i].Start(testutils.Context(t))) + + // API speed is > observation timeout set in ContractSetConfigArgsForIntegrationTest + slowServers[i] = httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { + time.Sleep(5 * time.Second) + var result string + metaLock.Lock() + result = fmt.Sprintf(`{"data":%d}`, returnData) + metaLock.Unlock() + res.WriteHeader(http.StatusOK) + t.Logf("Slow Bridge %d returning data:10", s) + _, err := res.Write([]byte(result)) + assert.NoError(t, err) + })) + t.Cleanup(slowServers[s].Close) + servers[i] = httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { + b, err := io.ReadAll(req.Body) + if !assert.NoError(t, err) { + res.WriteHeader(http.StatusInternalServerError) + return + } + var m bridges.BridgeMetaDataJSON + if !assert.NoError(t, json.Unmarshal(b, &m)) { + res.WriteHeader(http.StatusInternalServerError) + return + } + var result string + metaLock.Lock() + result = fmt.Sprintf(`{"data":%d}`, returnData) + metaLock.Unlock() + if m.Meta.LatestAnswer != nil && m.Meta.UpdatedAt != nil { + t.Logf("Bridge %d deleting %s, from request body: %s", s, m.Meta.LatestAnswer, b) + metaLock.Lock() + delete(expectedMeta, m.Meta.LatestAnswer.String()) + metaLock.Unlock() + } + res.WriteHeader(http.StatusOK) + _, err = res.Write([]byte(result)) + assert.NoError(t, err) + })) + t.Cleanup(servers[s].Close) + u, _ := url.Parse(servers[i].URL) + bridgeName := fmt.Sprintf("bridge%d", i) + require.NoError(t, apps[i].BridgeORM().CreateBridgeType(testutils.Context(t), &bridges.BridgeType{ + Name: bridges.BridgeName(bridgeName), + URL: models.WebURL(*u), + })) + + var chainReaderSpec string + if test.chainReaderAndCodec { + chainReaderSpec = ` +[relayConfig.chainReader.contracts.median] +contractPollingFilter.genericEventNames = ["LatestRoundRequested"] + +contractABI = ''' +[ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "requester", + "type": "address" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "configDigest", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "uint32", + "name": "epoch", + "type": "uint32" + }, + { + "indexed": false, + "internalType": "uint8", + "name": "round", + "type": "uint8" + } + ], + "name": "RoundRequested", + "type": "event" + }, + { + "inputs": [], + "name": "latestTransmissionDetails", + "outputs": [ + { + "internalType": "bytes32", + "name": "configDigest", + "type": "bytes32" + }, + { + "internalType": "uint32", + "name": "epoch", + "type": "uint32" + }, + { + "internalType": "uint8", + "name": "round", + "type": "uint8" + }, + { + "internalType": "int192", + "name": "latestAnswer_", + "type": "int192" + }, + { + "internalType": "uint64", + "name": "latestTimestamp_", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + } +] +''' + +[relayConfig.chainReader.contracts.median.configs] +LatestRoundRequested = ''' +{ + "chainSpecificName": "RoundRequested", + "readType": "event" +} +''' +LatestTransmissionDetails = ''' +{ + "chainSpecificName": "latestTransmissionDetails", + "outputModifications": [ + { + "Fields": [ + "LatestTimestamp_" + ], + "type": "epoch to time" + }, + { + "Fields": { + "LatestAnswer_": "LatestAnswer", + "LatestTimestamp_": "LatestTimestamp" + }, + "type": "rename" + } + ] +} +''' + +[relayConfig.codec.configs.MedianReport] +typeABI = ''' +[ + { + "Name": "Timestamp", + "Type": "uint32" + }, + { + "Name": "Observers", + "Type": "bytes32" + }, + { + "Name": "Observations", + "Type": "int192[]" + }, + { + "Name": "JuelsPerFeeCoin", + "Type": "int192" + } +] +''' +` + } + ocrJob, err := validate.ValidatedOracleSpecToml(testutils.Context(t), apps[i].Config.OCR2(), apps[i].Config.Insecure(), fmt.Sprintf(` +type = "offchainreporting2" +relay = "evm" +schemaVersion = 1 +pluginType = "median" +name = "web oracle spec" +contractID = "%s" +ocrKeyBundleID = "%s" +transmitterID = "%s" +contractConfigConfirmations = 1 +contractConfigTrackerPollInterval = "1s" +observationSource = """ + // data source 1 + ds1 [type=bridge name="%s"]; + ds1_parse [type=jsonparse path="data"]; + ds1_multiply [type=multiply times=%d]; + + // data source 2 + ds2 [type=http method=GET url="%s"]; + ds2_parse [type=jsonparse path="data"]; + ds2_multiply [type=multiply times=%d]; + + ds1 -> ds1_parse -> ds1_multiply -> answer1; + ds2 -> ds2_parse -> ds2_multiply -> answer1; + + answer1 [type=median index=0]; +""" + +[relayConfig] +chainID = 1337 +fromBlock = %d +%s + +[pluginConfig] +juelsPerFeeCoinSource = """ + // data source 1 + ds1 [type=bridge name="%s"]; + ds1_parse [type=jsonparse path="data"]; + ds1_multiply [type=multiply times=%d]; + + // data source 2 + ds2 [type=http method=GET url="%s"]; + ds2_parse [type=jsonparse path="data"]; + ds2_multiply [type=multiply times=%d]; + + ds1 -> ds1_parse -> ds1_multiply -> answer1; + ds2 -> ds2_parse -> ds2_multiply -> answer1; + + answer1 [type=median index=0]; +""" +gasPriceSubunitsSource = """ + // data source + dsp [type=bridge name="%s"]; + dsp_parse [type=jsonparse path="data"]; + dsp -> dsp_parse; +""" +[pluginConfig.juelsPerFeeCoinCache] +updateInterval = "1m" +`, ocrContractAddress, kbs[i].ID(), transmitters[i], bridgeName, i, slowServers[i].URL, i, blockBeforeConfig.Number().Int64(), chainReaderSpec, bridgeName, i, slowServers[i].URL, i, bridgeName), nil) + require.NoError(t, err) + err = apps[i].AddJobV2(testutils.Context(t), &ocrJob) + require.NoError(t, err) + jids = append(jids, ocrJob.ID) + } + + // Watch for OCR2AggregatorTransmitted events + start := uint64(0) + txEvents := make(chan *ocr2aggregator.OCR2AggregatorTransmitted) + _, err := ocrContract.WatchTransmitted(&bind.WatchOpts{Start: &start, Context: testutils.Context(t)}, txEvents) + require.NoError(t, err) + newTxEvents := make(chan *ocr2aggregator.OCR2AggregatorNewTransmission) + _, err = ocrContract.WatchNewTransmission(&bind.WatchOpts{Start: &start, Context: testutils.Context(t)}, newTxEvents, []uint32{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}) + require.NoError(t, err) + + go func() { + var newTxEvent *ocr2aggregator.OCR2AggregatorNewTransmission + select { + case txEvent := <-txEvents: + t.Logf("txEvent: %v", txEvent) + if newTxEvent != nil { + assert.Equal(t, uint64(txEvent.Epoch), newTxEvent.EpochAndRound.Uint64()) + } + case newTxEvent = <-newTxEvents: + t.Logf("newTxEvent: %v", newTxEvent) + } + }() + + ctx := testutils.Context(t) + for trial := 0; trial < 2; trial++ { + var retVal int + + metaLock.Lock() + returnData = 10 * (trial + 1) + retVal = returnData + for i := 0; i < 4; i++ { + expectedMeta[strconv.Itoa(returnData*i)] = struct{}{} + } + metaLock.Unlock() + + // Assert that all the OCR jobs get a run with valid values eventually. + var wg sync.WaitGroup + for i := 0; i < 4; i++ { + ic := i + wg.Add(1) + go func() { + defer wg.Done() + completedRuns, err2 := apps[ic].JobORM().FindPipelineRunIDsByJobID(ctx, jids[ic], 0, 1000) + if !assert.NoError(t, err2) { + return + } + // Want at least 2 runs so we see all the metadata. + pr := cltest.WaitForPipelineComplete(t, ic, jids[ic], len(completedRuns)+2, 7, apps[ic].JobORM(), 2*time.Minute, 5*time.Second) + jb, err2 := pr[0].Outputs.MarshalJSON() + if !assert.NoError(t, err2) { + return + } + assert.Equal(t, []byte(fmt.Sprintf("[\"%d\"]", retVal*ic)), jb, "pr[0] %+v pr[1] %+v", pr[0], pr[1]) + }() + } + wg.Wait() + + // Trail #1: 4 oracles reporting 0, 10, 20, 30. Answer should be 20 (results[4/2]). + // Trial #2: 4 oracles reporting 0, 20, 40, 60. Answer should be 40 (results[4/2]). + gomega.NewGomegaWithT(t).Eventually(func() string { + answer, err2 := ocrContract.LatestAnswer(nil) + require.NoError(t, err2) + return answer.String() + }, 1*time.Minute, 200*time.Millisecond).Should(gomega.Equal(strconv.Itoa(2 * retVal))) + + for _, app := range apps { + jobs, _, err2 := app.JobORM().FindJobs(ctx, 0, 1000) + require.NoError(t, err2) + // No spec errors + for _, j := range jobs { + ignore := 0 + for i := range j.JobSpecErrors { + // Non-fatal timing related error, ignore for testing. + if strings.Contains(j.JobSpecErrors[i].Description, "leader's phase conflicts tGrace timeout") { + ignore++ + } + } + require.Len(t, j.JobSpecErrors, ignore) + } + } + em := map[string]struct{}{} + metaLock.Lock() + maps.Copy(em, expectedMeta) + metaLock.Unlock() + assert.Empty(t, em, "expected metadata %v", em) + + t.Logf("======= Summary =======") + roundID, err2 := ocrContract.LatestRound(nil) + require.NoError(t, err2) + for i := 0; i <= int(roundID.Int64()); i++ { + roundData, err3 := ocrContract.GetRoundData(nil, big.NewInt(int64(i))) + require.NoError(t, err3) + t.Logf("RoundId: %d, AnsweredInRound: %d, Answer: %d, StartedAt: %v, UpdatedAt: %v", roundData.RoundId, roundData.AnsweredInRound, roundData.Answer, roundData.StartedAt, roundData.UpdatedAt) + } + + expectedAnswer := big.NewInt(2 * int64(retVal)) + + // Assert we can read the latest config digest and epoch after a report has been submitted. + contractABI, err2 := abi.JSON(strings.NewReader(ocr2aggregator.OCR2AggregatorABI)) + require.NoError(t, err2) + apps[0].GetRelayers().LegacyEVMChains().Slice() + ct, err2 := evm.NewOCRContractTransmitter(testutils.Context(t), ocrContractAddress, apps[0].GetRelayers().LegacyEVMChains().Slice()[0].Client(), contractABI, nil, apps[0].GetRelayers().LegacyEVMChains().Slice()[0].LogPoller(), lggr) + require.NoError(t, err2) + configDigest, epoch, err2 := ct.LatestConfigDigestAndEpoch(testutils.Context(t)) + require.NoError(t, err2) + details, err2 := ocrContract.LatestConfigDetails(nil) + require.NoError(t, err2) + assert.True(t, bytes.Equal(configDigest[:], details.ConfigDigest[:])) + digestAndEpoch, err2 := ocrContract.LatestConfigDigestAndEpoch(nil) + require.NoError(t, err2) + assert.Equal(t, digestAndEpoch.Epoch, epoch) + latestTransmissionDetails, err2 := ocrContract.LatestTransmissionDetails(nil) + require.NoError(t, err2) + assert.Equal(t, expectedAnswer, latestTransmissionDetails.LatestAnswer) + require.NoError(t, err2) + newTransmissionEvents, err2 := ocrContract.FilterTransmitted(&bind.FilterOpts{Start: 0, End: nil}) + require.NoError(t, err2) + for newTransmissionEvents.Next() { + assert.Equal(t, 3, newTransmissionEvents.Event.Epoch) + } + } + }) + } +} + +func InitOCR2(t *testing.T, lggr logger.Logger, b *simulated.Backend, + ocrContract *ocr2aggregator.OCR2Aggregator, + owner *bind.TransactOpts, + bootstrapNode *Node, + oracles []confighelper2.OracleIdentityExtra, + transmitters []common.Address, + payees []common.Address, + specFn func(int64) string, +) ( + blockBeforeConfig *types.Block, +) { + lggr.Debugw("Setting Payees on OraclePlugin Contract", "transmitters", payees) + _, err := ocrContract.SetPayees( + owner, + transmitters, + payees, + ) + require.NoError(t, err) + b.Commit() + blockBeforeConfig, err = b.Client().BlockByNumber(testutils.Context(t), nil) + require.NoError(t, err) + signers, effectiveTransmitters, threshold, _, encodedConfigVersion, encodedConfig, err := confighelper2.ContractSetConfigArgsForEthereumIntegrationTest( + oracles, + 1, + 1000000000/100, // threshold PPB + ) + require.NoError(t, err) + + minAnswer, maxAnswer := new(big.Int), new(big.Int) + minAnswer.Exp(big.NewInt(-2), big.NewInt(191), nil) + maxAnswer.Exp(big.NewInt(2), big.NewInt(191), nil) + maxAnswer.Sub(maxAnswer, big.NewInt(1)) + + onchainConfig, err := testhelpers.GenerateDefaultOCR2OnchainConfig(minAnswer, maxAnswer) + require.NoError(t, err) + + lggr.Debugw("Setting Config on Oracle Contract", + "signers", signers, + "transmitters", transmitters, + "effectiveTransmitters", effectiveTransmitters, + "threshold", threshold, + "onchainConfig", onchainConfig, + "encodedConfigVersion", encodedConfigVersion, + ) + _, err = ocrContract.SetConfig( + owner, + signers, + effectiveTransmitters, + threshold, + onchainConfig, + encodedConfigVersion, + encodedConfig, + ) + require.NoError(t, err) + b.Commit() + + err = bootstrapNode.App.Start(testutils.Context(t)) + require.NoError(t, err) + + chainSet := bootstrapNode.App.GetRelayers().LegacyEVMChains() + require.NotNil(t, chainSet) + ocrJob, err := ocrbootstrap.ValidatedBootstrapSpecToml(specFn(blockBeforeConfig.Number().Int64())) + require.NoError(t, err) + err = bootstrapNode.App.AddJobV2(testutils.Context(t), &ocrJob) + require.NoError(t, err) + return +} + +func ptr[T any](v T) *T { return &v } diff --git a/core/internal/features/ocr2/features_ocr2_plugin_test.go b/core/internal/features/ocr2/features_ocr2_plugin_test.go deleted file mode 100644 index 96a9f32e957..00000000000 --- a/core/internal/features/ocr2/features_ocr2_plugin_test.go +++ /dev/null @@ -1,14 +0,0 @@ -//go:build integration - -package ocr2_test - -import ( - "testing" - - "github.com/smartcontractkit/chainlink/v2/core/config/env" -) - -func TestIntegration_OCR2_plugins(t *testing.T) { - t.Setenv(string(env.MedianPlugin.Cmd), "chainlink-feeds") - testIntegration_OCR2(t) -} diff --git a/core/internal/features/ocr2/features_ocr2_test.go b/core/internal/features/ocr2/features_ocr2_test.go index c8f49ac9328..01c269d19e3 100644 --- a/core/internal/features/ocr2/features_ocr2_test.go +++ b/core/internal/features/ocr2/features_ocr2_test.go @@ -1,4 +1,4 @@ -package ocr2_test +package ocr2 import ( "bytes" @@ -6,7 +6,6 @@ import ( "fmt" "io" "maps" - "math/big" "net/http" "net/http/httptest" "net/url" @@ -16,11 +15,7 @@ import ( "time" "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/eth/ethconfig" - "github.com/ethereum/go-ethereum/ethclient/simulated" "github.com/hashicorp/consul/sdk/freeport" "github.com/onsi/gomega" "github.com/stretchr/testify/assert" @@ -29,681 +24,31 @@ import ( "github.com/smartcontractkit/libocr/commontypes" "github.com/smartcontractkit/libocr/gethwrappers2/ocr2aggregator" - testoffchainaggregator2 "github.com/smartcontractkit/libocr/gethwrappers2/testocr2aggregator" confighelper2 "github.com/smartcontractkit/libocr/offchainreporting2plus/confighelper" ocrtypes2 "github.com/smartcontractkit/libocr/offchainreporting2plus/types" - commonconfig "github.com/smartcontractkit/chainlink-common/pkg/config" - "github.com/smartcontractkit/chainlink/v2/core/bridges" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm/forwarders" - ubig "github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils/big" - "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/authorized_forwarder" - "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/link_token_interface" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" "github.com/smartcontractkit/chainlink/v2/core/logger" - "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/ocr2key" - "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/p2pkey" - "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/testhelpers" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/validate" - "github.com/smartcontractkit/chainlink/v2/core/services/ocrbootstrap" "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm" "github.com/smartcontractkit/chainlink/v2/core/store/models" - "github.com/smartcontractkit/chainlink/v2/core/utils/testutils/heavyweight" ) -type ocr2Node struct { - app *cltest.TestApplication - peerID string - transmitter common.Address - effectiveTransmitter common.Address - keybundle ocr2key.KeyBundle -} - -func setupOCR2Contracts(t *testing.T) (*bind.TransactOpts, *simulated.Backend, common.Address, *ocr2aggregator.OCR2Aggregator) { - owner := testutils.MustNewSimTransactor(t) - sb := new(big.Int) - sb, _ = sb.SetString("100000000000000000000", 10) // 1 eth - genesisData := types.GenesisAlloc{owner.From: {Balance: sb}} - gasLimit := ethconfig.Defaults.Miner.GasCeil * 2 - b := simulated.NewBackend(genesisData, simulated.WithBlockGasLimit(gasLimit)) - linkTokenAddress, _, linkContract, err := link_token_interface.DeployLinkToken(owner, b.Client()) - require.NoError(t, err) - b.Commit() - accessAddress, _, _, err := testoffchainaggregator2.DeploySimpleWriteAccessController(owner, b.Client()) - require.NoError(t, err, "failed to deploy test access controller contract") - b.Commit() - - minAnswer, maxAnswer := new(big.Int), new(big.Int) - minAnswer.Exp(big.NewInt(-2), big.NewInt(191), nil) - maxAnswer.Exp(big.NewInt(2), big.NewInt(191), nil) - maxAnswer.Sub(maxAnswer, big.NewInt(1)) - ocrContractAddress, _, ocrContract, err := ocr2aggregator.DeployOCR2Aggregator( - owner, - b.Client(), - linkTokenAddress, // _link common.Address, - minAnswer, // -2**191 - maxAnswer, // 2**191 - 1 - accessAddress, - accessAddress, - 9, - "TEST", - ) - // Ensure we have finality depth worth of blocks to start. - for i := 0; i < 20; i++ { - b.Commit() - } - require.NoError(t, err) - _, err = linkContract.Transfer(owner, ocrContractAddress, big.NewInt(1000)) - require.NoError(t, err) - b.Commit() - return owner, b, ocrContractAddress, ocrContract -} - -func setupNodeOCR2( - t *testing.T, - owner *bind.TransactOpts, - port int, - useForwarder bool, - b *simulated.Backend, - p2pV2Bootstrappers []commontypes.BootstrapperLocator, -) *ocr2Node { - ctx := testutils.Context(t) - p2pKey := p2pkey.MustNewV2XXXTestingOnly(big.NewInt(int64(port))) - config, _ := heavyweight.FullTestDBV2(t, func(c *chainlink.Config, s *chainlink.Secrets) { - c.Insecure.OCRDevelopmentMode = ptr(true) // Disables ocr spec validation so we can have fast polling for the test. - - c.Feature.LogPoller = ptr(true) - - c.OCR.Enabled = ptr(false) - c.OCR2.Enabled = ptr(true) - - c.P2P.PeerID = ptr(p2pKey.PeerID()) - c.P2P.V2.Enabled = ptr(true) - c.P2P.V2.DeltaDial = commonconfig.MustNewDuration(500 * time.Millisecond) - c.P2P.V2.DeltaReconcile = commonconfig.MustNewDuration(5 * time.Second) - c.P2P.V2.ListenAddresses = &[]string{fmt.Sprintf("127.0.0.1:%d", port)} - if len(p2pV2Bootstrappers) > 0 { - c.P2P.V2.DefaultBootstrappers = &p2pV2Bootstrappers - } - - c.EVM[0].LogPollInterval = commonconfig.MustNewDuration(5 * time.Second) - c.EVM[0].Transactions.ForwardersEnabled = &useForwarder - }) - - app := cltest.NewApplicationWithConfigV2AndKeyOnSimulatedBlockchain(t, config, b, p2pKey) - - sendingKeys, err := app.KeyStore.Eth().EnabledKeysForChain(testutils.Context(t), testutils.SimulatedChainID) - require.NoError(t, err) - require.Len(t, sendingKeys, 1) - transmitter := sendingKeys[0].Address - effectiveTransmitter := sendingKeys[0].Address - - // Fund the transmitter address with some ETH - n, err := b.Client().NonceAt(testutils.Context(t), owner.From, nil) - require.NoError(t, err) - - tx := cltest.NewLegacyTransaction( - n, transmitter, - assets.Ether(1).ToInt(), - 21000, - assets.GWei(1).ToInt(), - nil) - signedTx, err := owner.Signer(owner.From, tx) - require.NoError(t, err) - err = b.Client().SendTransaction(testutils.Context(t), signedTx) - require.NoError(t, err) - b.Commit() - - kb, err := app.GetKeyStore().OCR2().Create(ctx, "evm") - require.NoError(t, err) - - if useForwarder { - // deploy a forwarder - faddr, _, authorizedForwarder, err2 := authorized_forwarder.DeployAuthorizedForwarder(owner, b.Client(), common.HexToAddress("0x326C977E6efc84E512bB9C30f76E30c160eD06FB"), owner.From, common.Address{}, []byte{}) - require.NoError(t, err2) - b.Commit() - - // set EOA as an authorized sender for the forwarder - _, err2 = authorizedForwarder.SetAuthorizedSenders(owner, []common.Address{transmitter}) - require.NoError(t, err2) - b.Commit() - - // add forwarder address to be tracked in db - forwarderORM := forwarders.NewORM(app.GetDB()) - chainID, err := b.Client().ChainID(testutils.Context(t)) - require.NoError(t, err) - _, err2 = forwarderORM.CreateForwarder(testutils.Context(t), faddr, ubig.Big(*chainID)) - require.NoError(t, err2) - - effectiveTransmitter = faddr - } - return &ocr2Node{ - app: app, - peerID: p2pKey.PeerID().Raw(), - transmitter: transmitter, - effectiveTransmitter: effectiveTransmitter, - keybundle: kb, - } -} - func TestIntegration_OCR2(t *testing.T) { t.Parallel() - testIntegration_OCR2(t) -} - -func testIntegration_OCR2(t *testing.T) { - for _, test := range []struct { - name string - chainReaderAndCodec bool - }{ - {"legacy", false}, - {"chain-reader", true}, - } { - test := test - t.Run(test.name, func(t *testing.T) { - owner, b, ocrContractAddress, ocrContract := setupOCR2Contracts(t) - - lggr := logger.TestLogger(t) - bootstrapNodePort := freeport.GetOne(t) - bootstrapNode := setupNodeOCR2(t, owner, bootstrapNodePort, false /* useForwarders */, b, nil) - - var ( - oracles []confighelper2.OracleIdentityExtra - transmitters []common.Address - kbs []ocr2key.KeyBundle - apps []*cltest.TestApplication - ) - ports := freeport.GetN(t, 4) - for i := 0; i < 4; i++ { - node := setupNodeOCR2(t, owner, ports[i], false /* useForwarders */, b, []commontypes.BootstrapperLocator{ - // Supply the bootstrap IP and port as a V2 peer address - {PeerID: bootstrapNode.peerID, Addrs: []string{fmt.Sprintf("127.0.0.1:%d", bootstrapNodePort)}}, - }) - - kbs = append(kbs, node.keybundle) - apps = append(apps, node.app) - transmitters = append(transmitters, node.transmitter) - - oracles = append(oracles, confighelper2.OracleIdentityExtra{ - OracleIdentity: confighelper2.OracleIdentity{ - OnchainPublicKey: node.keybundle.PublicKey(), - TransmitAccount: ocrtypes2.Account(node.transmitter.String()), - OffchainPublicKey: node.keybundle.OffchainPublicKey(), - PeerID: node.peerID, - }, - ConfigEncryptionPublicKey: node.keybundle.ConfigEncryptionPublicKey(), - }) - } - - blockBeforeConfig := initOCR2(t, lggr, b, ocrContract, owner, bootstrapNode, oracles, transmitters, transmitters, func(blockNum int64) string { - return fmt.Sprintf(` -type = "bootstrap" -name = "bootstrap" -relay = "evm" -schemaVersion = 1 -contractID = "%s" -[relayConfig] -chainID = 1337 -fromBlock = %d -`, ocrContractAddress, blockNum) - }) - - tick := time.NewTicker(1 * time.Second) - defer tick.Stop() - go func() { - for range tick.C { - b.Commit() - } - }() - - var jids []int32 - var servers, slowServers = make([]*httptest.Server, 4), make([]*httptest.Server, 4) - // We expect metadata of: - // latestAnswer:nil // First call - // latestAnswer:0 - // latestAnswer:10 - // latestAnswer:20 - // latestAnswer:30 - var metaLock sync.Mutex - expectedMeta := map[string]struct{}{ - "0": {}, "10": {}, "20": {}, "30": {}, - } - returnData := int(10) - for i := 0; i < 4; i++ { - s := i - require.NoError(t, apps[i].Start(testutils.Context(t))) - - // API speed is > observation timeout set in ContractSetConfigArgsForIntegrationTest - slowServers[i] = httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { - time.Sleep(5 * time.Second) - var result string - metaLock.Lock() - result = fmt.Sprintf(`{"data":%d}`, returnData) - metaLock.Unlock() - res.WriteHeader(http.StatusOK) - t.Logf("Slow Bridge %d returning data:10", s) - _, err := res.Write([]byte(result)) - require.NoError(t, err) - })) - t.Cleanup(slowServers[s].Close) - servers[i] = httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { - b, err := io.ReadAll(req.Body) - require.NoError(t, err) - var m bridges.BridgeMetaDataJSON - require.NoError(t, json.Unmarshal(b, &m)) - var result string - metaLock.Lock() - result = fmt.Sprintf(`{"data":%d}`, returnData) - metaLock.Unlock() - if m.Meta.LatestAnswer != nil && m.Meta.UpdatedAt != nil { - t.Logf("Bridge %d deleting %s, from request body: %s", s, m.Meta.LatestAnswer, b) - metaLock.Lock() - delete(expectedMeta, m.Meta.LatestAnswer.String()) - metaLock.Unlock() - } - res.WriteHeader(http.StatusOK) - _, err = res.Write([]byte(result)) - require.NoError(t, err) - })) - t.Cleanup(servers[s].Close) - u, _ := url.Parse(servers[i].URL) - bridgeName := fmt.Sprintf("bridge%d", i) - require.NoError(t, apps[i].BridgeORM().CreateBridgeType(testutils.Context(t), &bridges.BridgeType{ - Name: bridges.BridgeName(bridgeName), - URL: models.WebURL(*u), - })) - - var chainReaderSpec string - if test.chainReaderAndCodec { - chainReaderSpec = ` -[relayConfig.chainReader.contracts.median] -contractPollingFilter.genericEventNames = ["LatestRoundRequested"] - -contractABI = ''' -[ - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "requester", - "type": "address" - }, - { - "indexed": false, - "internalType": "bytes32", - "name": "configDigest", - "type": "bytes32" - }, - { - "indexed": false, - "internalType": "uint32", - "name": "epoch", - "type": "uint32" - }, - { - "indexed": false, - "internalType": "uint8", - "name": "round", - "type": "uint8" - } - ], - "name": "RoundRequested", - "type": "event" - }, - { - "inputs": [], - "name": "latestTransmissionDetails", - "outputs": [ - { - "internalType": "bytes32", - "name": "configDigest", - "type": "bytes32" - }, - { - "internalType": "uint32", - "name": "epoch", - "type": "uint32" - }, - { - "internalType": "uint8", - "name": "round", - "type": "uint8" - }, - { - "internalType": "int192", - "name": "latestAnswer_", - "type": "int192" - }, - { - "internalType": "uint64", - "name": "latestTimestamp_", - "type": "uint64" - } - ], - "stateMutability": "view", - "type": "function" - } -] -''' - -[relayConfig.chainReader.contracts.median.configs] -LatestRoundRequested = ''' -{ - "chainSpecificName": "RoundRequested", - "readType": "event" -} -''' -LatestTransmissionDetails = ''' -{ - "chainSpecificName": "latestTransmissionDetails", - "outputModifications": [ - { - "Fields": [ - "LatestTimestamp_" - ], - "type": "epoch to time" - }, - { - "Fields": { - "LatestAnswer_": "LatestAnswer", - "LatestTimestamp_": "LatestTimestamp" - }, - "type": "rename" - } - ] -} -''' - -[relayConfig.codec.configs.MedianReport] -typeABI = ''' -[ - { - "Name": "Timestamp", - "Type": "uint32" - }, - { - "Name": "Observers", - "Type": "bytes32" - }, - { - "Name": "Observations", - "Type": "int192[]" - }, - { - "Name": "JuelsPerFeeCoin", - "Type": "int192" - } -] -''' -` - } - ocrJob, err := validate.ValidatedOracleSpecToml(testutils.Context(t), apps[i].Config.OCR2(), apps[i].Config.Insecure(), fmt.Sprintf(` -type = "offchainreporting2" -relay = "evm" -schemaVersion = 1 -pluginType = "median" -name = "web oracle spec" -contractID = "%s" -ocrKeyBundleID = "%s" -transmitterID = "%s" -contractConfigConfirmations = 1 -contractConfigTrackerPollInterval = "1s" -observationSource = """ - // data source 1 - ds1 [type=bridge name="%s"]; - ds1_parse [type=jsonparse path="data"]; - ds1_multiply [type=multiply times=%d]; - - // data source 2 - ds2 [type=http method=GET url="%s"]; - ds2_parse [type=jsonparse path="data"]; - ds2_multiply [type=multiply times=%d]; - - ds1 -> ds1_parse -> ds1_multiply -> answer1; - ds2 -> ds2_parse -> ds2_multiply -> answer1; - - answer1 [type=median index=0]; -""" - -[relayConfig] -chainID = 1337 -fromBlock = %d -%s - -[pluginConfig] -juelsPerFeeCoinSource = """ - // data source 1 - ds1 [type=bridge name="%s"]; - ds1_parse [type=jsonparse path="data"]; - ds1_multiply [type=multiply times=%d]; - - // data source 2 - ds2 [type=http method=GET url="%s"]; - ds2_parse [type=jsonparse path="data"]; - ds2_multiply [type=multiply times=%d]; - - ds1 -> ds1_parse -> ds1_multiply -> answer1; - ds2 -> ds2_parse -> ds2_multiply -> answer1; - - answer1 [type=median index=0]; -""" -gasPriceSubunitsSource = """ - // data source - dsp [type=bridge name="%s"]; - dsp_parse [type=jsonparse path="data"]; - dsp -> dsp_parse; -""" -[pluginConfig.juelsPerFeeCoinCache] -updateInterval = "1m" -`, ocrContractAddress, kbs[i].ID(), transmitters[i], bridgeName, i, slowServers[i].URL, i, blockBeforeConfig.Number().Int64(), chainReaderSpec, bridgeName, i, slowServers[i].URL, i, bridgeName), nil) - require.NoError(t, err) - err = apps[i].AddJobV2(testutils.Context(t), &ocrJob) - require.NoError(t, err) - jids = append(jids, ocrJob.ID) - } - - // Watch for OCR2AggregatorTransmitted events - start := uint64(0) - txEvents := make(chan *ocr2aggregator.OCR2AggregatorTransmitted) - _, err := ocrContract.WatchTransmitted(&bind.WatchOpts{Start: &start, Context: testutils.Context(t)}, txEvents) - require.NoError(t, err) - newTxEvents := make(chan *ocr2aggregator.OCR2AggregatorNewTransmission) - _, err = ocrContract.WatchNewTransmission(&bind.WatchOpts{Start: &start, Context: testutils.Context(t)}, newTxEvents, []uint32{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}) - require.NoError(t, err) - - go func() { - var newTxEvent *ocr2aggregator.OCR2AggregatorNewTransmission - select { - case txEvent := <-txEvents: - t.Logf("txEvent: %v", txEvent) - if newTxEvent != nil { - assert.Equal(t, txEvent.Epoch, uint32(newTxEvent.EpochAndRound.Uint64())) - } - case newTxEvent = <-newTxEvents: - t.Logf("newTxEvent: %v", newTxEvent) - } - }() - - ctx := testutils.Context(t) - for trial := 0; trial < 2; trial++ { - var retVal int - - metaLock.Lock() - returnData = 10 * (trial + 1) - retVal = returnData - for i := 0; i < 4; i++ { - expectedMeta[fmt.Sprintf("%d", returnData*i)] = struct{}{} - } - metaLock.Unlock() - - // Assert that all the OCR jobs get a run with valid values eventually. - var wg sync.WaitGroup - for i := 0; i < 4; i++ { - ic := i - wg.Add(1) - go func() { - defer wg.Done() - completedRuns, err2 := apps[ic].JobORM().FindPipelineRunIDsByJobID(ctx, jids[ic], 0, 1000) - require.NoError(t, err2) - // Want at least 2 runs so we see all the metadata. - pr := cltest.WaitForPipelineComplete(t, ic, jids[ic], len(completedRuns)+2, 7, apps[ic].JobORM(), 2*time.Minute, 5*time.Second) - jb, err2 := pr[0].Outputs.MarshalJSON() - require.NoError(t, err2) - assert.Equal(t, []byte(fmt.Sprintf("[\"%d\"]", retVal*ic)), jb, "pr[0] %+v pr[1] %+v", pr[0], pr[1]) - require.NoError(t, err2) - }() - } - wg.Wait() - - // Trail #1: 4 oracles reporting 0, 10, 20, 30. Answer should be 20 (results[4/2]). - // Trial #2: 4 oracles reporting 0, 20, 40, 60. Answer should be 40 (results[4/2]). - gomega.NewGomegaWithT(t).Eventually(func() string { - answer, err2 := ocrContract.LatestAnswer(nil) - require.NoError(t, err2) - return answer.String() - }, 1*time.Minute, 200*time.Millisecond).Should(gomega.Equal(fmt.Sprintf("%d", 2*retVal))) - - for _, app := range apps { - jobs, _, err2 := app.JobORM().FindJobs(ctx, 0, 1000) - require.NoError(t, err2) - // No spec errors - for _, j := range jobs { - ignore := 0 - for i := range j.JobSpecErrors { - // Non-fatal timing related error, ignore for testing. - if strings.Contains(j.JobSpecErrors[i].Description, "leader's phase conflicts tGrace timeout") { - ignore++ - } - } - require.Len(t, j.JobSpecErrors, ignore) - } - } - em := map[string]struct{}{} - metaLock.Lock() - maps.Copy(em, expectedMeta) - metaLock.Unlock() - assert.Len(t, em, 0, "expected metadata %v", em) - - t.Logf("======= Summary =======") - roundId, err2 := ocrContract.LatestRound(nil) - require.NoError(t, err2) - for i := 0; i <= int(roundId.Int64()); i++ { - roundData, err3 := ocrContract.GetRoundData(nil, big.NewInt(int64(i))) - require.NoError(t, err3) - t.Logf("RoundId: %d, AnsweredInRound: %d, Answer: %d, StartedAt: %v, UpdatedAt: %v", roundData.RoundId, roundData.AnsweredInRound, roundData.Answer, roundData.StartedAt, roundData.UpdatedAt) - } - - expectedAnswer := big.NewInt(2 * int64(retVal)) - - // Assert we can read the latest config digest and epoch after a report has been submitted. - contractABI, err2 := abi.JSON(strings.NewReader(ocr2aggregator.OCR2AggregatorABI)) - require.NoError(t, err2) - apps[0].GetRelayers().LegacyEVMChains().Slice() - ct, err2 := evm.NewOCRContractTransmitter(testutils.Context(t), ocrContractAddress, apps[0].GetRelayers().LegacyEVMChains().Slice()[0].Client(), contractABI, nil, apps[0].GetRelayers().LegacyEVMChains().Slice()[0].LogPoller(), lggr) - require.NoError(t, err2) - configDigest, epoch, err2 := ct.LatestConfigDigestAndEpoch(testutils.Context(t)) - require.NoError(t, err2) - details, err2 := ocrContract.LatestConfigDetails(nil) - require.NoError(t, err2) - assert.True(t, bytes.Equal(configDigest[:], details.ConfigDigest[:])) - digestAndEpoch, err2 := ocrContract.LatestConfigDigestAndEpoch(nil) - require.NoError(t, err2) - assert.Equal(t, digestAndEpoch.Epoch, epoch) - latestTransmissionDetails, err2 := ocrContract.LatestTransmissionDetails(nil) - require.NoError(t, err2) - assert.Equal(t, expectedAnswer, latestTransmissionDetails.LatestAnswer) - require.NoError(t, err2) - newTransmissionEvents, err2 := ocrContract.FilterTransmitted(&bind.FilterOpts{Start: 0, End: nil}) - require.NoError(t, err2) - for newTransmissionEvents.Next() { - assert.Equal(t, 3, newTransmissionEvents.Event.Epoch) - } - } - }) - } -} - -func initOCR2(t *testing.T, lggr logger.Logger, b *simulated.Backend, - ocrContract *ocr2aggregator.OCR2Aggregator, - owner *bind.TransactOpts, - bootstrapNode *ocr2Node, - oracles []confighelper2.OracleIdentityExtra, - transmitters []common.Address, - payees []common.Address, - specFn func(int64) string, -) ( - blockBeforeConfig *types.Block, -) { - lggr.Debugw("Setting Payees on OraclePlugin Contract", "transmitters", payees) - _, err := ocrContract.SetPayees( - owner, - transmitters, - payees, - ) - require.NoError(t, err) - b.Commit() - blockBeforeConfig, err = b.Client().BlockByNumber(testutils.Context(t), nil) - require.NoError(t, err) - signers, effectiveTransmitters, threshold, _, encodedConfigVersion, encodedConfig, err := confighelper2.ContractSetConfigArgsForEthereumIntegrationTest( - oracles, - 1, - 1000000000/100, // threshold PPB - ) - require.NoError(t, err) - - minAnswer, maxAnswer := new(big.Int), new(big.Int) - minAnswer.Exp(big.NewInt(-2), big.NewInt(191), nil) - maxAnswer.Exp(big.NewInt(2), big.NewInt(191), nil) - maxAnswer.Sub(maxAnswer, big.NewInt(1)) - - onchainConfig, err := testhelpers.GenerateDefaultOCR2OnchainConfig(minAnswer, maxAnswer) - require.NoError(t, err) - - lggr.Debugw("Setting Config on Oracle Contract", - "signers", signers, - "transmitters", transmitters, - "effectiveTransmitters", effectiveTransmitters, - "threshold", threshold, - "onchainConfig", onchainConfig, - "encodedConfigVersion", encodedConfigVersion, - ) - _, err = ocrContract.SetConfig( - owner, - signers, - effectiveTransmitters, - threshold, - onchainConfig, - encodedConfigVersion, - encodedConfig, - ) - require.NoError(t, err) - b.Commit() - - err = bootstrapNode.app.Start(testutils.Context(t)) - require.NoError(t, err) - - chainSet := bootstrapNode.app.GetRelayers().LegacyEVMChains() - require.NotNil(t, chainSet) - ocrJob, err := ocrbootstrap.ValidatedBootstrapSpecToml(specFn(blockBeforeConfig.Number().Int64())) - require.NoError(t, err) - err = bootstrapNode.app.AddJobV2(testutils.Context(t), &ocrJob) - require.NoError(t, err) - return + RunTestIntegrationOCR2(t) } func TestIntegration_OCR2_ForwarderFlow(t *testing.T) { t.Parallel() - owner, b, ocrContractAddress, ocrContract := setupOCR2Contracts(t) + owner, b, ocrContractAddress, ocrContract := SetupOCR2Contracts(t) lggr := logger.TestLogger(t) bootstrapNodePort := freeport.GetOne(t) - bootstrapNode := setupNodeOCR2(t, owner, bootstrapNodePort, true /* useForwarders */, b, nil) + bootstrapNode := SetupNodeOCR2(t, owner, bootstrapNodePort, true /* useForwarders */, b, nil) var ( oracles []confighelper2.OracleIdentityExtra @@ -714,31 +59,31 @@ func TestIntegration_OCR2_ForwarderFlow(t *testing.T) { ) ports := freeport.GetN(t, 4) for i := uint16(0); i < 4; i++ { - node := setupNodeOCR2(t, owner, ports[i], true /* useForwarders */, b, []commontypes.BootstrapperLocator{ + node := SetupNodeOCR2(t, owner, ports[i], true /* useForwarders */, b, []commontypes.BootstrapperLocator{ // Supply the bootstrap IP and port as a V2 peer address - {PeerID: bootstrapNode.peerID, Addrs: []string{fmt.Sprintf("127.0.0.1:%d", bootstrapNodePort)}}, + {PeerID: bootstrapNode.PeerID, Addrs: []string{fmt.Sprintf("127.0.0.1:%d", bootstrapNodePort)}}, }) // Effective transmitter should be a forwarder not an EOA. - require.NotEqual(t, node.effectiveTransmitter, node.transmitter) + require.NotEqual(t, node.EffectiveTransmitter, node.Transmitter) - kbs = append(kbs, node.keybundle) - apps = append(apps, node.app) - forwarderContracts = append(forwarderContracts, node.effectiveTransmitter) - transmitters = append(transmitters, node.transmitter) + kbs = append(kbs, node.KeyBundle) + apps = append(apps, node.App) + forwarderContracts = append(forwarderContracts, node.EffectiveTransmitter) + transmitters = append(transmitters, node.Transmitter) oracles = append(oracles, confighelper2.OracleIdentityExtra{ OracleIdentity: confighelper2.OracleIdentity{ - OnchainPublicKey: node.keybundle.PublicKey(), - TransmitAccount: ocrtypes2.Account(node.effectiveTransmitter.String()), - OffchainPublicKey: node.keybundle.OffchainPublicKey(), - PeerID: node.peerID, + OnchainPublicKey: node.KeyBundle.PublicKey(), + TransmitAccount: ocrtypes2.Account(node.EffectiveTransmitter.String()), + OffchainPublicKey: node.KeyBundle.OffchainPublicKey(), + PeerID: node.PeerID, }, - ConfigEncryptionPublicKey: node.keybundle.ConfigEncryptionPublicKey(), + ConfigEncryptionPublicKey: node.KeyBundle.ConfigEncryptionPublicKey(), }) } - blockBeforeConfig := initOCR2(t, lggr, b, ocrContract, owner, bootstrapNode, oracles, forwarderContracts, transmitters, func(int64) string { + blockBeforeConfig := InitOCR2(t, lggr, b, ocrContract, owner, bootstrapNode, oracles, forwarderContracts, transmitters, func(int64) string { return fmt.Sprintf(` type = "bootstrap" name = "bootstrap" @@ -869,7 +214,7 @@ updateInterval = "1m" for _, app := range apps { require.NoError(t, app.GetRelayers().LegacyEVMChains().Slice()[0].LogPoller().Replay(testutils.Context(t), blockBeforeConfig.Number().Int64())) } - require.NoError(t, bootstrapNode.app.GetRelayers().LegacyEVMChains().Slice()[0].LogPoller().Replay(testutils.Context(t), blockBeforeConfig.Number().Int64())) + require.NoError(t, bootstrapNode.App.GetRelayers().LegacyEVMChains().Slice()[0].LogPoller().Replay(testutils.Context(t), blockBeforeConfig.Number().Int64())) // Assert that all the OCR jobs get a run with valid values eventually. var wg sync.WaitGroup @@ -930,5 +275,3 @@ updateInterval = "1m" require.NoError(t, err) assert.Equal(t, digestAndEpoch.Epoch, epoch) } - -func ptr[T any](v T) *T { return &v } diff --git a/core/internal/features/ocr2/plugins/features_ocr2_plugin_test.go b/core/internal/features/ocr2/plugins/features_ocr2_plugin_test.go new file mode 100644 index 00000000000..260a931a65f --- /dev/null +++ b/core/internal/features/ocr2/plugins/features_ocr2_plugin_test.go @@ -0,0 +1,16 @@ +//go:build integration + +// This package exists to separate a long-running test which sets environment variables, and so cannot be run in parallel. +package plugins + +import ( + "testing" + + "github.com/smartcontractkit/chainlink/v2/core/config/env" + "github.com/smartcontractkit/chainlink/v2/core/internal/features/ocr2" +) + +func TestIntegration_OCR2_plugins(t *testing.T) { + t.Setenv(string(env.MedianPlugin.Cmd), "chainlink-feeds") + ocr2.RunTestIntegrationOCR2(t) +} diff --git a/core/internal/features/ocr2/plugins/plugins.go b/core/internal/features/ocr2/plugins/plugins.go new file mode 100644 index 00000000000..8e3c36c581d --- /dev/null +++ b/core/internal/features/ocr2/plugins/plugins.go @@ -0,0 +1,3 @@ +package plugins + +// empty file w/o build tag so the package "builds" diff --git a/core/internal/testutils/configtest/general_config.go b/core/internal/testutils/configtest/general_config.go index 63aba18c351..f0851c67740 100644 --- a/core/internal/testutils/configtest/general_config.go +++ b/core/internal/testutils/configtest/general_config.go @@ -7,14 +7,16 @@ import ( "github.com/stretchr/testify/require" + pgcommon "github.com/smartcontractkit/chainlink-common/pkg/sqlutil/pg" + commonconfig "github.com/smartcontractkit/chainlink-common/pkg/config" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" evmclient "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" evmcfg "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/toml" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils/big" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" - "github.com/smartcontractkit/chainlink/v2/core/store/dialects" "github.com/smartcontractkit/chainlink/v2/core/store/models" ) @@ -48,7 +50,7 @@ func overrides(c *chainlink.Config, s *chainlink.Secrets) { c.InsecureFastScrypt = ptr(true) c.ShutdownGracePeriod = commonconfig.MustNewDuration(testutils.DefaultWaitTimeout) - c.Database.Dialect = dialects.TransactionWrappedPostgres + c.Database.Dialect = pgcommon.TransactionWrappedPostgres c.Database.Lock.Enabled = ptr(false) c.Database.MaxIdleConns = ptr[int64](20) c.Database.MaxOpenConns = ptr[int64](20) diff --git a/core/internal/testutils/pgtest/pgtest.go b/core/internal/testutils/pgtest/pgtest.go index 8464604b667..ee17d8f4d14 100644 --- a/core/internal/testutils/pgtest/pgtest.go +++ b/core/internal/testutils/pgtest/pgtest.go @@ -3,26 +3,25 @@ package pgtest import ( "testing" - "github.com/google/uuid" "github.com/jmoiron/sqlx" - "github.com/scylladb/go-reflectx" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/smartcontractkit/chainlink-common/pkg/sqlutil" + "github.com/smartcontractkit/chainlink-common/pkg/sqlutil/pg" "github.com/smartcontractkit/chainlink-common/pkg/utils" + + "github.com/smartcontractkit/chainlink/v2/core/config/env" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - "github.com/smartcontractkit/chainlink/v2/core/store/dialects" ) func NewSqlxDB(t testing.TB) *sqlx.DB { testutils.SkipShortDB(t) - db, err := sqlx.Open(string(dialects.TransactionWrappedPostgres), uuid.New().String()) - require.NoError(t, err) - t.Cleanup(func() { assert.NoError(t, db.Close()) }) - db.MapperFunc(reflectx.CamelToSnakeASCII) - - return db + dbURL := string(env.DatabaseURL.Get()) + if dbURL == "" { + t.Errorf("you must provide a CL_DATABASE_URL environment variable") + return nil + } + return pg.NewTestDB(t, dbURL) } func MustExec(t *testing.T, ds sqlutil.DataSource, stmt string, args ...interface{}) { diff --git a/core/internal/testutils/pgtest/txdb.go b/core/internal/testutils/pgtest/txdb.go deleted file mode 100644 index 7591054305c..00000000000 --- a/core/internal/testutils/pgtest/txdb.go +++ /dev/null @@ -1,509 +0,0 @@ -package pgtest - -import ( - "context" - "database/sql" - "database/sql/driver" - "flag" - "fmt" - "io" - "net/url" - "strings" - "sync" - "testing" - - "github.com/jmoiron/sqlx" - "go.uber.org/multierr" - - "github.com/smartcontractkit/chainlink/v2/core/config/env" - "github.com/smartcontractkit/chainlink/v2/core/store/dialects" -) - -// txdb is a simplified version of https://github.com/DATA-DOG/go-txdb -// -// The original lib has various problems and is hard to understand because it -// tries to be more general. The version in this file is more tightly focused -// to our needs and should be easier to reason about and less likely to have -// subtle bugs/races. -// -// It doesn't currently support savepoints but could be made to if necessary. -// -// Transaction BEGIN/ROLLBACK effectively becomes a no-op, this should have no -// negative impact on normal test operation. -// -// If you MUST test BEGIN/ROLLBACK behaviour, you will have to configure your -// store to use the raw DialectPostgres dialect and setup a one-use database. -// See heavyweight.FullTestDB() as a convenience function to help you do this, -// but please use sparingly because as it's name implies, it is expensive. -func init() { - testing.Init() - if !flag.Parsed() { - flag.Parse() - } - if testing.Short() { - // -short tests don't need a DB - return - } - dbURL := string(env.DatabaseURL.Get()) - if dbURL == "" { - panic("you must provide a CL_DATABASE_URL environment variable") - } - - parsed, err := url.Parse(dbURL) - if err != nil { - panic(err) - } - if parsed.Path == "" { - msg := fmt.Sprintf("invalid %[1]s: `%[2]s`. You must set %[1]s env var to point to your test database. Note that the test database MUST end in `_test` to differentiate from a possible production DB. HINT: Try %[1]s=postgresql://postgres@localhost:5432/chainlink_test?sslmode=disable", env.DatabaseURL, parsed.String()) - panic(msg) - } - if !strings.HasSuffix(parsed.Path, "_test") { - msg := fmt.Sprintf("cannot run tests against database named `%s`. Note that the test database MUST end in `_test` to differentiate from a possible production DB. HINT: Try %s=postgresql://postgres@localhost:5432/chainlink_test?sslmode=disable", parsed.Path[1:], env.DatabaseURL) - panic(msg) - } - name := string(dialects.TransactionWrappedPostgres) - sql.Register(name, &txDriver{ - dbURL: dbURL, - conns: make(map[string]*conn), - }) - sqlx.BindDriver(name, sqlx.DOLLAR) -} - -var _ driver.Conn = &conn{} - -var _ driver.Validator = &conn{} -var _ driver.SessionResetter = &conn{} - -// txDriver is an sql driver which runs on a single transaction. -// When `Close` is called, transaction is rolled back. -type txDriver struct { - sync.Mutex - db *sql.DB - conns map[string]*conn - - dbURL string -} - -func (d *txDriver) Open(dsn string) (driver.Conn, error) { - d.Lock() - defer d.Unlock() - // Open real db connection if its the first call - if d.db == nil { - db, err := sql.Open(string(dialects.Postgres), d.dbURL) - if err != nil { - return nil, err - } - d.db = db - } - c, exists := d.conns[dsn] - if !exists || !c.tryOpen() { - tx, err := d.db.Begin() - if err != nil { - return nil, err - } - c = &conn{tx: tx, opened: 1, dsn: dsn} - c.removeSelf = func() error { - return d.deleteConn(c) - } - d.conns[dsn] = c - } - return c, nil -} - -// deleteConn is called by a connection when it is closed via the `close` method. -// It also auto-closes the DB when the last checked out connection is closed. -func (d *txDriver) deleteConn(c *conn) error { - // must lock here to avoid racing with Open - d.Lock() - defer d.Unlock() - - if d.conns[c.dsn] != c { - return nil // already been replaced - } - delete(d.conns, c.dsn) - if len(d.conns) == 0 && d.db != nil { - if err := d.db.Close(); err != nil { - return err - } - d.db = nil - } - return nil -} - -type conn struct { - sync.Mutex - dsn string - tx *sql.Tx // tx may be shared by many conns, definitive one lives in the map keyed by DSN on the txDriver. Do not modify from conn - closed bool - opened int - removeSelf func() error -} - -func (c *conn) Begin() (driver.Tx, error) { - c.Lock() - defer c.Unlock() - if c.closed { - panic("conn is closed") - } - // Begin is a noop because the transaction was already opened - return tx{c.tx}, nil -} - -// Implement the "ConnBeginTx" interface -func (c *conn) BeginTx(_ context.Context, opts driver.TxOptions) (driver.Tx, error) { - // Context is ignored, because single transaction is shared by all callers, thus caller should not be able to - // control it with local context - return c.Begin() -} - -// Prepare returns a prepared statement, bound to this connection. -func (c *conn) Prepare(query string) (driver.Stmt, error) { - return c.PrepareContext(context.Background(), query) -} - -// Implement the "ConnPrepareContext" interface -func (c *conn) PrepareContext(ctx context.Context, query string) (driver.Stmt, error) { - c.Lock() - defer c.Unlock() - if c.closed { - panic("conn is closed") - } - - // TODO: Fix context handling - // FIXME: It is not safe to give the passed in context to the tx directly - // because the tx is shared by many conns and cancelling the context will - // destroy the tx which can affect other conns - st, err := c.tx.PrepareContext(context.Background(), query) - if err != nil { - return nil, err - } - return &stmt{st, c}, nil -} - -// IsValid is called prior to placing the connection into the -// connection pool by database/sql. The connection will be discarded if false is returned. -func (c *conn) IsValid() bool { - c.Lock() - defer c.Unlock() - return !c.closed -} - -func (c *conn) ResetSession(ctx context.Context) error { - // Ensure bad connections are reported: From database/sql/driver: - // If a connection is never returned to the connection pool but immediately reused, then - // ResetSession is called prior to reuse but IsValid is not called. - c.Lock() - defer c.Unlock() - if c.closed { - return driver.ErrBadConn - } - - return nil -} - -// pgx returns nil -func (c *conn) CheckNamedValue(nv *driver.NamedValue) error { - return nil -} - -// Implement the "QueryerContext" interface -func (c *conn) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Rows, error) { - c.Lock() - defer c.Unlock() - if c.closed { - panic("conn is closed") - } - - // TODO: Fix context handling - rs, err := c.tx.QueryContext(context.Background(), query, mapNamedArgs(args)...) - if err != nil { - return nil, err - } - defer rs.Close() - - return buildRows(rs) -} - -// Implement the "ExecerContext" interface -func (c *conn) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Result, error) { - c.Lock() - defer c.Unlock() - if c.closed { - panic("conn is closed") - } - // TODO: Fix context handling - return c.tx.ExecContext(context.Background(), query, mapNamedArgs(args)...) -} - -// tryOpen attempts to increment the open count, but returns false if closed. -func (c *conn) tryOpen() bool { - c.Lock() - defer c.Unlock() - if c.closed { - return false - } - c.opened++ - return true -} - -// Close invalidates and potentially stops any current -// prepared statements and transactions, marking this -// connection as no longer in use. -// -// Because the sql package maintains a free pool of -// connections and only calls Close when there's a surplus of -// idle connections, it shouldn't be necessary for drivers to -// do their own connection caching. -// -// Drivers must ensure all network calls made by Close -// do not block indefinitely (e.g. apply a timeout). -func (c *conn) Close() (err error) { - if !c.close() { - return - } - // Wait to remove self to avoid nesting locks. - if err := c.removeSelf(); err != nil { - panic(err) - } - return -} - -func (c *conn) close() bool { - c.Lock() - defer c.Unlock() - if c.closed { - // Double close, should be a safe to make this a noop - // PGX allows double close - // See: https://github.com/jackc/pgx/blob/a457da8bffa4f90ad672fa093ee87f20cf06687b/conn.go#L249 - return false - } - - c.opened-- - if c.opened > 0 { - return false - } - if c.tx != nil { - if err := c.tx.Rollback(); err != nil { - panic(err) - } - c.tx = nil - } - c.closed = true - return true -} - -type tx struct { - tx *sql.Tx -} - -func (tx tx) Commit() error { - // Commit is a noop because the transaction will be rolled back at the end - return nil -} - -func (tx tx) Rollback() error { - // Rollback is a noop because the transaction will be rolled back at the end - return nil -} - -type stmt struct { - st *sql.Stmt - conn *conn -} - -func (s stmt) Exec(args []driver.Value) (driver.Result, error) { - s.conn.Lock() - defer s.conn.Unlock() - if s.conn.closed { - panic("conn is closed") - } - return s.st.Exec(mapArgs(args)...) -} - -// Implement the "StmtExecContext" interface -func (s *stmt) ExecContext(ctx context.Context, args []driver.NamedValue) (driver.Result, error) { - s.conn.Lock() - defer s.conn.Unlock() - if s.conn.closed { - panic("conn is closed") - } - // TODO: Fix context handling - return s.st.ExecContext(context.Background(), mapNamedArgs(args)...) -} - -func mapArgs(args []driver.Value) (res []interface{}) { - res = make([]interface{}, len(args)) - for i := range args { - res[i] = args[i] - } - return -} - -func (s stmt) NumInput() int { - return -1 -} - -func (s stmt) Query(args []driver.Value) (driver.Rows, error) { - s.conn.Lock() - defer s.conn.Unlock() - if s.conn.closed { - panic("conn is closed") - } - rows, err := s.st.Query(mapArgs(args)...) - defer func() { - err = multierr.Combine(err, rows.Close()) - }() - if err != nil { - return nil, err - } - return buildRows(rows) -} - -// Implement the "StmtQueryContext" interface -func (s *stmt) QueryContext(ctx context.Context, args []driver.NamedValue) (driver.Rows, error) { - s.conn.Lock() - defer s.conn.Unlock() - if s.conn.closed { - panic("conn is closed") - } - // TODO: Fix context handling - rows, err := s.st.QueryContext(context.Background(), mapNamedArgs(args)...) - if err != nil { - return nil, err - } - return buildRows(rows) -} - -func (s stmt) Close() error { - s.conn.Lock() - defer s.conn.Unlock() - return s.st.Close() -} - -func buildRows(r *sql.Rows) (driver.Rows, error) { - set := &rowSets{} - rs := &rows{} - if err := rs.read(r); err != nil { - return set, err - } - set.sets = append(set.sets, rs) - for r.NextResultSet() { - rss := &rows{} - if err := rss.read(r); err != nil { - return set, err - } - set.sets = append(set.sets, rss) - } - return set, nil -} - -// Implement the "RowsNextResultSet" interface -func (rs *rowSets) HasNextResultSet() bool { - return rs.pos+1 < len(rs.sets) -} - -// Implement the "RowsNextResultSet" interface -func (rs *rowSets) NextResultSet() error { - if !rs.HasNextResultSet() { - return io.EOF - } - - rs.pos++ - return nil -} - -type rows struct { - rows [][]driver.Value - pos int - cols []string - colTypes []*sql.ColumnType -} - -func (r *rows) Columns() []string { - return r.cols -} - -func (r *rows) ColumnTypeDatabaseTypeName(index int) string { - return r.colTypes[index].DatabaseTypeName() -} - -func (r *rows) Next(dest []driver.Value) error { - r.pos++ - if r.pos > len(r.rows) { - return io.EOF - } - - for i, val := range r.rows[r.pos-1] { - dest[i] = *(val.(*interface{})) - } - - return nil -} - -func (r *rows) Close() error { - return nil -} - -func (r *rows) read(rs *sql.Rows) error { - var err error - r.cols, err = rs.Columns() - if err != nil { - return err - } - - r.colTypes, err = rs.ColumnTypes() - if err != nil { - return err - } - - for rs.Next() { - values := make([]interface{}, len(r.cols)) - for i := range values { - values[i] = new(interface{}) - } - if err := rs.Scan(values...); err != nil { - return err - } - row := make([]driver.Value, len(r.cols)) - for i, v := range values { - row[i] = driver.Value(v) - } - r.rows = append(r.rows, row) - } - return rs.Err() -} - -type rowSets struct { - sets []*rows - pos int -} - -func (rs *rowSets) Columns() []string { - return rs.sets[rs.pos].cols -} - -func (rs *rowSets) ColumnTypeDatabaseTypeName(index int) string { - return rs.sets[rs.pos].ColumnTypeDatabaseTypeName(index) -} - -func (rs *rowSets) Close() error { - return nil -} - -// advances to next row -func (rs *rowSets) Next(dest []driver.Value) error { - return rs.sets[rs.pos].Next(dest) -} - -func mapNamedArgs(args []driver.NamedValue) (res []interface{}) { - res = make([]interface{}, len(args)) - for i := range args { - name := args[i].Name - if name != "" { - res[i] = sql.Named(name, args[i].Value) - } else { - res[i] = args[i].Value - } - } - return -} diff --git a/core/internal/testutils/testutils.go b/core/internal/testutils/testutils.go index 0504570365b..53b555c0e8b 100644 --- a/core/internal/testutils/testutils.go +++ b/core/internal/testutils/testutils.go @@ -32,6 +32,7 @@ import ( // NOTE: To avoid circular dependencies, this package MUST NOT import // anything from "github.com/smartcontractkit/chainlink/v2/core" "github.com/smartcontractkit/chainlink-common/pkg/sqlutil" + "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" ) const ( @@ -415,16 +416,9 @@ func WaitForLogMessageCount(t *testing.T, observedLogs *observer.ObservedLogs, m }) } -// SkipShort skips tb during -short runs, and notes why. -func SkipShort(tb testing.TB, why string) { - if testing.Short() { - tb.Skipf("skipping: %s", why) - } -} - // SkipShortDB skips tb during -short runs, and notes the DB dependency. func SkipShortDB(tb testing.TB) { - SkipShort(tb, "DB dependency") + tests.SkipShort(tb, "DB dependency") } func AssertCount(t *testing.T, ds sqlutil.DataSource, tableName string, expected int64) { @@ -454,10 +448,6 @@ func MustDecodeBase64(s string) (b []byte) { return } -func SkipFlakey(t *testing.T, ticketURL string) { - t.Skip("Flakey", ticketURL) -} - func MustRandBytes(n int) (b []byte) { b = make([]byte, n) _, err := rand.Read(b) diff --git a/core/scripts/go.mod b/core/scripts/go.mod index 8f57442c8a1..47ab9cf536c 100644 --- a/core/scripts/go.mod +++ b/core/scripts/go.mod @@ -9,6 +9,13 @@ replace github.com/smartcontractkit/chainlink/v2 => ../../ replace github.com/smartcontractkit/chainlink/deployment => ../../deployment +// Using a separate `require` here to avoid surrounding line changes +// creating potential merge conflicts. +require ( + github.com/smartcontractkit/chainlink/deployment v0.0.0-20241206210521-125d98cdaf66 + github.com/smartcontractkit/chainlink/v2 v2.0.0-20241206210521-125d98cdaf66 +) + require ( github.com/docker/docker v27.3.1+incompatible github.com/docker/go-connections v0.5.0 @@ -26,9 +33,7 @@ require ( github.com/prometheus/client_golang v1.20.5 github.com/shopspring/decimal v1.4.0 github.com/smartcontractkit/chainlink-automation v0.8.1 - github.com/smartcontractkit/chainlink-common v0.3.1-0.20241209151352-70300ddcc776 - github.com/smartcontractkit/chainlink/deployment v0.0.0-00010101000000-000000000000 - github.com/smartcontractkit/chainlink/v2 v2.14.0-mercury-20240807.0.20241106193309-5560cd76211a + github.com/smartcontractkit/chainlink-common v0.3.1-0.20241214155818-b403079b2805 github.com/smartcontractkit/libocr v0.0.0-20241007185508-adbe57025f12 github.com/spf13/cobra v1.8.1 github.com/spf13/viper v1.19.0 @@ -64,7 +69,7 @@ require ( github.com/NethermindEth/starknet.go v0.7.1-0.20240401080518-34a506f3cfdb // indirect github.com/VictoriaMetrics/fastcache v1.12.2 // indirect github.com/XSAM/otelsql v0.27.0 // indirect - github.com/andybalholm/brotli v1.1.0 // indirect + github.com/andybalholm/brotli v1.1.1 // indirect github.com/armon/go-metrics v0.4.1 // indirect github.com/atombender/go-jsonschema v0.16.1-0.20240916205339-a74cd4e2851c // indirect github.com/avast/retry-go/v4 v4.6.0 // indirect @@ -139,7 +144,7 @@ require ( github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08 // indirect github.com/gedex/inflector v0.0.0-20170307190818-16278e9db813 // indirect github.com/getsentry/sentry-go v0.27.0 // indirect - github.com/gin-contrib/cors v1.5.0 // indirect + github.com/gin-contrib/cors v1.7.2 // indirect github.com/gin-contrib/expvar v0.0.1 // indirect github.com/gin-contrib/sessions v0.0.5 // indirect github.com/gin-contrib/size v0.0.0-20230212012657-e14a14094dc4 // indirect @@ -165,7 +170,7 @@ require ( github.com/go-viper/mapstructure/v2 v2.1.0 // indirect github.com/go-webauthn/webauthn v0.9.4 // indirect github.com/go-webauthn/x v0.1.5 // indirect - github.com/goccy/go-json v0.10.2 // indirect + github.com/goccy/go-json v0.10.3 // indirect github.com/goccy/go-yaml v1.12.0 // indirect github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect github.com/gofrs/flock v0.8.1 // indirect @@ -233,8 +238,8 @@ require ( github.com/josharian/intern v1.0.0 // indirect github.com/jpillora/backoff v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/compress v1.17.9 // indirect - github.com/klauspost/cpuid/v2 v2.2.7 // indirect + github.com/klauspost/compress v1.17.11 // indirect + github.com/klauspost/cpuid/v2 v2.2.8 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect github.com/kylelemons/godebug v1.1.0 // indirect @@ -296,15 +301,15 @@ require ( github.com/sethvargo/go-retry v0.2.4 // indirect github.com/shirou/gopsutil v3.21.11+incompatible // indirect github.com/shirou/gopsutil/v3 v3.24.3 // indirect - github.com/smartcontractkit/ccip-owner-contracts v0.0.0-20240926212305-a6deabdfce86 // indirect + github.com/smartcontractkit/ccip-owner-contracts v0.0.0-salt-fix // indirect github.com/smartcontractkit/chain-selectors v1.0.34 // indirect - github.com/smartcontractkit/chainlink-ccip v0.0.0-20241204015713-8956bb614e9e // indirect + github.com/smartcontractkit/chainlink-ccip v0.0.0-20241213122413-5e8f65dd6b1b // indirect github.com/smartcontractkit/chainlink-cosmos v0.5.2-0.20241202195413-82468150ac1e // indirect - github.com/smartcontractkit/chainlink-data-streams v0.1.1-0.20241202141438-a90db35252db // indirect + github.com/smartcontractkit/chainlink-data-streams v0.1.1-0.20241216163550-fa030d178ba3 // indirect github.com/smartcontractkit/chainlink-feeds v0.1.1 // indirect github.com/smartcontractkit/chainlink-protos/job-distributor v0.6.0 // indirect - github.com/smartcontractkit/chainlink-protos/orchestrator v0.3.2 // indirect - github.com/smartcontractkit/chainlink-solana v1.1.1-0.20241204153209-c3a71b0eef99 // indirect + github.com/smartcontractkit/chainlink-protos/orchestrator v0.4.0 // indirect + github.com/smartcontractkit/chainlink-solana v1.1.1-0.20241210172617-6fd1891d0fbc // indirect github.com/smartcontractkit/chainlink-starknet/relayer v0.1.1-0.20241202202529-2033490e77b8 // indirect github.com/smartcontractkit/chainlink-testing-framework/lib v1.50.13 // indirect github.com/smartcontractkit/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7 // indirect @@ -375,15 +380,15 @@ require ( go.uber.org/ratelimit v0.3.1 // indirect go.uber.org/zap v1.27.0 // indirect golang.org/x/arch v0.11.0 // indirect - golang.org/x/crypto v0.28.0 // indirect + golang.org/x/crypto v0.31.0 // indirect golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c // indirect golang.org/x/mod v0.21.0 // indirect golang.org/x/net v0.30.0 // indirect golang.org/x/oauth2 v0.23.0 // indirect - golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.26.0 // indirect - golang.org/x/term v0.25.0 // indirect - golang.org/x/text v0.19.0 // indirect + golang.org/x/sync v0.10.0 // indirect + golang.org/x/sys v0.28.0 // indirect + golang.org/x/term v0.27.0 // indirect + golang.org/x/text v0.21.0 // indirect golang.org/x/time v0.7.0 // indirect golang.org/x/tools v0.26.0 // indirect golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect diff --git a/core/scripts/go.sum b/core/scripts/go.sum index 2a777f569b1..2840c675dfa 100644 --- a/core/scripts/go.sum +++ b/core/scripts/go.sum @@ -139,9 +139,11 @@ github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax github.com/allegro/bigcache v1.2.1 h1:hg1sY1raCwic3Vnsvje6TT7/pnZba83LeFck5NrFKSc= github.com/allegro/bigcache v1.2.1/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129/go.mod h1:rFgpPQZYZ8vdbc+48xibu8ALc3yeyd64IhHS+PU6Yyg= -github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= -github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= +github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA= +github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/apache/arrow-go/v18 v18.0.0 h1:1dBDaSbH3LtulTyOVYaBCHO3yVRwjV+TZaqn3g6V7ZM= +github.com/apache/arrow-go/v18 v18.0.0/go.mod h1:t6+cWRSmKgdQ6HsxisQjok+jBpKGhRDiqcf3p0p/F+A= github.com/appleboy/gofight/v2 v2.1.2 h1:VOy3jow4vIK8BRQJoC/I9muxyYlJ2yb9ht2hZoS3rf4= github.com/appleboy/gofight/v2 v2.1.2/go.mod h1:frW+U1QZEdDgixycTj4CygQ48yLTUhplt43+Wczp3rw= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= @@ -412,8 +414,8 @@ github.com/gedex/inflector v0.0.0-20170307190818-16278e9db813/go.mod h1:P+oSoE9y github.com/getsentry/sentry-go v0.27.0 h1:Pv98CIbtB3LkMWmXi4Joa5OOcwbmnX88sF5qbK3r3Ps= github.com/getsentry/sentry-go v0.27.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/gin-contrib/cors v1.5.0 h1:DgGKV7DDoOn36DFkNtbHrjoRiT5ExCe+PC9/xp7aKvk= -github.com/gin-contrib/cors v1.5.0/go.mod h1:TvU7MAZ3EwrPLI2ztzTt3tqgvBCq+wn8WpZmfADjupI= +github.com/gin-contrib/cors v1.7.2 h1:oLDHxdg8W/XDoN/8zamqk/Drgt4oVZDvaV0YmvVICQw= +github.com/gin-contrib/cors v1.7.2/go.mod h1:SUJVARKgQ40dmrzgXEVxj2m7Ig1v1qIboQkPDTQ9t2E= github.com/gin-contrib/expvar v0.0.1 h1:IuU5ArEgihz50vG8Onrwz22kJr7Mcvgv9xSSpfU5g+w= github.com/gin-contrib/expvar v0.0.1/go.mod h1:8o2CznfQi1JjktORdHr2/abg3wSV6OCnXh0yGypvvVw= github.com/gin-contrib/sessions v0.0.5 h1:CATtfHmLMQrMNpJRgzjWXD7worTh7g7ritsQfmF+0jE= @@ -497,8 +499,8 @@ github.com/go-webauthn/webauthn v0.9.4/go.mod h1:LqupCtzSef38FcxzaklmOn7AykGKhAh github.com/go-webauthn/x v0.1.5 h1:V2TCzDU2TGLd0kSZOXdrqDVV5JB9ILnKxA9S53CSBw0= github.com/go-webauthn/x v0.1.5/go.mod h1:qbzWwcFcv4rTwtCLOZd+icnr6B7oSsAGZJqlt8cukqY= github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= -github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= -github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= +github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/goccy/go-yaml v1.12.0 h1:/1WHjnMsI1dlIBQutrvSMGZRQufVO3asrHfTwfACoPM= github.com/goccy/go-yaml v1.12.0/go.mod h1:wKnAMd44+9JAAnGQpWVEgBzGt3YuTaQ4uXoHvE4m7WU= github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 h1:ZpnhV/YsD2/4cESfV5+Hoeu/iUR3ruzNvZ+yQfO03a0= @@ -563,6 +565,8 @@ github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Z github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= +github.com/google/flatbuffers v24.3.25+incompatible h1:CX395cjN9Kke9mmalRoL3d81AtFUxJM+yDthflgJGkI= +github.com/google/flatbuffers v24.3.25+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 h1:0VpGH+cDhbDtdcweoyCVsF3fhN8kejK6rFe/2FFX2nU= github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49/go.mod h1:BkkQ4L1KS1xMt2aWSPStnn55ChGC0DPOn2FQYj+f25M= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -830,11 +834,11 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= -github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= -github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= +github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= -github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM= +github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -881,6 +885,8 @@ github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYt github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg= github.com/manyminds/api2go v0.0.0-20171030193247-e7b693844a6f h1:tVvGiZQFjOXP+9YyGqSA6jE55x1XVxmoPYudncxrZ8U= github.com/manyminds/api2go v0.0.0-20171030193247-e7b693844a6f/go.mod h1:Z60vy0EZVSu0bOugCHdcN5ZxFMKSpjRgsnh0XKPFqqk= +github.com/marcboeker/go-duckdb v1.8.3 h1:ZkYwiIZhbYsT6MmJsZ3UPTHrTZccDdM4ztoqSlEMXiQ= +github.com/marcboeker/go-duckdb v1.8.3/go.mod h1:C9bYRE1dPYb1hhfu/SSomm78B0FXmNgRvv6YBW/Hooc= github.com/maruel/natural v1.1.1 h1:Hja7XhhmvEFhcByqDoHz9QZbkWey+COd9xWfCfn1ioo= github.com/maruel/natural v1.1.1/go.mod h1:v+Rfd79xlw1AgVBjbO0BEQmptqb5HvL/k9GRHB7ZKEg= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= @@ -1018,6 +1024,8 @@ github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xl github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5/go.mod h1:jvVRKCrJTQWu0XVbaOlby/2lO20uSCHEMzzplHXte1o= github.com/petermattis/goid v0.0.0-20230317030725-371a4b8eda08 h1:hDSdbBuw3Lefr6R18ax0tZ2BJeNB3NehB3trOwYBsdU= github.com/petermattis/goid v0.0.0-20230317030725-371a4b8eda08/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= +github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= +github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= @@ -1134,28 +1142,28 @@ github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6Mwd github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/smartcontractkit/ccip-owner-contracts v0.0.0-20240926212305-a6deabdfce86 h1:qQH6fZZe31nBAG6INHph3z5ysDTPptyu0TR9uoJ1+ok= -github.com/smartcontractkit/ccip-owner-contracts v0.0.0-20240926212305-a6deabdfce86/go.mod h1:WtWOoVQQEHxRHL2hNmuRrvDfYfQG/CioFNoa9Rr2mBE= +github.com/smartcontractkit/ccip-owner-contracts v0.0.0-salt-fix h1:DPJD++yKLSx0EfT+U14P8vLVxjXFmoIETiCO9lVwQo8= +github.com/smartcontractkit/ccip-owner-contracts v0.0.0-salt-fix/go.mod h1:NnT6w4Kj42OFFXhSx99LvJZWPpMjmo4+CpDEWfw61xY= github.com/smartcontractkit/chain-selectors v1.0.34 h1:MJ17OGu8+jjl426pcKrJkCf3fePb3eCreuAnUA3RBj4= github.com/smartcontractkit/chain-selectors v1.0.34/go.mod h1:xsKM0aN3YGcQKTPRPDDtPx2l4mlTN1Djmg0VVXV40b8= github.com/smartcontractkit/chainlink-automation v0.8.1 h1:sTc9LKpBvcKPc1JDYAmgBc2xpDKBco/Q4h4ydl6+UUU= github.com/smartcontractkit/chainlink-automation v0.8.1/go.mod h1:Iij36PvWZ6blrdC5A/nrQUBuf3MH3JvsBB9sSyc9W08= -github.com/smartcontractkit/chainlink-ccip v0.0.0-20241204015713-8956bb614e9e h1:GnM6ZWV6vlk2+n6c6o+v/R1LtXzBGVVx7r37nt/h6Uc= -github.com/smartcontractkit/chainlink-ccip v0.0.0-20241204015713-8956bb614e9e/go.mod h1:80vGBbOfertJig0xFKsRfm+i17FkjdKkk1dAaGE45Os= -github.com/smartcontractkit/chainlink-common v0.3.1-0.20241209151352-70300ddcc776 h1:NATQA1LfrEPXCdtEed9/G4SxaVuF8EZp5O2ucOK5C98= -github.com/smartcontractkit/chainlink-common v0.3.1-0.20241209151352-70300ddcc776/go.mod h1:bQktEJf7sJ0U3SmIcXvbGUox7SmXcnSEZ4kUbT8R5Nk= +github.com/smartcontractkit/chainlink-ccip v0.0.0-20241213122413-5e8f65dd6b1b h1:iSQJ6ng4FhEswf8SXunGkaJlVP3E3JlgLB8Oo2f3Ud4= +github.com/smartcontractkit/chainlink-ccip v0.0.0-20241213122413-5e8f65dd6b1b/go.mod h1:F8xQAIW0ymb2BZhqn89sWZLXreJhM5KDVF6Qb4y44N0= +github.com/smartcontractkit/chainlink-common v0.3.1-0.20241214155818-b403079b2805 h1:Pz8jB/6qe10xT10h2S3LFYJrnebNpG5rJ/w16HZGwPQ= +github.com/smartcontractkit/chainlink-common v0.3.1-0.20241214155818-b403079b2805/go.mod h1:yti7e1+G9hhkYhj+L5sVUULn9Bn3bBL5/AxaNqdJ5YQ= github.com/smartcontractkit/chainlink-cosmos v0.5.2-0.20241202195413-82468150ac1e h1:PRoeby6ZlTuTkv2f+7tVU4+zboTfRzI+beECynF4JQ0= github.com/smartcontractkit/chainlink-cosmos v0.5.2-0.20241202195413-82468150ac1e/go.mod h1:mUh5/woemsVaHgTorA080hrYmO3syBCmPdnWc/5dOqk= -github.com/smartcontractkit/chainlink-data-streams v0.1.1-0.20241202141438-a90db35252db h1:N1RH1hSr2ACzOFc9hkCcjE8pRBTdcU3p8nsTJByaLes= -github.com/smartcontractkit/chainlink-data-streams v0.1.1-0.20241202141438-a90db35252db/go.mod h1:yjb9d4q7+m8aGbjfTbkNoNuA4PeSxcUszsSZHDrvS0E= +github.com/smartcontractkit/chainlink-data-streams v0.1.1-0.20241216163550-fa030d178ba3 h1:aeiBdBHGY8QNftps+VqrIk6OnfeeOD5z4jrAabW4ZSc= +github.com/smartcontractkit/chainlink-data-streams v0.1.1-0.20241216163550-fa030d178ba3/go.mod h1:AS6zY2BkcRwfiGzNabGbHhfrLSrXrcI/GmjnT4jQ5/s= 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/job-distributor v0.6.0 h1:0ewLMbAz3rZrovdRUCgd028yOXX8KigB4FndAUdI2kM= github.com/smartcontractkit/chainlink-protos/job-distributor v0.6.0/go.mod h1:/dVVLXrsp+V0AbcYGJo3XMzKg3CkELsweA/TTopCsKE= -github.com/smartcontractkit/chainlink-protos/orchestrator v0.3.2 h1:onBe3DqNrbtOAzKS4PrPIiJX65BGo1aYiYZxFVEW+jc= -github.com/smartcontractkit/chainlink-protos/orchestrator v0.3.2/go.mod h1:m/A3lqD7ms/RsQ9BT5P2uceYY0QX5mIt4KQxT2G6qEo= -github.com/smartcontractkit/chainlink-solana v1.1.1-0.20241204153209-c3a71b0eef99 h1:lvn9Yxah+QD1/PcgijLO0dNRa28HuQWZl8Kkxh46KJc= -github.com/smartcontractkit/chainlink-solana v1.1.1-0.20241204153209-c3a71b0eef99/go.mod h1:p8aUDfJeley6oer7y+Ucd3edOtRlMTnWg3mN6rhaLWo= +github.com/smartcontractkit/chainlink-protos/orchestrator v0.4.0 h1:ZBat8EBvE2LpSQR9U1gEbRV6PfAkiFdINmQ8nVnXIAQ= +github.com/smartcontractkit/chainlink-protos/orchestrator v0.4.0/go.mod h1:m/A3lqD7ms/RsQ9BT5P2uceYY0QX5mIt4KQxT2G6qEo= +github.com/smartcontractkit/chainlink-solana v1.1.1-0.20241210172617-6fd1891d0fbc h1:dssRwJhmzJkUN/OajaDj2GsxBn+Tupk3bI1BkPEoJg0= +github.com/smartcontractkit/chainlink-solana v1.1.1-0.20241210172617-6fd1891d0fbc/go.mod h1:p8aUDfJeley6oer7y+Ucd3edOtRlMTnWg3mN6rhaLWo= github.com/smartcontractkit/chainlink-starknet/relayer v0.1.1-0.20241202202529-2033490e77b8 h1:tNS7U9lrxkFvEuyxQv11HHOiV9LPDGC9wYEy+yM/Jv4= github.com/smartcontractkit/chainlink-starknet/relayer v0.1.1-0.20241202202529-2033490e77b8/go.mod h1:EBrEgcdIbwepqguClkv8Ohy7CbyWSJaE4EC9aBJlQK0= github.com/smartcontractkit/chainlink-testing-framework/lib v1.50.13 h1:T0kbw07Vb6xUyA9MIJZfErMgWseWi1zf7cYvRpoq7ug= @@ -1304,6 +1312,8 @@ github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= +github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= +github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -1313,6 +1323,8 @@ github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1 github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0= +github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= github.com/zondax/hid v0.9.2 h1:WCJFnEDMiqGF64nlZz28E9qLVZ0KSJ7xpc5DLEyma2U= github.com/zondax/hid v0.9.2/go.mod h1:l5wttcP0jwtdLjqjMMWFVEE7d1zO0jvSPA9OPZxWpEM= @@ -1447,8 +1459,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= -golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= -golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= +golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= +golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -1572,8 +1584,8 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1656,8 +1668,8 @@ golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= -golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -1666,8 +1678,8 @@ golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= -golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= -golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= +golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= +golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1681,8 +1693,8 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= -golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= diff --git a/core/scripts/keystone/src/05_deploy_initialize_capabilities_registry.go b/core/scripts/keystone/src/05_deploy_initialize_capabilities_registry.go index 166022ac753..86dbfa0c404 100644 --- a/core/scripts/keystone/src/05_deploy_initialize_capabilities_registry.go +++ b/core/scripts/keystone/src/05_deploy_initialize_capabilities_registry.go @@ -294,7 +294,7 @@ func (c *deployAndInitializeCapabilitiesRegistryCommand) Run(args []string) { panic(innerErr) } - n.HashedCapabilityIds = [][32]byte{ocrid, ctid} + n.HashedCapabilityIds = [][32]byte{ocrid, ctid, aid} nodes = append(nodes, n) } @@ -337,7 +337,7 @@ func (c *deployAndInitializeCapabilitiesRegistryCommand) Run(args []string) { Config: ccfgb, }, } - _, err = reg.AddDON(env.Owner, ps, cfgs, true, true, 2) + _, err = reg.AddDON(env.Owner, ps, cfgs, true, true, 1) if err != nil { log.Printf("workflowDON: failed to AddDON: %s", err) } diff --git a/core/services/chainlink/application.go b/core/services/chainlink/application.go index 9ed432c17d2..76922844471 100644 --- a/core/services/chainlink/application.go +++ b/core/services/chainlink/application.go @@ -312,12 +312,19 @@ func NewApplication(opts ApplicationOpts) (Application, error) { }, eventHandler) globalLogger.Debugw("Creating WorkflowRegistrySyncer") - wfSyncer := syncer.NewWorkflowRegistry(lggr, func(ctx context.Context, bytes []byte) (syncer.ContractReader, error) { - return relayer.NewContractReader(ctx, bytes) - }, cfg.Capabilities().WorkflowRegistry().Address(), + wfSyncer := syncer.NewWorkflowRegistry( + lggr, + func(ctx context.Context, bytes []byte) (syncer.ContractReader, error) { + return relayer.NewContractReader(ctx, bytes) + }, + cfg.Capabilities().WorkflowRegistry().Address(), syncer.WorkflowEventPollerConfig{ QueryCount: 100, - }, eventHandler, loader, workflowDonNotifier) + }, + eventHandler, + loader, + workflowDonNotifier, + ) srvcs = append(srvcs, fetcher, wfSyncer) } diff --git a/core/services/chainlink/config_database.go b/core/services/chainlink/config_database.go index fe10c63f71b..fd8aa96419d 100644 --- a/core/services/chainlink/config_database.go +++ b/core/services/chainlink/config_database.go @@ -4,9 +4,10 @@ import ( "net/url" "time" + pgcommon "github.com/smartcontractkit/chainlink-common/pkg/sqlutil/pg" + "github.com/smartcontractkit/chainlink/v2/core/config" "github.com/smartcontractkit/chainlink/v2/core/config/toml" - "github.com/smartcontractkit/chainlink/v2/core/store/dialects" ) type backupConfig struct { @@ -109,7 +110,7 @@ func (d *databaseConfig) URL() url.URL { return *d.s.URL.URL() } -func (d *databaseConfig) Dialect() dialects.DialectName { +func (d *databaseConfig) Dialect() pgcommon.DialectName { return d.c.Dialect } diff --git a/core/services/chainlink/config_database_test.go b/core/services/chainlink/config_database_test.go index b52d17452aa..f8f864f97ab 100644 --- a/core/services/chainlink/config_database_test.go +++ b/core/services/chainlink/config_database_test.go @@ -7,8 +7,9 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + pgcommon "github.com/smartcontractkit/chainlink-common/pkg/sqlutil/pg" + "github.com/smartcontractkit/chainlink/v2/core/config" - "github.com/smartcontractkit/chainlink/v2/core/store/dialects" ) func TestDatabaseConfig(t *testing.T) { @@ -21,31 +22,31 @@ URL = "postgresql://doesnotexist:justtopassvalidationtests@localhost:5432/chainl require.NoError(t, err) backup := cfg.Database().Backup() - assert.Equal(t, backup.Dir(), "test/backup/dir") - assert.Equal(t, backup.Frequency(), 1*time.Hour) - assert.Equal(t, backup.Mode(), config.DatabaseBackupModeFull) - assert.Equal(t, backup.OnVersionUpgrade(), true) + assert.Equal(t, "test/backup/dir", backup.Dir()) + assert.Equal(t, 1*time.Hour, backup.Frequency()) + assert.Equal(t, config.DatabaseBackupModeFull, backup.Mode()) + assert.True(t, backup.OnVersionUpgrade()) assert.Nil(t, backup.URL()) db := cfg.Database() - assert.Equal(t, db.DefaultIdleInTxSessionTimeout(), 1*time.Minute) - assert.Equal(t, db.DefaultLockTimeout(), 1*time.Hour) - assert.Equal(t, db.DefaultQueryTimeout(), 1*time.Second) - assert.Equal(t, db.LogSQL(), true) - assert.Equal(t, db.MaxIdleConns(), 7) - assert.Equal(t, db.MaxOpenConns(), 13) - assert.Equal(t, db.MigrateDatabase(), true) - assert.Equal(t, db.Dialect(), dialects.Postgres) + assert.Equal(t, 1*time.Minute, db.DefaultIdleInTxSessionTimeout()) + assert.Equal(t, 1*time.Hour, db.DefaultLockTimeout()) + assert.Equal(t, 1*time.Second, db.DefaultQueryTimeout()) + assert.True(t, db.LogSQL()) + assert.Equal(t, 7, db.MaxIdleConns()) + assert.Equal(t, 13, db.MaxOpenConns()) + assert.True(t, db.MigrateDatabase()) + assert.Equal(t, pgcommon.Postgres, db.Dialect()) url := db.URL() assert.NotEqual(t, url.String(), "") lock := db.Lock() - assert.Equal(t, lock.LockingMode(), "none") - assert.Equal(t, lock.LeaseDuration(), 1*time.Minute) - assert.Equal(t, lock.LeaseRefreshInterval(), 1*time.Second) + assert.Equal(t, "none", lock.LockingMode()) + assert.Equal(t, 1*time.Minute, lock.LeaseDuration()) + assert.Equal(t, 1*time.Second, lock.LeaseRefreshInterval()) l := db.Listener() - assert.Equal(t, l.MaxReconnectDuration(), 1*time.Minute) - assert.Equal(t, l.MinReconnectInterval(), 5*time.Minute) - assert.Equal(t, l.FallbackPollInterval(), 2*time.Minute) + assert.Equal(t, 1*time.Minute, l.MaxReconnectDuration()) + assert.Equal(t, 5*time.Minute, l.MinReconnectInterval()) + assert.Equal(t, 2*time.Minute, l.FallbackPollInterval()) } diff --git a/core/services/chainlink/mocks/relayer_chain_interoperators.go b/core/services/chainlink/mocks/relayer_chain_interoperators.go index 3ebe1411c19..bc30ede77ee 100644 --- a/core/services/chainlink/mocks/relayer_chain_interoperators.go +++ b/core/services/chainlink/mocks/relayer_chain_interoperators.go @@ -4,6 +4,7 @@ import ( "context" "slices" + commonTypes "github.com/smartcontractkit/chainlink/v2/common/types" services2 "github.com/smartcontractkit/chainlink/v2/core/services" "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" @@ -69,6 +70,6 @@ func (f *FakeRelayerChainInteroperators) ChainStatus(ctx context.Context, id typ panic("unimplemented") } -func (f *FakeRelayerChainInteroperators) ChainStatuses(ctx context.Context, offset, limit int) ([]types.ChainStatus, int, error) { +func (f *FakeRelayerChainInteroperators) ChainStatuses(ctx context.Context, offset, limit int) ([]commonTypes.ChainStatusWithID, int, error) { panic("unimplemented") } diff --git a/core/services/chainlink/relayer_chain_interoperators.go b/core/services/chainlink/relayer_chain_interoperators.go index 1be6e9337d1..2fc671bfe6e 100644 --- a/core/services/chainlink/relayer_chain_interoperators.go +++ b/core/services/chainlink/relayer_chain_interoperators.go @@ -14,6 +14,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/services/relay" + commonTypes "github.com/smartcontractkit/chainlink/v2/common/types" "github.com/smartcontractkit/chainlink/v2/core/chains" "github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm" "github.com/smartcontractkit/chainlink/v2/core/services" @@ -53,7 +54,7 @@ type LegacyChainer interface { type ChainStatuser interface { ChainStatus(ctx context.Context, id types.RelayID) (types.ChainStatus, error) - ChainStatuses(ctx context.Context, offset, limit int) ([]types.ChainStatus, int, error) + ChainStatuses(ctx context.Context, offset, limit int) ([]commonTypes.ChainStatusWithID, int, error) } // NodesStatuser is an interface for node configuration and state. @@ -261,9 +262,9 @@ func (rs *CoreRelayerChainInteroperators) ChainStatus(ctx context.Context, id ty return lr.GetChainStatus(ctx) } -func (rs *CoreRelayerChainInteroperators) ChainStatuses(ctx context.Context, offset, limit int) ([]types.ChainStatus, int, error) { +func (rs *CoreRelayerChainInteroperators) ChainStatuses(ctx context.Context, offset, limit int) ([]commonTypes.ChainStatusWithID, int, error) { var ( - stats []types.ChainStatus + stats []commonTypes.ChainStatusWithID totalErr error ) rs.mu.Lock() @@ -283,7 +284,7 @@ func (rs *CoreRelayerChainInteroperators) ChainStatuses(ctx context.Context, off totalErr = errors.Join(totalErr, err) continue } - stats = append(stats, stat) + stats = append(stats, commonTypes.ChainStatusWithID{ChainStatus: stat, RelayID: rid}) } if totalErr != nil { diff --git a/core/services/feeds/models.go b/core/services/feeds/models.go index 93dddd86dae..a6cf103b4e9 100644 --- a/core/services/feeds/models.go +++ b/core/services/feeds/models.go @@ -12,6 +12,7 @@ import ( "gopkg.in/guregu/null.v4" proto "github.com/smartcontractkit/chainlink-protos/orchestrator/feedsmanager" + "github.com/smartcontractkit/chainlink/v2/core/utils/crypto" ) @@ -82,6 +83,7 @@ const ( ChainTypeEVM ChainType = "EVM" ChainTypeSolana ChainType = "SOLANA" ChainTypeStarknet ChainType = "STARKNET" + ChainTypeTron ChainType = "TRON" ) func NewChainType(s string) (ChainType, error) { @@ -94,6 +96,8 @@ func NewChainType(s string) (ChainType, error) { return ChainTypeSolana, nil case "APTOS": return ChainTypeAptos, nil + case "TRON": + return ChainTypeTron, nil default: return ChainTypeUnknown, errors.New("invalid chain type") } diff --git a/core/services/feeds/models_test.go b/core/services/feeds/models_test.go index 13567281735..d0d4382b055 100644 --- a/core/services/feeds/models_test.go +++ b/core/services/feeds/models_test.go @@ -28,6 +28,11 @@ func Test_NewChainType(t *testing.T) { give: "STARKNET", want: ChainTypeStarknet, }, + { + name: "Tron Chain Type", + give: "TRON", + want: ChainTypeTron, + }, { name: "Invalid Chain Type", give: "", diff --git a/core/services/fluxmonitorv2/flux_monitor_test.go b/core/services/fluxmonitorv2/flux_monitor_test.go index 88b364cdeb3..150db269e45 100644 --- a/core/services/fluxmonitorv2/flux_monitor_test.go +++ b/core/services/fluxmonitorv2/flux_monitor_test.go @@ -150,7 +150,7 @@ type setupOptions struct { // functional options to configure the setup func setup(t *testing.T, ds sqlutil.DataSource, optionFns ...func(*setupOptions)) (*fluxmonitorv2.FluxMonitor, *testMocks) { t.Helper() - testutils.SkipShort(t, "long test") + tests.SkipShort(t, "long test") tm := setupMocks(t) options := setupOptions{ diff --git a/core/services/fluxmonitorv2/integrations_test.go b/core/services/fluxmonitorv2/integrations_test.go index 1d77b694cbe..d30ff4479a8 100644 --- a/core/services/fluxmonitorv2/integrations_test.go +++ b/core/services/fluxmonitorv2/integrations_test.go @@ -25,6 +25,7 @@ import ( commonconfig "github.com/smartcontractkit/chainlink-common/pkg/config" "github.com/smartcontractkit/chainlink-common/pkg/sqlutil" + "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" "github.com/smartcontractkit/chainlink/v2/core/bridges" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" @@ -95,7 +96,7 @@ func WithMinMaxSubmission(min, max *big.Int) func(cfg *fluxAggregatorUniverseCon // arguments match the arguments of the same name in the FluxAggregator // constructor. func setupFluxAggregatorUniverse(t *testing.T, configOptions ...func(cfg *fluxAggregatorUniverseConfig)) fluxAggregatorUniverse { - testutils.SkipShort(t, "VRFCoordinatorV2Universe") + tests.SkipShort(t, "VRFCoordinatorV2Universe") cfg := &fluxAggregatorUniverseConfig{ MinSubmission: big.NewInt(0), MaxSubmission: big.NewInt(100000000000), diff --git a/core/services/gateway/connector/connector.go b/core/services/gateway/connector/connector.go index a8d356478e9..cab123d4ce5 100644 --- a/core/services/gateway/connector/connector.go +++ b/core/services/gateway/connector/connector.go @@ -28,13 +28,14 @@ type GatewayConnector interface { AddHandler(methods []string, handler GatewayConnectorHandler) error // SendToGateway takes a signed message as argument and sends it to the specified gateway - SendToGateway(ctx context.Context, gatewayId string, msg *api.Message) error + SendToGateway(ctx context.Context, gatewayID string, msg *api.Message) error // SignAndSendToGateway signs the message and sends the message to the specified gateway SignAndSendToGateway(ctx context.Context, gatewayID string, msg *api.MessageBody) error // GatewayIDs returns the list of Gateway IDs GatewayIDs() []string // DonID returns the DON ID DonID() string + AwaitConnection(ctx context.Context, gatewayID string) error } // Signer implementation needs to be provided by a GatewayConnector user (node) @@ -78,12 +79,30 @@ func (c *gatewayConnector) HealthReport() map[string]error { func (c *gatewayConnector) Name() string { return c.lggr.Name() } type gatewayState struct { + // signal channel is closed once the gateway is connected + signalCh chan struct{} + conn network.WSConnectionWrapper config ConnectorGatewayConfig url *url.URL wsClient network.WebSocketClient } +// A gatewayState is connected when the signal channel is closed +func (gs *gatewayState) signal() { + close(gs.signalCh) +} + +// awaitConn blocks until the gateway is connected or the context is done +func (gs *gatewayState) awaitConn(ctx context.Context) error { + select { + case <-ctx.Done(): + return fmt.Errorf("await connection failed: %w", ctx.Err()) + case <-gs.signalCh: + return nil + } +} + func NewGatewayConnector(config *ConnectorConfig, signer Signer, clock clockwork.Clock, lggr logger.Logger) (GatewayConnector, error) { if config == nil || signer == nil || clock == nil || lggr == nil { return nil, errors.New("nil dependency") @@ -125,6 +144,7 @@ func NewGatewayConnector(config *ConnectorConfig, signer Signer, clock clockwork config: gw, url: parsedURL, wsClient: network.NewWebSocketClient(config.WsClientConfig, connector, lggr), + signalCh: make(chan struct{}), } gateways[gw.Id] = gateway urlToId[gw.URL] = gw.Id @@ -150,17 +170,25 @@ func (c *gatewayConnector) AddHandler(methods []string, handler GatewayConnector return nil } -func (c *gatewayConnector) SendToGateway(ctx context.Context, gatewayId string, msg *api.Message) error { +func (c *gatewayConnector) AwaitConnection(ctx context.Context, gatewayID string) error { + gateway, ok := c.gateways[gatewayID] + if !ok { + return fmt.Errorf("invalid Gateway ID %s", gatewayID) + } + return gateway.awaitConn(ctx) +} + +func (c *gatewayConnector) SendToGateway(ctx context.Context, gatewayID string, msg *api.Message) error { data, err := c.codec.EncodeResponse(msg) if err != nil { - return fmt.Errorf("error encoding response for gateway %s: %v", gatewayId, err) + return fmt.Errorf("error encoding response for gateway %s: %w", gatewayID, err) } - gateway, ok := c.gateways[gatewayId] + gateway, ok := c.gateways[gatewayID] if !ok { - return fmt.Errorf("invalid Gateway ID %s", gatewayId) + return fmt.Errorf("invalid Gateway ID %s", gatewayID) } if gateway.conn == nil { - return fmt.Errorf("connector not started") + return errors.New("connector not started") } return gateway.conn.Write(ctx, websocket.BinaryMessage, data) } @@ -242,10 +270,15 @@ func (c *gatewayConnector) reconnectLoop(gatewayState *gatewayState) { } else { c.lggr.Infow("connected successfully", "url", gatewayState.url) closeCh := gatewayState.conn.Reset(conn) + gatewayState.signal() <-closeCh c.lggr.Infow("connection closed", "url", gatewayState.url) + // reset backoff redialBackoff = utils.NewRedialBackoff() + + // reset signal channel + gatewayState.signalCh = make(chan struct{}) } select { case <-c.shutdownCh: diff --git a/core/services/gateway/connector/mocks/gateway_connector.go b/core/services/gateway/connector/mocks/gateway_connector.go index 183fc949cd5..ba5c2213b5f 100644 --- a/core/services/gateway/connector/mocks/gateway_connector.go +++ b/core/services/gateway/connector/mocks/gateway_connector.go @@ -73,6 +73,53 @@ func (_c *GatewayConnector_AddHandler_Call) RunAndReturn(run func([]string, conn return _c } +// AwaitConnection provides a mock function with given fields: ctx, gatewayID +func (_m *GatewayConnector) AwaitConnection(ctx context.Context, gatewayID string) error { + ret := _m.Called(ctx, gatewayID) + + if len(ret) == 0 { + panic("no return value specified for AwaitConnection") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { + r0 = rf(ctx, gatewayID) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// GatewayConnector_AwaitConnection_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'AwaitConnection' +type GatewayConnector_AwaitConnection_Call struct { + *mock.Call +} + +// AwaitConnection is a helper method to define mock.On call +// - ctx context.Context +// - gatewayID string +func (_e *GatewayConnector_Expecter) AwaitConnection(ctx interface{}, gatewayID interface{}) *GatewayConnector_AwaitConnection_Call { + return &GatewayConnector_AwaitConnection_Call{Call: _e.mock.On("AwaitConnection", ctx, gatewayID)} +} + +func (_c *GatewayConnector_AwaitConnection_Call) Run(run func(ctx context.Context, gatewayID string)) *GatewayConnector_AwaitConnection_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string)) + }) + return _c +} + +func (_c *GatewayConnector_AwaitConnection_Call) Return(_a0 error) *GatewayConnector_AwaitConnection_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *GatewayConnector_AwaitConnection_Call) RunAndReturn(run func(context.Context, string) error) *GatewayConnector_AwaitConnection_Call { + _c.Call.Return(run) + return _c +} + // ChallengeResponse provides a mock function with given fields: _a0, challenge func (_m *GatewayConnector) ChallengeResponse(_a0 *url.URL, challenge []byte) ([]byte, error) { ret := _m.Called(_a0, challenge) @@ -464,9 +511,9 @@ func (_c *GatewayConnector_Ready_Call) RunAndReturn(run func() error) *GatewayCo return _c } -// SendToGateway provides a mock function with given fields: ctx, gatewayId, msg -func (_m *GatewayConnector) SendToGateway(ctx context.Context, gatewayId string, msg *api.Message) error { - ret := _m.Called(ctx, gatewayId, msg) +// SendToGateway provides a mock function with given fields: ctx, gatewayID, msg +func (_m *GatewayConnector) SendToGateway(ctx context.Context, gatewayID string, msg *api.Message) error { + ret := _m.Called(ctx, gatewayID, msg) if len(ret) == 0 { panic("no return value specified for SendToGateway") @@ -474,7 +521,7 @@ func (_m *GatewayConnector) SendToGateway(ctx context.Context, gatewayId string, var r0 error if rf, ok := ret.Get(0).(func(context.Context, string, *api.Message) error); ok { - r0 = rf(ctx, gatewayId, msg) + r0 = rf(ctx, gatewayID, msg) } else { r0 = ret.Error(0) } @@ -489,13 +536,13 @@ type GatewayConnector_SendToGateway_Call struct { // SendToGateway is a helper method to define mock.On call // - ctx context.Context -// - gatewayId string +// - gatewayID string // - msg *api.Message -func (_e *GatewayConnector_Expecter) SendToGateway(ctx interface{}, gatewayId interface{}, msg interface{}) *GatewayConnector_SendToGateway_Call { - return &GatewayConnector_SendToGateway_Call{Call: _e.mock.On("SendToGateway", ctx, gatewayId, msg)} +func (_e *GatewayConnector_Expecter) SendToGateway(ctx interface{}, gatewayID interface{}, msg interface{}) *GatewayConnector_SendToGateway_Call { + return &GatewayConnector_SendToGateway_Call{Call: _e.mock.On("SendToGateway", ctx, gatewayID, msg)} } -func (_c *GatewayConnector_SendToGateway_Call) Run(run func(ctx context.Context, gatewayId string, msg *api.Message)) *GatewayConnector_SendToGateway_Call { +func (_c *GatewayConnector_SendToGateway_Call) Run(run func(ctx context.Context, gatewayID string, msg *api.Message)) *GatewayConnector_SendToGateway_Call { _c.Call.Run(func(args mock.Arguments) { run(args[0].(context.Context), args[1].(string), args[2].(*api.Message)) }) diff --git a/core/services/job/job_orm_test.go b/core/services/job/job_orm_test.go index fd54a39d431..27223e0d706 100644 --- a/core/services/job/job_orm_test.go +++ b/core/services/job/job_orm_test.go @@ -43,6 +43,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/services/ocrbootstrap" "github.com/smartcontractkit/chainlink/v2/core/services/pipeline" "github.com/smartcontractkit/chainlink/v2/core/services/relay" + "github.com/smartcontractkit/chainlink/v2/core/services/streams" "github.com/smartcontractkit/chainlink/v2/core/services/vrf/vrfcommon" "github.com/smartcontractkit/chainlink/v2/core/services/webhook" "github.com/smartcontractkit/chainlink/v2/core/services/workflows/syncer" @@ -444,6 +445,18 @@ func TestORM_DeleteJob_DeletesAssociatedRecords(t *testing.T) { cltest.AssertCount(t, db, "jobs", 0) }) + t.Run("it creates and deletes records for stream jobs", func(t *testing.T) { + ctx := testutils.Context(t) + jb, err := streams.ValidatedStreamSpec(testspecs.GenerateStreamSpec(testspecs.StreamSpecParams{Name: "Test-stream", StreamID: 1}).Toml()) + require.NoError(t, err) + err = jobORM.CreateJob(ctx, &jb) + require.NoError(t, err) + cltest.AssertCount(t, db, "jobs", 1) + err = jobORM.DeleteJob(ctx, jb.ID, jb.Type) + require.NoError(t, err) + cltest.AssertCount(t, db, "jobs", 0) + }) + t.Run("does not allow to delete external initiators if they have referencing external_initiator_webhook_specs", func(t *testing.T) { // create new db because this will rollback transaction and poison it db := pgtest.NewSqlxDB(t) diff --git a/core/services/job/orm.go b/core/services/job/orm.go index 38e3fa492ce..cfd8060d60c 100644 --- a/core/services/job/orm.go +++ b/core/services/job/orm.go @@ -747,6 +747,7 @@ func (o *orm) DeleteJob(ctx context.Context, id int32, jobType Type) error { Workflow: `DELETE FROM workflow_specs WHERE id in (SELECT workflow_spec_id FROM deleted_jobs)`, StandardCapabilities: `DELETE FROM standardcapabilities_specs WHERE id in (SELECT standard_capabilities_spec_id FROM deleted_jobs)`, CCIP: `DELETE FROM ccip_specs WHERE id in (SELECT ccip_spec_id FROM deleted_jobs)`, + Stream: ``, } q, ok := queries[jobType] if !ok { @@ -757,7 +758,7 @@ func (o *orm) DeleteJob(ctx context.Context, id int32, jobType Type) error { // and this query was taking ~40secs. ctx, cancel := context.WithTimeout(sqlutil.WithoutDefaultTimeout(ctx), time.Minute) defer cancel() - query := fmt.Sprintf(` + query := ` WITH deleted_jobs AS ( DELETE FROM jobs WHERE id = $1 RETURNING id, @@ -775,15 +776,19 @@ func (o *orm) DeleteJob(ctx context.Context, id int32, jobType Type) error { gateway_spec_id, workflow_spec_id, standard_capabilities_spec_id, - ccip_spec_id - ), - deleted_specific_specs AS ( - %s - ), + ccip_spec_id, + stream_id + ),` + if len(q) > 0 { + query += fmt.Sprintf(`deleted_specific_specs AS ( + %s + ),`, q) + } + query += ` deleted_job_pipeline_specs AS ( DELETE FROM job_pipeline_specs WHERE job_id IN (SELECT id FROM deleted_jobs) RETURNING pipeline_spec_id ) - DELETE FROM pipeline_specs WHERE id IN (SELECT pipeline_spec_id FROM deleted_job_pipeline_specs)`, q) + DELETE FROM pipeline_specs WHERE id IN (SELECT pipeline_spec_id FROM deleted_job_pipeline_specs)` res, err := o.ds.ExecContext(ctx, query, id) if err != nil { return errors.Wrap(err, "DeleteJob failed to delete job") diff --git a/core/services/llo/mercurytransmitter/server.go b/core/services/llo/mercurytransmitter/server.go index 4e97c0483b3..3ce2b0a4b4a 100644 --- a/core/services/llo/mercurytransmitter/server.go +++ b/core/services/llo/mercurytransmitter/server.go @@ -62,6 +62,22 @@ var ( }, []string{"donID", "serverURL", "code"}, ) + promTransmitConcurrentTransmitGauge = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: "llo", + Subsystem: "mercurytransmitter", + Name: "concurrent_transmit_gauge", + Help: "Gauge that measures the number of transmit threads currently waiting on a remote transmit call. You may wish to alert if this exceeds some number for a given period of time, or if it ever reaches its max.", + }, + []string{"donID", "serverURL"}, + ) + promTransmitConcurrentDeleteGauge = prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: "llo", + Subsystem: "mercurytransmitter", + Name: "concurrent_delete_gauge", + Help: "Gauge that measures the number of delete threads currently waiting on a delete call to the DB. You may wish to alert if this exceeds some number for a given period of time, or if it ever reaches its max.", + }, + []string{"donID", "serverURL"}, + ) ) type ReportPacker interface { @@ -87,12 +103,14 @@ type server struct { evmPremiumLegacyPacker ReportPacker jsonPacker ReportPacker - transmitSuccessCount prometheus.Counter - transmitDuplicateCount prometheus.Counter - transmitConnectionErrorCount prometheus.Counter - transmitQueueDeleteErrorCount prometheus.Counter - transmitQueueInsertErrorCount prometheus.Counter - transmitQueuePushErrorCount prometheus.Counter + transmitSuccessCount prometheus.Counter + transmitDuplicateCount prometheus.Counter + transmitConnectionErrorCount prometheus.Counter + transmitQueueDeleteErrorCount prometheus.Counter + transmitQueueInsertErrorCount prometheus.Counter + transmitQueuePushErrorCount prometheus.Counter + transmitConcurrentTransmitGauge prometheus.Gauge + transmitConcurrentDeleteGauge prometheus.Gauge transmitThreadBusyCount atomic.Int32 deleteThreadBusyCount atomic.Int32 @@ -130,6 +148,8 @@ func newServer(lggr logger.Logger, verboseLogging bool, cfg QueueConfig, client promTransmitQueueDeleteErrorCount.WithLabelValues(donIDStr, serverURL), promTransmitQueueInsertErrorCount.WithLabelValues(donIDStr, serverURL), promTransmitQueuePushErrorCount.WithLabelValues(donIDStr, serverURL), + promTransmitConcurrentTransmitGauge.WithLabelValues(donIDStr, serverURL), + promTransmitConcurrentDeleteGauge.WithLabelValues(donIDStr, serverURL), atomic.Int32{}, atomic.Int32{}, } @@ -161,7 +181,7 @@ func (s *server) runDeleteQueueLoop(stopCh services.StopChan, wg *sync.WaitGroup select { case hash := <-s.deleteQueue: for { - s.deleteThreadBusyCount.Add(1) + s.deleteThreadBusyCountInc() if err := s.pm.orm.Delete(ctx, [][32]byte{hash}); err != nil { s.lggr.Errorw("Failed to delete transmission record", "err", err, "transmissionHash", hash) s.transmitQueueDeleteErrorCount.Inc() @@ -170,7 +190,7 @@ func (s *server) runDeleteQueueLoop(stopCh services.StopChan, wg *sync.WaitGroup // Wait a backoff duration before trying to delete again continue case <-stopCh: - s.deleteThreadBusyCount.Add(-1) + s.deleteThreadBusyCountDec() // abort and return immediately on stop even if items remain in queue return } @@ -179,7 +199,7 @@ func (s *server) runDeleteQueueLoop(stopCh services.StopChan, wg *sync.WaitGroup } // success b.Reset() - s.deleteThreadBusyCount.Add(-1) + s.deleteThreadBusyCountDec() case <-stopCh: // abort and return immediately on stop even if items remain in queue return @@ -187,6 +207,23 @@ func (s *server) runDeleteQueueLoop(stopCh services.StopChan, wg *sync.WaitGroup } } +func (s *server) transmitThreadBusyCountInc() { + val := s.transmitThreadBusyCount.Add(1) + s.transmitConcurrentTransmitGauge.Set(float64(val)) +} +func (s *server) transmitThreadBusyCountDec() { + val := s.transmitThreadBusyCount.Add(-1) + s.transmitConcurrentTransmitGauge.Set(float64(val)) +} +func (s *server) deleteThreadBusyCountInc() { + val := s.deleteThreadBusyCount.Add(1) + s.transmitConcurrentDeleteGauge.Set(float64(val)) +} +func (s *server) deleteThreadBusyCountDec() { + val := s.deleteThreadBusyCount.Add(-1) + s.transmitConcurrentDeleteGauge.Set(float64(val)) +} + func (s *server) runQueueLoop(stopCh services.StopChan, wg *sync.WaitGroup, donIDStr string) { defer wg.Done() // Exponential backoff with very short retry interval (since latency is a priority) @@ -208,8 +245,8 @@ func (s *server) runQueueLoop(stopCh services.StopChan, wg *sync.WaitGroup, donI return false } - s.transmitThreadBusyCount.Add(1) - defer s.transmitThreadBusyCount.Add(-1) + s.transmitThreadBusyCountInc() + defer s.transmitThreadBusyCountDec() req, res, err := func(ctx context.Context) (*pb.TransmitRequest, *pb.TransmitResponse, error) { ctx, cancelFn := context.WithTimeout(ctx, utils.WithJitter(s.transmitTimeout)) diff --git a/core/services/llo/mercurytransmitter/transmitter.go b/core/services/llo/mercurytransmitter/transmitter.go index 8e60bf938a5..23aa4b79e58 100644 --- a/core/services/llo/mercurytransmitter/transmitter.go +++ b/core/services/llo/mercurytransmitter/transmitter.go @@ -116,7 +116,6 @@ type transmitter struct { orm ORM servers map[string]*server registerer prometheus.Registerer - collectors []prometheus.Collector donID uint32 fromAccount string @@ -155,7 +154,6 @@ func newTransmitter(opts Opts) *transmitter { opts.ORM, servers, opts.Registerer, - nil, opts.DonID, fmt.Sprintf("%x", opts.FromAccount), make(services.StopChan), @@ -194,31 +192,6 @@ func (mt *transmitter) Start(ctx context.Context) (err error) { go s.runDeleteQueueLoop(mt.stopCh, mt.wg) go s.runQueueLoop(mt.stopCh, mt.wg, donIDStr) } - mt.collectors = append(mt.collectors, prometheus.NewGaugeFunc( - prometheus.GaugeOpts{ - Namespace: "llo", - Subsystem: "mercurytransmitter", - Name: "concurrent_transmit_gauge", - Help: "Gauge that measures the number of transmit threads currently waiting on a remote transmit call. You may wish to alert if this exceeds some number for a given period of time, or if it ever reaches its max.", - ConstLabels: prometheus.Labels{"donID": donIDStr, "serverURL": s.url, "maxConcurrentTransmits": strconv.FormatInt(int64(nThreads), 10)}, - }, func() float64 { - return float64(s.transmitThreadBusyCount.Load()) - })) - mt.collectors = append(mt.collectors, prometheus.NewGaugeFunc( - prometheus.GaugeOpts{ - Namespace: "llo", - Subsystem: "mercurytransmitter", - Name: "concurrent_delete_gauge", - Help: "Gauge that measures the number of delete threads currently waiting on a delete call to the DB. You may wish to alert if this exceeds some number for a given period of time, or if it ever reaches its max.", - ConstLabels: prometheus.Labels{"donID": donIDStr, "serverURL": s.url, "maxConcurrentDeletes": strconv.FormatInt(int64(nThreads), 10)}, - }, func() float64 { - return float64(s.deleteThreadBusyCount.Load()) - })) - for _, c := range mt.collectors { - if err := mt.registerer.Register(c); err != nil { - return err - } - } } if err := (&services.MultiStart{}).Start(ctx, startClosers...); err != nil { return err @@ -250,12 +223,7 @@ func (mt *transmitter) Close() error { closers = append(closers, s.pm) closers = append(closers, s.c) } - err := services.CloseAll(closers...) - // Unregister all the gauge funcs - for _, c := range mt.collectors { - mt.registerer.Unregister(c) - } - return err + return services.CloseAll(closers...) }) } diff --git a/core/services/ocr2/plugins/mercury/plugin.go b/core/services/ocr2/plugins/mercury/plugin.go index 8a4101804dd..b0983e55c89 100644 --- a/core/services/ocr2/plugins/mercury/plugin.go +++ b/core/services/ocr2/plugins/mercury/plugin.go @@ -1,6 +1,7 @@ package mercury import ( + "context" "encoding/json" "fmt" "os/exec" @@ -79,14 +80,13 @@ func NewServices( return nil, errors.New("expected job to have a non-nil PipelineSpec") } - var err error var pluginConfig config.PluginConfig if len(jb.OCR2OracleSpec.PluginConfig) == 0 { if !enableTriggerCapability { return nil, fmt.Errorf("at least one transmission option must be configured") } } else { - err = json.Unmarshal(jb.OCR2OracleSpec.PluginConfig.Bytes(), &pluginConfig) + err := json.Unmarshal(jb.OCR2OracleSpec.PluginConfig.Bytes(), &pluginConfig) if err != nil { return nil, errors.WithStack(err) } @@ -101,8 +101,8 @@ func NewServices( // encapsulate all the subservices and ensure we close them all if any fail to start srvs := []job.ServiceCtx{ocr2Provider} abort := func() { - if err = services.MultiCloser(srvs).Close(); err != nil { - lggr.Errorw("Error closing unused services", "err", err) + if cerr := services.MultiCloser(srvs).Close(); cerr != nil { + lggr.Errorw("Error closing unused services", "err", cerr) } } saver := ocrcommon.NewResultRunSaver(pipelineRunner, lggr, cfg.MaxSuccessfulRuns(), cfg.ResultWriteQueueDepth()) @@ -112,6 +112,7 @@ func NewServices( var ( factory ocr3types.MercuryPluginFactory factoryServices []job.ServiceCtx + fErr error ) fCfg := factoryCfg{ orm: orm, @@ -127,31 +128,31 @@ func NewServices( } switch feedID.Version() { case 1: - factory, factoryServices, err = newv1factory(fCfg) - if err != nil { + factory, factoryServices, fErr = newv1factory(fCfg) + if fErr != nil { abort() - return nil, fmt.Errorf("failed to create mercury v1 factory: %w", err) + return nil, fmt.Errorf("failed to create mercury v1 factory: %w", fErr) } srvs = append(srvs, factoryServices...) case 2: - factory, factoryServices, err = newv2factory(fCfg) - if err != nil { + factory, factoryServices, fErr = newv2factory(fCfg) + if fErr != nil { abort() - return nil, fmt.Errorf("failed to create mercury v2 factory: %w", err) + return nil, fmt.Errorf("failed to create mercury v2 factory: %w", fErr) } srvs = append(srvs, factoryServices...) case 3: - factory, factoryServices, err = newv3factory(fCfg) - if err != nil { + factory, factoryServices, fErr = newv3factory(fCfg) + if fErr != nil { abort() - return nil, fmt.Errorf("failed to create mercury v3 factory: %w", err) + return nil, fmt.Errorf("failed to create mercury v3 factory: %w", fErr) } srvs = append(srvs, factoryServices...) case 4: - factory, factoryServices, err = newv4factory(fCfg) - if err != nil { + factory, factoryServices, fErr = newv4factory(fCfg) + if fErr != nil { abort() - return nil, fmt.Errorf("failed to create mercury v4 factory: %w", err) + return nil, fmt.Errorf("failed to create mercury v4 factory: %w", fErr) } srvs = append(srvs, factoryServices...) default: @@ -214,13 +215,14 @@ func newv4factory(factoryCfg factoryCfg) (ocr3types.MercuryPluginFactory, []job. loopEnabled := loopCmd != "" if loopEnabled { - cmdFn, opts, mercuryLggr, err := initLoop(loopCmd, factoryCfg.cfg, factoryCfg.feedID, factoryCfg.lggr) + cmdFn, unregisterer, opts, mercuryLggr, err := initLoop(loopCmd, factoryCfg.cfg, factoryCfg.feedID, factoryCfg.lggr) if err != nil { return nil, nil, fmt.Errorf("failed to init loop for feed %s: %w", factoryCfg.feedID, err) } // in loop mode, the factory is grpc server, and we need to handle the server lifecycle + // and unregistration of the loop factoryServer := loop.NewMercuryV4Service(mercuryLggr, opts, cmdFn, factoryCfg.ocr2Provider, ds) - srvs = append(srvs, factoryServer) + srvs = append(srvs, factoryServer, unregisterer) // adapt the grpc server to the vanilla mercury plugin factory interface used by the oracle factory = factoryServer } else { @@ -253,13 +255,14 @@ func newv3factory(factoryCfg factoryCfg) (ocr3types.MercuryPluginFactory, []job. loopEnabled := loopCmd != "" if loopEnabled { - cmdFn, opts, mercuryLggr, err := initLoop(loopCmd, factoryCfg.cfg, factoryCfg.feedID, factoryCfg.lggr) + cmdFn, unregisterer, opts, mercuryLggr, err := initLoop(loopCmd, factoryCfg.cfg, factoryCfg.feedID, factoryCfg.lggr) if err != nil { return nil, nil, fmt.Errorf("failed to init loop for feed %s: %w", factoryCfg.feedID, err) } // in loopp mode, the factory is grpc server, and we need to handle the server lifecycle + // and unregistration of the loop factoryServer := loop.NewMercuryV3Service(mercuryLggr, opts, cmdFn, factoryCfg.ocr2Provider, ds) - srvs = append(srvs, factoryServer) + srvs = append(srvs, factoryServer, unregisterer) // adapt the grpc server to the vanilla mercury plugin factory interface used by the oracle factory = factoryServer } else { @@ -292,13 +295,14 @@ func newv2factory(factoryCfg factoryCfg) (ocr3types.MercuryPluginFactory, []job. loopEnabled := loopCmd != "" if loopEnabled { - cmdFn, opts, mercuryLggr, err := initLoop(loopCmd, factoryCfg.cfg, factoryCfg.feedID, factoryCfg.lggr) + cmdFn, unregisterer, opts, mercuryLggr, err := initLoop(loopCmd, factoryCfg.cfg, factoryCfg.feedID, factoryCfg.lggr) if err != nil { return nil, nil, fmt.Errorf("failed to init loop for feed %s: %w", factoryCfg.feedID, err) } // in loopp mode, the factory is grpc server, and we need to handle the server lifecycle + // and unregistration of the loop factoryServer := loop.NewMercuryV2Service(mercuryLggr, opts, cmdFn, factoryCfg.ocr2Provider, ds) - srvs = append(srvs, factoryServer) + srvs = append(srvs, factoryServer, unregisterer) // adapt the grpc server to the vanilla mercury plugin factory interface used by the oracle factory = factoryServer } else { @@ -329,13 +333,14 @@ func newv1factory(factoryCfg factoryCfg) (ocr3types.MercuryPluginFactory, []job. loopEnabled := loopCmd != "" if loopEnabled { - cmdFn, opts, mercuryLggr, err := initLoop(loopCmd, factoryCfg.cfg, factoryCfg.feedID, factoryCfg.lggr) + cmdFn, unregisterer, opts, mercuryLggr, err := initLoop(loopCmd, factoryCfg.cfg, factoryCfg.feedID, factoryCfg.lggr) if err != nil { return nil, nil, fmt.Errorf("failed to init loop for feed %s: %w", factoryCfg.feedID, err) } // in loopp mode, the factory is grpc server, and we need to handle the server lifecycle + // and unregistration of the loop factoryServer := loop.NewMercuryV1Service(mercuryLggr, opts, cmdFn, factoryCfg.ocr2Provider, ds) - srvs = append(srvs, factoryServer) + srvs = append(srvs, factoryServer, unregisterer) // adapt the grpc server to the vanilla mercury plugin factory interface used by the oracle factory = factoryServer } else { @@ -344,20 +349,46 @@ func newv1factory(factoryCfg factoryCfg) (ocr3types.MercuryPluginFactory, []job. return factory, srvs, nil } -func initLoop(cmd string, cfg Config, feedID utils.FeedID, lggr logger.Logger) (func() *exec.Cmd, loop.GRPCOpts, logger.Logger, error) { +func initLoop(cmd string, cfg Config, feedID utils.FeedID, lggr logger.Logger) (func() *exec.Cmd, *loopUnregisterCloser, loop.GRPCOpts, logger.Logger, error) { lggr.Debugw("Initializing Mercury loop", "command", cmd) mercuryLggr := lggr.Named(fmt.Sprintf("MercuryV%d", feedID.Version())).Named(feedID.String()) envVars, err := plugins.ParseEnvFile(env.MercuryPlugin.Env.Get()) if err != nil { - return nil, loop.GRPCOpts{}, nil, fmt.Errorf("failed to parse mercury env file: %w", err) + return nil, nil, loop.GRPCOpts{}, nil, fmt.Errorf("failed to parse mercury env file: %w", err) } + loopID := mercuryLggr.Name() cmdFn, opts, err := cfg.RegisterLOOP(plugins.CmdConfig{ - ID: mercuryLggr.Name(), + ID: loopID, Cmd: cmd, Env: envVars, }) if err != nil { - return nil, loop.GRPCOpts{}, nil, fmt.Errorf("failed to register loop: %w", err) + return nil, nil, loop.GRPCOpts{}, nil, fmt.Errorf("failed to register loop: %w", err) + } + return cmdFn, newLoopUnregister(cfg, loopID), opts, mercuryLggr, nil +} + +// loopUnregisterCloser is a helper to unregister a loop +// as a service +// TODO BCF-3451 all other jobs that use custom plugin providers that should be refactored to use this pattern +// perhaps it can be implemented in the delegate on job delete. +type loopUnregisterCloser struct { + r plugins.RegistrarConfig + id string +} + +func (l *loopUnregisterCloser) Close() error { + l.r.UnregisterLOOP(l.id) + return nil +} + +func (l *loopUnregisterCloser) Start(ctx context.Context) error { + return nil +} + +func newLoopUnregister(r plugins.RegistrarConfig, id string) *loopUnregisterCloser { + return &loopUnregisterCloser{ + r: r, + id: id, } - return cmdFn, opts, mercuryLggr, nil } diff --git a/core/services/ocr2/plugins/mercury/plugin_test.go b/core/services/ocr2/plugins/mercury/plugin_test.go index 22aaf7522de..eb67da53100 100644 --- a/core/services/ocr2/plugins/mercury/plugin_test.go +++ b/core/services/ocr2/plugins/mercury/plugin_test.go @@ -2,6 +2,7 @@ package mercury_test import ( "context" + "errors" "os/exec" "reflect" "testing" @@ -9,6 +10,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/google/uuid" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/smartcontractkit/chainlink/v2/core/config/env" "github.com/smartcontractkit/chainlink/v2/core/logger" @@ -22,6 +24,7 @@ import ( v2 "github.com/smartcontractkit/chainlink-common/pkg/types/mercury/v2" v3 "github.com/smartcontractkit/chainlink-common/pkg/types/mercury/v3" v4 "github.com/smartcontractkit/chainlink-common/pkg/types/mercury/v4" + "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" mercuryocr2 "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/mercury" @@ -92,21 +95,23 @@ var ( // this is kind of gross, but it's the best way to test return values of the services expectedEmbeddedServiceCnt = 3 - expectedLoopServiceCnt = expectedEmbeddedServiceCnt + 1 + expectedLoopServiceCnt = expectedEmbeddedServiceCnt + 2 // factory server and loop unregisterer ) func TestNewServices(t *testing.T) { type args struct { pluginConfig job.JSONConfig feedID utils.FeedID + cfg mercuryocr2.Config } - tests := []struct { + testCases := []struct { name string args args loopMode bool wantLoopFactory any wantServiceCnt int wantErr bool + wantErrStr string }{ { name: "no plugin config error ", @@ -186,6 +191,19 @@ func TestNewServices(t *testing.T) { wantErr: false, wantLoopFactory: &loop.MercuryV3Service{}, }, + { + name: "v3 loop err", + loopMode: true, + args: args{ + pluginConfig: v3jsonCfg, + feedID: v3FeedId, + cfg: mercuryocr2.NewMercuryConfig(1, 1, &testRegistrarConfig{failRegister: true}), + }, + wantServiceCnt: expectedLoopServiceCnt, + wantErr: true, + wantLoopFactory: &loop.MercuryV3Service{}, + wantErrStr: "failed to init loop for feed", + }, { name: "v4 loop", loopMode: true, @@ -198,17 +216,27 @@ func TestNewServices(t *testing.T) { wantLoopFactory: &loop.MercuryV4Service{}, }, } - for _, tt := range tests { + for _, tt := range testCases { t.Run(tt.name, func(t *testing.T) { if tt.loopMode { t.Setenv(string(env.MercuryPlugin.Cmd), "fake_cmd") assert.NotEmpty(t, env.MercuryPlugin.Cmd.Get()) } - got, err := newServicesTestWrapper(t, tt.args.pluginConfig, tt.args.feedID) + // use default config if not provided + if tt.args.cfg == nil { + tt.args.cfg = testCfg + } + got, err := newServicesTestWrapper(t, tt.args.pluginConfig, tt.args.feedID, tt.args.cfg) if (err != nil) != tt.wantErr { t.Errorf("NewServices() error = %v, wantErr %v", err, tt.wantErr) return } + if err != nil { + if tt.wantErrStr != "" { + assert.Contains(t, err.Error(), tt.wantErrStr) + } + return + } assert.Len(t, got, tt.wantServiceCnt) if tt.loopMode { foundLoopFactory := false @@ -222,15 +250,97 @@ func TestNewServices(t *testing.T) { } }) } + + t.Run("restartable loop", func(t *testing.T) { + // setup a real loop registry to test restartability + registry := plugins.NewLoopRegistry(logger.TestLogger(t), nil, nil, nil, "") + loopRegistrarConfig := plugins.NewRegistrarConfig(loop.GRPCOpts{}, registry.Register, registry.Unregister) + prodCfg := mercuryocr2.NewMercuryConfig(1, 1, loopRegistrarConfig) + type args struct { + pluginConfig job.JSONConfig + feedID utils.FeedID + cfg mercuryocr2.Config + } + testCases := []struct { + name string + args args + wantErr bool + }{ + { + name: "v1 loop", + args: args{ + pluginConfig: v1jsonCfg, + feedID: v1FeedId, + cfg: prodCfg, + }, + wantErr: false, + }, + { + name: "v2 loop", + args: args{ + pluginConfig: v2jsonCfg, + feedID: v2FeedId, + cfg: prodCfg, + }, + wantErr: false, + }, + { + name: "v3 loop", + args: args{ + pluginConfig: v3jsonCfg, + feedID: v3FeedId, + cfg: prodCfg, + }, + wantErr: false, + }, + { + name: "v4 loop", + args: args{ + pluginConfig: v4jsonCfg, + feedID: v4FeedId, + cfg: prodCfg, + }, + wantErr: false, + }, + } + + for _, tt := range testCases { + t.Run(tt.name, func(t *testing.T) { + t.Setenv(string(env.MercuryPlugin.Cmd), "fake_cmd") + assert.NotEmpty(t, env.MercuryPlugin.Cmd.Get()) + + got, err := newServicesTestWrapper(t, tt.args.pluginConfig, tt.args.feedID, tt.args.cfg) + if (err != nil) != tt.wantErr { + t.Errorf("NewServices() error = %v, wantErr %v", err, tt.wantErr) + return + } + // hack to simulate a restart. we don't have enough boilerplate to start the oracle service + // only care about the subservices so we start all except the oracle, which happens to be the last one + for i := 0; i < len(got)-1; i++ { + require.NoError(t, got[i].Start(tests.Context(t))) + } + // if we don't close the services, we get conflicts with the loop registry + _, err = newServicesTestWrapper(t, tt.args.pluginConfig, tt.args.feedID, tt.args.cfg) + require.ErrorContains(t, err, "plugin already registered") + + // close all services and try again + for i := len(got) - 2; i >= 0; i-- { + require.NoError(t, got[i].Close()) + } + _, err = newServicesTestWrapper(t, tt.args.pluginConfig, tt.args.feedID, tt.args.cfg) + require.NoError(t, err) + }) + } + }) } // we are only varying the version via feedID (and the plugin config) // this wrapper supplies dummy values for the rest of the arguments -func newServicesTestWrapper(t *testing.T, pluginConfig job.JSONConfig, feedID utils.FeedID) ([]job.ServiceCtx, error) { +func newServicesTestWrapper(t *testing.T, pluginConfig job.JSONConfig, feedID utils.FeedID, cfg mercuryocr2.Config) ([]job.ServiceCtx, error) { t.Helper() jb := testJob jb.OCR2OracleSpec.PluginConfig = pluginConfig - return mercuryocr2.NewServices(jb, &testProvider{}, nil, logger.TestLogger(t), testArgsNoPlugin, testCfg, nil, &testDataSourceORM{}, feedID, false) + return mercuryocr2.NewServices(jb, &testProvider{}, nil, logger.TestLogger(t), testArgsNoPlugin, cfg, nil, &testDataSourceORM{}, feedID, false) } type testProvider struct{} @@ -292,16 +402,21 @@ func (*testProvider) ReportCodecV3() v3.ReportCodec { return nil } func (*testProvider) ReportCodecV4() v4.ReportCodec { return nil } // Start implements types.MercuryProvider. -func (*testProvider) Start(context.Context) error { panic("unimplemented") } +func (*testProvider) Start(context.Context) error { return nil } var _ commontypes.MercuryProvider = (*testProvider)(nil) -type testRegistrarConfig struct{} +type testRegistrarConfig struct { + failRegister bool +} func (c *testRegistrarConfig) UnregisterLOOP(ID string) {} // RegisterLOOP implements plugins.RegistrarConfig. -func (*testRegistrarConfig) RegisterLOOP(config plugins.CmdConfig) (func() *exec.Cmd, loop.GRPCOpts, error) { +func (c *testRegistrarConfig) RegisterLOOP(config plugins.CmdConfig) (func() *exec.Cmd, loop.GRPCOpts, error) { + if c.failRegister { + return nil, loop.GRPCOpts{}, errors.New("failed to register") + } return nil, loop.GRPCOpts{}, nil } diff --git a/core/services/ocr2/plugins/ocr2keeper/integration_test.go b/core/services/ocr2/plugins/ocr2keeper/integration_test.go index 66112756370..2283559365c 100644 --- a/core/services/ocr2/plugins/ocr2keeper/integration_test.go +++ b/core/services/ocr2/plugins/ocr2keeper/integration_test.go @@ -29,6 +29,8 @@ import ( "github.com/smartcontractkit/chainlink-automation/pkg/v2/config" commonconfig "github.com/smartcontractkit/chainlink-common/pkg/config" "github.com/smartcontractkit/chainlink-common/pkg/types" + "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" + evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" @@ -200,7 +202,7 @@ func getUpkeepIDFromTx(t *testing.T, registry *keeper_registry_wrapper2_0.Keeper } func TestIntegration_KeeperPluginBasic(t *testing.T) { - testutils.SkipFlakey(t, "https://smartcontract-it.atlassian.net/browse/AUTO-11072") + tests.SkipFlakey(t, "https://smartcontract-it.atlassian.net/browse/AUTO-11072") runKeeperPluginBasic(t) } diff --git a/core/services/ocr3/promwrapper/factory.go b/core/services/ocr3/promwrapper/factory.go index 6518cea3c0d..e369b3260ef 100644 --- a/core/services/ocr3/promwrapper/factory.go +++ b/core/services/ocr3/promwrapper/factory.go @@ -47,6 +47,7 @@ func (r ReportingPluginFactory[RI]) NewReportingPlugin(ctx context.Context, conf config.ConfigDigest.String(), promOCR3ReportsGenerated, promOCR3Durations, + promOCR3Sizes, promOCR3PluginStatus, ) return wrapped, info, err diff --git a/core/services/ocr3/promwrapper/plugin.go b/core/services/ocr3/promwrapper/plugin.go index dcee5050d1e..aa5fb87a6ee 100644 --- a/core/services/ocr3/promwrapper/plugin.go +++ b/core/services/ocr3/promwrapper/plugin.go @@ -21,6 +21,7 @@ type reportingPlugin[RI any] struct { // Prometheus components for tracking metrics reportsGenerated *prometheus.CounterVec durations *prometheus.HistogramVec + sizes *prometheus.CounterVec status *prometheus.GaugeVec } @@ -31,6 +32,7 @@ func newReportingPlugin[RI any]( configDigest string, reportsGenerated *prometheus.CounterVec, durations *prometheus.HistogramVec, + sizes *prometheus.CounterVec, status *prometheus.GaugeVec, ) *reportingPlugin[RI] { return &reportingPlugin[RI]{ @@ -40,6 +42,7 @@ func newReportingPlugin[RI any]( configDigest: configDigest, reportsGenerated: reportsGenerated, durations: durations, + sizes: sizes, status: status, } } @@ -51,9 +54,11 @@ func (p *reportingPlugin[RI]) Query(ctx context.Context, outctx ocr3types.Outcom } func (p *reportingPlugin[RI]) Observation(ctx context.Context, outctx ocr3types.OutcomeContext, query ocrtypes.Query) (ocrtypes.Observation, error) { - return withObservedExecution(p, observation, func() (ocrtypes.Observation, error) { + result, err := withObservedExecution(p, observation, func() (ocrtypes.Observation, error) { return p.ReportingPlugin.Observation(ctx, outctx, query) }) + p.trackSize(observation, len(result), err) + return result, err } func (p *reportingPlugin[RI]) ValidateObservation(ctx context.Context, outctx ocr3types.OutcomeContext, query ocrtypes.Query, ao ocrtypes.AttributedObservation) error { @@ -65,9 +70,11 @@ func (p *reportingPlugin[RI]) ValidateObservation(ctx context.Context, outctx oc } func (p *reportingPlugin[RI]) Outcome(ctx context.Context, outctx ocr3types.OutcomeContext, query ocrtypes.Query, aos []ocrtypes.AttributedObservation) (ocr3types.Outcome, error) { - return withObservedExecution(p, outcome, func() (ocr3types.Outcome, error) { + result, err := withObservedExecution(p, outcome, func() (ocr3types.Outcome, error) { return p.ReportingPlugin.Outcome(ctx, outctx, query, aos) }) + p.trackSize(outcome, len(result), err) + return result, err } func (p *reportingPlugin[RI]) Reports(ctx context.Context, seqNr uint64, outcome ocr3types.Outcome) ([]ocr3types.ReportPlus[RI], error) { @@ -111,6 +118,15 @@ func (p *reportingPlugin[RI]) updateStatus(status bool) { Set(float64(boolToInt(status))) } +func (p *reportingPlugin[RI]) trackSize(function functionType, size int, err error) { + if err != nil { + return + } + p.sizes. + WithLabelValues(p.chainID, p.plugin, string(function)). + Add(float64(size)) +} + func boolToInt(arg bool) int { if arg { return 1 diff --git a/core/services/ocr3/promwrapper/plugin_test.go b/core/services/ocr3/promwrapper/plugin_test.go index 9a7b6f2e648..a10a467799f 100644 --- a/core/services/ocr3/promwrapper/plugin_test.go +++ b/core/services/ocr3/promwrapper/plugin_test.go @@ -17,17 +17,20 @@ import ( ) func Test_ReportsGeneratedGauge(t *testing.T) { + pluginObservationSize := 5 + pluginOutcomeSize := 3 + plugin1 := newReportingPlugin( fakePlugin[uint]{reports: make([]ocr3types.ReportPlus[uint], 2)}, - "123", "empty", "abc", promOCR3ReportsGenerated, promOCR3Durations, promOCR3PluginStatus, + "123", "empty", "abc", promOCR3ReportsGenerated, promOCR3Durations, promOCR3Sizes, promOCR3PluginStatus, ) plugin2 := newReportingPlugin( - fakePlugin[bool]{reports: make([]ocr3types.ReportPlus[bool], 10)}, - "solana", "different_plugin", "abc", promOCR3ReportsGenerated, promOCR3Durations, promOCR3PluginStatus, + fakePlugin[bool]{reports: make([]ocr3types.ReportPlus[bool], 10), observationSize: pluginObservationSize, outcomeSize: pluginOutcomeSize}, + "solana", "different_plugin", "abc", promOCR3ReportsGenerated, promOCR3Durations, promOCR3Sizes, promOCR3PluginStatus, ) plugin3 := newReportingPlugin( fakePlugin[string]{err: errors.New("error")}, - "1234", "empty", "abc", promOCR3ReportsGenerated, promOCR3Durations, promOCR3PluginStatus, + "1234", "empty", "abc", promOCR3ReportsGenerated, promOCR3Durations, promOCR3Sizes, promOCR3PluginStatus, ) r1, err := plugin1.Reports(tests.Context(t), 1, nil) @@ -64,20 +67,33 @@ func Test_ReportsGeneratedGauge(t *testing.T) { require.NoError(t, plugin1.Close()) pluginHealth = testutil.ToFloat64(promOCR3PluginStatus.WithLabelValues("123", "empty", "abc")) require.Equal(t, 0, int(pluginHealth)) + + iterations := 10 + for i := 0; i < iterations; i++ { + _, err1 := plugin2.Outcome(tests.Context(t), ocr3types.OutcomeContext{}, nil, nil) + require.NoError(t, err1) + } + _, err1 := plugin2.Observation(tests.Context(t), ocr3types.OutcomeContext{}, nil) + require.NoError(t, err1) + + outcomesLen := testutil.ToFloat64(promOCR3Sizes.WithLabelValues("solana", "different_plugin", "outcome")) + require.Equal(t, pluginOutcomeSize*iterations, int(outcomesLen)) + observationLen := testutil.ToFloat64(promOCR3Sizes.WithLabelValues("solana", "different_plugin", "observation")) + require.Equal(t, pluginObservationSize, int(observationLen)) } func Test_DurationHistograms(t *testing.T) { plugin1 := newReportingPlugin( fakePlugin[uint]{}, - "123", "empty", "abc", promOCR3ReportsGenerated, promOCR3Durations, promOCR3PluginStatus, + "123", "empty", "abc", promOCR3ReportsGenerated, promOCR3Durations, promOCR3Sizes, promOCR3PluginStatus, ) plugin2 := newReportingPlugin( fakePlugin[uint]{err: errors.New("error")}, - "123", "empty", "abc", promOCR3ReportsGenerated, promOCR3Durations, promOCR3PluginStatus, + "123", "empty", "abc", promOCR3ReportsGenerated, promOCR3Durations, promOCR3Sizes, promOCR3PluginStatus, ) plugin3 := newReportingPlugin( fakePlugin[uint]{}, - "solana", "commit", "abc", promOCR3ReportsGenerated, promOCR3Durations, promOCR3PluginStatus, + "solana", "commit", "abc", promOCR3ReportsGenerated, promOCR3Durations, promOCR3Sizes, promOCR3PluginStatus, ) for _, p := range []*reportingPlugin[uint]{plugin1, plugin2, plugin3} { @@ -102,8 +118,10 @@ func Test_DurationHistograms(t *testing.T) { } type fakePlugin[RI any] struct { - reports []ocr3types.ReportPlus[RI] - err error + reports []ocr3types.ReportPlus[RI] + observationSize int + outcomeSize int + err error } func (f fakePlugin[RI]) Query(context.Context, ocr3types.OutcomeContext) (ocrtypes.Query, error) { @@ -117,7 +135,7 @@ func (f fakePlugin[RI]) Observation(context.Context, ocr3types.OutcomeContext, o if f.err != nil { return nil, f.err } - return ocrtypes.Observation{}, nil + return make([]byte, f.observationSize), nil } func (f fakePlugin[RI]) ValidateObservation(context.Context, ocr3types.OutcomeContext, ocrtypes.Query, ocrtypes.AttributedObservation) error { @@ -132,7 +150,7 @@ func (f fakePlugin[RI]) Outcome(context.Context, ocr3types.OutcomeContext, ocrty if f.err != nil { return nil, f.err } - return ocr3types.Outcome{}, nil + return make([]byte, f.outcomeSize), nil } func (f fakePlugin[RI]) Reports(context.Context, uint64, ocr3types.Outcome) ([]ocr3types.ReportPlus[RI], error) { diff --git a/core/services/ocr3/promwrapper/types.go b/core/services/ocr3/promwrapper/types.go index 2fa29dcdf20..59468358783 100644 --- a/core/services/ocr3/promwrapper/types.go +++ b/core/services/ocr3/promwrapper/types.go @@ -48,6 +48,13 @@ var ( }, []string{"chainID", "plugin", "function", "success"}, ) + promOCR3Sizes = promauto.NewCounterVec( + prometheus.CounterOpts{ + Name: "ocr3_reporting_plugin_data_sizes", + Help: "Tracks the size of the data produced by OCR3 plugin in bytes (e.g. reports, observations etc.)", + }, + []string{"chainID", "plugin", "function"}, + ) promOCR3PluginStatus = promauto.NewGaugeVec( prometheus.GaugeOpts{ Name: "ocr3_reporting_plugin_status", diff --git a/core/services/pg/connection.go b/core/services/pg/connection.go index 64a137762fc..bf3663e82ce 100644 --- a/core/services/pg/connection.go +++ b/core/services/pg/connection.go @@ -19,7 +19,7 @@ import ( "go.opentelemetry.io/otel" semconv "go.opentelemetry.io/otel/semconv/v1.4.0" - "github.com/smartcontractkit/chainlink/v2/core/store/dialects" + pgcommon "github.com/smartcontractkit/chainlink-common/pkg/sqlutil/pg" ) // NOTE: This is the default level in Postgres anyway, we just make it @@ -51,7 +51,7 @@ type ConnectionConfig interface { MaxIdleConns() int } -func NewConnection(ctx context.Context, uri string, dialect dialects.DialectName, config ConnectionConfig) (*sqlx.DB, error) { +func NewConnection(ctx context.Context, uri string, dialect pgcommon.DialectName, config ConnectionConfig) (*sqlx.DB, error) { opts := []otelsql.Option{otelsql.WithAttributes(semconv.DBSystemPostgreSQL), otelsql.WithTracerProvider(otel.GetTracerProvider()), otelsql.WithSQLCommenter(true), @@ -70,7 +70,7 @@ func NewConnection(ctx context.Context, uri string, dialect dialects.DialectName lockTimeout, idleInTxSessionTimeout, defaultIsolation.String()) var sqldb *sql.DB - if dialect == dialects.TransactionWrappedPostgres { + if dialect == pgcommon.TransactionWrappedPostgres { // Dbtx uses the uri as a unique identifier for each transaction. Each ORM // should be encapsulated in it's own transaction, and thus needs its own // unique id. @@ -78,7 +78,11 @@ func NewConnection(ctx context.Context, uri string, dialect dialects.DialectName // We can happily throw away the original uri here because if we are using // txdb it should have already been set at the point where we called // txdb.Register - var err error + + err := pgcommon.RegisterTxDb(uri) + if err != nil { + return nil, fmt.Errorf("failed to register txdb: %w", err) + } sqldb, err = otelsql.Open(string(dialect), uuid.New().String(), opts...) if err != nil { return nil, fmt.Errorf("failed to open txdb: %w", err) diff --git a/core/services/pg/connection_test.go b/core/services/pg/connection_test.go index c4314bfb309..3ae70d14637 100644 --- a/core/services/pg/connection_test.go +++ b/core/services/pg/connection_test.go @@ -4,15 +4,13 @@ import ( "testing" "time" - "github.com/google/uuid" _ "github.com/jackc/pgx/v4/stdlib" - "github.com/jmoiron/sqlx" "github.com/pkg/errors" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - "github.com/smartcontractkit/chainlink/v2/core/store/dialects" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" ) var _ Getter = &mockGetter{} @@ -67,11 +65,9 @@ func Test_checkVersion(t *testing.T) { func Test_disallowReplica(t *testing.T) { testutils.SkipShortDB(t) - db, err := sqlx.Open(string(dialects.TransactionWrappedPostgres), uuid.New().String()) - require.NoError(t, err) - t.Cleanup(func() { require.NoError(t, db.Close()) }) + db := pgtest.NewSqlxDB(t) - _, err = db.Exec("SET session_replication_role= 'origin'") + _, err := db.Exec("SET session_replication_role= 'origin'") require.NoError(t, err) err = disallowReplica(db) require.NoError(t, err) diff --git a/core/services/pg/locked_db.go b/core/services/pg/locked_db.go index 14ddb2317a5..baea01b43a5 100644 --- a/core/services/pg/locked_db.go +++ b/core/services/pg/locked_db.go @@ -11,10 +11,11 @@ import ( "github.com/jmoiron/sqlx" "github.com/smartcontractkit/chainlink-common/pkg/services" + "github.com/smartcontractkit/chainlink-common/pkg/sqlutil/pg" + "github.com/smartcontractkit/chainlink/v2/core/config" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/static" - "github.com/smartcontractkit/chainlink/v2/core/store/dialects" ) // LockedDB bounds DB connection and DB locks. @@ -28,7 +29,7 @@ type LockedDBConfig interface { ConnectionConfig URL() url.URL DefaultQueryTimeout() time.Duration - Dialect() dialects.DialectName + Dialect() pg.DialectName } type lockedDb struct { diff --git a/core/services/relay/evm/cap_encoder.go b/core/services/relay/evm/cap_encoder.go index 2a6f288a5de..713a9796dd2 100644 --- a/core/services/relay/evm/cap_encoder.go +++ b/core/services/relay/evm/cap_encoder.go @@ -8,6 +8,7 @@ import ( "fmt" consensustypes "github.com/smartcontractkit/chainlink-common/pkg/capabilities/consensus/ocr3/types" + commoncodec "github.com/smartcontractkit/chainlink-common/pkg/codec" commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" "github.com/smartcontractkit/chainlink-common/pkg/values" @@ -17,8 +18,9 @@ import ( ) const ( - abiConfigFieldName = "abi" - encoderName = "user" + abiConfigFieldName = "abi" + subabiConfigFieldName = "subabi" + encoderName = "user" ) type capEncoder struct { @@ -46,9 +48,33 @@ func NewEVMEncoder(config *values.Map) (consensustypes.Encoder, error) { return nil, err } + chainCodecConfig := types.ChainCodecConfig{ + TypeABI: string(jsonSelector), + } + + var subabi map[string]string + subabiConfig, ok := config.Underlying[subabiConfigFieldName] + if ok { + err2 := subabiConfig.UnwrapTo(&subabi) + if err2 != nil { + return nil, err2 + } + codecs, err2 := makePreCodecModifierCodecs(subabi) + if err2 != nil { + return nil, err2 + } + chainCodecConfig.ModifierConfigs = commoncodec.ModifiersConfig{ + &commoncodec.PreCodecModifierConfig{ + Fields: subabi, + Codecs: codecs, + }, + } + } + codecConfig := types.CodecConfig{Configs: map[string]types.ChainCodecConfig{ - encoderName: {TypeABI: string(jsonSelector)}, + encoderName: chainCodecConfig, }} + c, err := codec.NewCodec(codecConfig) if err != nil { return nil, err @@ -57,6 +83,32 @@ func NewEVMEncoder(config *values.Map) (consensustypes.Encoder, error) { return &capEncoder{codec: c}, nil } +func makePreCodecModifierCodecs(subabi map[string]string) (map[string]commontypes.RemoteCodec, error) { + codecs := map[string]commontypes.RemoteCodec{} + for _, abi := range subabi { + selector, err := abiutil.ParseSelector("inner(" + abi + ")") + if err != nil { + return nil, err + } + jsonSelector, err := json.Marshal(selector.Inputs) + if err != nil { + return nil, err + } + emptyName := "" + codecConfig := types.CodecConfig{Configs: map[string]types.ChainCodecConfig{ + emptyName: { + TypeABI: string(jsonSelector), + }, + }} + codec, err := codec.NewCodec(codecConfig) + if err != nil { + return nil, err + } + codecs[abi] = codec + } + return codecs, nil +} + func (c *capEncoder) Encode(ctx context.Context, input values.Map) ([]byte, error) { unwrappedInput, err := input.Unwrap() if err != nil { diff --git a/core/services/relay/evm/cap_encoder_test.go b/core/services/relay/evm/cap_encoder_test.go index d290a7fd2b0..4c0285fc987 100644 --- a/core/services/relay/evm/cap_encoder_test.go +++ b/core/services/relay/evm/cap_encoder_test.go @@ -217,6 +217,78 @@ func TestEVMEncoder_InvalidIDs(t *testing.T) { assert.ErrorContains(t, err, "incorrect length for id") } +func TestEVMEncoder_SubABI(t *testing.T) { + config := map[string]any{ + "abi": "(bytes32 FeedID, bytes Bundle, uint32 Timestamp)[] Reports", + "subabi": map[string]string{ + "Reports.Bundle": "uint256 Ask, uint256 Bid", + }, + } + wrapped, err := values.NewMap(config) + require.NoError(t, err) + enc, err := evm.NewEVMEncoder(wrapped) + require.NoError(t, err) + + type SubReport struct { + Ask int + Bid int + } + type ReportStruct struct { + FeedID [32]byte + Bundle SubReport + Timestamp uint32 + } + reportOne := ReportStruct{ + FeedID: [32]byte{1}, + Bundle: SubReport{ + Ask: 5, + Bid: 6, + }, + Timestamp: 47890122, + } + reportTwo := ReportStruct{ + FeedID: [32]byte{2}, + Bundle: SubReport{ + Ask: 7, + Bid: 8, + }, + Timestamp: 47890122, + } + + // output of a reduce aggregator + metadata fields appended by OCR + input := map[string]any{ + "Reports": []any{reportOne, reportTwo}, + consensustypes.MetadataFieldName: getMetadata(workflowID), + } + wrapped, err = values.NewMap(input) + require.NoError(t, err) + encoded, err := enc.Encode(testutils.Context(t), *wrapped) + require.NoError(t, err) + + expected := + // start of the outer tuple + getHexMetadata() + + // start of the inner tuple (user_fields) + "0000000000000000000000000000000000000000000000000000000000000020" + // offset of Reports array + "0000000000000000000000000000000000000000000000000000000000000002" + // length of Reports array + "0000000000000000000000000000000000000000000000000000000000000040" + // offset of ReportOne + "0000000000000000000000000000000000000000000000000000000000000100" + // offset of ReportTwo + "0100000000000000000000000000000000000000000000000000000000000000" + // ReportOne FeedID + "0000000000000000000000000000000000000000000000000000000000000060" + // offset of ReportOne Bundle + "0000000000000000000000000000000000000000000000000000000002dabeca" + // ReportOne Timestamp + "0000000000000000000000000000000000000000000000000000000000000040" + // length of ReportOne Bundle + "0000000000000000000000000000000000000000000000000000000000000005" + // ReportOne Ask + "0000000000000000000000000000000000000000000000000000000000000006" + // ReportOne Bid + "0200000000000000000000000000000000000000000000000000000000000000" + // ReportTwo FeedID + "0000000000000000000000000000000000000000000000000000000000000060" + // offset of ReportTwo Bundle + "0000000000000000000000000000000000000000000000000000000002dabeca" + // ReportTwo Timestamp + "0000000000000000000000000000000000000000000000000000000000000040" + // length of ReportTwo Bundle + "0000000000000000000000000000000000000000000000000000000000000007" + // ReportTwo Ask + "0000000000000000000000000000000000000000000000000000000000000008" // ReportTwo Bid + + require.Equal(t, expected, hex.EncodeToString(encoded)) +} + func getHexMetadata() string { return "01" + executionID + timestampHex + donIDHex + configVersionHex + workflowID + workflowName + workflowOwnerID + reportID } diff --git a/core/services/relay/evm/capabilities/testutils/backend.go b/core/services/relay/evm/capabilities/testutils/backend.go index e76dbc3bc73..cd04373e29e 100644 --- a/core/services/relay/evm/capabilities/testutils/backend.go +++ b/core/services/relay/evm/capabilities/testutils/backend.go @@ -118,5 +118,5 @@ func (th *EVMBackendTH) NewContractReader(ctx context.Context, t *testing.T, cfg return nil, err } - return svc, svc.Start(ctx) + return svc, err } diff --git a/core/services/relay/evm/capabilities/workflows/syncer/workflow_syncer_test.go b/core/services/relay/evm/capabilities/workflows/syncer/workflow_syncer_test.go index 3bcf8164a7b..c7c164803cb 100644 --- a/core/services/relay/evm/capabilities/workflows/syncer/workflow_syncer_test.go +++ b/core/services/relay/evm/capabilities/workflows/syncer/workflow_syncer_test.go @@ -3,9 +3,12 @@ package workflow_registry_syncer_test import ( "context" "crypto/rand" + "encoding/base64" "encoding/hex" - "encoding/json" "fmt" + rand2 "math/rand/v2" + "strings" + "sync" "testing" "time" @@ -16,30 +19,52 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/capabilities" "github.com/smartcontractkit/chainlink-common/pkg/custmsg" + "github.com/smartcontractkit/chainlink-common/pkg/services" "github.com/smartcontractkit/chainlink-common/pkg/services/servicetest" "github.com/smartcontractkit/chainlink-common/pkg/types" + "github.com/smartcontractkit/chainlink-common/pkg/workflows" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/workflow/generated/workflow_registry_wrapper" coretestutils "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/workflowkey" "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/capabilities/testutils" - evmtypes "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/types" "github.com/smartcontractkit/chainlink/v2/core/services/workflows/syncer" "github.com/smartcontractkit/chainlink/v2/core/utils/crypto" "github.com/stretchr/testify/require" + + crypto2 "github.com/ethereum/go-ethereum/crypto" ) type testEvtHandler struct { events []syncer.Event + mux sync.Mutex } func (m *testEvtHandler) Handle(ctx context.Context, event syncer.Event) error { + m.mux.Lock() + defer m.mux.Unlock() m.events = append(m.events, event) return nil } +func (m *testEvtHandler) ClearEvents() { + m.mux.Lock() + defer m.mux.Unlock() + m.events = make([]syncer.Event, 0) +} + +func (m *testEvtHandler) GetEvents() []syncer.Event { + m.mux.Lock() + defer m.mux.Unlock() + + eventsCopy := make([]syncer.Event, len(m.events)) + copy(eventsCopy, m.events) + + return eventsCopy +} + func newTestEvtHandler() *testEvtHandler { return &testEvtHandler{ events: make([]syncer.Event, 0), @@ -66,6 +91,138 @@ func (m *testWorkflowRegistryContractLoader) LoadWorkflows(ctx context.Context, }, nil } +func Test_EventHandlerStateSync(t *testing.T) { + lggr := logger.TestLogger(t) + backendTH := testutils.NewEVMBackendTH(t) + donID := uint32(1) + + eventPollTicker := time.NewTicker(50 * time.Millisecond) + defer eventPollTicker.Stop() + + // Deploy a test workflow_registry + wfRegistryAddr, _, wfRegistryC, err := workflow_registry_wrapper.DeployWorkflowRegistry(backendTH.ContractsOwner, backendTH.Backend.Client()) + backendTH.Backend.Commit() + require.NoError(t, err) + + // setup contract state to allow the secrets to be updated + updateAllowedDONs(t, backendTH, wfRegistryC, []uint32{donID}, true) + updateAuthorizedAddress(t, backendTH, wfRegistryC, []common.Address{backendTH.ContractsOwner.From}, true) + + // Create some initial static state + numberWorkflows := 20 + for i := 0; i < numberWorkflows; i++ { + var workflowID [32]byte + _, err = rand.Read((workflowID)[:]) + require.NoError(t, err) + workflow := RegisterWorkflowCMD{ + Name: fmt.Sprintf("test-wf-%d", i), + DonID: donID, + Status: uint8(1), + SecretsURL: "someurl", + } + workflow.ID = workflowID + registerWorkflow(t, backendTH, wfRegistryC, workflow) + } + + testEventHandler := newTestEvtHandler() + loader := syncer.NewWorkflowRegistryContractLoader(lggr, wfRegistryAddr.Hex(), func(ctx context.Context, bytes []byte) (syncer.ContractReader, error) { + return backendTH.NewContractReader(ctx, t, bytes) + }, testEventHandler) + + // Create the registry + registry := syncer.NewWorkflowRegistry( + lggr, + func(ctx context.Context, bytes []byte) (syncer.ContractReader, error) { + return backendTH.NewContractReader(ctx, t, bytes) + }, + wfRegistryAddr.Hex(), + syncer.WorkflowEventPollerConfig{ + QueryCount: 20, + }, + testEventHandler, + loader, + &testDonNotifier{ + don: capabilities.DON{ + ID: donID, + }, + err: nil, + }, + syncer.WithTicker(eventPollTicker.C), + ) + + servicetest.Run(t, registry) + + require.Eventually(t, func() bool { + numEvents := len(testEventHandler.GetEvents()) + return numEvents == numberWorkflows + }, 5*time.Second, time.Second) + + for _, event := range testEventHandler.GetEvents() { + assert.Equal(t, syncer.WorkflowRegisteredEvent, event.GetEventType()) + } + + testEventHandler.ClearEvents() + + // Create different event types for a number of workflows and confirm that the event handler processes them in order + numberOfEventCycles := 50 + for i := 0; i < numberOfEventCycles; i++ { + var workflowID [32]byte + _, err = rand.Read((workflowID)[:]) + require.NoError(t, err) + workflow := RegisterWorkflowCMD{ + Name: "test-wf-register-event", + DonID: donID, + Status: uint8(1), + SecretsURL: "", + } + workflow.ID = workflowID + + // Generate events of different types with some jitter + registerWorkflow(t, backendTH, wfRegistryC, workflow) + time.Sleep(time.Millisecond * time.Duration(rand2.IntN(10))) + data := append(backendTH.ContractsOwner.From.Bytes(), []byte(workflow.Name)...) + workflowKey := crypto2.Keccak256Hash(data) + activateWorkflow(t, backendTH, wfRegistryC, workflowKey) + time.Sleep(time.Millisecond * time.Duration(rand2.IntN(10))) + pauseWorkflow(t, backendTH, wfRegistryC, workflowKey) + time.Sleep(time.Millisecond * time.Duration(rand2.IntN(10))) + var newWorkflowID [32]byte + _, err = rand.Read((newWorkflowID)[:]) + require.NoError(t, err) + updateWorkflow(t, backendTH, wfRegistryC, workflowKey, newWorkflowID, workflow.BinaryURL+"2", workflow.ConfigURL, workflow.SecretsURL) + time.Sleep(time.Millisecond * time.Duration(rand2.IntN(10))) + deleteWorkflow(t, backendTH, wfRegistryC, workflowKey) + } + + // Confirm the expected number of events are received in the correct order + require.Eventually(t, func() bool { + events := testEventHandler.GetEvents() + numEvents := len(events) + expectedNumEvents := 5 * numberOfEventCycles + + if numEvents == expectedNumEvents { + // verify the events are the expected types in the expected order + for idx, event := range events { + switch idx % 5 { + case 0: + assert.Equal(t, syncer.WorkflowRegisteredEvent, event.GetEventType()) + case 1: + assert.Equal(t, syncer.WorkflowActivatedEvent, event.GetEventType()) + case 2: + assert.Equal(t, syncer.WorkflowPausedEvent, event.GetEventType()) + case 3: + assert.Equal(t, syncer.WorkflowUpdatedEvent, event.GetEventType()) + case 4: + assert.Equal(t, syncer.WorkflowDeletedEvent, event.GetEventType()) + } + } + return true + } + + return false + }, 50*time.Second, time.Second) +} + func Test_InitialStateSync(t *testing.T) { lggr := logger.TestLogger(t) backendTH := testutils.NewEVMBackendTH(t) @@ -126,10 +283,10 @@ func Test_InitialStateSync(t *testing.T) { servicetest.Run(t, worker) require.Eventually(t, func() bool { - return len(testEventHandler.events) == numberWorkflows + return len(testEventHandler.GetEvents()) == numberWorkflows }, 5*time.Second, time.Second) - for _, event := range testEventHandler.events { + for _, event := range testEventHandler.GetEvents() { assert.Equal(t, syncer.WorkflowRegisteredEvent, event.GetEventType()) } } @@ -157,8 +314,6 @@ func Test_SecretsWorker(t *testing.T) { fetcherFn = func(_ context.Context, _ string) ([]byte, error) { return []byte(wantContents), nil } - contractName = syncer.WorkflowRegistryContractName - forceUpdateSecretsEvent = string(syncer.ForceUpdateSecretsEvent) ) defer giveTicker.Stop() @@ -174,38 +329,6 @@ func Test_SecretsWorker(t *testing.T) { backendTH.Backend.Commit() require.NoError(t, err) - lggr.Infof("deployed workflow registry at %s\n", wfRegistryAddr.Hex()) - - // Build the ContractReader config - contractReaderCfg := evmtypes.ChainReaderConfig{ - Contracts: map[string]evmtypes.ChainContractReader{ - contractName: { - ContractPollingFilter: evmtypes.ContractPollingFilter{ - GenericEventNames: []string{forceUpdateSecretsEvent}, - }, - ContractABI: workflow_registry_wrapper.WorkflowRegistryABI, - Configs: map[string]*evmtypes.ChainReaderDefinition{ - forceUpdateSecretsEvent: { - ChainSpecificName: forceUpdateSecretsEvent, - ReadType: evmtypes.Event, - }, - syncer.GetWorkflowMetadataListByDONMethodName: { - ChainSpecificName: syncer.GetWorkflowMetadataListByDONMethodName, - }, - }, - }, - }, - } - - contractReaderCfgBytes, err := json.Marshal(contractReaderCfg) - require.NoError(t, err) - - contractReader, err := backendTH.NewContractReader(ctx, t, contractReaderCfgBytes) - require.NoError(t, err) - - err = contractReader.Bind(ctx, []types.BoundContract{{Name: contractName, Address: wfRegistryAddr.Hex()}}) - require.NoError(t, err) - // Seed the DB hash, err := crypto.Keccak256(append(backendTH.ContractsOwner.From[:], []byte(giveSecretsURL)...)) require.NoError(t, err) @@ -226,17 +349,23 @@ func Test_SecretsWorker(t *testing.T) { handler := syncer.NewEventHandler(lggr, orm, fetcherFn, nil, nil, emitter, clockwork.NewFakeClock(), workflowkey.Key{}) - worker := syncer.NewWorkflowRegistry(lggr, func(ctx context.Context, bytes []byte) (syncer.ContractReader, error) { - return contractReader, nil - }, wfRegistryAddr.Hex(), - syncer.WorkflowEventPollerConfig{ - QueryCount: 20, - }, handler, &testWorkflowRegistryContractLoader{}, &testDonNotifier{ + worker := syncer.NewWorkflowRegistry( + lggr, + func(ctx context.Context, bytes []byte) (syncer.ContractReader, error) { + return backendTH.NewContractReader(ctx, t, bytes) + }, + wfRegistryAddr.Hex(), + syncer.WorkflowEventPollerConfig{QueryCount: 20}, + handler, + &testWorkflowRegistryContractLoader{}, + &testDonNotifier{ don: capabilities.DON{ ID: donID, }, err: nil, - }, syncer.WithTicker(giveTicker.C)) + }, + syncer.WithTicker(giveTicker.C), + ) // setup contract state to allow the secrets to be updated updateAllowedDONs(t, backendTH, wfRegistryC, []uint32{donID}, true) @@ -257,6 +386,195 @@ func Test_SecretsWorker(t *testing.T) { }, 15*time.Second, time.Second) } +func Test_RegistrySyncer_WorkflowRegistered_InitiallyPaused(t *testing.T) { + var ( + ctx = coretestutils.Context(t) + lggr = logger.TestLogger(t) + emitter = custmsg.NewLabeler() + backendTH = testutils.NewEVMBackendTH(t) + db = pgtest.NewSqlxDB(t) + orm = syncer.NewWorkflowRegistryDS(db, lggr) + + giveTicker = time.NewTicker(500 * time.Millisecond) + giveBinaryURL = "https://original-url.com" + donID = uint32(1) + giveWorkflow = RegisterWorkflowCMD{ + Name: "test-wf", + DonID: donID, + Status: uint8(1), + BinaryURL: giveBinaryURL, + } + wantContents = "updated contents" + fetcherFn = func(_ context.Context, _ string) ([]byte, error) { + return []byte(base64.StdEncoding.EncodeToString([]byte(wantContents))), nil + } + ) + + defer giveTicker.Stop() + + // Deploy a test workflow_registry + wfRegistryAddr, _, wfRegistryC, err := workflow_registry_wrapper.DeployWorkflowRegistry(backendTH.ContractsOwner, backendTH.Backend.Client()) + backendTH.Backend.Commit() + require.NoError(t, err) + + from := [20]byte(backendTH.ContractsOwner.From) + id, err := workflows.GenerateWorkflowID(from[:], "test-wf", []byte(wantContents), []byte(""), "") + require.NoError(t, err) + giveWorkflow.ID = id + + er := syncer.NewEngineRegistry() + handler := syncer.NewEventHandler(lggr, orm, fetcherFn, nil, nil, + emitter, clockwork.NewFakeClock(), workflowkey.Key{}, syncer.WithEngineRegistry(er)) + + worker := syncer.NewWorkflowRegistry( + lggr, + func(ctx context.Context, bytes []byte) (syncer.ContractReader, error) { + return backendTH.NewContractReader(ctx, t, bytes) + }, + wfRegistryAddr.Hex(), + syncer.WorkflowEventPollerConfig{QueryCount: 20}, + handler, + &testWorkflowRegistryContractLoader{}, + &testDonNotifier{ + don: capabilities.DON{ + ID: donID, + }, + err: nil, + }, + syncer.WithTicker(giveTicker.C), + ) + + // setup contract state to allow the secrets to be updated + updateAllowedDONs(t, backendTH, wfRegistryC, []uint32{donID}, true) + updateAuthorizedAddress(t, backendTH, wfRegistryC, []common.Address{backendTH.ContractsOwner.From}, true) + + servicetest.Run(t, worker) + + // generate a log event + registerWorkflow(t, backendTH, wfRegistryC, giveWorkflow) + + // Require the secrets contents to eventually be updated + require.Eventually(t, func() bool { + _, err = er.Get("test-wf") + if err == nil { + return false + } + + owner := strings.ToLower(backendTH.ContractsOwner.From.Hex()[2:]) + _, err := orm.GetWorkflowSpec(ctx, owner, "test-wf") + return err == nil + }, 15*time.Second, time.Second) +} + +type mockService struct{} + +func (m *mockService) Start(context.Context) error { return nil } + +func (m *mockService) Close() error { return nil } + +func (m *mockService) HealthReport() map[string]error { return map[string]error{"svc": nil} } + +func (m *mockService) Ready() error { return nil } + +func (m *mockService) Name() string { return "svc" } + +type mockEngineFactory struct{} + +func (m *mockEngineFactory) new(ctx context.Context, wfid string, owner string, name string, config []byte, binary []byte) (services.Service, error) { + return &mockService{}, nil +} + +func Test_RegistrySyncer_WorkflowRegistered_InitiallyActivated(t *testing.T) { + var ( + ctx = coretestutils.Context(t) + lggr = logger.TestLogger(t) + emitter = custmsg.NewLabeler() + backendTH = testutils.NewEVMBackendTH(t) + db = pgtest.NewSqlxDB(t) + orm = syncer.NewWorkflowRegistryDS(db, lggr) + + giveTicker = time.NewTicker(500 * time.Millisecond) + giveBinaryURL = "https://original-url.com" + donID = uint32(1) + giveWorkflow = RegisterWorkflowCMD{ + Name: "test-wf", + DonID: donID, + Status: uint8(0), + BinaryURL: giveBinaryURL, + } + wantContents = "updated contents" + fetcherFn = func(_ context.Context, _ string) ([]byte, error) { + return []byte(base64.StdEncoding.EncodeToString([]byte(wantContents))), nil + } + ) + + defer giveTicker.Stop() + + // Deploy a test workflow_registry + wfRegistryAddr, _, wfRegistryC, err := workflow_registry_wrapper.DeployWorkflowRegistry(backendTH.ContractsOwner, backendTH.Backend.Client()) + backendTH.Backend.Commit() + require.NoError(t, err) + + from := [20]byte(backendTH.ContractsOwner.From) + id, err := workflows.GenerateWorkflowID(from[:], "test-wf", []byte(wantContents), []byte(""), "") + require.NoError(t, err) + giveWorkflow.ID = id + + mf := &mockEngineFactory{} + er := syncer.NewEngineRegistry() + handler := syncer.NewEventHandler( + lggr, + orm, + fetcherFn, + nil, + nil, + emitter, + clockwork.NewFakeClock(), + workflowkey.Key{}, + syncer.WithEngineRegistry(er), + syncer.WithEngineFactoryFn(mf.new), + ) + + worker := syncer.NewWorkflowRegistry( + lggr, + func(ctx context.Context, bytes []byte) (syncer.ContractReader, error) { + return backendTH.NewContractReader(ctx, t, bytes) + }, + wfRegistryAddr.Hex(), + syncer.WorkflowEventPollerConfig{QueryCount: 20}, + handler, + &testWorkflowRegistryContractLoader{}, + &testDonNotifier{ + don: capabilities.DON{ + ID: donID, + }, + err: nil, + }, + syncer.WithTicker(giveTicker.C), + ) + + // setup contract state to allow the secrets to be updated + updateAllowedDONs(t, backendTH, wfRegistryC, []uint32{donID}, true) + updateAuthorizedAddress(t, backendTH, wfRegistryC, []common.Address{backendTH.ContractsOwner.From}, true) + + servicetest.Run(t, worker) + + // generate a log event + registerWorkflow(t, backendTH, wfRegistryC, giveWorkflow) + + // Require the secrets contents to eventually be updated + require.Eventually(t, func() bool { + _, err := er.Get("test-wf") + if err != nil { + return err != nil + } + + owner := strings.ToLower(backendTH.ContractsOwner.From.Hex()[2:]) + _, err = orm.GetWorkflowSpec(ctx, owner, "test-wf") + return err == nil + }, 15*time.Second, time.Second) +} + func updateAuthorizedAddress( t *testing.T, th *testutils.EVMBackendTH, @@ -334,3 +652,59 @@ func requestForceUpdateSecrets( th.Backend.Commit() th.Backend.Commit() } + +func activateWorkflow( + t *testing.T, + th *testutils.EVMBackendTH, + wfRegC *workflow_registry_wrapper.WorkflowRegistry, + workflowKey [32]byte, +) { + t.Helper() + _, err := wfRegC.ActivateWorkflow(th.ContractsOwner, workflowKey) + require.NoError(t, err, "failed to activate workflow") + th.Backend.Commit() + th.Backend.Commit() + th.Backend.Commit() +} + +func pauseWorkflow( + t *testing.T, + th *testutils.EVMBackendTH, + wfRegC *workflow_registry_wrapper.WorkflowRegistry, + workflowKey [32]byte, +) { + t.Helper() + _, err := wfRegC.PauseWorkflow(th.ContractsOwner, workflowKey) + require.NoError(t, err, "failed to pause workflow") + th.Backend.Commit() + th.Backend.Commit() + th.Backend.Commit() +} + +func deleteWorkflow( + t *testing.T, + th *testutils.EVMBackendTH, + wfRegC *workflow_registry_wrapper.WorkflowRegistry, + workflowKey [32]byte, +) { + t.Helper() + _, err := wfRegC.DeleteWorkflow(th.ContractsOwner, workflowKey) + require.NoError(t, err, "failed to delete workflow") + th.Backend.Commit() + th.Backend.Commit() + th.Backend.Commit() +} + +func updateWorkflow( + t *testing.T, + th *testutils.EVMBackendTH, + wfRegC *workflow_registry_wrapper.WorkflowRegistry, + workflowKey [32]byte, newWorkflowID [32]byte, binaryURL string, configURL string, secretsURL string, +) { + t.Helper() + _, err := wfRegC.UpdateWorkflow(th.ContractsOwner, workflowKey, newWorkflowID, binaryURL, configURL, secretsURL) + require.NoError(t, err, "failed to update workflow") + th.Backend.Commit() + th.Backend.Commit() + th.Backend.Commit() +} diff --git a/core/services/relay/evm/chain_components_test.go b/core/services/relay/evm/chain_components_test.go index bc2703d9678..1e8c47c51ec 100644 --- a/core/services/relay/evm/chain_components_test.go +++ b/core/services/relay/evm/chain_components_test.go @@ -3,6 +3,7 @@ package evm_test import ( "context" "crypto/ecdsa" + "errors" "fmt" "math" "math/big" @@ -12,6 +13,7 @@ import ( "time" "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" evmtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethclient/simulated" @@ -19,15 +21,21 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/smartcontractkit/chainlink-common/pkg/services" + commonconfig "github.com/smartcontractkit/chainlink-common/pkg/config" commontestutils "github.com/smartcontractkit/chainlink-common/pkg/loop/testutils" clcommontypes "github.com/smartcontractkit/chainlink-common/pkg/types" "github.com/smartcontractkit/chainlink-common/pkg/types/interfacetests" + "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" + htMocks "github.com/smartcontractkit/chainlink/v2/common/headtracker/mocks" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" + lpMocks "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller/mocks" evmtxmgr "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" + clevmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" @@ -206,8 +214,21 @@ func TestContractReaderEventsInitValidation(t *testing.T) { } } +func TestChainReader_HealthReport(t *testing.T) { + lp := lpMocks.NewLogPoller(t) + lp.EXPECT().HealthReport().Return(map[string]error{"lp_name": clcommontypes.ErrFinalityViolated}).Once() + ht := htMocks.NewHeadTracker[*clevmtypes.Head, common.Hash](t) + htError := errors.New("head tracker error") + ht.EXPECT().HealthReport().Return(map[string]error{"ht_name": htError}).Once() + cr, err := evm.NewChainReaderService(testutils.Context(t), logger.NullLogger, lp, ht, nil, types.ChainReaderConfig{Contracts: nil}) + require.NoError(t, err) + healthReport := cr.HealthReport() + require.True(t, services.ContainsError(healthReport, clcommontypes.ErrFinalityViolated), "expected chain reader to propagate logpoller's error") + require.True(t, services.ContainsError(healthReport, htError), "expected chain reader to propagate headtracker's error") +} + func TestChainComponents(t *testing.T) { - testutils.SkipFlakey(t, "https://smartcontract-it.atlassian.net/browse/BCFR-1083") + tests.SkipFlakey(t, "https://smartcontract-it.atlassian.net/browse/BCFR-1083") t.Parallel() it := &EVMChainComponentsInterfaceTester[*testing.T]{Helper: &helper{}} // TODO, generated binding tests are broken diff --git a/core/services/relay/evm/chain_reader.go b/core/services/relay/evm/chain_reader.go index 99be89eae17..ffe9cd19aea 100644 --- a/core/services/relay/evm/chain_reader.go +++ b/core/services/relay/evm/chain_reader.go @@ -20,6 +20,7 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/types/query" "github.com/smartcontractkit/chainlink-common/pkg/types/query/primitives" "github.com/smartcontractkit/chainlink-common/pkg/values" + evmclient "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" @@ -179,7 +180,13 @@ func (cr *chainReader) Close() error { func (cr *chainReader) Ready() error { return nil } func (cr *chainReader) HealthReport() map[string]error { - return map[string]error{cr.Name(): nil} + report := map[string]error{ + cr.Name(): cr.Healthy(), + } + + commonservices.CopyHealth(report, cr.lp.HealthReport()) + commonservices.CopyHealth(report, cr.ht.HealthReport()) + return report } func (cr *chainReader) Bind(ctx context.Context, bindings []commontypes.BoundContract) error { @@ -199,7 +206,11 @@ func (cr *chainReader) GetLatestValue(ctx context.Context, readName string, conf ptrToValue, isValue := returnVal.(*values.Value) if !isValue { _, err = binding.GetLatestValueWithHeadData(ctx, common.HexToAddress(address), confidenceLevel, params, returnVal) - return err + if err != nil { + return err + } + + return nil } contractType, err := cr.CreateContractType(readName, false) diff --git a/core/services/relay/evm/mercury/transmitter.go b/core/services/relay/evm/mercury/transmitter.go index 4e57a3d07cf..bdd324988bf 100644 --- a/core/services/relay/evm/mercury/transmitter.go +++ b/core/services/relay/evm/mercury/transmitter.go @@ -106,7 +106,6 @@ type BenchmarkPriceDecoder func(ctx context.Context, feedID mercuryutils.FeedID, var _ Transmitter = (*mercuryTransmitter)(nil) type TransmitterConfig interface { - TransmitQueueMaxSize() uint32 TransmitTimeout() commonconfig.Duration } @@ -287,14 +286,17 @@ func (s *server) runQueueLoop(stopCh services.StopChan, wg *sync.WaitGroup, feed } } +const TransmitQueueMaxSize = 10_000 // hardcode this for legacy transmitter since we want the config var to apply only to LLO + func newServer(lggr logger.Logger, cfg TransmitterConfig, client wsrpc.Client, pm *PersistenceManager, serverURL, feedIDHex string) *server { + return &server{ logger.Sugared(lggr), cfg.TransmitTimeout().Duration(), client, pm, - NewTransmitQueue(lggr, serverURL, feedIDHex, int(cfg.TransmitQueueMaxSize()), pm), - make(chan *pb.TransmitRequest, int(cfg.TransmitQueueMaxSize())), + NewTransmitQueue(lggr, serverURL, feedIDHex, TransmitQueueMaxSize, pm), + make(chan *pb.TransmitRequest, TransmitQueueMaxSize), serverURL, transmitSuccessCount.WithLabelValues(feedIDHex, serverURL), transmitDuplicateCount.WithLabelValues(feedIDHex, serverURL), @@ -311,7 +313,7 @@ func NewTransmitter(lggr logger.Logger, cfg TransmitterConfig, clients map[strin servers := make(map[string]*server, len(clients)) for serverURL, client := range clients { cLggr := sugared.Named(serverURL).With("serverURL", serverURL) - pm := NewPersistenceManager(cLggr, serverURL, orm, jobID, int(cfg.TransmitQueueMaxSize()), flushDeletesFrequency, pruneFrequency) + pm := NewPersistenceManager(cLggr, serverURL, orm, jobID, TransmitQueueMaxSize, flushDeletesFrequency, pruneFrequency) servers[serverURL] = newServer(cLggr, cfg, client, pm, serverURL, feedIDHex) } return &mercuryTransmitter{ diff --git a/core/services/relay/evm/read/batch.go b/core/services/relay/evm/read/batch.go index 16333149f11..ce1c546ad73 100644 --- a/core/services/relay/evm/read/batch.go +++ b/core/services/relay/evm/read/batch.go @@ -241,32 +241,36 @@ func (c *defaultEvmBatchCaller) unpackBatchResults( return nil, callErr } - if err = c.codec.Decode( - ctx, - packedBytes, - call.ReturnVal, - codec.WrapItemType(call.ContractName, call.ReadName, false), - ); err != nil { - if len(packedBytes) == 0 { - callErr := newErrorFromCall( - fmt.Errorf("%w: %w: %s", types.ErrInternal, errEmptyOutput, err.Error()), - call, block, batchReadType, - ) - - callErr.Result = &hexEncodedOutputs[idx] - - results[idx].err = callErr - } else { - callErr := newErrorFromCall( - fmt.Errorf("%w: codec decode result: %s", types.ErrInvalidType, err.Error()), - call, block, batchReadType, - ) - - callErr.Result = &hexEncodedOutputs[idx] - results[idx].err = callErr - } + // the codec can't do anything with no bytes, so skip decoding and allow + // the result to be the empty struct or value + if len(packedBytes) > 0 { + if err = c.codec.Decode( + ctx, + packedBytes, + call.ReturnVal, + codec.WrapItemType(call.ContractName, call.ReadName, false), + ); err != nil { + if len(packedBytes) == 0 { + callErr := newErrorFromCall( + fmt.Errorf("%w: %w: %s", types.ErrInternal, errEmptyOutput, err.Error()), + call, block, batchReadType, + ) + + callErr.Result = &hexEncodedOutputs[idx] + + results[idx].err = callErr + } else { + callErr := newErrorFromCall( + fmt.Errorf("%w: codec decode result: %s", types.ErrInvalidType, err.Error()), + call, block, batchReadType, + ) + + callErr.Result = &hexEncodedOutputs[idx] + results[idx].err = callErr + } - continue + continue + } } results[idx].returnVal = call.ReturnVal diff --git a/core/services/relay/evm/read/method.go b/core/services/relay/evm/read/method.go index e988e4352f7..ed44e1aa9ca 100644 --- a/core/services/relay/evm/read/method.go +++ b/core/services/relay/evm/read/method.go @@ -2,6 +2,7 @@ package read import ( "context" + "errors" "fmt" "math/big" "sync" @@ -22,6 +23,8 @@ import ( evmclient "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" ) +var ErrEmptyContractReturnValue = errors.New("the contract return value was empty") + type MethodBinding struct { // read-only properties contractName string @@ -173,6 +176,12 @@ func (b *MethodBinding) GetLatestValueWithHeadData(ctx context.Context, addr com return nil, callErr } + // there may be cases where the contract value has not been set and the RPC returns with a value of 0x + // which is a set of empty bytes. there is no need for the codec to run in this case. + if len(bytes) == 0 { + return block.ToChainAgnosticHead(), nil + } + if err = b.codec.Decode(ctx, bytes, returnVal, codec.WrapItemType(b.contractName, b.method, false)); err != nil { callErr := newErrorFromCall( fmt.Errorf("%w: decode return data: %s", commontypes.ErrInvalidType, err.Error()), diff --git a/core/services/relay/evm/statuschecker/txm_status_checker_test.go b/core/services/relay/evm/statuschecker/txm_status_checker_test.go index 456d07e7a7d..7a682d708e2 100644 --- a/core/services/relay/evm/statuschecker/txm_status_checker_test.go +++ b/core/services/relay/evm/statuschecker/txm_status_checker_test.go @@ -10,12 +10,13 @@ import ( "github.com/stretchr/testify/mock" "github.com/smartcontractkit/chainlink-common/pkg/types" + "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr/mocks" - "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" ) func Test_CheckMessageStatus(t *testing.T) { - testutils.SkipShort(t, "") + tests.SkipShort(t, "") ctx := context.Background() mockTxManager := mocks.NewMockEvmTxManager(t) checker := NewTxmStatusChecker(mockTxManager.GetTransactionStatus) diff --git a/core/services/vrf/v2/integration_v2_plus_test.go b/core/services/vrf/v2/integration_v2_plus_test.go index 75cffe1057c..d1cc030043d 100644 --- a/core/services/vrf/v2/integration_v2_plus_test.go +++ b/core/services/vrf/v2/integration_v2_plus_test.go @@ -17,6 +17,7 @@ import ( "github.com/stretchr/testify/require" commonconfig "github.com/smartcontractkit/chainlink-common/pkg/config" + "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/toml" @@ -66,7 +67,7 @@ type coordinatorV2PlusUniverse struct { } func newVRFCoordinatorV2PlusUniverse(t *testing.T, key ethkey.KeyV2, numConsumers int, trusting bool) coordinatorV2PlusUniverse { - testutils.SkipShort(t, "VRFCoordinatorV2Universe") + tests.SkipShort(t, "VRFCoordinatorV2Universe") oracleTransactor, err := bind.NewKeyedTransactorWithChainID(key.ToEcdsaPrivKey(), testutils.SimulatedChainID) require.NoError(t, err) var ( diff --git a/core/services/vrf/v2/integration_v2_test.go b/core/services/vrf/v2/integration_v2_test.go index d9086a52a33..6cbcc799e1b 100644 --- a/core/services/vrf/v2/integration_v2_test.go +++ b/core/services/vrf/v2/integration_v2_test.go @@ -31,6 +31,8 @@ import ( commonassets "github.com/smartcontractkit/chainlink-common/pkg/assets" commonconfig "github.com/smartcontractkit/chainlink-common/pkg/config" "github.com/smartcontractkit/chainlink-common/pkg/sqlutil" + "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" + txmgrcommon "github.com/smartcontractkit/chainlink/v2/common/txmgr" txmgrtypes "github.com/smartcontractkit/chainlink/v2/common/txmgr/types" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" @@ -145,7 +147,7 @@ func makeTestTxm(t *testing.T, txStore txmgr.TestEvmTxStore, keyStore keystore.M } func newVRFCoordinatorV2Universe(t *testing.T, key ethkey.KeyV2, numConsumers int) coordinatorV2Universe { - testutils.SkipShort(t, "VRFCoordinatorV2Universe") + tests.SkipShort(t, "VRFCoordinatorV2Universe") oracleTransactor, err := bind.NewKeyedTransactorWithChainID(key.ToEcdsaPrivKey(), testutils.SimulatedChainID) require.NoError(t, err) var ( diff --git a/core/services/workflows/engine.go b/core/services/workflows/engine.go index 11d2bdc3480..d153e53bc07 100644 --- a/core/services/workflows/engine.go +++ b/core/services/workflows/engine.go @@ -28,7 +28,7 @@ import ( ) const ( - fifteenMinutesMs = 15 * 60 * 1000 + fifteenMinutesSec = 15 * 60 reservedFieldNameStepTimeout = "cre_step_timeout" maxStepTimeoutOverrideSec = 10 * 60 // 10 minutes ) @@ -446,7 +446,7 @@ func (e *Engine) registerTrigger(ctx context.Context, t *triggerCapability, trig } eventsCh, err := t.trigger.RegisterTrigger(ctx, triggerRegRequest) if err != nil { - e.metrics.incrementRegisterTriggerFailureCounter(ctx) + e.metrics.with(platform.KeyTriggerID, triggerID).incrementRegisterTriggerFailureCounter(ctx) // It's confusing that t.ID is different from triggerID, but // t.ID is the capability ID, and triggerID is the trigger ID. // @@ -704,7 +704,7 @@ func (e *Engine) finishExecution(ctx context.Context, cma custmsg.MessageEmitter e.metrics.updateWorkflowTimeoutDurationHistogram(ctx, executionDuration) } - if executionDuration > fifteenMinutesMs { + if executionDuration > fifteenMinutesSec { logCustMsg(ctx, cma, fmt.Sprintf("execution duration exceeded 15 minutes: %d (seconds)", executionDuration), l) l.Warnf("execution duration exceeded 15 minutes: %d (seconds)", executionDuration) } @@ -1179,6 +1179,14 @@ func (e *Engine) Close() error { }) } +func (e *Engine) HealthReport() map[string]error { + return map[string]error{e.Name(): nil} +} + +func (e *Engine) Name() string { + return e.logger.Name() +} + type Config struct { Workflow sdk.WorkflowSpec WorkflowID string diff --git a/core/services/workflows/monitoring.go b/core/services/workflows/monitoring.go index 8457dadeb60..b73ee6e5eda 100644 --- a/core/services/workflows/monitoring.go +++ b/core/services/workflows/monitoring.go @@ -143,19 +143,19 @@ func MetricViews() []sdkmetric.View { sdkmetric.NewView( sdkmetric.Instrument{Name: "platform_engine_workflow_earlyexit_time_seconds"}, sdkmetric.Stream{Aggregation: sdkmetric.AggregationExplicitBucketHistogram{ - Boundaries: []float64{0, 1, 10, 100}, + Boundaries: []float64{0, 1, 10, 30, 120}, }}, ), sdkmetric.NewView( sdkmetric.Instrument{Name: "platform_engine_workflow_completed_time_seconds"}, sdkmetric.Stream{Aggregation: sdkmetric.AggregationExplicitBucketHistogram{ - Boundaries: []float64{0, 100, 1000, 10_000, 50_000, 100_0000, 500_000}, + Boundaries: []float64{0, 10, 30, 60, 120, 300, 600, 900, 1200}, }}, ), sdkmetric.NewView( sdkmetric.Instrument{Name: "platform_engine_workflow_error_time_seconds"}, sdkmetric.Stream{Aggregation: sdkmetric.AggregationExplicitBucketHistogram{ - Boundaries: []float64{0, 20, 60, 120, 240}, + Boundaries: []float64{0, 30, 60, 120, 240, 600}, }}, ), sdkmetric.NewView( diff --git a/core/services/workflows/syncer/contract_reader_mock.go b/core/services/workflows/syncer/contract_reader_mock.go deleted file mode 100644 index 391ba5eacdb..00000000000 --- a/core/services/workflows/syncer/contract_reader_mock.go +++ /dev/null @@ -1,211 +0,0 @@ -// Code generated by mockery v2.46.3. DO NOT EDIT. - -package syncer - -import ( - context "context" - - query "github.com/smartcontractkit/chainlink-common/pkg/types/query" - primitives "github.com/smartcontractkit/chainlink-common/pkg/types/query/primitives" - mock "github.com/stretchr/testify/mock" - - types "github.com/smartcontractkit/chainlink-common/pkg/types" -) - -// MockContractReader is an autogenerated mock type for the ContractReader type -type MockContractReader struct { - mock.Mock -} - -type MockContractReader_Expecter struct { - mock *mock.Mock -} - -func (_m *MockContractReader) EXPECT() *MockContractReader_Expecter { - return &MockContractReader_Expecter{mock: &_m.Mock} -} - -// Bind provides a mock function with given fields: _a0, _a1 -func (_m *MockContractReader) Bind(_a0 context.Context, _a1 []types.BoundContract) error { - ret := _m.Called(_a0, _a1) - - if len(ret) == 0 { - panic("no return value specified for Bind") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, []types.BoundContract) error); ok { - r0 = rf(_a0, _a1) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// MockContractReader_Bind_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Bind' -type MockContractReader_Bind_Call struct { - *mock.Call -} - -// Bind is a helper method to define mock.On call -// - _a0 context.Context -// - _a1 []types.BoundContract -func (_e *MockContractReader_Expecter) Bind(_a0 interface{}, _a1 interface{}) *MockContractReader_Bind_Call { - return &MockContractReader_Bind_Call{Call: _e.mock.On("Bind", _a0, _a1)} -} - -func (_c *MockContractReader_Bind_Call) Run(run func(_a0 context.Context, _a1 []types.BoundContract)) *MockContractReader_Bind_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].([]types.BoundContract)) - }) - return _c -} - -func (_c *MockContractReader_Bind_Call) Return(_a0 error) *MockContractReader_Bind_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *MockContractReader_Bind_Call) RunAndReturn(run func(context.Context, []types.BoundContract) error) *MockContractReader_Bind_Call { - _c.Call.Return(run) - return _c -} - -// GetLatestValueWithHeadData provides a mock function with given fields: ctx, readName, confidenceLevel, params, returnVal -func (_m *MockContractReader) GetLatestValueWithHeadData(ctx context.Context, readName string, confidenceLevel primitives.ConfidenceLevel, params any, returnVal any) (*types.Head, error) { - ret := _m.Called(ctx, readName, confidenceLevel, params, returnVal) - - if len(ret) == 0 { - panic("no return value specified for GetLatestValueWithHeadData") - } - - var r0 *types.Head - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string, primitives.ConfidenceLevel, any, any) (*types.Head, error)); ok { - return rf(ctx, readName, confidenceLevel, params, returnVal) - } - if rf, ok := ret.Get(0).(func(context.Context, string, primitives.ConfidenceLevel, any, any) *types.Head); ok { - r0 = rf(ctx, readName, confidenceLevel, params, returnVal) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*types.Head) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, string, primitives.ConfidenceLevel, any, any) error); ok { - r1 = rf(ctx, readName, confidenceLevel, params, returnVal) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// MockContractReader_GetLatestValueWithHeadData_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetLatestValueWithHeadData' -type MockContractReader_GetLatestValueWithHeadData_Call struct { - *mock.Call -} - -// GetLatestValueWithHeadData is a helper method to define mock.On call -// - ctx context.Context -// - readName string -// - confidenceLevel primitives.ConfidenceLevel -// - params any -// - returnVal any -func (_e *MockContractReader_Expecter) GetLatestValueWithHeadData(ctx interface{}, readName interface{}, confidenceLevel interface{}, params interface{}, returnVal interface{}) *MockContractReader_GetLatestValueWithHeadData_Call { - return &MockContractReader_GetLatestValueWithHeadData_Call{Call: _e.mock.On("GetLatestValueWithHeadData", ctx, readName, confidenceLevel, params, returnVal)} -} - -func (_c *MockContractReader_GetLatestValueWithHeadData_Call) Run(run func(ctx context.Context, readName string, confidenceLevel primitives.ConfidenceLevel, params any, returnVal any)) *MockContractReader_GetLatestValueWithHeadData_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(string), args[2].(primitives.ConfidenceLevel), args[3].(any), args[4].(any)) - }) - return _c -} - -func (_c *MockContractReader_GetLatestValueWithHeadData_Call) Return(head *types.Head, err error) *MockContractReader_GetLatestValueWithHeadData_Call { - _c.Call.Return(head, err) - return _c -} - -func (_c *MockContractReader_GetLatestValueWithHeadData_Call) RunAndReturn(run func(context.Context, string, primitives.ConfidenceLevel, any, any) (*types.Head, error)) *MockContractReader_GetLatestValueWithHeadData_Call { - _c.Call.Return(run) - return _c -} - -// QueryKey provides a mock function with given fields: _a0, _a1, _a2, _a3, _a4 -func (_m *MockContractReader) QueryKey(_a0 context.Context, _a1 types.BoundContract, _a2 query.KeyFilter, _a3 query.LimitAndSort, _a4 any) ([]types.Sequence, error) { - ret := _m.Called(_a0, _a1, _a2, _a3, _a4) - - if len(ret) == 0 { - panic("no return value specified for QueryKey") - } - - var r0 []types.Sequence - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, types.BoundContract, query.KeyFilter, query.LimitAndSort, any) ([]types.Sequence, error)); ok { - return rf(_a0, _a1, _a2, _a3, _a4) - } - if rf, ok := ret.Get(0).(func(context.Context, types.BoundContract, query.KeyFilter, query.LimitAndSort, any) []types.Sequence); ok { - r0 = rf(_a0, _a1, _a2, _a3, _a4) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]types.Sequence) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, types.BoundContract, query.KeyFilter, query.LimitAndSort, any) error); ok { - r1 = rf(_a0, _a1, _a2, _a3, _a4) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// MockContractReader_QueryKey_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'QueryKey' -type MockContractReader_QueryKey_Call struct { - *mock.Call -} - -// QueryKey is a helper method to define mock.On call -// - _a0 context.Context -// - _a1 types.BoundContract -// - _a2 query.KeyFilter -// - _a3 query.LimitAndSort -// - _a4 any -func (_e *MockContractReader_Expecter) QueryKey(_a0 interface{}, _a1 interface{}, _a2 interface{}, _a3 interface{}, _a4 interface{}) *MockContractReader_QueryKey_Call { - return &MockContractReader_QueryKey_Call{Call: _e.mock.On("QueryKey", _a0, _a1, _a2, _a3, _a4)} -} - -func (_c *MockContractReader_QueryKey_Call) Run(run func(_a0 context.Context, _a1 types.BoundContract, _a2 query.KeyFilter, _a3 query.LimitAndSort, _a4 any)) *MockContractReader_QueryKey_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(types.BoundContract), args[2].(query.KeyFilter), args[3].(query.LimitAndSort), args[4].(any)) - }) - return _c -} - -func (_c *MockContractReader_QueryKey_Call) Return(_a0 []types.Sequence, _a1 error) *MockContractReader_QueryKey_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *MockContractReader_QueryKey_Call) RunAndReturn(run func(context.Context, types.BoundContract, query.KeyFilter, query.LimitAndSort, any) ([]types.Sequence, error)) *MockContractReader_QueryKey_Call { - _c.Call.Return(run) - return _c -} - -// NewMockContractReader creates a new instance of MockContractReader. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -// The first argument is typically a *testing.T value. -func NewMockContractReader(t interface { - mock.TestingT - Cleanup(func()) -}) *MockContractReader { - mock := &MockContractReader{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/core/services/workflows/syncer/engine_registry.go b/core/services/workflows/syncer/engine_registry.go index 809381c191c..fa3771c5a0f 100644 --- a/core/services/workflows/syncer/engine_registry.go +++ b/core/services/workflows/syncer/engine_registry.go @@ -4,29 +4,29 @@ import ( "errors" "sync" - "github.com/smartcontractkit/chainlink/v2/core/services/workflows" + "github.com/smartcontractkit/chainlink-common/pkg/services" ) -type engineRegistry struct { - engines map[string]*workflows.Engine +type EngineRegistry struct { + engines map[string]services.Service mu sync.RWMutex } -func newEngineRegistry() *engineRegistry { - return &engineRegistry{ - engines: make(map[string]*workflows.Engine), +func NewEngineRegistry() *EngineRegistry { + return &EngineRegistry{ + engines: make(map[string]services.Service), } } // Add adds an engine to the registry. -func (r *engineRegistry) Add(id string, engine *workflows.Engine) { +func (r *EngineRegistry) Add(id string, engine services.Service) { r.mu.Lock() defer r.mu.Unlock() r.engines[id] = engine } // Get retrieves an engine from the registry. -func (r *engineRegistry) Get(id string) (*workflows.Engine, error) { +func (r *EngineRegistry) Get(id string) (services.Service, error) { r.mu.RLock() defer r.mu.RUnlock() engine, found := r.engines[id] @@ -37,7 +37,7 @@ func (r *engineRegistry) Get(id string) (*workflows.Engine, error) { } // IsRunning is true if the engine exists and is ready. -func (r *engineRegistry) IsRunning(id string) bool { +func (r *EngineRegistry) IsRunning(id string) bool { r.mu.RLock() defer r.mu.RUnlock() engine, found := r.engines[id] @@ -49,7 +49,7 @@ func (r *engineRegistry) IsRunning(id string) bool { } // Pop removes an engine from the registry and returns the engine if found. -func (r *engineRegistry) Pop(id string) (*workflows.Engine, error) { +func (r *EngineRegistry) Pop(id string) (services.Service, error) { r.mu.Lock() defer r.mu.Unlock() engine, ok := r.engines[id] @@ -61,7 +61,7 @@ func (r *engineRegistry) Pop(id string) (*workflows.Engine, error) { } // Close closes all engines in the registry. -func (r *engineRegistry) Close() error { +func (r *EngineRegistry) Close() error { r.mu.Lock() defer r.mu.Unlock() var err error diff --git a/core/services/workflows/syncer/fetcher.go b/core/services/workflows/syncer/fetcher.go index fdd0134909d..6a80739bbfe 100644 --- a/core/services/workflows/syncer/fetcher.go +++ b/core/services/workflows/syncer/fetcher.go @@ -18,10 +18,6 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers/common" ) -const ( - defaultFetchTimeoutMs = 20_000 -) - type FetcherService struct { services.StateMachine lggr logger.Logger @@ -88,17 +84,11 @@ func hash(url string) string { } func (s *FetcherService) Fetch(ctx context.Context, url string) ([]byte, error) { - payloadBytes, err := json.Marshal(ghcapabilities.Request{ - URL: url, - Method: http.MethodGet, - TimeoutMs: defaultFetchTimeoutMs, - }) - if err != nil { - return nil, fmt.Errorf("failed to marshal fetch request: %w", err) - } - messageID := strings.Join([]string{ghcapabilities.MethodWorkflowSyncer, hash(url)}, "/") - resp, err := s.och.HandleSingleNodeRequest(ctx, messageID, payloadBytes) + resp, err := s.och.HandleSingleNodeRequest(ctx, messageID, ghcapabilities.Request{ + URL: url, + Method: http.MethodGet, + }) if err != nil { return nil, err } diff --git a/core/services/workflows/syncer/fetcher_test.go b/core/services/workflows/syncer/fetcher_test.go index 8e3e58fba0d..ee59d22608a 100644 --- a/core/services/workflows/syncer/fetcher_test.go +++ b/core/services/workflows/syncer/fetcher_test.go @@ -15,6 +15,7 @@ import ( gcmocks "github.com/smartcontractkit/chainlink/v2/core/services/gateway/connector/mocks" "github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers/capabilities" ghcapabilities "github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers/capabilities" + "github.com/smartcontractkit/chainlink/v2/core/utils/matches" ) type wrapper struct { @@ -48,6 +49,7 @@ func TestNewFetcherService(t *testing.T) { fetcher.och.HandleGatewayMessage(ctx, "gateway1", gatewayResp) }).Return(nil).Times(1) connector.EXPECT().DonID().Return("don-id") + connector.EXPECT().AwaitConnection(matches.AnyContext, "gateway1").Return(nil) connector.EXPECT().GatewayIDs().Return([]string{"gateway1", "gateway2"}) payload, err := fetcher.Fetch(ctx, url) diff --git a/core/services/workflows/syncer/handler.go b/core/services/workflows/syncer/handler.go index 077dbc0cedb..534dfd57e7b 100644 --- a/core/services/workflows/syncer/handler.go +++ b/core/services/workflows/syncer/handler.go @@ -3,6 +3,7 @@ package syncer import ( "bytes" "context" + "encoding/base64" "encoding/hex" "encoding/json" "errors" @@ -13,6 +14,7 @@ import ( "github.com/jonboulle/clockwork" "github.com/smartcontractkit/chainlink-common/pkg/custmsg" + "github.com/smartcontractkit/chainlink-common/pkg/services" "github.com/smartcontractkit/chainlink-common/pkg/types/core" pkgworkflows "github.com/smartcontractkit/chainlink-common/pkg/workflows" "github.com/smartcontractkit/chainlink-common/pkg/workflows/secrets" @@ -59,14 +61,14 @@ type WorkflowRegistryForceUpdateSecretsRequestedV1 struct { } type WorkflowRegistryWorkflowRegisteredV1 struct { - WorkflowID [32]byte - Owner []byte - DonID uint32 - Status uint8 - WorkflowName string - BinaryURL string - ConfigURL string - SecretsURL string + WorkflowID [32]byte + WorkflowOwner []byte + DonID uint32 + Status uint8 + WorkflowName string + BinaryURL string + ConfigURL string + SecretsURL string } type WorkflowRegistryWorkflowUpdatedV1 struct { @@ -125,6 +127,8 @@ func newLastFetchedAtMap() *lastFetchedAtMap { } } +type engineFactoryFn func(ctx context.Context, wfid string, owner string, name string, config []byte, binary []byte) (services.Service, error) + // eventHandler is a handler for WorkflowRegistryEvent events. Each event type has a corresponding // method that handles the event. type eventHandler struct { @@ -133,12 +137,13 @@ type eventHandler struct { fetcher FetcherFunc workflowStore store.Store capRegistry core.CapabilitiesRegistry - engineRegistry *engineRegistry + engineRegistry *EngineRegistry emitter custmsg.MessageEmitter lastFetchedAtMap *lastFetchedAtMap clock clockwork.Clock secretsFreshnessDuration time.Duration encryptionKey workflowkey.Key + engineFactory engineFactoryFn } type Event interface { @@ -148,6 +153,18 @@ type Event interface { var defaultSecretsFreshnessDuration = 24 * time.Hour +func WithEngineRegistry(er *EngineRegistry) func(*eventHandler) { + return func(e *eventHandler) { + e.engineRegistry = er + } +} + +func WithEngineFactoryFn(efn engineFactoryFn) func(*eventHandler) { + return func(e *eventHandler) { + e.engineFactory = efn + } +} + // NewEventHandler returns a new eventHandler instance. func NewEventHandler( lggr logger.Logger, @@ -158,20 +175,26 @@ func NewEventHandler( emitter custmsg.MessageEmitter, clock clockwork.Clock, encryptionKey workflowkey.Key, + opts ...func(*eventHandler), ) *eventHandler { - return &eventHandler{ + eh := &eventHandler{ lggr: lggr, orm: orm, fetcher: gateway, workflowStore: workflowStore, capRegistry: capRegistry, - engineRegistry: newEngineRegistry(), + engineRegistry: NewEngineRegistry(), emitter: emitter, lastFetchedAtMap: newLastFetchedAtMap(), clock: clock, secretsFreshnessDuration: defaultSecretsFreshnessDuration, encryptionKey: encryptionKey, } + eh.engineFactory = eh.engineFactoryFn + for _, o := range opts { + o(eh) + } + return eh } func (h *eventHandler) refreshSecrets(ctx context.Context, workflowOwner, workflowName, workflowID, secretsURLHash string) (string, error) { @@ -276,7 +299,7 @@ func (h *eventHandler) Handle(ctx context.Context, event Event) error { cma := h.emitter.With( platform.KeyWorkflowID, wfID, platform.KeyWorkflowName, payload.WorkflowName, - platform.KeyWorkflowOwner, hex.EncodeToString(payload.Owner), + platform.KeyWorkflowOwner, hex.EncodeToString(payload.WorkflowOwner), ) if err := h.workflowRegisteredEvent(ctx, payload); err != nil { @@ -377,20 +400,13 @@ func (h *eventHandler) workflowRegisteredEvent( ctx context.Context, payload WorkflowRegistryWorkflowRegisteredV1, ) error { - // Download the contents of binaryURL, configURL and secretsURL and cache them locally. - binary, err := h.fetcher(ctx, payload.BinaryURL) + // Fetch the workflow artifacts from the database or download them from the specified URLs + decodedBinary, config, err := h.getWorkflowArtifacts(ctx, payload) if err != nil { - return fmt.Errorf("failed to fetch binary from %s : %w", payload.BinaryURL, err) - } - - var config []byte - if payload.ConfigURL != "" { - config, err = h.fetcher(ctx, payload.ConfigURL) - if err != nil { - return fmt.Errorf("failed to fetch config from %s : %w", payload.ConfigURL, err) - } + return err } + // Always fetch secrets from the SecretsURL var secrets []byte if payload.SecretsURL != "" { secrets, err = h.fetcher(ctx, payload.SecretsURL) @@ -400,7 +416,7 @@ func (h *eventHandler) workflowRegisteredEvent( } // Calculate the hash of the binary and config files - hash, err := pkgworkflows.GenerateWorkflowID(payload.Owner, binary, config, payload.SecretsURL) + hash, err := pkgworkflows.GenerateWorkflowID(payload.WorkflowOwner, payload.WorkflowName, decodedBinary, config, payload.SecretsURL) if err != nil { return fmt.Errorf("failed to generate workflow id: %w", err) } @@ -410,8 +426,13 @@ func (h *eventHandler) workflowRegisteredEvent( return fmt.Errorf("workflowID mismatch: %x != %x", hash, payload.WorkflowID) } + // Ensure that there is no running workflow engine for the given workflow ID. + if h.engineRegistry.IsRunning(hex.EncodeToString(payload.WorkflowID[:])) { + return fmt.Errorf("workflow is already running, so not starting it : %s", hex.EncodeToString(payload.WorkflowID[:])) + } + // Save the workflow secrets - urlHash, err := h.orm.GetSecretsURLHash(payload.Owner, []byte(payload.SecretsURL)) + urlHash, err := h.orm.GetSecretsURLHash(payload.WorkflowOwner, []byte(payload.SecretsURL)) if err != nil { return fmt.Errorf("failed to get secrets URL hash: %w", err) } @@ -423,12 +444,13 @@ func (h *eventHandler) workflowRegisteredEvent( } wfID := hex.EncodeToString(payload.WorkflowID[:]) + owner := hex.EncodeToString(payload.WorkflowOwner) entry := &job.WorkflowSpec{ - Workflow: hex.EncodeToString(binary), + Workflow: hex.EncodeToString(decodedBinary), Config: string(config), WorkflowID: wfID, Status: status, - WorkflowOwner: hex.EncodeToString(payload.Owner), + WorkflowOwner: owner, WorkflowName: payload.WorkflowName, SpecType: job.WASMFile, BinaryURL: payload.BinaryURL, @@ -444,36 +466,83 @@ func (h *eventHandler) workflowRegisteredEvent( } // If status == active, start a new WorkflowEngine instance, and add it to local engine registry + engine, err := h.engineFactory( + ctx, + wfID, + owner, + payload.WorkflowName, + config, + decodedBinary, + ) + if err != nil { + return fmt.Errorf("failed to create workflow engine: %w", err) + } + + if err := engine.Start(ctx); err != nil { + return fmt.Errorf("failed to start workflow engine: %w", err) + } + + h.engineRegistry.Add(wfID, engine) + + return nil +} + +// getWorkflowArtifacts retrieves the workflow artifacts from the database if they exist, +// or downloads them from the specified URLs if they are not found in the database. +func (h *eventHandler) getWorkflowArtifacts( + ctx context.Context, + payload WorkflowRegistryWorkflowRegisteredV1, +) ([]byte, []byte, error) { + spec, err := h.orm.GetWorkflowSpecByID(ctx, hex.EncodeToString(payload.WorkflowID[:])) + if err != nil { + binary, err2 := h.fetcher(ctx, payload.BinaryURL) + if err2 != nil { + return nil, nil, fmt.Errorf("failed to fetch binary from %s : %w", payload.BinaryURL, err) + } + + decodedBinary, err2 := base64.StdEncoding.DecodeString(string(binary)) + if err2 != nil { + return nil, nil, fmt.Errorf("failed to decode binary: %w", err) + } + + var config []byte + if payload.ConfigURL != "" { + config, err2 = h.fetcher(ctx, payload.ConfigURL) + if err2 != nil { + return nil, nil, fmt.Errorf("failed to fetch config from %s : %w", payload.ConfigURL, err) + } + } + return decodedBinary, config, nil + } + + // there is no update in the BinaryURL or ConfigURL, lets decode the stored artifacts + decodedBinary, err := hex.DecodeString(spec.Workflow) + if err != nil { + return nil, nil, fmt.Errorf("failed to decode stored workflow spec: %w", err) + } + return decodedBinary, []byte(spec.Config), nil +} + +func (h *eventHandler) engineFactoryFn(ctx context.Context, id string, owner string, name string, config []byte, binary []byte) (services.Service, error) { moduleConfig := &host.ModuleConfig{Logger: h.lggr, Labeler: h.emitter} sdkSpec, err := host.GetWorkflowSpec(ctx, moduleConfig, binary, config) if err != nil { - return fmt.Errorf("failed to get workflow sdk spec: %w", err) + return nil, fmt.Errorf("failed to get workflow sdk spec: %w", err) } cfg := workflows.Config{ Lggr: h.lggr, Workflow: *sdkSpec, - WorkflowID: wfID, - WorkflowOwner: string(payload.Owner), // this gets hex encoded in the engine. - WorkflowName: payload.WorkflowName, + WorkflowID: id, + WorkflowOwner: owner, // this gets hex encoded in the engine. + WorkflowName: name, Registry: h.capRegistry, Store: h.workflowStore, Config: config, Binary: binary, SecretsFetcher: h, } - e, err := workflows.NewEngine(ctx, cfg) - if err != nil { - return fmt.Errorf("failed to create workflow engine: %w", err) - } - - if err := e.Start(ctx); err != nil { - return fmt.Errorf("failed to start workflow engine: %w", err) - } - - h.engineRegistry.Add(wfID, e) - - return nil + return workflows.NewEngine(ctx, cfg) } // workflowUpdatedEvent handles the WorkflowUpdatedEvent event type by first finding the @@ -489,14 +558,14 @@ func (h *eventHandler) workflowUpdatedEvent( } registeredEvent := WorkflowRegistryWorkflowRegisteredV1{ - WorkflowID: payload.NewWorkflowID, - Owner: payload.WorkflowOwner, - DonID: payload.DonID, - Status: 0, - WorkflowName: payload.WorkflowName, - BinaryURL: payload.BinaryURL, - ConfigURL: payload.ConfigURL, - SecretsURL: payload.SecretsURL, + WorkflowID: payload.NewWorkflowID, + WorkflowOwner: payload.WorkflowOwner, + DonID: payload.DonID, + Status: 0, + WorkflowName: payload.WorkflowName, + BinaryURL: payload.BinaryURL, + ConfigURL: payload.ConfigURL, + SecretsURL: payload.SecretsURL, } return h.workflowRegisteredEvent(ctx, registeredEvent) @@ -551,14 +620,14 @@ func (h *eventHandler) workflowActivatedEvent( // start a new workflow engine registeredEvent := WorkflowRegistryWorkflowRegisteredV1{ - WorkflowID: payload.WorkflowID, - Owner: payload.WorkflowOwner, - DonID: payload.DonID, - Status: 0, - WorkflowName: payload.WorkflowName, - BinaryURL: spec.BinaryURL, - ConfigURL: spec.ConfigURL, - SecretsURL: secretsURL, + WorkflowID: payload.WorkflowID, + WorkflowOwner: payload.WorkflowOwner, + DonID: payload.DonID, + Status: 0, + WorkflowName: payload.WorkflowName, + BinaryURL: spec.BinaryURL, + ConfigURL: spec.ConfigURL, + SecretsURL: secretsURL, } return h.workflowRegisteredEvent(ctx, registeredEvent) diff --git a/core/services/workflows/syncer/handler_test.go b/core/services/workflows/syncer/handler_test.go index 7d11d347a02..f205cbde1cd 100644 --- a/core/services/workflows/syncer/handler_test.go +++ b/core/services/workflows/syncer/handler_test.go @@ -3,6 +3,7 @@ package syncer import ( "context" "database/sql" + "encoding/base64" "encoding/hex" "encoding/json" "errors" @@ -10,6 +11,7 @@ import ( "time" "github.com/smartcontractkit/chainlink-common/pkg/custmsg" + "github.com/smartcontractkit/chainlink-common/pkg/services" pkgworkflows "github.com/smartcontractkit/chainlink-common/pkg/workflows" "github.com/smartcontractkit/chainlink-common/pkg/workflows/secrets" "github.com/smartcontractkit/chainlink/v2/core/capabilities" @@ -46,6 +48,28 @@ func newMockFetcher(m map[string]mockFetchResp) FetcherFunc { return (&mockFetcher{responseMap: m}).Fetch } +type mockEngine struct { + CloseErr error + ReadyErr error + StartErr error +} + +func (m *mockEngine) Ready() error { + return m.ReadyErr +} + +func (m *mockEngine) Close() error { + return m.CloseErr +} + +func (m *mockEngine) Start(_ context.Context) error { + return m.StartErr +} + +func (m *mockEngine) HealthReport() map[string]error { return nil } + +func (m *mockEngine) Name() string { return "mockEngine" } + func Test_Handler(t *testing.T) { lggr := logger.TestLogger(t) emitter := custmsg.NewLabeler() @@ -179,7 +203,12 @@ func Test_workflowRegisteredHandler(t *testing.T) { var config = []byte("") var wfOwner = []byte("0xOwner") var binary = wasmtest.CreateTestBinary(binaryCmd, binaryLocation, true, t) - defaultValidationFn := func(t *testing.T, ctx context.Context, h *eventHandler, wfOwner []byte, wfName string, wfID string) { + var encodedBinary = []byte(base64.StdEncoding.EncodeToString(binary)) + + defaultValidationFn := func(t *testing.T, ctx context.Context, event WorkflowRegistryWorkflowRegisteredV1, h *eventHandler, wfOwner []byte, wfName string, wfID string) { + err := h.workflowRegisteredEvent(ctx, event) + require.NoError(t, err) + // Verify the record is updated in the database dbSpec, err := h.orm.GetWorkflowSpec(ctx, hex.EncodeToString(wfOwner), "workflow-name") require.NoError(t, err) @@ -198,10 +227,13 @@ func Test_workflowRegisteredHandler(t *testing.T) { { Name: "success with active workflow registered", fetcher: newMockFetcher(map[string]mockFetchResp{ - binaryURL: {Body: binary, Err: nil}, + binaryURL: {Body: encodedBinary, Err: nil}, configURL: {Body: config, Err: nil}, secretsURL: {Body: []byte("secrets"), Err: nil}, }), + engineFactoryFn: func(ctx context.Context, wfid string, owner string, name string, config []byte, binary []byte) (services.Service, error) { + return &mockEngine{}, nil + }, GiveConfig: config, ConfigURL: configURL, SecretsURL: secretsURL, @@ -210,21 +242,86 @@ func Test_workflowRegisteredHandler(t *testing.T) { WFOwner: wfOwner, Event: func(wfID []byte) WorkflowRegistryWorkflowRegisteredV1 { return WorkflowRegistryWorkflowRegisteredV1{ - Status: uint8(0), - WorkflowID: [32]byte(wfID), - Owner: wfOwner, - WorkflowName: "workflow-name", - BinaryURL: binaryURL, - ConfigURL: configURL, - SecretsURL: secretsURL, + Status: uint8(0), + WorkflowID: [32]byte(wfID), + WorkflowOwner: wfOwner, + WorkflowName: "workflow-name", + BinaryURL: binaryURL, + ConfigURL: configURL, + SecretsURL: secretsURL, } }, validationFn: defaultValidationFn, }, + { + Name: "fails to start engine", + fetcher: newMockFetcher(map[string]mockFetchResp{ + binaryURL: {Body: encodedBinary, Err: nil}, + configURL: {Body: config, Err: nil}, + secretsURL: {Body: []byte("secrets"), Err: nil}, + }), + engineFactoryFn: func(ctx context.Context, wfid string, owner string, name string, config []byte, binary []byte) (services.Service, error) { + return &mockEngine{StartErr: assert.AnError}, nil + }, + GiveConfig: config, + ConfigURL: configURL, + SecretsURL: secretsURL, + BinaryURL: binaryURL, + GiveBinary: binary, + WFOwner: wfOwner, + Event: func(wfID []byte) WorkflowRegistryWorkflowRegisteredV1 { + return WorkflowRegistryWorkflowRegisteredV1{ + Status: uint8(0), + WorkflowID: [32]byte(wfID), + WorkflowOwner: wfOwner, + WorkflowName: "workflow-name", + BinaryURL: binaryURL, + ConfigURL: configURL, + SecretsURL: secretsURL, + } + }, + validationFn: func(t *testing.T, ctx context.Context, event WorkflowRegistryWorkflowRegisteredV1, h *eventHandler, wfOwner []byte, wfName string, wfID string) { + err := h.workflowRegisteredEvent(ctx, event) + require.Error(t, err) + require.ErrorIs(t, err, assert.AnError) + }, + }, + { + Name: "fails if running engine exists", + fetcher: newMockFetcher(map[string]mockFetchResp{ + binaryURL: {Body: encodedBinary, Err: nil}, + configURL: {Body: config, Err: nil}, + secretsURL: {Body: []byte("secrets"), Err: nil}, + }), + GiveConfig: config, + ConfigURL: configURL, + SecretsURL: secretsURL, + BinaryURL: binaryURL, + GiveBinary: binary, + WFOwner: wfOwner, + Event: func(wfID []byte) WorkflowRegistryWorkflowRegisteredV1 { + return WorkflowRegistryWorkflowRegisteredV1{ + Status: uint8(0), + WorkflowID: [32]byte(wfID), + WorkflowOwner: wfOwner, + WorkflowName: "workflow-name", + BinaryURL: binaryURL, + ConfigURL: configURL, + SecretsURL: secretsURL, + } + }, + validationFn: func(t *testing.T, ctx context.Context, event WorkflowRegistryWorkflowRegisteredV1, h *eventHandler, wfOwner []byte, wfName string, wfID string) { + me := &mockEngine{} + h.engineRegistry.Add(wfID, me) + err := h.workflowRegisteredEvent(ctx, event) + require.Error(t, err) + require.ErrorContains(t, err, "workflow is already running") + }, + }, { Name: "success with paused workflow registered", fetcher: newMockFetcher(map[string]mockFetchResp{ - binaryURL: {Body: binary, Err: nil}, + binaryURL: {Body: encodedBinary, Err: nil}, configURL: {Body: config, Err: nil}, secretsURL: {Body: []byte("secrets"), Err: nil}, }), @@ -236,16 +333,19 @@ func Test_workflowRegisteredHandler(t *testing.T) { WFOwner: wfOwner, Event: func(wfID []byte) WorkflowRegistryWorkflowRegisteredV1 { return WorkflowRegistryWorkflowRegisteredV1{ - Status: uint8(1), - WorkflowID: [32]byte(wfID), - Owner: wfOwner, - WorkflowName: "workflow-name", - BinaryURL: binaryURL, - ConfigURL: configURL, - SecretsURL: secretsURL, + Status: uint8(1), + WorkflowID: [32]byte(wfID), + WorkflowOwner: wfOwner, + WorkflowName: "workflow-name", + BinaryURL: binaryURL, + ConfigURL: configURL, + SecretsURL: secretsURL, } }, - validationFn: func(t *testing.T, ctx context.Context, h *eventHandler, wfOwner []byte, wfName string, wfID string) { + validationFn: func(t *testing.T, ctx context.Context, event WorkflowRegistryWorkflowRegisteredV1, h *eventHandler, wfOwner []byte, wfName string, wfID string) { + err := h.workflowRegisteredEvent(ctx, event) + require.NoError(t, err) + // Verify the record is updated in the database dbSpec, err := h.orm.GetWorkflowSpec(ctx, hex.EncodeToString(wfOwner), "workflow-name") require.NoError(t, err) @@ -267,18 +367,18 @@ func Test_workflowRegisteredHandler(t *testing.T) { GiveBinary: binary, WFOwner: wfOwner, fetcher: newMockFetcher(map[string]mockFetchResp{ - binaryURL: {Body: binary, Err: nil}, + binaryURL: {Body: encodedBinary, Err: nil}, secretsURL: {Body: []byte("secrets"), Err: nil}, }), validationFn: defaultValidationFn, Event: func(wfID []byte) WorkflowRegistryWorkflowRegisteredV1 { return WorkflowRegistryWorkflowRegisteredV1{ - Status: uint8(0), - WorkflowID: [32]byte(wfID), - Owner: wfOwner, - WorkflowName: "workflow-name", - BinaryURL: binaryURL, - SecretsURL: secretsURL, + Status: uint8(0), + WorkflowID: [32]byte(wfID), + WorkflowOwner: wfOwner, + WorkflowName: "workflow-name", + BinaryURL: binaryURL, + SecretsURL: secretsURL, } }, }, @@ -290,18 +390,18 @@ func Test_workflowRegisteredHandler(t *testing.T) { GiveBinary: binary, WFOwner: wfOwner, fetcher: newMockFetcher(map[string]mockFetchResp{ - binaryURL: {Body: binary, Err: nil}, + binaryURL: {Body: encodedBinary, Err: nil}, configURL: {Body: config, Err: nil}, }), validationFn: defaultValidationFn, Event: func(wfID []byte) WorkflowRegistryWorkflowRegisteredV1 { return WorkflowRegistryWorkflowRegisteredV1{ - Status: uint8(0), - WorkflowID: [32]byte(wfID), - Owner: wfOwner, - WorkflowName: "workflow-name", - BinaryURL: binaryURL, - ConfigURL: configURL, + Status: uint8(0), + WorkflowID: [32]byte(wfID), + WorkflowOwner: wfOwner, + WorkflowName: "workflow-name", + BinaryURL: binaryURL, + ConfigURL: configURL, } }, }, @@ -313,21 +413,22 @@ func Test_workflowRegisteredHandler(t *testing.T) { } type testCase struct { - Name string - SecretsURL string - BinaryURL string - GiveBinary []byte - GiveConfig []byte - ConfigURL string - WFOwner []byte - fetcher FetcherFunc - Event func([]byte) WorkflowRegistryWorkflowRegisteredV1 - validationFn func(t *testing.T, ctx context.Context, h *eventHandler, wfOwner []byte, wfName string, wfID string) + Name string + SecretsURL string + BinaryURL string + GiveBinary []byte + GiveConfig []byte + ConfigURL string + WFOwner []byte + fetcher FetcherFunc + Event func([]byte) WorkflowRegistryWorkflowRegisteredV1 + validationFn func(t *testing.T, ctx context.Context, event WorkflowRegistryWorkflowRegisteredV1, h *eventHandler, wfOwner []byte, wfName string, wfID string) + engineFactoryFn func(ctx context.Context, wfid string, owner string, name string, config []byte, binary []byte) (services.Service, error) } -func testRunningWorkflow(t *testing.T, cmd testCase) { +func testRunningWorkflow(t *testing.T, tc testCase) { t.Helper() - t.Run(cmd.Name, func(t *testing.T) { + t.Run(tc.Name, func(t *testing.T) { var ( ctx = testutils.Context(t) lggr = logger.TestLogger(t) @@ -335,38 +436,35 @@ func testRunningWorkflow(t *testing.T, cmd testCase) { orm = NewWorkflowRegistryDS(db, lggr) emitter = custmsg.NewLabeler() - binary = cmd.GiveBinary - config = cmd.GiveConfig - secretsURL = cmd.SecretsURL - wfOwner = cmd.WFOwner + binary = tc.GiveBinary + config = tc.GiveConfig + secretsURL = tc.SecretsURL + wfOwner = tc.WFOwner - fetcher = cmd.fetcher + fetcher = tc.fetcher ) - giveWFID, err := pkgworkflows.GenerateWorkflowID(wfOwner, binary, config, secretsURL) + giveWFID, err := pkgworkflows.GenerateWorkflowID(wfOwner, "workflow-name", binary, config, secretsURL) require.NoError(t, err) wfID := hex.EncodeToString(giveWFID[:]) - event := cmd.Event(giveWFID[:]) + event := tc.Event(giveWFID[:]) - er := newEngineRegistry() + er := NewEngineRegistry() + opts := []func(*eventHandler){ + WithEngineRegistry(er), + } + if tc.engineFactoryFn != nil { + opts = append(opts, WithEngineFactoryFn(tc.engineFactoryFn)) + } store := wfstore.NewDBStore(db, lggr, clockwork.NewFakeClock()) registry := capabilities.NewRegistry(lggr) registry.SetLocalRegistry(&capabilities.TestMetadataRegistry{}) - h := &eventHandler{ - lggr: lggr, - orm: orm, - fetcher: fetcher, - emitter: emitter, - engineRegistry: er, - capRegistry: registry, - workflowStore: store, - } - err = h.workflowRegisteredEvent(ctx, event) - require.NoError(t, err) + h := NewEventHandler(lggr, orm, fetcher, store, registry, emitter, clockwork.NewFakeClock(), + workflowkey.Key{}, opts...) - cmd.validationFn(t, ctx, h, wfOwner, "workflow-name", wfID) + tc.validationFn(t, ctx, event, h, wfOwner, "workflow-name", wfID) }) } @@ -379,48 +477,51 @@ func Test_workflowDeletedHandler(t *testing.T) { orm = NewWorkflowRegistryDS(db, lggr) emitter = custmsg.NewLabeler() - binary = wasmtest.CreateTestBinary(binaryCmd, binaryLocation, true, t) - config = []byte("") - secretsURL = "http://example.com" - binaryURL = "http://example.com/binary" - configURL = "http://example.com/config" - wfOwner = []byte("0xOwner") + binary = wasmtest.CreateTestBinary(binaryCmd, binaryLocation, true, t) + encodedBinary = []byte(base64.StdEncoding.EncodeToString(binary)) + config = []byte("") + secretsURL = "http://example.com" + binaryURL = "http://example.com/binary" + configURL = "http://example.com/config" + wfOwner = []byte("0xOwner") fetcher = newMockFetcher(map[string]mockFetchResp{ - binaryURL: {Body: binary, Err: nil}, + binaryURL: {Body: encodedBinary, Err: nil}, configURL: {Body: config, Err: nil}, secretsURL: {Body: []byte("secrets"), Err: nil}, }) ) - giveWFID, err := pkgworkflows.GenerateWorkflowID(wfOwner, binary, config, secretsURL) + giveWFID, err := pkgworkflows.GenerateWorkflowID(wfOwner, "workflow-name", binary, config, secretsURL) require.NoError(t, err) wfIDs := hex.EncodeToString(giveWFID[:]) active := WorkflowRegistryWorkflowRegisteredV1{ - Status: uint8(0), - WorkflowID: giveWFID, - Owner: wfOwner, - WorkflowName: "workflow-name", - BinaryURL: binaryURL, - ConfigURL: configURL, - SecretsURL: secretsURL, + Status: uint8(0), + WorkflowID: giveWFID, + WorkflowOwner: wfOwner, + WorkflowName: "workflow-name", + BinaryURL: binaryURL, + ConfigURL: configURL, + SecretsURL: secretsURL, } - er := newEngineRegistry() + er := NewEngineRegistry() store := wfstore.NewDBStore(db, lggr, clockwork.NewFakeClock()) registry := capabilities.NewRegistry(lggr) registry.SetLocalRegistry(&capabilities.TestMetadataRegistry{}) - h := &eventHandler{ - lggr: lggr, - orm: orm, - fetcher: fetcher, - emitter: emitter, - engineRegistry: er, - capRegistry: registry, - workflowStore: store, - } + h := NewEventHandler( + lggr, + orm, + fetcher, + store, + registry, + emitter, + clockwork.NewFakeClock(), + workflowkey.Key{}, + WithEngineRegistry(er), + ) err = h.workflowRegisteredEvent(ctx, active) require.NoError(t, err) @@ -465,26 +566,27 @@ func Test_workflowPausedActivatedUpdatedHandler(t *testing.T) { orm = NewWorkflowRegistryDS(db, lggr) emitter = custmsg.NewLabeler() - binary = wasmtest.CreateTestBinary(binaryCmd, binaryLocation, true, t) - config = []byte("") - updateConfig = []byte("updated") - secretsURL = "http://example.com" - binaryURL = "http://example.com/binary" - configURL = "http://example.com/config" - newConfigURL = "http://example.com/new-config" - wfOwner = []byte("0xOwner") + binary = wasmtest.CreateTestBinary(binaryCmd, binaryLocation, true, t) + encodedBinary = []byte(base64.StdEncoding.EncodeToString(binary)) + config = []byte("") + updateConfig = []byte("updated") + secretsURL = "http://example.com" + binaryURL = "http://example.com/binary" + configURL = "http://example.com/config" + newConfigURL = "http://example.com/new-config" + wfOwner = []byte("0xOwner") fetcher = newMockFetcher(map[string]mockFetchResp{ - binaryURL: {Body: binary, Err: nil}, + binaryURL: {Body: encodedBinary, Err: nil}, configURL: {Body: config, Err: nil}, newConfigURL: {Body: updateConfig, Err: nil}, secretsURL: {Body: []byte("secrets"), Err: nil}, }) ) - giveWFID, err := pkgworkflows.GenerateWorkflowID(wfOwner, binary, config, secretsURL) + giveWFID, err := pkgworkflows.GenerateWorkflowID(wfOwner, "workflow-name", binary, config, secretsURL) require.NoError(t, err) - updatedWFID, err := pkgworkflows.GenerateWorkflowID(wfOwner, binary, updateConfig, secretsURL) + updatedWFID, err := pkgworkflows.GenerateWorkflowID(wfOwner, "workflow-name", binary, updateConfig, secretsURL) require.NoError(t, err) require.NoError(t, err) @@ -494,28 +596,30 @@ func Test_workflowPausedActivatedUpdatedHandler(t *testing.T) { newWFIDs := hex.EncodeToString(updatedWFID[:]) active := WorkflowRegistryWorkflowRegisteredV1{ - Status: uint8(0), - WorkflowID: giveWFID, - Owner: wfOwner, - WorkflowName: "workflow-name", - BinaryURL: binaryURL, - ConfigURL: configURL, - SecretsURL: secretsURL, + Status: uint8(0), + WorkflowID: giveWFID, + WorkflowOwner: wfOwner, + WorkflowName: "workflow-name", + BinaryURL: binaryURL, + ConfigURL: configURL, + SecretsURL: secretsURL, } - er := newEngineRegistry() + er := NewEngineRegistry() store := wfstore.NewDBStore(db, lggr, clockwork.NewFakeClock()) registry := capabilities.NewRegistry(lggr) registry.SetLocalRegistry(&capabilities.TestMetadataRegistry{}) - h := &eventHandler{ - lggr: lggr, - orm: orm, - fetcher: fetcher, - emitter: emitter, - engineRegistry: er, - capRegistry: registry, - workflowStore: store, - } + h := NewEventHandler( + lggr, + orm, + fetcher, + store, + registry, + emitter, + clockwork.NewFakeClock(), + workflowkey.Key{}, + WithEngineRegistry(er), + ) err = h.workflowRegisteredEvent(ctx, active) require.NoError(t, err) diff --git a/core/services/workflows/syncer/heap.go b/core/services/workflows/syncer/heap.go deleted file mode 100644 index 061293928a3..00000000000 --- a/core/services/workflows/syncer/heap.go +++ /dev/null @@ -1,63 +0,0 @@ -package syncer - -import "container/heap" - -type Heap interface { - // Push adds a new item to the heap. - Push(x WorkflowRegistryEventResponse) - - // Pop removes the smallest item from the heap and returns it. - Pop() WorkflowRegistryEventResponse - - // Len returns the number of items in the heap. - Len() int -} - -// publicHeap is a wrapper around the heap.Interface that exposes the Push and Pop methods. -type publicHeap[T any] struct { - heap heap.Interface -} - -func (h *publicHeap[T]) Push(x T) { - heap.Push(h.heap, x) -} - -func (h *publicHeap[T]) Pop() T { - return heap.Pop(h.heap).(T) -} - -func (h *publicHeap[T]) Len() int { - return h.heap.Len() -} - -// blockHeightHeap is a heap.Interface that sorts WorkflowRegistryEventResponses by block height. -type blockHeightHeap []WorkflowRegistryEventResponse - -// newBlockHeightHeap returns an initialized heap that sorts WorkflowRegistryEventResponses by block height. -func newBlockHeightHeap() Heap { - h := blockHeightHeap(make([]WorkflowRegistryEventResponse, 0)) - heap.Init(&h) - return &publicHeap[WorkflowRegistryEventResponse]{heap: &h} -} - -func (h *blockHeightHeap) Len() int { return len(*h) } - -func (h *blockHeightHeap) Less(i, j int) bool { - return (*h)[i].Event.Head.Height < (*h)[j].Event.Head.Height -} - -func (h *blockHeightHeap) Swap(i, j int) { - (*h)[i], (*h)[j] = (*h)[j], (*h)[i] -} - -func (h *blockHeightHeap) Push(x any) { - *h = append(*h, x.(WorkflowRegistryEventResponse)) -} - -func (h *blockHeightHeap) Pop() any { - old := *h - n := len(old) - x := old[n-1] - *h = old[0 : n-1] - return x -} diff --git a/core/services/workflows/syncer/mocks/orm.go b/core/services/workflows/syncer/mocks/orm.go index da96f422361..09a543d65e3 100644 --- a/core/services/workflows/syncer/mocks/orm.go +++ b/core/services/workflows/syncer/mocks/orm.go @@ -540,6 +540,65 @@ func (_c *ORM_GetWorkflowSpec_Call) RunAndReturn(run func(context.Context, strin return _c } +// GetWorkflowSpecByID provides a mock function with given fields: ctx, id +func (_m *ORM) GetWorkflowSpecByID(ctx context.Context, id string) (*job.WorkflowSpec, error) { + ret := _m.Called(ctx, id) + + if len(ret) == 0 { + panic("no return value specified for GetWorkflowSpecByID") + } + + var r0 *job.WorkflowSpec + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string) (*job.WorkflowSpec, error)); ok { + return rf(ctx, id) + } + if rf, ok := ret.Get(0).(func(context.Context, string) *job.WorkflowSpec); ok { + r0 = rf(ctx, id) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*job.WorkflowSpec) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, id) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ORM_GetWorkflowSpecByID_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetWorkflowSpecByID' +type ORM_GetWorkflowSpecByID_Call struct { + *mock.Call +} + +// GetWorkflowSpecByID is a helper method to define mock.On call +// - ctx context.Context +// - id string +func (_e *ORM_Expecter) GetWorkflowSpecByID(ctx interface{}, id interface{}) *ORM_GetWorkflowSpecByID_Call { + return &ORM_GetWorkflowSpecByID_Call{Call: _e.mock.On("GetWorkflowSpecByID", ctx, id)} +} + +func (_c *ORM_GetWorkflowSpecByID_Call) Run(run func(ctx context.Context, id string)) *ORM_GetWorkflowSpecByID_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string)) + }) + return _c +} + +func (_c *ORM_GetWorkflowSpecByID_Call) Return(_a0 *job.WorkflowSpec, _a1 error) *ORM_GetWorkflowSpecByID_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *ORM_GetWorkflowSpecByID_Call) RunAndReturn(run func(context.Context, string) (*job.WorkflowSpec, error)) *ORM_GetWorkflowSpecByID_Call { + _c.Call.Return(run) + return _c +} + // Update provides a mock function with given fields: ctx, secretsURL, contents func (_m *ORM) Update(ctx context.Context, secretsURL string, contents string) (int64, error) { ret := _m.Called(ctx, secretsURL, contents) diff --git a/core/services/workflows/syncer/orm.go b/core/services/workflows/syncer/orm.go index 97f2c834f36..ff9336a0893 100644 --- a/core/services/workflows/syncer/orm.go +++ b/core/services/workflows/syncer/orm.go @@ -52,6 +52,9 @@ type WorkflowSpecsDS interface { // DeleteWorkflowSpec deletes the workflow spec for the given owner and name. DeleteWorkflowSpec(ctx context.Context, owner, name string) error + + // GetWorkflowSpecByID returns the workflow spec for the given workflowID. + GetWorkflowSpecByID(ctx context.Context, id string) (*job.WorkflowSpec, error) } type ORM interface { @@ -161,6 +164,10 @@ func (orm *orm) GetContentsByWorkflowID(ctx context.Context, workflowID string) return "", "", ErrEmptySecrets } + if jr.Contents.String == "" { + return "", "", ErrEmptySecrets + } + return jr.SecretsURLHash.String, jr.Contents.String, nil } @@ -206,65 +213,73 @@ func (orm *orm) GetSecretsURLHash(owner, secretsURL []byte) ([]byte, error) { func (orm *orm) UpsertWorkflowSpec(ctx context.Context, spec *job.WorkflowSpec) (int64, error) { var id int64 + err := sqlutil.TransactDataSource(ctx, orm.ds, nil, func(tx sqlutil.DataSource) error { + txErr := tx.QueryRowxContext( + ctx, + `DELETE FROM workflow_specs WHERE workflow_owner = $1 AND workflow_name = $2 AND workflow_id != $3`, + spec.WorkflowOwner, + spec.WorkflowName, + spec.WorkflowID, + ).Scan(nil) + if txErr != nil && !errors.Is(txErr, sql.ErrNoRows) { + return fmt.Errorf("failed to clean up previous workflow specs: %w", txErr) + } - query := ` - INSERT INTO workflow_specs ( - workflow, - config, - workflow_id, - workflow_owner, - workflow_name, - status, - binary_url, - config_url, - secrets_id, - created_at, - updated_at, - spec_type - ) VALUES ( - :workflow, - :config, - :workflow_id, - :workflow_owner, - :workflow_name, - :status, - :binary_url, - :config_url, - :secrets_id, - :created_at, - :updated_at, - :spec_type - ) ON CONFLICT (workflow_owner, workflow_name) DO UPDATE - SET - workflow = EXCLUDED.workflow, - config = EXCLUDED.config, - workflow_id = EXCLUDED.workflow_id, - workflow_owner = EXCLUDED.workflow_owner, - workflow_name = EXCLUDED.workflow_name, - status = EXCLUDED.status, - binary_url = EXCLUDED.binary_url, - config_url = EXCLUDED.config_url, - secrets_id = EXCLUDED.secrets_id, - created_at = EXCLUDED.created_at, - updated_at = EXCLUDED.updated_at, - spec_type = EXCLUDED.spec_type - RETURNING id - ` - - stmt, err := orm.ds.PrepareNamedContext(ctx, query) - if err != nil { - return 0, err - } - defer stmt.Close() + query := ` + INSERT INTO workflow_specs ( + workflow, + config, + workflow_id, + workflow_owner, + workflow_name, + status, + binary_url, + config_url, + secrets_id, + created_at, + updated_at, + spec_type + ) VALUES ( + :workflow, + :config, + :workflow_id, + :workflow_owner, + :workflow_name, + :status, + :binary_url, + :config_url, + :secrets_id, + :created_at, + :updated_at, + :spec_type + ) ON CONFLICT (workflow_owner, workflow_name) DO UPDATE + SET + workflow = EXCLUDED.workflow, + config = EXCLUDED.config, + workflow_id = EXCLUDED.workflow_id, + workflow_owner = EXCLUDED.workflow_owner, + workflow_name = EXCLUDED.workflow_name, + status = EXCLUDED.status, + binary_url = EXCLUDED.binary_url, + config_url = EXCLUDED.config_url, + secrets_id = EXCLUDED.secrets_id, + created_at = EXCLUDED.created_at, + updated_at = EXCLUDED.updated_at, + spec_type = EXCLUDED.spec_type + RETURNING id + ` - spec.UpdatedAt = time.Now() - err = stmt.QueryRowxContext(ctx, spec).Scan(&id) + stmt, err := orm.ds.PrepareNamedContext(ctx, query) + if err != nil { + return err + } + defer stmt.Close() - if err != nil { - return 0, err - } + spec.UpdatedAt = time.Now() + return stmt.QueryRowxContext(ctx, spec).Scan(&id) + }) - return id, nil + return id, err } func (orm *orm) UpsertWorkflowSpecWithSecrets( @@ -289,6 +304,17 @@ func (orm *orm) UpsertWorkflowSpecWithSecrets( return fmt.Errorf("failed to create workflow secrets: %w", txErr) } + txErr = tx.QueryRowxContext( + ctx, + `DELETE FROM workflow_specs WHERE workflow_owner = $1 AND workflow_name = $2 AND workflow_id != $3`, + spec.WorkflowOwner, + spec.WorkflowName, + spec.WorkflowID, + ).Scan(nil) + if txErr != nil && !errors.Is(txErr, sql.ErrNoRows) { + return fmt.Errorf("failed to clean up previous workflow specs: %w", txErr) + } + spec.SecretsID = sql.NullInt64{Int64: sid, Valid: true} query := ` @@ -328,10 +354,10 @@ func (orm *orm) UpsertWorkflowSpecWithSecrets( status = EXCLUDED.status, binary_url = EXCLUDED.binary_url, config_url = EXCLUDED.config_url, - secrets_id = EXCLUDED.secrets_id, created_at = EXCLUDED.created_at, updated_at = EXCLUDED.updated_at, - spec_type = EXCLUDED.spec_type + spec_type = EXCLUDED.spec_type, + secrets_id = EXCLUDED.secrets_id RETURNING id ` @@ -363,6 +389,22 @@ func (orm *orm) GetWorkflowSpec(ctx context.Context, owner, name string) (*job.W return &spec, nil } +func (orm *orm) GetWorkflowSpecByID(ctx context.Context, id string) (*job.WorkflowSpec, error) { + query := ` + SELECT * + FROM workflow_specs + WHERE workflow_id = $1 + ` + + var spec job.WorkflowSpec + err := orm.ds.GetContext(ctx, &spec, query, id) + if err != nil { + return nil, err + } + + return &spec, nil +} + func (orm *orm) DeleteWorkflowSpec(ctx context.Context, owner, name string) error { query := ` DELETE FROM workflow_specs diff --git a/core/services/workflows/syncer/orm_test.go b/core/services/workflows/syncer/orm_test.go index 08c60447498..f47bd6c3731 100644 --- a/core/services/workflows/syncer/orm_test.go +++ b/core/services/workflows/syncer/orm_test.go @@ -6,6 +6,8 @@ import ( "testing" "time" + "github.com/google/uuid" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" "github.com/smartcontractkit/chainlink/v2/core/logger" @@ -197,6 +199,45 @@ func Test_GetWorkflowSpec(t *testing.T) { }) } +func Test_GetWorkflowSpecByID(t *testing.T) { + db := pgtest.NewSqlxDB(t) + ctx := testutils.Context(t) + lggr := logger.TestLogger(t) + orm := &orm{ds: db, lggr: lggr} + + t.Run("gets a workflow spec by ID", func(t *testing.T) { + spec := &job.WorkflowSpec{ + Workflow: "test_workflow", + Config: "test_config", + WorkflowID: "cid-123", + WorkflowOwner: "owner-123", + WorkflowName: "Test Workflow", + Status: job.WorkflowSpecStatusActive, + BinaryURL: "http://example.com/binary", + ConfigURL: "http://example.com/config", + CreatedAt: time.Now(), + SpecType: job.WASMFile, + } + + id, err := orm.UpsertWorkflowSpec(ctx, spec) + require.NoError(t, err) + require.NotZero(t, id) + + dbSpec, err := orm.GetWorkflowSpecByID(ctx, spec.WorkflowID) + require.NoError(t, err) + require.Equal(t, spec.Workflow, dbSpec.Workflow) + + err = orm.DeleteWorkflowSpec(ctx, spec.WorkflowOwner, spec.WorkflowName) + require.NoError(t, err) + }) + + t.Run("fails if no workflow spec exists", func(t *testing.T) { + dbSpec, err := orm.GetWorkflowSpecByID(ctx, "inexistent-workflow-id") + require.Error(t, err) + require.Nil(t, dbSpec) + }) +} + func Test_GetContentsByWorkflowID(t *testing.T) { db := pgtest.NewSqlxDB(t) ctx := testutils.Context(t) @@ -256,3 +297,160 @@ func Test_GetContentsByWorkflowID(t *testing.T) { assert.Equal(t, giveHash, gotHash) assert.Equal(t, giveContent, gotContent) } + +func Test_GetContentsByWorkflowID_SecretsProvidedButEmpty(t *testing.T) { + db := pgtest.NewSqlxDB(t) + ctx := testutils.Context(t) + lggr := logger.TestLogger(t) + orm := &orm{ds: db, lggr: lggr} + + // workflow_id is missing + _, _, err := orm.GetContentsByWorkflowID(ctx, "doesnt-exist") + require.ErrorContains(t, err, "no rows in result set") + + // secrets_id is nil; should return EmptySecrets + workflowID := "aWorkflowID" + giveURL := "https://example.com" + giveBytes, err := crypto.Keccak256([]byte(giveURL)) + require.NoError(t, err) + giveHash := hex.EncodeToString(giveBytes) + giveContent := "" + _, err = orm.UpsertWorkflowSpecWithSecrets(ctx, &job.WorkflowSpec{ + Workflow: "", + Config: "", + WorkflowID: workflowID, + WorkflowOwner: "aWorkflowOwner", + WorkflowName: "aWorkflowName", + BinaryURL: "", + ConfigURL: "", + CreatedAt: time.Now(), + SpecType: job.DefaultSpecType, + }, giveURL, giveHash, giveContent) + require.NoError(t, err) + + _, _, err = orm.GetContentsByWorkflowID(ctx, workflowID) + require.ErrorIs(t, err, ErrEmptySecrets) +} + +func Test_UpsertWorkflowSpecWithSecrets(t *testing.T) { + db := pgtest.NewSqlxDB(t) + ctx := testutils.Context(t) + lggr := logger.TestLogger(t) + orm := &orm{ds: db, lggr: lggr} + + t.Run("inserts new spec and new secrets", func(t *testing.T) { + giveURL := "https://example.com" + giveBytes, err := crypto.Keccak256([]byte(giveURL)) + require.NoError(t, err) + giveHash := hex.EncodeToString(giveBytes) + giveContent := "some contents" + + spec := &job.WorkflowSpec{ + Workflow: "test_workflow", + Config: "test_config", + WorkflowID: "cid-123", + WorkflowOwner: "owner-123", + WorkflowName: "Test Workflow", + Status: job.WorkflowSpecStatusActive, + BinaryURL: "http://example.com/binary", + ConfigURL: "http://example.com/config", + CreatedAt: time.Now(), + SpecType: job.WASMFile, + } + + _, err = orm.UpsertWorkflowSpecWithSecrets(ctx, spec, giveURL, giveHash, giveContent) + require.NoError(t, err) + + // Verify the record exists in the database + var dbSpec job.WorkflowSpec + err = db.Get(&dbSpec, `SELECT * FROM workflow_specs WHERE workflow_owner = $1 AND workflow_name = $2`, spec.WorkflowOwner, spec.WorkflowName) + require.NoError(t, err) + require.Equal(t, spec.Workflow, dbSpec.Workflow) + + // Verify the secrets exists in the database + contents, err := orm.GetContents(ctx, giveURL) + require.NoError(t, err) + require.Equal(t, giveContent, contents) + }) + + t.Run("updates existing spec and secrets", func(t *testing.T) { + giveURL := "https://example.com" + giveBytes, err := crypto.Keccak256([]byte(giveURL)) + require.NoError(t, err) + giveHash := hex.EncodeToString(giveBytes) + giveContent := "some contents" + + spec := &job.WorkflowSpec{ + Workflow: "test_workflow", + Config: "test_config", + WorkflowID: "cid-123", + WorkflowOwner: "owner-123", + WorkflowName: "Test Workflow", + Status: job.WorkflowSpecStatusActive, + BinaryURL: "http://example.com/binary", + ConfigURL: "http://example.com/config", + CreatedAt: time.Now(), + SpecType: job.WASMFile, + } + + _, err = orm.UpsertWorkflowSpecWithSecrets(ctx, spec, giveURL, giveHash, giveContent) + require.NoError(t, err) + + // Update the status + spec.Status = job.WorkflowSpecStatusPaused + + _, err = orm.UpsertWorkflowSpecWithSecrets(ctx, spec, giveURL, giveHash, "new contents") + require.NoError(t, err) + + // Verify the record is updated in the database + var dbSpec job.WorkflowSpec + err = db.Get(&dbSpec, `SELECT * FROM workflow_specs WHERE workflow_owner = $1 AND workflow_name = $2`, spec.WorkflowOwner, spec.WorkflowName) + require.NoError(t, err) + require.Equal(t, spec.Config, dbSpec.Config) + + // Verify the secrets is updated in the database + contents, err := orm.GetContents(ctx, giveURL) + require.NoError(t, err) + require.Equal(t, "new contents", contents) + }) + + t.Run("updates existing spec and secrets if spec has executions", func(t *testing.T) { + giveURL := "https://example.com" + giveBytes, err := crypto.Keccak256([]byte(giveURL)) + require.NoError(t, err) + giveHash := hex.EncodeToString(giveBytes) + giveContent := "some contents" + + spec := &job.WorkflowSpec{ + Workflow: "test_workflow", + Config: "test_config", + WorkflowID: "cid-123", + WorkflowOwner: "owner-123", + WorkflowName: "Test Workflow", + Status: job.WorkflowSpecStatusActive, + BinaryURL: "http://example.com/binary", + ConfigURL: "http://example.com/config", + CreatedAt: time.Now(), + SpecType: job.WASMFile, + } + + _, err = orm.UpsertWorkflowSpecWithSecrets(ctx, spec, giveURL, giveHash, giveContent) + require.NoError(t, err) + + _, err = db.ExecContext( + ctx, + `INSERT INTO workflow_executions (id, workflow_id, status, created_at) VALUES ($1, $2, $3, $4)`, + uuid.New().String(), + "cid-123", + "started", + time.Now(), + ) + require.NoError(t, err) + + // Update the status + spec.WorkflowID = "cid-456" + + _, err = orm.UpsertWorkflowSpecWithSecrets(ctx, spec, giveURL, giveHash, "new contents") + require.NoError(t, err) + }) +} diff --git a/core/services/workflows/syncer/workflow_registry.go b/core/services/workflows/syncer/workflow_registry.go index 024975539af..4809f3563ca 100644 --- a/core/services/workflows/syncer/workflow_registry.go +++ b/core/services/workflows/syncer/workflow_registry.go @@ -5,13 +5,15 @@ import ( "encoding/hex" "encoding/json" "fmt" + "iter" + "strings" "sync" "time" "github.com/smartcontractkit/chainlink-common/pkg/capabilities" "github.com/smartcontractkit/chainlink-common/pkg/services" - types "github.com/smartcontractkit/chainlink-common/pkg/types" - query "github.com/smartcontractkit/chainlink-common/pkg/types/query" + "github.com/smartcontractkit/chainlink-common/pkg/types" + "github.com/smartcontractkit/chainlink-common/pkg/types/query" "github.com/smartcontractkit/chainlink-common/pkg/types/query/primitives" "github.com/smartcontractkit/chainlink-common/pkg/values" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/workflow/generated/workflow_registry_wrapper" @@ -39,8 +41,19 @@ type GetWorkflowMetadataListByDONParams struct { Limit uint64 } +type GetWorkflowMetadata struct { + WorkflowID [32]byte + Owner []byte + DonID uint32 + Status uint8 + WorkflowName string + BinaryURL string + ConfigURL string + SecretsURL string +} + type GetWorkflowMetadataListByDONReturnVal struct { - WorkflowMetadataList []WorkflowRegistryWorkflowRegisteredV1 + WorkflowMetadataList []GetWorkflowMetadata } // WorkflowRegistryEvent is an event emitted by the WorkflowRegistry. Each event is typed @@ -79,17 +92,19 @@ type WorkflowLoadConfig struct { // FetcherFunc is an abstraction for fetching the contents stored at a URL. type FetcherFunc func(ctx context.Context, url string) ([]byte, error) -type ContractReaderFactory interface { - NewContractReader(context.Context, []byte) (types.ContractReader, error) -} - // ContractReader is a subset of types.ContractReader defined locally to enable mocking. type ContractReader interface { + Start(ctx context.Context) error + Close() error Bind(context.Context, []types.BoundContract) error - QueryKey(context.Context, types.BoundContract, query.KeyFilter, query.LimitAndSort, any) ([]types.Sequence, error) + QueryKeys(ctx context.Context, keyQueries []types.ContractKeyFilter, limitAndSort query.LimitAndSort) (iter.Seq2[string, types.Sequence], error) GetLatestValueWithHeadData(ctx context.Context, readName string, confidenceLevel primitives.ConfidenceLevel, params any, returnVal any) (head *types.Head, err error) } +type ContractReaderFactory interface { + NewContractReader(context.Context, []byte) (types.ContractReader, error) +} + // WorkflowRegistrySyncer is the public interface of the package. type WorkflowRegistrySyncer interface { services.Service @@ -115,21 +130,11 @@ type workflowRegistry struct { newContractReaderFn newContractReaderFn - eventPollerCfg WorkflowEventPollerConfig - eventTypes []WorkflowRegistryEventType - - // eventsCh is read by the handler and each event is handled once received. - eventsCh chan WorkflowRegistryEventResponse + eventPollerCfg WorkflowEventPollerConfig + eventTypes []WorkflowRegistryEventType handler evtHandler initialWorkflowsStateLoader initialWorkflowsStateLoader - // batchCh is a channel that receives batches of events from the contract query goroutines. - batchCh chan []WorkflowRegistryEventResponse - - // heap is a min heap that merges batches of events from the contract query goroutines. The - // default min heap is sorted by block height. - heap Heap - workflowDonNotifier donNotifier reader ContractReader @@ -171,17 +176,21 @@ func NewWorkflowRegistry( workflowDonNotifier donNotifier, opts ...func(*workflowRegistry), ) *workflowRegistry { - ets := []WorkflowRegistryEventType{ForceUpdateSecretsEvent} + ets := []WorkflowRegistryEventType{ + ForceUpdateSecretsEvent, + WorkflowActivatedEvent, + WorkflowDeletedEvent, + WorkflowPausedEvent, + WorkflowRegisteredEvent, + WorkflowUpdatedEvent, + } wr := &workflowRegistry{ lggr: lggr, newContractReaderFn: newContractReaderFn, workflowRegistryAddress: addr, eventPollerCfg: eventPollerConfig, - heap: newBlockHeightHeap(), stopCh: make(services.StopChan), eventTypes: ets, - eventsCh: make(chan WorkflowRegistryEventResponse), - batchCh: make(chan []WorkflowRegistryEventResponse, len(ets)), handler: handler, initialWorkflowsStateLoader: initialWorkflowsStateLoader, workflowDonNotifier: workflowDonNotifier, @@ -207,26 +216,32 @@ func (w *workflowRegistry) Start(_ context.Context) error { w.lggr.Debugw("Waiting for DON...") don, err := w.workflowDonNotifier.WaitForDon(ctx) if err != nil { - w.lggr.Errorf("failed to wait for don: %v", err) + w.lggr.Errorw("failed to wait for don", "err", err) return } w.lggr.Debugw("Loading initial workflows for DON", "DON", don.ID) loadWorkflowsHead, err := w.initialWorkflowsStateLoader.LoadWorkflows(ctx, don) if err != nil { - w.lggr.Errorf("failed to load workflows: %v", err) - return - } + // TODO - this is a temporary fix to handle the case where the chainreader errors because the contract + // contains no workflows. To track: https://smartcontract-it.atlassian.net/browse/CAPPL-393 + if !strings.Contains(err.Error(), "attempting to unmarshal an empty string while arguments are expected") { + w.lggr.Errorw("failed to load workflows", "err", err) + return + } - w.syncEventsLoop(ctx, loadWorkflowsHead.Height) - }() + loadWorkflowsHead = &types.Head{ + Height: "0", + } + } - w.wg.Add(1) - go func() { - defer w.wg.Done() - defer cancel() + reader, err := w.getContractReader(ctx) + if err != nil { + w.lggr.Criticalf("contract reader unavailable : %s", err) + return + } - w.handlerLoop(ctx) + w.readRegistryEvents(ctx, reader, loadWorkflowsHead.Height) }() return nil @@ -253,135 +268,82 @@ func (w *workflowRegistry) Name() string { return name } -// handlerLoop handles the events that are emitted by the contract. -func (w *workflowRegistry) handlerLoop(ctx context.Context) { +// readRegistryEvents polls the contract for events and send them to the events channel. +func (w *workflowRegistry) readRegistryEvents(ctx context.Context, reader ContractReader, lastReadBlockNumber string) { + ticker := w.getTicker() + + var keyQueries = make([]types.ContractKeyFilter, 0, len(w.eventTypes)) + for _, et := range w.eventTypes { + var logData values.Value + keyQueries = append(keyQueries, types.ContractKeyFilter{ + KeyFilter: query.KeyFilter{ + Key: string(et), + Expressions: []query.Expression{ + query.Confidence(primitives.Finalized), + query.Block(lastReadBlockNumber, primitives.Gt), + }, + }, + Contract: types.BoundContract{ + Name: WorkflowRegistryContractName, + Address: w.workflowRegistryAddress, + }, + SequenceDataType: &logData, + }) + } + + cursor := "" for { select { case <-ctx.Done(): return - case resp, open := <-w.eventsCh: - if !open { - return + case <-ticker: + limitAndSort := query.LimitAndSort{ + SortBy: []query.SortBy{query.NewSortByTimestamp(query.Asc)}, + Limit: query.Limit{Count: w.eventPollerCfg.QueryCount}, } - - if resp.Err != nil || resp.Event == nil { - w.lggr.Errorf("failed to handle event: %+v", resp.Err) - continue + if cursor != "" { + limitAndSort.Limit = query.CursorLimit(cursor, query.CursorFollowing, w.eventPollerCfg.QueryCount) } - event := resp.Event - w.lggr.Debugf("handling event: %+v", event) - if err := w.handler.Handle(ctx, *event); err != nil { - w.lggr.Errorf("failed to handle event: %+v", event) + logsIter, err := reader.QueryKeys(ctx, keyQueries, limitAndSort) + if err != nil { + w.lggr.Errorw("failed to query keys", "err", err) continue } - } - } -} -// syncEventsLoop polls the contract for events and passes them to a channel for handling. -func (w *workflowRegistry) syncEventsLoop(ctx context.Context, lastReadBlockNumber string) { - var ( - // sendLog is a helper that sends a WorkflowRegistryEventResponse to the eventsCh in a - // blocking way that will send the response or be canceled. - sendLog = func(resp WorkflowRegistryEventResponse) { - select { - case w.eventsCh <- resp: - case <-ctx.Done(): + var logs []sequenceWithEventType + for eventType, log := range logsIter { + logs = append(logs, sequenceWithEventType{ + Sequence: log, + EventType: WorkflowRegistryEventType(eventType), + }) } - } - - ticker = w.getTicker() - - signals = make(map[WorkflowRegistryEventType]chan struct{}, 0) - ) - - // critical failure if there is no reader, the loop will exit and the parent context will be - // canceled. - reader, err := w.getContractReader(ctx) - if err != nil { - w.lggr.Criticalf("contract reader unavailable : %s", err) - return - } - - // fan out and query for each event type - for i := 0; i < len(w.eventTypes); i++ { - signal := make(chan struct{}, 1) - signals[w.eventTypes[i]] = signal - w.wg.Add(1) - go func() { - defer w.wg.Done() + w.lggr.Debugw("QueryKeys called", "logs", len(logs), "eventTypes", w.eventTypes, "lastReadBlockNumber", lastReadBlockNumber, "logCursor", cursor) - queryEvent( - ctx, - signal, - w.lggr, - reader, - lastReadBlockNumber, - queryEventConfig{ - ContractName: WorkflowRegistryContractName, - ContractAddress: w.workflowRegistryAddress, - WorkflowEventPollerConfig: w.eventPollerCfg, - }, - w.eventTypes[i], - w.batchCh, - ) - }() - } - - // Periodically send a signal to all the queryEvent goroutines to query the contract - for { - select { - case <-ctx.Done(): - return - case <-ticker: - w.lggr.Debugw("Syncing with WorkflowRegistry") - // for each event type, send a signal for it to execute a query and produce a new - // batch of event logs - for i := 0; i < len(w.eventTypes); i++ { - signal := signals[w.eventTypes[i]] - select { - case signal <- struct{}{}: - case <-ctx.Done(): - return - } + // ChainReader QueryKey API provides logs including the cursor value and not + // after the cursor value. If the response only consists of the log corresponding + // to the cursor and no log after it, then we understand that there are no new + // logs + if len(logs) == 1 && logs[0].Sequence.Cursor == cursor { + w.lggr.Infow("No new logs since", "cursor", cursor) + continue } - // block on fan-in until all fetched event logs are sent to the handlers - w.orderAndSend( - ctx, - len(w.eventTypes), - w.batchCh, - sendLog, - ) - } - } -} + var events []WorkflowRegistryEventResponse + for _, log := range logs { + if log.Sequence.Cursor == cursor { + continue + } -// orderAndSend reads n batches from the batch channel, heapifies all the batches then dequeues -// the min heap via the sendLog function. -func (w *workflowRegistry) orderAndSend( - ctx context.Context, - batchCount int, - batchCh <-chan []WorkflowRegistryEventResponse, - sendLog func(WorkflowRegistryEventResponse), -) { - for { - select { - case <-ctx.Done(): - return - case batch := <-batchCh: - for _, response := range batch { - w.heap.Push(response) + events = append(events, toWorkflowRegistryEventResponse(log.Sequence, log.EventType, w.lggr)) + cursor = log.Sequence.Cursor } - batchCount-- - // If we have received responses for all the events, then we can drain the heap. - if batchCount == 0 { - for w.heap.Len() > 0 { - sendLog(w.heap.Pop()) + for _, event := range events { + err := w.handler.Handle(ctx, event.Event) + if err != nil { + w.lggr.Errorw("failed to handle event", "err", err, "type", event.Event.EventType) } - return } } } @@ -417,89 +379,9 @@ func (w *workflowRegistry) getContractReader(ctx context.Context) (ContractReade return w.reader, nil } -type queryEventConfig struct { - ContractName string - ContractAddress string - WorkflowEventPollerConfig -} - -// queryEvent queries the contract for events of the given type on each tick from the ticker. -// Sends a batch of event logs to the batch channel. The batch represents all the -// event logs read since the last query. Loops until the context is canceled. -func queryEvent( - ctx context.Context, - ticker <-chan struct{}, - lggr logger.Logger, - reader ContractReader, - lastReadBlockNumber string, - cfg queryEventConfig, - et WorkflowRegistryEventType, - batchCh chan<- []WorkflowRegistryEventResponse, -) { - // create query - var ( - responseBatch []WorkflowRegistryEventResponse - logData values.Value - cursor = "" - limitAndSort = query.LimitAndSort{ - SortBy: []query.SortBy{query.NewSortByTimestamp(query.Asc)}, - Limit: query.Limit{Count: cfg.QueryCount}, - } - bc = types.BoundContract{ - Name: cfg.ContractName, - Address: cfg.ContractAddress, - } - ) - - // Loop until canceled - for { - select { - case <-ctx.Done(): - return - case <-ticker: - if cursor != "" { - limitAndSort.Limit = query.CursorLimit(cursor, query.CursorFollowing, cfg.QueryCount) - } - - logs, err := reader.QueryKey( - ctx, - bc, - query.KeyFilter{ - Key: string(et), - Expressions: []query.Expression{ - query.Confidence(primitives.Finalized), - query.Block(lastReadBlockNumber, primitives.Gt), - }, - }, - limitAndSort, - &logData, - ) - - if err != nil { - lggr.Errorw("QueryKey failure", "err", err) - continue - } - - // ChainReader QueryKey API provides logs including the cursor value and not - // after the cursor value. If the response only consists of the log corresponding - // to the cursor and no log after it, then we understand that there are no new - // logs - if len(logs) == 1 && logs[0].Cursor == cursor { - lggr.Infow("No new logs since", "cursor", cursor) - continue - } - - for _, log := range logs { - if log.Cursor == cursor { - continue - } - - responseBatch = append(responseBatch, toWorkflowRegistryEventResponse(log, et, lggr)) - cursor = log.Cursor - } - batchCh <- responseBatch - } - } +type sequenceWithEventType struct { + Sequence types.Sequence + EventType WorkflowRegistryEventType } func getWorkflowRegistryEventReader( @@ -511,7 +393,14 @@ func getWorkflowRegistryEventReader( Contracts: map[string]evmtypes.ChainContractReader{ WorkflowRegistryContractName: { ContractPollingFilter: evmtypes.ContractPollingFilter{ - GenericEventNames: []string{string(ForceUpdateSecretsEvent)}, + GenericEventNames: []string{ + string(ForceUpdateSecretsEvent), + string(WorkflowActivatedEvent), + string(WorkflowDeletedEvent), + string(WorkflowPausedEvent), + string(WorkflowRegisteredEvent), + string(WorkflowUpdatedEvent), + }, }, ContractABI: workflow_registry_wrapper.WorkflowRegistryABI, Configs: map[string]*evmtypes.ChainReaderDefinition{ @@ -519,6 +408,26 @@ func getWorkflowRegistryEventReader( ChainSpecificName: string(ForceUpdateSecretsEvent), ReadType: evmtypes.Event, }, + string(WorkflowActivatedEvent): { + ChainSpecificName: string(WorkflowActivatedEvent), + ReadType: evmtypes.Event, + }, + string(WorkflowDeletedEvent): { + ChainSpecificName: string(WorkflowDeletedEvent), + ReadType: evmtypes.Event, + }, + string(WorkflowPausedEvent): { + ChainSpecificName: string(WorkflowPausedEvent), + ReadType: evmtypes.Event, + }, + string(WorkflowRegisteredEvent): { + ChainSpecificName: string(WorkflowRegisteredEvent), + ReadType: evmtypes.Event, + }, + string(WorkflowUpdatedEvent): { + ChainSpecificName: string(WorkflowUpdatedEvent), + ReadType: evmtypes.Event, + }, }, }, }, @@ -539,6 +448,10 @@ func getWorkflowRegistryEventReader( return nil, err } + if err := reader.Start(ctx); err != nil { + return nil, err + } + return reader, nil } @@ -624,13 +537,23 @@ func (l *workflowRegistryContractLoader) LoadWorkflows(ctx context.Context, don var workflows GetWorkflowMetadataListByDONReturnVal headAtLastRead, err = contractReader.GetLatestValueWithHeadData(ctx, readIdentifier, primitives.Finalized, params, &workflows) if err != nil { - return nil, fmt.Errorf("failed to get workflow metadata for don %w", err) + return nil, fmt.Errorf("failed to get lastest value with head data %w", err) } l.lggr.Debugw("Rehydrating existing workflows", "len", len(workflows.WorkflowMetadataList)) for _, workflow := range workflows.WorkflowMetadataList { + toRegisteredEvent := WorkflowRegistryWorkflowRegisteredV1{ + WorkflowID: workflow.WorkflowID, + WorkflowOwner: workflow.Owner, + DonID: workflow.DonID, + Status: workflow.Status, + WorkflowName: workflow.WorkflowName, + BinaryURL: workflow.BinaryURL, + ConfigURL: workflow.ConfigURL, + SecretsURL: workflow.SecretsURL, + } if err = l.handler.Handle(ctx, workflowAsEvent{ - Data: workflow, + Data: toRegisteredEvent, EventType: WorkflowRegisteredEvent, }); err != nil { l.lggr.Errorf("failed to handle workflow registration: %s", err) @@ -682,6 +605,51 @@ func toWorkflowRegistryEventResponse( return resp } resp.Event.Data = data + case WorkflowRegisteredEvent: + var data WorkflowRegistryWorkflowRegisteredV1 + if err := dataAsValuesMap.UnwrapTo(&data); err != nil { + lggr.Errorf("failed to unwrap data: %+v", log.Data) + resp.Event = nil + resp.Err = err + return resp + } + resp.Event.Data = data + case WorkflowUpdatedEvent: + var data WorkflowRegistryWorkflowUpdatedV1 + if err := dataAsValuesMap.UnwrapTo(&data); err != nil { + lggr.Errorf("failed to unwrap data: %+v", log.Data) + resp.Event = nil + resp.Err = err + return resp + } + resp.Event.Data = data + case WorkflowPausedEvent: + var data WorkflowRegistryWorkflowPausedV1 + if err := dataAsValuesMap.UnwrapTo(&data); err != nil { + lggr.Errorf("failed to unwrap data: %+v", log.Data) + resp.Event = nil + resp.Err = err + return resp + } + resp.Event.Data = data + case WorkflowActivatedEvent: + var data WorkflowRegistryWorkflowActivatedV1 + if err := dataAsValuesMap.UnwrapTo(&data); err != nil { + lggr.Errorf("failed to unwrap data: %+v", log.Data) + resp.Event = nil + resp.Err = err + return resp + } + resp.Event.Data = data + case WorkflowDeletedEvent: + var data WorkflowRegistryWorkflowDeletedV1 + if err := dataAsValuesMap.UnwrapTo(&data); err != nil { + lggr.Errorf("failed to unwrap data: %+v", log.Data) + resp.Event = nil + resp.Err = err + return resp + } + resp.Event.Data = data default: lggr.Errorf("unknown event type: %s", evt) resp.Event = nil diff --git a/core/services/workflows/syncer/workflow_registry_test.go b/core/services/workflows/syncer/workflow_registry_test.go deleted file mode 100644 index 8a7c3bb0a7c..00000000000 --- a/core/services/workflows/syncer/workflow_registry_test.go +++ /dev/null @@ -1,138 +0,0 @@ -package syncer - -import ( - "context" - "encoding/hex" - "testing" - "time" - - "github.com/stretchr/testify/mock" - - "github.com/jonboulle/clockwork" - - "github.com/smartcontractkit/chainlink-common/pkg/capabilities" - "github.com/smartcontractkit/chainlink-common/pkg/custmsg" - "github.com/smartcontractkit/chainlink-common/pkg/services/servicetest" - types "github.com/smartcontractkit/chainlink-common/pkg/types" - query "github.com/smartcontractkit/chainlink-common/pkg/types/query" - "github.com/smartcontractkit/chainlink-common/pkg/types/query/primitives" - "github.com/smartcontractkit/chainlink-common/pkg/values" - "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" - "github.com/smartcontractkit/chainlink/v2/core/logger" - "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/workflowkey" - "github.com/smartcontractkit/chainlink/v2/core/utils/crypto" - "github.com/smartcontractkit/chainlink/v2/core/utils/matches" - - "github.com/stretchr/testify/require" -) - -type testDonNotifier struct { - don capabilities.DON - err error -} - -func (t *testDonNotifier) WaitForDon(ctx context.Context) (capabilities.DON, error) { - return t.don, t.err -} - -func Test_Workflow_Registry_Syncer(t *testing.T) { - var ( - giveContents = "contents" - wantContents = "updated contents" - contractAddress = "0xdeadbeef" - giveCfg = WorkflowEventPollerConfig{ - QueryCount: 20, - } - giveURL = "http://example.com" - giveHash, err = crypto.Keccak256([]byte(giveURL)) - - giveLog = types.Sequence{ - Data: map[string]any{ - "SecretsURLHash": giveHash, - "Owner": "0xowneraddr", - }, - Cursor: "cursor", - } - ) - - require.NoError(t, err) - - var ( - lggr = logger.TestLogger(t) - db = pgtest.NewSqlxDB(t) - orm = &orm{ds: db, lggr: lggr} - ctx, cancel = context.WithCancel(testutils.Context(t)) - reader = NewMockContractReader(t) - emitter = custmsg.NewLabeler() - gateway = func(_ context.Context, _ string) ([]byte, error) { - return []byte(wantContents), nil - } - ticker = make(chan time.Time) - - handler = NewEventHandler(lggr, orm, gateway, nil, nil, - emitter, clockwork.NewFakeClock(), workflowkey.Key{}) - loader = NewWorkflowRegistryContractLoader(lggr, contractAddress, func(ctx context.Context, bytes []byte) (ContractReader, error) { - return reader, nil - }, handler) - - worker = NewWorkflowRegistry(lggr, func(ctx context.Context, bytes []byte) (ContractReader, error) { - return reader, nil - }, contractAddress, - WorkflowEventPollerConfig{ - QueryCount: 20, - }, handler, loader, - &testDonNotifier{ - don: capabilities.DON{ - ID: 1, - }, - err: nil, - }, - WithTicker(ticker)) - ) - - // Cleanup the worker - defer cancel() - - // Seed the DB with an original entry - _, err = orm.Create(ctx, giveURL, hex.EncodeToString(giveHash), giveContents) - require.NoError(t, err) - - // Mock out the contract reader query - reader.EXPECT().QueryKey( - matches.AnyContext, - types.BoundContract{ - Name: WorkflowRegistryContractName, - Address: contractAddress, - }, - query.KeyFilter{ - Key: string(ForceUpdateSecretsEvent), - Expressions: []query.Expression{ - query.Confidence(primitives.Finalized), - query.Block("0", primitives.Gt), - }, - }, - query.LimitAndSort{ - SortBy: []query.SortBy{query.NewSortByTimestamp(query.Asc)}, - Limit: query.Limit{Count: giveCfg.QueryCount}, - }, - new(values.Value), - ).Return([]types.Sequence{giveLog}, nil) - reader.EXPECT().GetLatestValueWithHeadData(mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&types.Head{ - Height: "0", - }, nil) - reader.EXPECT().Bind(mock.Anything, mock.Anything).Return(nil) - - // Go run the worker - servicetest.Run(t, worker) - - // Send a tick to start a query - ticker <- time.Now() - - // Require the secrets contents to eventually be updated - require.Eventually(t, func() bool { - secrets, err := orm.GetContents(ctx, giveURL) - require.NoError(t, err) - return secrets == wantContents - }, 5*time.Second, time.Second) -} diff --git a/core/store/dialects/dialects.go b/core/store/dialects/dialects.go deleted file mode 100644 index d250fa1b99b..00000000000 --- a/core/store/dialects/dialects.go +++ /dev/null @@ -1,18 +0,0 @@ -package dialects - -import ( - // need to make sure pgx driver is registered before opening connection - _ "github.com/jackc/pgx/v4/stdlib" -) - -// DialectName is a compiler enforced type used that maps to database dialect names -type DialectName string - -const ( - // Postgres represents the postgres dialect. - Postgres DialectName = "pgx" - // TransactionWrappedPostgres is useful for tests. - // When the connection is opened, it starts a transaction and all - // operations performed on the DB will be within that transaction. - TransactionWrappedPostgres DialectName = "txdb" -) diff --git a/core/store/migrate/migrations/0259_add_workflow_secrets.sql b/core/store/migrate/migrations/0259_add_workflow_secrets.sql index fb76d945571..420f7ed6e49 100644 --- a/core/store/migrate/migrations/0259_add_workflow_secrets.sql +++ b/core/store/migrate/migrations/0259_add_workflow_secrets.sql @@ -38,4 +38,5 @@ DROP INDEX IF EXISTS idx_secrets_url_hash; -- Drop the workflow_artifacts table DROP TABLE IF EXISTS workflow_secrets; --- +goose StatementEnd \ No newline at end of file +-- +goose StatementEnd + diff --git a/core/store/migrate/migrations/0261_remove_unique_constraint_secrets.sql b/core/store/migrate/migrations/0261_remove_unique_constraint_secrets.sql new file mode 100644 index 00000000000..15f59d8ae28 --- /dev/null +++ b/core/store/migrate/migrations/0261_remove_unique_constraint_secrets.sql @@ -0,0 +1,10 @@ +-- +goose Up +-- +goose StatementBegin +-- unique constraint on workflow_owner and workflow_name +ALTER TABLE workflow_specs DROP CONSTRAINT workflow_specs_secrets_id_key; +-- +goose StatementEnd + +-- +goose Down +-- +goose StatementBegin +ALTER TABLE workflow_specs ADD CONSTRAINT workflow_specs_secrets_id_key unique (secrets_id); +-- +goose StatementEnd diff --git a/core/utils/testutils/heavyweight/orm.go b/core/utils/testutils/heavyweight/orm.go index 536515e02e4..775eabab0c8 100644 --- a/core/utils/testutils/heavyweight/orm.go +++ b/core/utils/testutils/heavyweight/orm.go @@ -14,12 +14,13 @@ import ( "github.com/jmoiron/sqlx" + pgcommon "github.com/smartcontractkit/chainlink-common/pkg/sqlutil/pg" "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" "github.com/smartcontractkit/chainlink/v2/core/services/pg" - "github.com/smartcontractkit/chainlink/v2/core/store/dialects" "github.com/smartcontractkit/chainlink/v2/core/store/models" "github.com/smartcontractkit/chainlink/v2/internal/testdb" ) @@ -53,10 +54,10 @@ const ( ) func (c Kind) PrepareDB(t testing.TB, overrideFn func(c *chainlink.Config, s *chainlink.Secrets)) (chainlink.GeneralConfig, *sqlx.DB) { - testutils.SkipShort(t, "FullTestDB") + tests.SkipShort(t, "FullTestDB") gcfg := configtest.NewGeneralConfigSimulated(t, func(c *chainlink.Config, s *chainlink.Secrets) { - c.Database.Dialect = dialects.Postgres + c.Database.Dialect = pgcommon.Postgres if overrideFn != nil { overrideFn(c, s) } @@ -65,7 +66,7 @@ func (c Kind) PrepareDB(t testing.TB, overrideFn func(c *chainlink.Config, s *ch require.NoError(t, os.MkdirAll(gcfg.RootDir(), 0700)) migrationTestDBURL, err := testdb.CreateOrReplace(gcfg.Database().URL(), generateName(), c != KindEmpty) require.NoError(t, err) - db, err := pg.NewConnection(tests.Context(t), migrationTestDBURL, dialects.Postgres, gcfg.Database()) + db, err := pg.NewConnection(tests.Context(t), migrationTestDBURL, pgcommon.Postgres, gcfg.Database()) require.NoError(t, err) t.Cleanup(func() { require.NoError(t, db.Close()) // must close before dropping @@ -74,7 +75,7 @@ func (c Kind) PrepareDB(t testing.TB, overrideFn func(c *chainlink.Config, s *ch }) gcfg = configtest.NewGeneralConfigSimulated(t, func(c *chainlink.Config, s *chainlink.Secrets) { - c.Database.Dialect = dialects.Postgres + c.Database.Dialect = pgcommon.Postgres s.Database.URL = models.MustSecretURL(migrationTestDBURL) if overrideFn != nil { overrideFn(c, s) diff --git a/core/web/chains_controller.go b/core/web/chains_controller.go index 6bc5ee4daa3..a99cbf4ca4b 100644 --- a/core/web/chains_controller.go +++ b/core/web/chains_controller.go @@ -5,13 +5,14 @@ import ( "net/http" "github.com/gin-gonic/gin" - "github.com/manyminds/api2go/jsonapi" "github.com/smartcontractkit/chainlink-common/pkg/types" + commonTypes "github.com/smartcontractkit/chainlink/v2/common/types" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/logger/audit" "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" + "github.com/smartcontractkit/chainlink/v2/core/web/presenters" ) type ChainsController interface { @@ -21,16 +22,6 @@ type ChainsController interface { Show(*gin.Context) } -type chainsController[R jsonapi.EntityNamer] struct { - network string - resourceName string - chainStats chainlink.ChainStatuser - errNotEnabled error - newResource func(types.ChainStatus) R - lggr logger.Logger - auditLogger audit.AuditLogger -} - type errChainDisabled struct { name string tomlKey string @@ -40,50 +31,51 @@ func (e errChainDisabled) Error() string { return fmt.Sprintf("%s is disabled: Set %s=true to enable", e.name, e.tomlKey) } -func newChainsController[R jsonapi.EntityNamer](network string, chainStats chainlink.ChainsNodesStatuser, errNotEnabled error, - newResource func(types.ChainStatus) R, lggr logger.Logger, auditLogger audit.AuditLogger) *chainsController[R] { - return &chainsController[R]{ - network: network, - resourceName: network + "_chain", - chainStats: chainStats, - errNotEnabled: errNotEnabled, - newResource: newResource, - lggr: lggr, - auditLogger: auditLogger, +type chainsController struct { + chainStats chainlink.RelayerChainInteroperators + newResource func(commonTypes.ChainStatusWithID) presenters.ChainResource + lggr logger.Logger + auditLogger audit.AuditLogger +} + +func NewChainsController(chainStats chainlink.RelayerChainInteroperators, lggr logger.Logger, auditLogger audit.AuditLogger) *chainsController { + return &chainsController{ + chainStats: chainStats, + newResource: presenters.NewChainResource, + lggr: lggr, + auditLogger: auditLogger, } } -func (cc *chainsController[R]) Index(c *gin.Context, size, page, offset int) { - if cc.chainStats == nil { - jsonAPIError(c, http.StatusBadRequest, cc.errNotEnabled) - return +func (cc *chainsController) Index(c *gin.Context, size, page, offset int) { + chainStats := cc.chainStats + if network := c.Param("network"); network != "" { + chainStats = chainStats.List(chainlink.FilterRelayersByType(network)) } - chains, count, err := cc.chainStats.ChainStatuses(c.Request.Context(), offset, size) + + chains, count, err := chainStats.ChainStatuses(c.Request.Context(), offset, size) if err != nil { jsonAPIError(c, http.StatusBadRequest, err) return } - var resources []R + resources := []presenters.ChainResource{} for _, chain := range chains { resources = append(resources, cc.newResource(chain)) } - paginatedResponse(c, cc.resourceName, size, page, resources, count, err) + paginatedResponse(c, "chain", size, page, resources, count, err) } -func (cc *chainsController[R]) Show(c *gin.Context) { - if cc.chainStats == nil { - jsonAPIError(c, http.StatusBadRequest, cc.errNotEnabled) - return - } - relayID := types.RelayID{Network: cc.network, ChainID: c.Param("ID")} +func (cc *chainsController) Show(c *gin.Context) { + relayID := types.RelayID{Network: c.Param("network"), ChainID: c.Param("ID")} chain, err := cc.chainStats.ChainStatus(c.Request.Context(), relayID) + status := commonTypes.ChainStatusWithID{ChainStatus: chain, RelayID: relayID} if err != nil { jsonAPIError(c, http.StatusBadRequest, err) return } - jsonAPIResponse(c, cc.newResource(chain), cc.resourceName) + jsonAPIResponse(c, cc.newResource(status), "chain") } diff --git a/core/web/chains_controller_test.go b/core/web/chains_controller_test.go new file mode 100644 index 00000000000..9e6c32a9637 --- /dev/null +++ b/core/web/chains_controller_test.go @@ -0,0 +1,585 @@ +package web_test + +import ( + "fmt" + "math/big" + "net/http" + "sort" + "testing" + "time" + + "github.com/manyminds/api2go/jsonapi" + "github.com/shopspring/decimal" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "golang.org/x/exp/rand" + + commoncfg "github.com/smartcontractkit/chainlink-common/pkg/config" + commonTypes "github.com/smartcontractkit/chainlink-common/pkg/types" + coscfg "github.com/smartcontractkit/chainlink-cosmos/pkg/cosmos/config" + "github.com/smartcontractkit/chainlink-solana/pkg/solana/config" + + evmcfg "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/toml" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" + ubig "github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils/big" + "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/cosmostest" + "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" + "github.com/smartcontractkit/chainlink/v2/core/web" + "github.com/smartcontractkit/chainlink/v2/core/web/presenters" +) + +func Test_EVMChainsController_Show(t *testing.T) { + t.Parallel() + + validID := ubig.New(testutils.NewRandomEVMChainID()) + + testCases := []struct { + name string + inputID string + wantStatusCode int + want *evmcfg.EVMConfig + }{ + { + inputID: validID.String(), + name: "success", + want: &evmcfg.EVMConfig{ + ChainID: validID, + Enabled: ptr(true), + Chain: evmcfg.Defaults(nil, &evmcfg.Chain{ + GasEstimator: evmcfg.GasEstimator{ + EIP1559DynamicFees: ptr(true), + BlockHistory: evmcfg.BlockHistoryEstimator{ + BlockHistorySize: ptr[uint16](50), + }, + }, + RPCBlockQueryDelay: ptr[uint16](23), + MinIncomingConfirmations: ptr[uint32](12), + LinkContractAddress: ptr(types.EIP55AddressFromAddress(testutils.NewAddress())), + }), + }, + wantStatusCode: http.StatusOK, + }, + { + inputID: "invalidid", + name: "invalid id", + want: nil, + wantStatusCode: http.StatusBadRequest, + }, + { + inputID: "234", + name: "not found", + want: nil, + wantStatusCode: http.StatusBadRequest, + }, + } + + for _, testCase := range testCases { + tc := testCase + + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + controller := setupEVMChainsControllerTest(t, configtest.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { + if tc.want != nil { + c.EVM = evmcfg.EVMConfigs{tc.want} + } + })) + + wantedResult := tc.want + resp, cleanup := controller.client.Get( + "/v2/chains/evm/" + tc.inputID, + ) + t.Cleanup(cleanup) + require.Equal(t, tc.wantStatusCode, resp.StatusCode) + + if wantedResult != nil { + resource1 := presenters.ChainResource{} + err := web.ParseJSONAPIResponse(cltest.ParseResponseBody(t, resp), &resource1) + require.NoError(t, err) + + assert.Equal(t, resource1.ID, wantedResult.ChainID.String()) + toml, err := wantedResult.TOMLString() + require.NoError(t, err) + assert.Equal(t, toml, resource1.Config) + } + }) + } +} + +func Test_EVMChainsController_Index(t *testing.T) { + t.Parallel() + + // sort test chain ids to make expected comparison easy + chainIDs := []*big.Int{testutils.NewRandomEVMChainID(), testutils.NewRandomEVMChainID(), testutils.NewRandomEVMChainID()} + sort.Slice(chainIDs, func(i, j int) bool { + return chainIDs[i].String() < chainIDs[j].String() + }) + + configuredChains := evmcfg.EVMConfigs{ + {ChainID: ubig.New(chainIDs[0]), Chain: evmcfg.Defaults(nil)}, + { + ChainID: ubig.New(chainIDs[1]), + Chain: evmcfg.Defaults(nil, &evmcfg.Chain{ + RPCBlockQueryDelay: ptr[uint16](13), + GasEstimator: evmcfg.GasEstimator{ + EIP1559DynamicFees: ptr(true), + BlockHistory: evmcfg.BlockHistoryEstimator{ + BlockHistorySize: ptr[uint16](1), + }, + }, + MinIncomingConfirmations: ptr[uint32](120), + }), + }, + { + ChainID: ubig.New(chainIDs[2]), + Chain: evmcfg.Defaults(nil, &evmcfg.Chain{ + RPCBlockQueryDelay: ptr[uint16](5), + GasEstimator: evmcfg.GasEstimator{ + EIP1559DynamicFees: ptr(false), + BlockHistory: evmcfg.BlockHistoryEstimator{ + BlockHistorySize: ptr[uint16](2), + }, + }, + MinIncomingConfirmations: ptr[uint32](30), + }), + }, + } + + assert.Len(t, configuredChains, 3) + controller := setupEVMChainsControllerTest(t, configtest.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { + c.EVM = append(c.EVM, configuredChains...) + })) + + badResp, cleanup := controller.client.Get("/v2/chains/evm?size=asd") + t.Cleanup(cleanup) + require.Equal(t, http.StatusUnprocessableEntity, badResp.StatusCode) + + resp, cleanup := controller.client.Get("/v2/chains/evm?size=3") + t.Cleanup(cleanup) + require.Equal(t, http.StatusOK, resp.StatusCode) + + body := cltest.ParseResponseBody(t, resp) + + metaCount, err := cltest.ParseJSONAPIResponseMetaCount(body) + require.NoError(t, err) + require.Equal(t, 1+len(configuredChains), metaCount) + + var links jsonapi.Links + + var gotChains []presenters.ChainResource + err = web.ParsePaginatedResponse(body, &gotChains, &links) + require.NoError(t, err) + assert.NotEmpty(t, links["next"].Href) + assert.Empty(t, links["prev"].Href) + + assert.Len(t, links, 1) + // the difference in index value here seems to be due to the fact + // that cltest always has a default EVM chain, which is the off-by-one + // in the indices + assert.Equal(t, gotChains[2].ID, configuredChains[1].ChainID.String()) + toml, err := configuredChains[1].TOMLString() + require.NoError(t, err) + assert.Equal(t, toml, gotChains[2].Config) + + resp, cleanup = controller.client.Get(links["next"].Href) + t.Cleanup(cleanup) + require.Equal(t, http.StatusOK, resp.StatusCode) + + gotChains = []presenters.ChainResource{} + err = web.ParsePaginatedResponse(cltest.ParseResponseBody(t, resp), &gotChains, &links) + require.NoError(t, err) + assert.Empty(t, links["next"].Href) + assert.NotEmpty(t, links["prev"].Href) + + assert.Len(t, links, 1) + assert.Equal(t, gotChains[0].ID, configuredChains[2].ChainID.String()) + toml, err = configuredChains[2].TOMLString() + require.NoError(t, err) + assert.Equal(t, toml, gotChains[0].Config) +} + +type TestEVMChainsController struct { + app *cltest.TestApplication + client cltest.HTTPClientCleaner +} + +func setupEVMChainsControllerTest(t *testing.T, cfg chainlink.GeneralConfig) *TestEVMChainsController { + // Using this instead of `NewApplicationEVMDisabled` since we need the chain set to be loaded in the app + // for the sake of the API endpoints to work properly + app := cltest.NewApplicationWithConfig(t, cfg) + ctx := testutils.Context(t) + require.NoError(t, app.Start(ctx)) + + client := app.NewHTTPClient(nil) + + return &TestEVMChainsController{ + app: app, + client: client, + } +} + +func ptr[T any](t T) *T { return &t } + +func Test_CosmosChainsController_Show(t *testing.T) { + t.Parallel() + + const validID = "Chainlink-12" + + testCases := []struct { + name string + inputID string + wantStatusCode int + want func(t *testing.T, app *cltest.TestApplication) *commonTypes.ChainStatus + }{ + { + inputID: validID, + name: "success", + want: func(t *testing.T, app *cltest.TestApplication) *commonTypes.ChainStatus { + return &commonTypes.ChainStatus{ + ID: validID, + Enabled: true, + Config: `ChainID = 'Chainlink-12' +Enabled = true +Bech32Prefix = 'wasm' +BlockRate = '6s' +BlocksUntilTxTimeout = 30 +ConfirmPollPeriod = '1s' +FallbackGasPrice = '9.999' +GasToken = 'ucosm' +GasLimitMultiplier = '1.55555' +MaxMsgsPerBatch = 100 +OCR2CachePollPeriod = '4s' +OCR2CacheTTL = '1m0s' +TxMsgTimeout = '10m0s' +Nodes = [] +`, + } + }, + wantStatusCode: http.StatusOK, + }, + { + inputID: "234", + name: "not found", + want: func(t *testing.T, app *cltest.TestApplication) *commonTypes.ChainStatus { + return nil + }, + wantStatusCode: http.StatusBadRequest, + }, + } + + for _, testCase := range testCases { + tc := testCase + + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + controller := setupCosmosChainsControllerTestV2(t, &coscfg.TOMLConfig{ + ChainID: ptr(validID), + Enabled: ptr(true), + Chain: coscfg.Chain{ + FallbackGasPrice: ptr(decimal.RequireFromString("9.999")), + GasLimitMultiplier: ptr(decimal.RequireFromString("1.55555")), + }}) + + wantedResult := tc.want(t, controller.app) + resp, cleanup := controller.client.Get( + "/v2/chains/cosmos/" + tc.inputID, + ) + t.Cleanup(cleanup) + require.Equal(t, tc.wantStatusCode, resp.StatusCode) + + if wantedResult != nil { + resource1 := presenters.ChainResource{} + err := web.ParseJSONAPIResponse(cltest.ParseResponseBody(t, resp), &resource1) + require.NoError(t, err) + + assert.Equal(t, wantedResult.ID, resource1.ID) + assert.Equal(t, wantedResult.Config, resource1.Config) + } + }) + } +} + +func Test_CosmosChainsController_Index(t *testing.T) { + t.Parallel() + + chainA := &coscfg.TOMLConfig{ + ChainID: ptr("a" + cosmostest.RandomChainID()), + Enabled: ptr(true), + Chain: coscfg.Chain{ + FallbackGasPrice: ptr(decimal.RequireFromString("9.999")), + }, + } + + chainB := &coscfg.TOMLConfig{ + ChainID: ptr("b" + cosmostest.RandomChainID()), + Enabled: ptr(true), + Chain: coscfg.Chain{ + GasLimitMultiplier: ptr(decimal.RequireFromString("1.55555")), + }, + } + controller := setupCosmosChainsControllerTestV2(t, chainA, chainB) + + badResp, cleanup := controller.client.Get("/v2/chains/cosmos?size=asd") + t.Cleanup(cleanup) + require.Equal(t, http.StatusUnprocessableEntity, badResp.StatusCode) + + resp, cleanup := controller.client.Get("/v2/chains/cosmos?size=1") + t.Cleanup(cleanup) + require.Equal(t, http.StatusOK, resp.StatusCode) + + body := cltest.ParseResponseBody(t, resp) + + metaCount, err := cltest.ParseJSONAPIResponseMetaCount(body) + require.NoError(t, err) + require.Equal(t, 2, metaCount) + + var links jsonapi.Links + + var chains []presenters.ChainResource + err = web.ParsePaginatedResponse(body, &chains, &links) + require.NoError(t, err) + assert.NotEmpty(t, links["next"].Href) + assert.Empty(t, links["prev"].Href) + + assert.Len(t, links, 1) + assert.Equal(t, *chainA.ChainID, chains[0].ID) + tomlA, err := chainA.TOMLString() + require.NoError(t, err) + assert.Equal(t, tomlA, chains[0].Config) + + resp, cleanup = controller.client.Get(links["next"].Href) + t.Cleanup(cleanup) + require.Equal(t, http.StatusOK, resp.StatusCode) + + chains = []presenters.ChainResource{} + err = web.ParsePaginatedResponse(cltest.ParseResponseBody(t, resp), &chains, &links) + require.NoError(t, err) + assert.Empty(t, links["next"].Href) + assert.NotEmpty(t, links["prev"].Href) + + assert.Len(t, links, 1) + assert.Equal(t, *chainB.ChainID, chains[0].ID) + tomlB, err := chainB.TOMLString() + require.NoError(t, err) + assert.Equal(t, tomlB, chains[0].Config) +} + +type TestCosmosChainsController struct { + app *cltest.TestApplication + client cltest.HTTPClientCleaner +} + +func setupCosmosChainsControllerTestV2(t *testing.T, cfgs ...*coscfg.TOMLConfig) *TestCosmosChainsController { + for i := range cfgs { + cfgs[i].SetDefaults() + } + cfg := configtest.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { + c.Cosmos = cfgs + c.EVM = nil + }) + app := cltest.NewApplicationWithConfig(t, cfg) + ctx := testutils.Context(t) + require.NoError(t, app.Start(ctx)) + + client := app.NewHTTPClient(nil) + + return &TestCosmosChainsController{ + app: app, + client: client, + } +} +func Test_SolanaChainsController_Show(t *testing.T) { + t.Parallel() + + const validID = "Chainlink-12" + + testCases := []struct { + name string + inputID string + wantStatusCode int + want func(t *testing.T, app *cltest.TestApplication) *commonTypes.ChainStatus + }{ + { + inputID: validID, + name: "success", + want: func(t *testing.T, app *cltest.TestApplication) *commonTypes.ChainStatus { + return &commonTypes.ChainStatus{ + ID: validID, + Enabled: true, + Config: `ChainID = 'Chainlink-12' +BalancePollPeriod = '5s' +ConfirmPollPeriod = '500ms' +OCR2CachePollPeriod = '1s' +OCR2CacheTTL = '1m0s' +TxTimeout = '1h0m0s' +TxRetryTimeout = '10s' +TxConfirmTimeout = '30s' +TxRetentionTimeout = '0s' +SkipPreflight = false +Commitment = 'confirmed' +MaxRetries = 0 +FeeEstimatorMode = 'fixed' +ComputeUnitPriceMax = 1000 +ComputeUnitPriceMin = 0 +ComputeUnitPriceDefault = 0 +FeeBumpPeriod = '3s' +BlockHistoryPollPeriod = '5s' +BlockHistorySize = 1 +ComputeUnitLimitDefault = 200000 +EstimateComputeUnitLimit = false +Nodes = [] + +[MultiNode] +Enabled = false +PollFailureThreshold = 5 +PollInterval = '15s' +SelectionMode = 'PriorityLevel' +SyncThreshold = 10 +NodeIsSyncingEnabled = false +LeaseDuration = '1m0s' +FinalizedBlockPollInterval = '5s' +EnforceRepeatableRead = true +DeathDeclarationDelay = '20s' +NodeNoNewHeadsThreshold = '20s' +NoNewFinalizedHeadsThreshold = '20s' +FinalityDepth = 0 +FinalityTagEnabled = true +FinalizedBlockOffset = 50 +`, + } + }, + wantStatusCode: http.StatusOK, + }, + { + inputID: "234", + name: "not found", + want: func(t *testing.T, app *cltest.TestApplication) *commonTypes.ChainStatus { + return nil + }, + wantStatusCode: http.StatusBadRequest, + }, + } + + for _, testCase := range testCases { + tc := testCase + + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + controller := setupSolanaChainsControllerTestV2(t, &config.TOMLConfig{ + ChainID: ptr(validID), + Chain: config.Chain{ + SkipPreflight: ptr(false), + TxTimeout: commoncfg.MustNewDuration(time.Hour), + }, + }) + + wantedResult := tc.want(t, controller.app) + resp, cleanup := controller.client.Get( + "/v2/chains/solana/" + tc.inputID, + ) + t.Cleanup(cleanup) + require.Equal(t, tc.wantStatusCode, resp.StatusCode) + + if wantedResult != nil { + resource1 := presenters.ChainResource{} + err := web.ParseJSONAPIResponse(cltest.ParseResponseBody(t, resp), &resource1) + require.NoError(t, err) + + assert.Equal(t, wantedResult.ID, resource1.ID) + assert.Equal(t, wantedResult.Enabled, resource1.Enabled) + assert.Equal(t, wantedResult.Config, resource1.Config) + } + }) + } +} + +func Test_SolanaChainsController_Index(t *testing.T) { + t.Parallel() + + chainA := &config.TOMLConfig{ + ChainID: ptr(fmt.Sprintf("ChainlinktestA-%d", rand.Int31n(999999))), + Chain: config.Chain{ + TxTimeout: commoncfg.MustNewDuration(time.Hour), + }, + } + chainB := &config.TOMLConfig{ + ChainID: ptr(fmt.Sprintf("ChainlinktestB-%d", rand.Int31n(999999))), + Chain: config.Chain{ + SkipPreflight: ptr(false), + }, + } + controller := setupSolanaChainsControllerTestV2(t, chainA, chainB) + + badResp, cleanup := controller.client.Get("/v2/chains/solana?size=asd") + t.Cleanup(cleanup) + require.Equal(t, http.StatusUnprocessableEntity, badResp.StatusCode) + + resp, cleanup := controller.client.Get("/v2/chains/solana?size=1") + t.Cleanup(cleanup) + require.Equal(t, http.StatusOK, resp.StatusCode) + + body := cltest.ParseResponseBody(t, resp) + + metaCount, err := cltest.ParseJSONAPIResponseMetaCount(body) + require.NoError(t, err) + require.Equal(t, 2, metaCount) + + var links jsonapi.Links + + chains := []presenters.ChainResource{} + err = web.ParsePaginatedResponse(body, &chains, &links) + require.NoError(t, err) + assert.NotEmpty(t, links["next"].Href) + assert.Empty(t, links["prev"].Href) + + assert.Len(t, links, 1) + assert.Equal(t, *chainA.ChainID, chains[0].ID) + tomlA, err := chainA.TOMLString() + require.NoError(t, err) + assert.Equal(t, tomlA, chains[0].Config) + + resp, cleanup = controller.client.Get(links["next"].Href) + t.Cleanup(cleanup) + require.Equal(t, http.StatusOK, resp.StatusCode) + + chains = []presenters.ChainResource{} + err = web.ParsePaginatedResponse(cltest.ParseResponseBody(t, resp), &chains, &links) + require.NoError(t, err) + assert.Empty(t, links["next"].Href) + assert.NotEmpty(t, links["prev"].Href) + + assert.Len(t, links, 1) + assert.Equal(t, *chainB.ChainID, chains[0].ID) + tomlB, err := chainB.TOMLString() + require.NoError(t, err) + assert.Equal(t, tomlB, chains[0].Config) +} + +type TestSolanaChainsController struct { + app *cltest.TestApplication + client cltest.HTTPClientCleaner +} + +func setupSolanaChainsControllerTestV2(t *testing.T, cfgs ...*config.TOMLConfig) *TestSolanaChainsController { + for i := range cfgs { + cfgs[i].SetDefaults() + } + cfg := configtest.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { + c.Solana = cfgs + c.EVM = nil + }) + app := cltest.NewApplicationWithConfig(t, cfg) + require.NoError(t, app.Start(testutils.Context(t))) + + client := app.NewHTTPClient(nil) + + return &TestSolanaChainsController{ + app: app, + client: client, + } +} diff --git a/core/web/cosmos_chains_controller.go b/core/web/cosmos_chains_controller.go deleted file mode 100644 index 27c3976ce39..00000000000 --- a/core/web/cosmos_chains_controller.go +++ /dev/null @@ -1,17 +0,0 @@ -package web - -import ( - "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" - "github.com/smartcontractkit/chainlink/v2/core/services/relay" - "github.com/smartcontractkit/chainlink/v2/core/web/presenters" -) - -func NewCosmosChainsController(app chainlink.Application) ChainsController { - return newChainsController[presenters.CosmosChainResource]( - relay.NetworkCosmos, - app.GetRelayers().List(chainlink.FilterRelayersByType(relay.NetworkCosmos)), - ErrCosmosNotEnabled, - presenters.NewCosmosChainResource, - app.GetLogger(), - app.GetAuditLogger()) -} diff --git a/core/web/cosmos_chains_controller_test.go b/core/web/cosmos_chains_controller_test.go deleted file mode 100644 index 2d5eb42515a..00000000000 --- a/core/web/cosmos_chains_controller_test.go +++ /dev/null @@ -1,193 +0,0 @@ -package web_test - -import ( - "fmt" - "net/http" - "testing" - - "github.com/manyminds/api2go/jsonapi" - "github.com/shopspring/decimal" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/smartcontractkit/chainlink-common/pkg/types" - coscfg "github.com/smartcontractkit/chainlink-cosmos/pkg/cosmos/config" - - "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" - "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" - "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/cosmostest" - "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" - "github.com/smartcontractkit/chainlink/v2/core/web" - "github.com/smartcontractkit/chainlink/v2/core/web/presenters" -) - -func Test_CosmosChainsController_Show(t *testing.T) { - t.Parallel() - - const validId = "Chainlink-12" - - testCases := []struct { - name string - inputId string - wantStatusCode int - want func(t *testing.T, app *cltest.TestApplication) *types.ChainStatus - }{ - { - inputId: validId, - name: "success", - want: func(t *testing.T, app *cltest.TestApplication) *types.ChainStatus { - return &types.ChainStatus{ - ID: validId, - Enabled: true, - Config: `ChainID = 'Chainlink-12' -Enabled = true -Bech32Prefix = 'wasm' -BlockRate = '6s' -BlocksUntilTxTimeout = 30 -ConfirmPollPeriod = '1s' -FallbackGasPrice = '9.999' -GasToken = 'ucosm' -GasLimitMultiplier = '1.55555' -MaxMsgsPerBatch = 100 -OCR2CachePollPeriod = '4s' -OCR2CacheTTL = '1m0s' -TxMsgTimeout = '10m0s' -Nodes = [] -`, - } - }, - wantStatusCode: http.StatusOK, - }, - { - inputId: "234", - name: "not found", - want: func(t *testing.T, app *cltest.TestApplication) *types.ChainStatus { - return nil - }, - wantStatusCode: http.StatusBadRequest, - }, - } - - for _, testCase := range testCases { - tc := testCase - - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - - controller := setupCosmosChainsControllerTestV2(t, &coscfg.TOMLConfig{ - ChainID: ptr(validId), - Enabled: ptr(true), - Chain: coscfg.Chain{ - FallbackGasPrice: ptr(decimal.RequireFromString("9.999")), - GasLimitMultiplier: ptr(decimal.RequireFromString("1.55555")), - }}) - - wantedResult := tc.want(t, controller.app) - resp, cleanup := controller.client.Get( - fmt.Sprintf("/v2/chains/cosmos/%s", tc.inputId), - ) - t.Cleanup(cleanup) - require.Equal(t, tc.wantStatusCode, resp.StatusCode) - - if wantedResult != nil { - resource1 := presenters.CosmosChainResource{} - err := web.ParseJSONAPIResponse(cltest.ParseResponseBody(t, resp), &resource1) - require.NoError(t, err) - - assert.Equal(t, wantedResult.ID, resource1.ID) - assert.Equal(t, wantedResult.Config, resource1.Config) - } - }) - } -} - -func Test_CosmosChainsController_Index(t *testing.T) { - t.Parallel() - - chainA := &coscfg.TOMLConfig{ - ChainID: ptr("a" + cosmostest.RandomChainID()), - Enabled: ptr(true), - Chain: coscfg.Chain{ - FallbackGasPrice: ptr(decimal.RequireFromString("9.999")), - }, - } - - chainB := &coscfg.TOMLConfig{ - ChainID: ptr("b" + cosmostest.RandomChainID()), - Enabled: ptr(true), - Chain: coscfg.Chain{ - GasLimitMultiplier: ptr(decimal.RequireFromString("1.55555")), - }, - } - controller := setupCosmosChainsControllerTestV2(t, chainA, chainB) - - badResp, cleanup := controller.client.Get("/v2/chains/cosmos?size=asd") - t.Cleanup(cleanup) - require.Equal(t, http.StatusUnprocessableEntity, badResp.StatusCode) - - resp, cleanup := controller.client.Get("/v2/chains/cosmos?size=1") - t.Cleanup(cleanup) - require.Equal(t, http.StatusOK, resp.StatusCode) - - body := cltest.ParseResponseBody(t, resp) - - metaCount, err := cltest.ParseJSONAPIResponseMetaCount(body) - require.NoError(t, err) - require.Equal(t, 2, metaCount) - - var links jsonapi.Links - - var chains []presenters.CosmosChainResource - err = web.ParsePaginatedResponse(body, &chains, &links) - assert.NoError(t, err) - assert.NotEmpty(t, links["next"].Href) - assert.Empty(t, links["prev"].Href) - - assert.Len(t, links, 1) - assert.Equal(t, *chainA.ChainID, chains[0].ID) - tomlA, err := chainA.TOMLString() - require.NoError(t, err) - assert.Equal(t, tomlA, chains[0].Config) - - resp, cleanup = controller.client.Get(links["next"].Href) - t.Cleanup(cleanup) - require.Equal(t, http.StatusOK, resp.StatusCode) - - chains = []presenters.CosmosChainResource{} - err = web.ParsePaginatedResponse(cltest.ParseResponseBody(t, resp), &chains, &links) - assert.NoError(t, err) - assert.Empty(t, links["next"].Href) - assert.NotEmpty(t, links["prev"].Href) - - assert.Len(t, links, 1) - assert.Equal(t, *chainB.ChainID, chains[0].ID) - tomlB, err := chainB.TOMLString() - require.NoError(t, err) - assert.Equal(t, tomlB, chains[0].Config) -} - -type TestCosmosChainsController struct { - app *cltest.TestApplication - client cltest.HTTPClientCleaner -} - -func setupCosmosChainsControllerTestV2(t *testing.T, cfgs ...*coscfg.TOMLConfig) *TestCosmosChainsController { - for i := range cfgs { - cfgs[i].SetDefaults() - } - cfg := configtest.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { - c.Cosmos = cfgs - c.EVM = nil - }) - app := cltest.NewApplicationWithConfig(t, cfg) - ctx := testutils.Context(t) - require.NoError(t, app.Start(ctx)) - - client := app.NewHTTPClient(nil) - - return &TestCosmosChainsController{ - app: app, - client: client, - } -} diff --git a/core/web/cosmos_nodes_controller.go b/core/web/cosmos_nodes_controller.go deleted file mode 100644 index f3a226721ca..00000000000 --- a/core/web/cosmos_nodes_controller.go +++ /dev/null @@ -1,18 +0,0 @@ -package web - -import ( - "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" - "github.com/smartcontractkit/chainlink/v2/core/services/relay" - "github.com/smartcontractkit/chainlink/v2/core/web/presenters" -) - -// ErrCosmosNotEnabled is returned when COSMOS_ENABLED is not true. -var ErrCosmosNotEnabled = errChainDisabled{name: "Cosmos", tomlKey: "Cosmos.Enabled"} - -func NewCosmosNodesController(app chainlink.Application) NodesController { - scopedNodeStatuser := NewNetworkScopedNodeStatuser(app.GetRelayers(), relay.NetworkCosmos) - - return newNodesController[presenters.CosmosNodeResource]( - scopedNodeStatuser, ErrCosmosNotEnabled, presenters.NewCosmosNodeResource, app.GetAuditLogger(), - ) -} diff --git a/core/web/cosmos_transfer_controller.go b/core/web/cosmos_transfer_controller.go index ab3d8c20f30..f80dda005eb 100644 --- a/core/web/cosmos_transfer_controller.go +++ b/core/web/cosmos_transfer_controller.go @@ -27,11 +27,13 @@ type CosmosTransfersController struct { App chainlink.Application } +var ErrCosmosNotEnabled = errChainDisabled{name: "Cosmos", tomlKey: "Cosmos.Enabled"} + // Create sends native coins from the Chainlink's account to a specified address. func (tc *CosmosTransfersController) Create(c *gin.Context) { relayers := tc.App.GetRelayers().List(chainlink.FilterRelayersByType(relay.NetworkCosmos)) if relayers == nil { - jsonAPIError(c, http.StatusBadRequest, ErrSolanaNotEnabled) + jsonAPIError(c, http.StatusBadRequest, ErrCosmosNotEnabled) return } diff --git a/core/web/evm_chains_controller.go b/core/web/evm_chains_controller.go deleted file mode 100644 index 9c887fa409c..00000000000 --- a/core/web/evm_chains_controller.go +++ /dev/null @@ -1,19 +0,0 @@ -package web - -import ( - "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" - "github.com/smartcontractkit/chainlink/v2/core/services/relay" - "github.com/smartcontractkit/chainlink/v2/core/web/presenters" -) - -var ErrEVMNotEnabled = errChainDisabled{name: "EVM", tomlKey: "EVM.Enabled"} - -func NewEVMChainsController(app chainlink.Application) ChainsController { - return newChainsController[presenters.EVMChainResource]( - relay.NetworkEVM, - app.GetRelayers().List(chainlink.FilterRelayersByType(relay.NetworkEVM)), - ErrEVMNotEnabled, - presenters.NewEVMChainResource, - app.GetLogger(), - app.GetAuditLogger()) -} diff --git a/core/web/evm_chains_controller_test.go b/core/web/evm_chains_controller_test.go deleted file mode 100644 index ab8bf35e6cb..00000000000 --- a/core/web/evm_chains_controller_test.go +++ /dev/null @@ -1,215 +0,0 @@ -package web_test - -import ( - "fmt" - "math/big" - "net/http" - "sort" - "testing" - - "github.com/manyminds/api2go/jsonapi" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - evmcfg "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/toml" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" - ubig "github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils/big" - "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" - "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" - "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" - "github.com/smartcontractkit/chainlink/v2/core/web" - "github.com/smartcontractkit/chainlink/v2/core/web/presenters" -) - -func Test_EVMChainsController_Show(t *testing.T) { - t.Parallel() - - validId := ubig.New(testutils.NewRandomEVMChainID()) - - testCases := []struct { - name string - inputId string - wantStatusCode int - want *evmcfg.EVMConfig - }{ - { - inputId: validId.String(), - name: "success", - want: &evmcfg.EVMConfig{ - ChainID: validId, - Enabled: ptr(true), - Chain: evmcfg.Defaults(nil, &evmcfg.Chain{ - GasEstimator: evmcfg.GasEstimator{ - EIP1559DynamicFees: ptr(true), - BlockHistory: evmcfg.BlockHistoryEstimator{ - BlockHistorySize: ptr[uint16](50), - }, - }, - RPCBlockQueryDelay: ptr[uint16](23), - MinIncomingConfirmations: ptr[uint32](12), - LinkContractAddress: ptr(types.EIP55AddressFromAddress(testutils.NewAddress())), - }), - }, - wantStatusCode: http.StatusOK, - }, - { - inputId: "invalidid", - name: "invalid id", - want: nil, - wantStatusCode: http.StatusBadRequest, - }, - { - inputId: "234", - name: "not found", - want: nil, - wantStatusCode: http.StatusBadRequest, - }, - } - - for _, testCase := range testCases { - tc := testCase - - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - - controller := setupEVMChainsControllerTest(t, configtest.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { - if tc.want != nil { - c.EVM = evmcfg.EVMConfigs{tc.want} - } - })) - - wantedResult := tc.want - resp, cleanup := controller.client.Get( - fmt.Sprintf("/v2/chains/evm/%s", tc.inputId), - ) - t.Cleanup(cleanup) - require.Equal(t, tc.wantStatusCode, resp.StatusCode) - - if wantedResult != nil { - resource1 := presenters.EVMChainResource{} - err := web.ParseJSONAPIResponse(cltest.ParseResponseBody(t, resp), &resource1) - require.NoError(t, err) - - assert.Equal(t, resource1.ID, wantedResult.ChainID.String()) - toml, err := wantedResult.TOMLString() - require.NoError(t, err) - assert.Equal(t, toml, resource1.Config) - } - }) - } -} - -func Test_EVMChainsController_Index(t *testing.T) { - t.Parallel() - - // sort test chain ids to make expected comparison easy - chainIDs := []*big.Int{testutils.NewRandomEVMChainID(), testutils.NewRandomEVMChainID(), testutils.NewRandomEVMChainID()} - sort.Slice(chainIDs, func(i, j int) bool { - return chainIDs[i].String() < chainIDs[j].String() - }) - - configuredChains := evmcfg.EVMConfigs{ - {ChainID: ubig.New(chainIDs[0]), Chain: evmcfg.Defaults(nil)}, - { - ChainID: ubig.New(chainIDs[1]), - Chain: evmcfg.Defaults(nil, &evmcfg.Chain{ - RPCBlockQueryDelay: ptr[uint16](13), - GasEstimator: evmcfg.GasEstimator{ - EIP1559DynamicFees: ptr(true), - BlockHistory: evmcfg.BlockHistoryEstimator{ - BlockHistorySize: ptr[uint16](1), - }, - }, - MinIncomingConfirmations: ptr[uint32](120), - }), - }, - { - ChainID: ubig.New(chainIDs[2]), - Chain: evmcfg.Defaults(nil, &evmcfg.Chain{ - RPCBlockQueryDelay: ptr[uint16](5), - GasEstimator: evmcfg.GasEstimator{ - EIP1559DynamicFees: ptr(false), - BlockHistory: evmcfg.BlockHistoryEstimator{ - BlockHistorySize: ptr[uint16](2), - }, - }, - MinIncomingConfirmations: ptr[uint32](30), - }), - }, - } - - assert.Len(t, configuredChains, 3) - controller := setupEVMChainsControllerTest(t, configtest.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { - c.EVM = append(c.EVM, configuredChains...) - })) - - badResp, cleanup := controller.client.Get("/v2/chains/evm?size=asd") - t.Cleanup(cleanup) - require.Equal(t, http.StatusUnprocessableEntity, badResp.StatusCode) - - resp, cleanup := controller.client.Get("/v2/chains/evm?size=3") - t.Cleanup(cleanup) - require.Equal(t, http.StatusOK, resp.StatusCode) - - body := cltest.ParseResponseBody(t, resp) - - metaCount, err := cltest.ParseJSONAPIResponseMetaCount(body) - require.NoError(t, err) - require.Equal(t, 1+len(configuredChains), metaCount) - - var links jsonapi.Links - - var gotChains []presenters.EVMChainResource - err = web.ParsePaginatedResponse(body, &gotChains, &links) - assert.NoError(t, err) - assert.NotEmpty(t, links["next"].Href) - assert.Empty(t, links["prev"].Href) - - assert.Len(t, links, 1) - // the difference in index value here seems to be due to the fact - // that cltest always has a default EVM chain, which is the off-by-one - // in the indices - assert.Equal(t, gotChains[2].ID, configuredChains[1].ChainID.String()) - toml, err := configuredChains[1].TOMLString() - require.NoError(t, err) - assert.Equal(t, toml, gotChains[2].Config) - - resp, cleanup = controller.client.Get(links["next"].Href) - t.Cleanup(cleanup) - require.Equal(t, http.StatusOK, resp.StatusCode) - - gotChains = []presenters.EVMChainResource{} - err = web.ParsePaginatedResponse(cltest.ParseResponseBody(t, resp), &gotChains, &links) - assert.NoError(t, err) - assert.Empty(t, links["next"].Href) - assert.NotEmpty(t, links["prev"].Href) - - assert.Len(t, links, 1) - assert.Equal(t, gotChains[0].ID, configuredChains[2].ChainID.String()) - toml, err = configuredChains[2].TOMLString() - require.NoError(t, err) - assert.Equal(t, toml, gotChains[0].Config) -} - -type TestEVMChainsController struct { - app *cltest.TestApplication - client cltest.HTTPClientCleaner -} - -func setupEVMChainsControllerTest(t *testing.T, cfg chainlink.GeneralConfig) *TestEVMChainsController { - // Using this instead of `NewApplicationEVMDisabled` since we need the chain set to be loaded in the app - // for the sake of the API endpoints to work properly - app := cltest.NewApplicationWithConfig(t, cfg) - ctx := testutils.Context(t) - require.NoError(t, app.Start(ctx)) - - client := app.NewHTTPClient(nil) - - return &TestEVMChainsController{ - app: app, - client: client, - } -} - -func ptr[T any](t T) *T { return &t } diff --git a/core/web/evm_nodes_controller.go b/core/web/evm_nodes_controller.go deleted file mode 100644 index 8872f51d7e3..00000000000 --- a/core/web/evm_nodes_controller.go +++ /dev/null @@ -1,14 +0,0 @@ -package web - -import ( - "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" - "github.com/smartcontractkit/chainlink/v2/core/services/relay" - "github.com/smartcontractkit/chainlink/v2/core/web/presenters" -) - -func NewEVMNodesController(app chainlink.Application) NodesController { - scopedNodeStatuser := NewNetworkScopedNodeStatuser(app.GetRelayers(), relay.NetworkEVM) - - return newNodesController[presenters.EVMNodeResource]( - scopedNodeStatuser, ErrEVMNotEnabled, presenters.NewEVMNodeResource, app.GetAuditLogger()) -} diff --git a/core/web/nodes_controller.go b/core/web/nodes_controller.go index 0e43316629a..ee40de9885c 100644 --- a/core/web/nodes_controller.go +++ b/core/web/nodes_controller.go @@ -2,7 +2,6 @@ package web import ( "context" - "net/http" "github.com/gin-gonic/gin" "github.com/manyminds/api2go/jsonapi" @@ -11,6 +10,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/logger/audit" "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" + "github.com/smartcontractkit/chainlink/v2/core/web/presenters" ) type NodesController interface { @@ -36,42 +36,38 @@ func (n *NetworkScopedNodeStatuser) NodeStatuses(ctx context.Context, offset, li } type nodesController[R jsonapi.EntityNamer] struct { - nodeSet *NetworkScopedNodeStatuser - errNotEnabled error - newResource func(status types.NodeStatus) R - auditLogger audit.AuditLogger + relayers chainlink.RelayerChainInteroperators + newResource func(status types.NodeStatus) R + auditLogger audit.AuditLogger } -func newNodesController[R jsonapi.EntityNamer]( - nodeSet *NetworkScopedNodeStatuser, - errNotEnabled error, - newResource func(status types.NodeStatus) R, - auditLogger audit.AuditLogger, +func NewNodesController( + relayers chainlink.RelayerChainInteroperators, auditLogger audit.AuditLogger, ) NodesController { - return &nodesController[R]{ - nodeSet: nodeSet, - errNotEnabled: errNotEnabled, - newResource: newResource, - auditLogger: auditLogger, + return &nodesController[presenters.NodeResource]{ + relayers: relayers, + newResource: presenters.NewNodeResource, + auditLogger: auditLogger, } } func (n *nodesController[R]) Index(c *gin.Context, size, page, offset int) { - if n.nodeSet == nil { - jsonAPIError(c, http.StatusBadRequest, n.errNotEnabled) - return - } - id := c.Param("ID") + network := c.Param("network") var nodes []types.NodeStatus var count int var err error + relayers := n.relayers + if network != "" { + relayers = relayers.List(chainlink.FilterRelayersByType(network)) + } + ctx := c.Request.Context() if id == "" { // fetch all nodes - nodes, count, err = n.nodeSet.NodeStatuses(ctx, offset, size) + nodes, count, err = relayers.NodeStatuses(ctx, offset, size) } else { // fetch nodes for chain ID // backward compatibility @@ -79,9 +75,9 @@ func (n *nodesController[R]) Index(c *gin.Context, size, page, offset int) { err = rid.UnmarshalString(id) if err != nil { rid.ChainID = id - rid.Network = n.nodeSet.network + rid.Network = network } - nodes, count, err = n.nodeSet.NodeStatuses(ctx, offset, size, rid) + nodes, count, err = relayers.NodeStatuses(ctx, offset, size, rid) } var resources []R diff --git a/core/web/presenters/chain.go b/core/web/presenters/chain.go index 99cf9a1d252..280f7a7f8d4 100644 --- a/core/web/presenters/chain.go +++ b/core/web/presenters/chain.go @@ -1,11 +1,32 @@ package presenters +import ( + "github.com/smartcontractkit/chainlink-common/pkg/types" + commonTypes "github.com/smartcontractkit/chainlink/v2/common/types" +) + type ChainResource struct { JAID + Network string `json:"network"` Enabled bool `json:"enabled"` Config string `json:"config"` // TOML } +// GetName implements the api2go EntityNamer interface +func (r ChainResource) GetName() string { + return "chain" +} + +// NewChainResource returns a new ChainResource for chain. +func NewChainResource(chain commonTypes.ChainStatusWithID) ChainResource { + return ChainResource{ + JAID: NewJAID(chain.RelayID.ChainID), + Network: chain.RelayID.Network, + Config: chain.Config, + Enabled: chain.Enabled, + } +} + type NodeResource struct { JAID ChainID string `json:"chainID"` @@ -13,3 +34,19 @@ type NodeResource struct { Config string `json:"config"` // TOML State string `json:"state"` } + +// NewNodeResource returns a new NodeResource for node. +func NewNodeResource(node types.NodeStatus) NodeResource { + return NodeResource{ + JAID: NewPrefixedJAID(node.Name, node.ChainID), + ChainID: node.ChainID, + Name: node.Name, + State: node.State, + Config: node.Config, + } +} + +// GetName implements the api2go EntityNamer interface +func (r NodeResource) GetName() string { + return "node" +} diff --git a/core/web/presenters/cosmos_chain.go b/core/web/presenters/cosmos_chain.go deleted file mode 100644 index c2bc4b52b61..00000000000 --- a/core/web/presenters/cosmos_chain.go +++ /dev/null @@ -1,45 +0,0 @@ -package presenters - -import ( - "github.com/smartcontractkit/chainlink-common/pkg/types" -) - -// CosmosChainResource is an Cosmos chain JSONAPI resource. -type CosmosChainResource struct { - ChainResource -} - -// GetName implements the api2go EntityNamer interface -func (r CosmosChainResource) GetName() string { - return "cosmos_chain" -} - -// NewCosmosChainResource returns a new CosmosChainResource for chain. -func NewCosmosChainResource(chain types.ChainStatus) CosmosChainResource { - return CosmosChainResource{ChainResource{ - JAID: NewJAID(chain.ID), - Config: chain.Config, - Enabled: chain.Enabled, - }} -} - -// CosmosNodeResource is a Cosmos node JSONAPI resource. -type CosmosNodeResource struct { - NodeResource -} - -// GetName implements the api2go EntityNamer interface -func (r CosmosNodeResource) GetName() string { - return "cosmos_node" -} - -// NewCosmosNodeResource returns a new CosmosNodeResource for node. -func NewCosmosNodeResource(node types.NodeStatus) CosmosNodeResource { - return CosmosNodeResource{NodeResource{ - JAID: NewPrefixedJAID(node.Name, node.ChainID), - ChainID: node.ChainID, - Name: node.Name, - State: node.State, - Config: node.Config, - }} -} diff --git a/core/web/presenters/evm_chain.go b/core/web/presenters/evm_chain.go deleted file mode 100644 index adf399d4b01..00000000000 --- a/core/web/presenters/evm_chain.go +++ /dev/null @@ -1,43 +0,0 @@ -package presenters - -import "github.com/smartcontractkit/chainlink-common/pkg/types" - -// EVMChainResource is an EVM chain JSONAPI resource. -type EVMChainResource struct { - ChainResource -} - -// GetName implements the api2go EntityNamer interface -func (r EVMChainResource) GetName() string { - return "evm_chain" -} - -// NewEVMChainResource returns a new EVMChainResource for chain. -func NewEVMChainResource(chain types.ChainStatus) EVMChainResource { - return EVMChainResource{ChainResource{ - JAID: NewJAID(chain.ID), - Config: chain.Config, - Enabled: chain.Enabled, - }} -} - -// EVMNodeResource is an EVM node JSONAPI resource. -type EVMNodeResource struct { - NodeResource -} - -// GetName implements the api2go EntityNamer interface -func (r EVMNodeResource) GetName() string { - return "evm_node" -} - -// NewEVMNodeResource returns a new EVMNodeResource for node. -func NewEVMNodeResource(node types.NodeStatus) EVMNodeResource { - return EVMNodeResource{NodeResource{ - JAID: NewPrefixedJAID(node.Name, node.ChainID), - ChainID: node.ChainID, - Name: node.Name, - State: node.State, - Config: node.Config, - }} -} diff --git a/core/web/presenters/job.go b/core/web/presenters/job.go index bb518650516..8b01eeb5005 100644 --- a/core/web/presenters/job.go +++ b/core/web/presenters/job.go @@ -176,6 +176,7 @@ type OffChainReporting2Spec struct { BlockchainTimeout models.Interval `json:"blockchainTimeout"` ContractConfigTrackerPollInterval models.Interval `json:"contractConfigTrackerPollInterval"` ContractConfigConfirmations uint16 `json:"contractConfigConfirmations"` + OnchainSigningStrategy map[string]interface{} `json:"onchainSigningStrategy"` CreatedAt time.Time `json:"createdAt"` UpdatedAt time.Time `json:"updatedAt"` CollectTelemetry bool `json:"collectTelemetry"` @@ -194,6 +195,7 @@ func NewOffChainReporting2Spec(spec *job.OCR2OracleSpec) *OffChainReporting2Spec BlockchainTimeout: spec.BlockchainTimeout, ContractConfigTrackerPollInterval: spec.ContractConfigTrackerPollInterval, ContractConfigConfirmations: spec.ContractConfigConfirmations, + OnchainSigningStrategy: spec.OnchainSigningStrategy, CreatedAt: spec.CreatedAt, UpdatedAt: spec.UpdatedAt, CollectTelemetry: spec.CaptureEATelemetry, diff --git a/core/web/presenters/jsonapi.go b/core/web/presenters/jsonapi.go index d14e24a7455..fe3aee8393f 100644 --- a/core/web/presenters/jsonapi.go +++ b/core/web/presenters/jsonapi.go @@ -16,8 +16,8 @@ func NewJAID(id string) JAID { } // NewPrefixedJAID prefixes JAID with chain id in %s/%s format. -func NewPrefixedJAID(id string, chainID string) JAID { - return JAID{ID: fmt.Sprintf("%s/%s", chainID, id)} +func NewPrefixedJAID(id string, prefix string) JAID { + return JAID{ID: fmt.Sprintf("%s/%s", prefix, id)} } // NewJAIDInt32 converts an int32 into a JAID diff --git a/core/web/presenters/node_test.go b/core/web/presenters/node_test.go index 34210a52166..d2db83009d9 100644 --- a/core/web/presenters/node_test.go +++ b/core/web/presenters/node_test.go @@ -13,7 +13,6 @@ import ( func TestNodeResource(t *testing.T) { var nodeResource NodeResource - var r interface{} state := "test" cfg := "cfg" testCases := []string{"solana", "cosmos", "starknet"} @@ -21,62 +20,25 @@ func TestNodeResource(t *testing.T) { chainID := fmt.Sprintf("%s chain ID", tc) nodeName := fmt.Sprintf("%s_node", tc) - switch tc { - case "evm": - evmNodeResource := NewEVMNodeResource( - types.NodeStatus{ - ChainID: chainID, - Name: nodeName, - Config: cfg, - State: state, - }) - r = evmNodeResource - nodeResource = evmNodeResource.NodeResource - case "solana": - solanaNodeResource := NewSolanaNodeResource( - types.NodeStatus{ - ChainID: chainID, - Name: nodeName, - Config: cfg, - State: state, - }) - r = solanaNodeResource - nodeResource = solanaNodeResource.NodeResource - case "cosmos": - cosmosNodeResource := NewCosmosNodeResource( - types.NodeStatus{ - ChainID: chainID, - Name: nodeName, - Config: cfg, - State: state, - }) - r = cosmosNodeResource - nodeResource = cosmosNodeResource.NodeResource - case "starknet": - starknetNodeResource := NewStarkNetNodeResource( - types.NodeStatus{ - ChainID: chainID, - Name: nodeName, - Config: cfg, - State: state, - }) - r = starknetNodeResource - nodeResource = starknetNodeResource.NodeResource - default: - t.Fail() - } + nodeResource = NewNodeResource(types.NodeStatus{ + ChainID: chainID, + Name: nodeName, + Config: cfg, + State: state, + }) + assert.Equal(t, chainID, nodeResource.ChainID) assert.Equal(t, nodeName, nodeResource.Name) assert.Equal(t, cfg, nodeResource.Config) assert.Equal(t, state, nodeResource.State) - b, err := jsonapi.Marshal(r) + b, err := jsonapi.Marshal(nodeResource) require.NoError(t, err) expected := fmt.Sprintf(` { "data":{ - "type":"%s_node", + "type":"node", "id":"%s/%s", "attributes":{ "chainID":"%s", @@ -86,7 +48,7 @@ func TestNodeResource(t *testing.T) { } } } - `, tc, chainID, nodeName, chainID, nodeName, cfg, state) + `, chainID, nodeName, chainID, nodeName, cfg, state) assert.JSONEq(t, expected, string(b)) } } diff --git a/core/web/presenters/solana_chain.go b/core/web/presenters/solana_chain.go deleted file mode 100644 index 798d98124a5..00000000000 --- a/core/web/presenters/solana_chain.go +++ /dev/null @@ -1,45 +0,0 @@ -package presenters - -import ( - "github.com/smartcontractkit/chainlink-common/pkg/types" -) - -// SolanaChainResource is an Solana chain JSONAPI resource. -type SolanaChainResource struct { - ChainResource -} - -// GetName implements the api2go EntityNamer interface -func (r SolanaChainResource) GetName() string { - return "solana_chain" -} - -// NewSolanaChainResource returns a new SolanaChainResource for chain. -func NewSolanaChainResource(chain types.ChainStatus) SolanaChainResource { - return SolanaChainResource{ChainResource{ - JAID: NewJAID(chain.ID), - Config: chain.Config, - Enabled: chain.Enabled, - }} -} - -// SolanaNodeResource is a Solana node JSONAPI resource. -type SolanaNodeResource struct { - NodeResource -} - -// GetName implements the api2go EntityNamer interface -func (r SolanaNodeResource) GetName() string { - return "solana_node" -} - -// NewSolanaNodeResource returns a new SolanaNodeResource for node. -func NewSolanaNodeResource(node types.NodeStatus) SolanaNodeResource { - return SolanaNodeResource{NodeResource{ - JAID: NewPrefixedJAID(node.Name, node.ChainID), - ChainID: node.ChainID, - Name: node.Name, - State: node.State, - Config: node.Config, - }} -} diff --git a/core/web/presenters/starknet_chain.go b/core/web/presenters/starknet_chain.go deleted file mode 100644 index addf798fe9f..00000000000 --- a/core/web/presenters/starknet_chain.go +++ /dev/null @@ -1,45 +0,0 @@ -package presenters - -import ( - "github.com/smartcontractkit/chainlink-common/pkg/types" -) - -// StarkNetChainResource is an StarkNet chain JSONAPI resource. -type StarkNetChainResource struct { - ChainResource -} - -// GetName implements the api2go EntityNamer interface -func (r StarkNetChainResource) GetName() string { - return "starknet_chain" -} - -// NewStarkNetChainResource returns a new StarkNetChainResource for chain. -func NewStarkNetChainResource(chain types.ChainStatus) StarkNetChainResource { - return StarkNetChainResource{ChainResource{ - JAID: NewJAID(chain.ID), - Config: chain.Config, - Enabled: chain.Enabled, - }} -} - -// StarkNetNodeResource is a StarkNet node JSONAPI resource. -type StarkNetNodeResource struct { - NodeResource -} - -// GetName implements the api2go EntityNamer interface -func (r StarkNetNodeResource) GetName() string { - return "starknet_node" -} - -// NewStarkNetNodeResource returns a new StarkNetNodeResource for node. -func NewStarkNetNodeResource(node types.NodeStatus) StarkNetNodeResource { - return StarkNetNodeResource{NodeResource{ - JAID: NewPrefixedJAID(node.Name, node.ChainID), - ChainID: node.ChainID, - Name: node.Name, - State: node.State, - Config: node.Config, - }} -} diff --git a/core/web/resolver/spec.go b/core/web/resolver/spec.go index ce23df49264..4a6989ae2dd 100644 --- a/core/web/resolver/spec.go +++ b/core/web/resolver/spec.go @@ -1,7 +1,7 @@ package resolver import ( - "fmt" + "strconv" "github.com/graph-gophers/graphql-go" @@ -139,8 +139,13 @@ func (r *SpecResolver) ToStreamSpec() (*StreamSpecResolver, bool) { if r.j.Type != job.Stream { return nil, false } + res := &StreamSpecResolver{} + if r.j.StreamID != nil { + sid := strconv.FormatUint(uint64(*r.j.StreamID), 10) + res.streamID = &sid + } - return &StreamSpecResolver{streamID: fmt.Sprintf("%d", r.j.StreamID)}, true + return res, true } type CronSpecResolver struct { @@ -987,6 +992,11 @@ func (r *BootstrapSpecResolver) ContractConfigConfirmations() *int32 { return &confirmations } +// RelayConfig resolves the spec's onchain signing strategy config +func (r *OCR2SpecResolver) OnchainSigningStrategy() gqlscalar.Map { + return gqlscalar.Map(r.spec.OnchainSigningStrategy) +} + // CreatedAt resolves the spec's created at timestamp. func (r *BootstrapSpecResolver) CreatedAt() graphql.Time { return graphql.Time{Time: r.spec.CreatedAt} @@ -1057,9 +1067,9 @@ func (r *StandardCapabilitiesSpecResolver) Config() *string { } type StreamSpecResolver struct { - streamID string + streamID *string } -func (r *StreamSpecResolver) StreamID() string { +func (r *StreamSpecResolver) StreamID() *string { return r.streamID } diff --git a/core/web/resolver/spec_test.go b/core/web/resolver/spec_test.go index 69d6a56509c..61a29d4f54a 100644 --- a/core/web/resolver/spec_test.go +++ b/core/web/resolver/spec_test.go @@ -2,6 +2,7 @@ package resolver import ( "context" + "fmt" "testing" "time" @@ -471,6 +472,12 @@ func TestResolver_OCR2Spec(t *testing.T) { pluginConfig := map[string]interface{}{ "juelsPerFeeCoinSource": 100000000, } + onchainSigningStrategy := map[string]interface{}{ + "strategyName": "multi-chain", + "config": map[string]any{ + "evm": "b3df4d8748b67731a1112e8b45a764941974f5590c93672eebbc4f3504dd10ed", + }, + } require.NoError(t, err) testCases := []GQLTestCase{ @@ -486,6 +493,7 @@ func TestResolver_OCR2Spec(t *testing.T) { ContractID: contractAddress.String(), ContractConfigConfirmations: 1, ContractConfigTrackerPollInterval: models.Interval(1 * time.Minute), + OnchainSigningStrategy: onchainSigningStrategy, CreatedAt: f.Timestamp(), OCRKeyBundleID: null.StringFrom(keyBundleID.String()), MonitoringEndpoint: null.StringFrom("https://monitor.endpoint"), @@ -509,6 +517,7 @@ func TestResolver_OCR2Spec(t *testing.T) { contractID contractConfigConfirmations contractConfigTrackerPollInterval + onchainSigningStrategy createdAt ocrKeyBundleID monitoringEndpoint @@ -533,6 +542,12 @@ func TestResolver_OCR2Spec(t *testing.T) { "contractID": "0x613a38AC1659769640aaE063C651F48E0250454C", "contractConfigConfirmations": 1, "contractConfigTrackerPollInterval": "1m0s", + "onchainSigningStrategy": { + "strategyName": "multi-chain", + "config": { + "evm": "b3df4d8748b67731a1112e8b45a764941974f5590c93672eebbc4f3504dd10ed" + } + }, "createdAt": "2021-01-01T00:00:00Z", "ocrKeyBundleID": "f5bf259689b26f1374efb3c9a9868796953a0f814bb2d39b968d0e61b58620a5", "monitoringEndpoint": "https://monitor.endpoint", @@ -1166,3 +1181,85 @@ func TestResolver_StandardCapabilitiesSpec(t *testing.T) { RunGQLTests(t, testCases) } + +func TestResolver_StreamSpec(t *testing.T) { + var ( + id1 = int32(1) + id2 = int32(2) + streamID = uint32(3) + ) + + testCases := []GQLTestCase{ + { + name: "stream spec with stream ID", + authenticated: true, + before: func(ctx context.Context, f *gqlTestFramework) { + f.App.On("JobORM").Return(f.Mocks.jobORM) + f.Mocks.jobORM.On("FindJobWithoutSpecErrors", mock.Anything, id1).Return(job.Job{ + Type: job.Stream, + StreamID: &streamID, + }, nil) + }, + query: fmt.Sprintf(` + query GetJob { + job(id: "%d") { + ... on Job { + spec { + __typename + ... on StreamSpec { + streamID + } + } + } + } + } + `, id1), + result: fmt.Sprintf(` + { + "job": { + "spec": { + "__typename": "StreamSpec", + "streamID": "%d" + } + } + } + `, streamID), + }, + { + name: "stream spec without stream ID", + authenticated: true, + before: func(ctx context.Context, f *gqlTestFramework) { + f.App.On("JobORM").Return(f.Mocks.jobORM) + f.Mocks.jobORM.On("FindJobWithoutSpecErrors", mock.Anything, id2).Return(job.Job{ + Type: job.Stream, + }, nil) + }, + query: fmt.Sprintf(` + query GetJob { + job(id: "%d") { + ... on Job { + spec { + __typename + ... on StreamSpec { + streamID + } + } + } + } + } + `, id2), + result: ` + { + "job": { + "spec": { + "__typename": "StreamSpec", + "streamID": null + } + } + } + `, + }, + } + + RunGQLTests(t, testCases) +} diff --git a/core/web/router.go b/core/web/router.go index 6e96b47981b..c57bf3c8095 100644 --- a/core/web/router.go +++ b/core/web/router.go @@ -390,36 +390,23 @@ func v2Routes(app chainlink.Application, r *gin.RouterGroup) { authv2.PATCH("/log", auth.RequiresAdminRole(lgc.Patch)) chains := authv2.Group("chains") - for _, chain := range []struct { - path string - cc ChainsController - }{ - {"evm", NewEVMChainsController(app)}, - {"solana", NewSolanaChainsController(app)}, - {"starknet", NewStarkNetChainsController(app)}, - {"cosmos", NewCosmosChainsController(app)}, - } { - chains.GET(chain.path, paginatedRequest(chain.cc.Index)) - chains.GET(chain.path+"/:ID", chain.cc.Show) - } + chainController := NewChainsController( + app.GetRelayers(), + app.GetLogger(), + app.GetAuditLogger(), + ) + chains.GET("", paginatedRequest(chainController.Index)) + chains.GET("/:network", paginatedRequest(chainController.Index)) + chains.GET("/:network/:ID", chainController.Show) nodes := authv2.Group("nodes") - for _, chain := range []struct { - path string - nc NodesController - }{ - {"evm", NewEVMNodesController(app)}, - {"solana", NewSolanaNodesController(app)}, - {"starknet", NewStarkNetNodesController(app)}, - {"cosmos", NewCosmosNodesController(app)}, - } { - if chain.path == "evm" { - // TODO still EVM only . Archive ticket: story/26276/multi-chain-type-ui-node-chain-configuration - nodes.GET("", paginatedRequest(chain.nc.Index)) - } - nodes.GET(chain.path, paginatedRequest(chain.nc.Index)) - chains.GET(chain.path+"/:ID/nodes", paginatedRequest(chain.nc.Index)) - } + nodesController := NewNodesController( + app.GetRelayers(), + app.GetAuditLogger(), + ) + nodes.GET("", paginatedRequest(nodesController.Index)) + nodes.GET("/:network", paginatedRequest(nodesController.Index)) + chains.GET("/:network/:ID/nodes", paginatedRequest(nodesController.Index)) efc := EVMForwardersController{app} authv2.GET("/nodes/evm/forwarders", paginatedRequest(efc.Index)) diff --git a/core/web/schema/type/spec.graphql b/core/web/schema/type/spec.graphql index db81001543c..984aad02a5a 100644 --- a/core/web/schema/type/spec.graphql +++ b/core/web/schema/type/spec.graphql @@ -82,6 +82,7 @@ type OCR2Spec { p2pv2Bootstrappers: [String!] relay: String! relayConfig: Map! + onchainSigningStrategy: Map! transmitterID: String pluginType: String! pluginConfig: Map! @@ -182,5 +183,5 @@ type StandardCapabilitiesSpec { } type StreamSpec { - streamID: String! + streamID: String } diff --git a/core/web/solana_chains_controller.go b/core/web/solana_chains_controller.go deleted file mode 100644 index 56d44d600ca..00000000000 --- a/core/web/solana_chains_controller.go +++ /dev/null @@ -1,17 +0,0 @@ -package web - -import ( - "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" - "github.com/smartcontractkit/chainlink/v2/core/services/relay" - "github.com/smartcontractkit/chainlink/v2/core/web/presenters" -) - -func NewSolanaChainsController(app chainlink.Application) ChainsController { - return newChainsController( - relay.NetworkSolana, - app.GetRelayers().List(chainlink.FilterRelayersByType(relay.NetworkSolana)), - ErrSolanaNotEnabled, - presenters.NewSolanaChainResource, - app.GetLogger(), - app.GetAuditLogger()) -} diff --git a/core/web/solana_chains_controller_test.go b/core/web/solana_chains_controller_test.go deleted file mode 100644 index fdc9bd16b9b..00000000000 --- a/core/web/solana_chains_controller_test.go +++ /dev/null @@ -1,216 +0,0 @@ -package web_test - -import ( - "fmt" - "math/rand" - "net/http" - "testing" - "time" - - "github.com/manyminds/api2go/jsonapi" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - commoncfg "github.com/smartcontractkit/chainlink-common/pkg/config" - "github.com/smartcontractkit/chainlink-common/pkg/types" - "github.com/smartcontractkit/chainlink-solana/pkg/solana/config" - - "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" - "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" - "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" - "github.com/smartcontractkit/chainlink/v2/core/web" - "github.com/smartcontractkit/chainlink/v2/core/web/presenters" -) - -func Test_SolanaChainsController_Show(t *testing.T) { - t.Parallel() - - const validId = "Chainlink-12" - - testCases := []struct { - name string - inputId string - wantStatusCode int - want func(t *testing.T, app *cltest.TestApplication) *types.ChainStatus - }{ - { - inputId: validId, - name: "success", - want: func(t *testing.T, app *cltest.TestApplication) *types.ChainStatus { - return &types.ChainStatus{ - ID: validId, - Enabled: true, - Config: `ChainID = 'Chainlink-12' -BalancePollPeriod = '5s' -ConfirmPollPeriod = '500ms' -OCR2CachePollPeriod = '1s' -OCR2CacheTTL = '1m0s' -TxTimeout = '1h0m0s' -TxRetryTimeout = '10s' -TxConfirmTimeout = '30s' -TxRetentionTimeout = '0s' -SkipPreflight = false -Commitment = 'confirmed' -MaxRetries = 0 -FeeEstimatorMode = 'fixed' -ComputeUnitPriceMax = 1000 -ComputeUnitPriceMin = 0 -ComputeUnitPriceDefault = 0 -FeeBumpPeriod = '3s' -BlockHistoryPollPeriod = '5s' -BlockHistorySize = 1 -ComputeUnitLimitDefault = 200000 -EstimateComputeUnitLimit = false -Nodes = [] - -[MultiNode] -Enabled = false -PollFailureThreshold = 5 -PollInterval = '15s' -SelectionMode = 'PriorityLevel' -SyncThreshold = 10 -NodeIsSyncingEnabled = false -LeaseDuration = '1m0s' -FinalizedBlockPollInterval = '5s' -EnforceRepeatableRead = true -DeathDeclarationDelay = '20s' -NodeNoNewHeadsThreshold = '20s' -NoNewFinalizedHeadsThreshold = '20s' -FinalityDepth = 0 -FinalityTagEnabled = true -FinalizedBlockOffset = 50 -`, - } - }, - wantStatusCode: http.StatusOK, - }, - { - inputId: "234", - name: "not found", - want: func(t *testing.T, app *cltest.TestApplication) *types.ChainStatus { - return nil - }, - wantStatusCode: http.StatusBadRequest, - }, - } - - for _, testCase := range testCases { - tc := testCase - - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - - controller := setupSolanaChainsControllerTestV2(t, &config.TOMLConfig{ - ChainID: ptr(validId), - Chain: config.Chain{ - SkipPreflight: ptr(false), - TxTimeout: commoncfg.MustNewDuration(time.Hour), - }, - }) - - wantedResult := tc.want(t, controller.app) - resp, cleanup := controller.client.Get( - fmt.Sprintf("/v2/chains/solana/%s", tc.inputId), - ) - t.Cleanup(cleanup) - require.Equal(t, tc.wantStatusCode, resp.StatusCode) - - if wantedResult != nil { - resource1 := presenters.SolanaChainResource{} - err := web.ParseJSONAPIResponse(cltest.ParseResponseBody(t, resp), &resource1) - require.NoError(t, err) - - assert.Equal(t, wantedResult.ID, resource1.ID) - assert.Equal(t, wantedResult.Enabled, resource1.Enabled) - assert.Equal(t, wantedResult.Config, resource1.Config) - } - }) - } -} - -func Test_SolanaChainsController_Index(t *testing.T) { - t.Parallel() - - chainA := &config.TOMLConfig{ - ChainID: ptr(fmt.Sprintf("ChainlinktestA-%d", rand.Int31n(999999))), - Chain: config.Chain{ - TxTimeout: commoncfg.MustNewDuration(time.Hour), - }, - } - chainB := &config.TOMLConfig{ - ChainID: ptr(fmt.Sprintf("ChainlinktestB-%d", rand.Int31n(999999))), - Chain: config.Chain{ - SkipPreflight: ptr(false), - }, - } - controller := setupSolanaChainsControllerTestV2(t, chainA, chainB) - - badResp, cleanup := controller.client.Get("/v2/chains/solana?size=asd") - t.Cleanup(cleanup) - require.Equal(t, http.StatusUnprocessableEntity, badResp.StatusCode) - - resp, cleanup := controller.client.Get("/v2/chains/solana?size=1") - t.Cleanup(cleanup) - require.Equal(t, http.StatusOK, resp.StatusCode) - - body := cltest.ParseResponseBody(t, resp) - - metaCount, err := cltest.ParseJSONAPIResponseMetaCount(body) - require.NoError(t, err) - require.Equal(t, 2, metaCount) - - var links jsonapi.Links - - chains := []presenters.SolanaChainResource{} - err = web.ParsePaginatedResponse(body, &chains, &links) - assert.NoError(t, err) - assert.NotEmpty(t, links["next"].Href) - assert.Empty(t, links["prev"].Href) - - assert.Len(t, links, 1) - assert.Equal(t, *chainA.ChainID, chains[0].ID) - tomlA, err := chainA.TOMLString() - require.NoError(t, err) - assert.Equal(t, tomlA, chains[0].Config) - - resp, cleanup = controller.client.Get(links["next"].Href) - t.Cleanup(cleanup) - require.Equal(t, http.StatusOK, resp.StatusCode) - - chains = []presenters.SolanaChainResource{} - err = web.ParsePaginatedResponse(cltest.ParseResponseBody(t, resp), &chains, &links) - assert.NoError(t, err) - assert.Empty(t, links["next"].Href) - assert.NotEmpty(t, links["prev"].Href) - - assert.Len(t, links, 1) - assert.Equal(t, *chainB.ChainID, chains[0].ID) - tomlB, err := chainB.TOMLString() - require.NoError(t, err) - assert.Equal(t, tomlB, chains[0].Config) -} - -type TestSolanaChainsController struct { - app *cltest.TestApplication - client cltest.HTTPClientCleaner -} - -func setupSolanaChainsControllerTestV2(t *testing.T, cfgs ...*config.TOMLConfig) *TestSolanaChainsController { - for i := range cfgs { - cfgs[i].SetDefaults() - } - cfg := configtest.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { - c.Solana = cfgs - c.EVM = nil - }) - app := cltest.NewApplicationWithConfig(t, cfg) - require.NoError(t, app.Start(testutils.Context(t))) - - client := app.NewHTTPClient(nil) - - return &TestSolanaChainsController{ - app: app, - client: client, - } -} diff --git a/core/web/solana_nodes_controller.go b/core/web/solana_nodes_controller.go deleted file mode 100644 index 71b8f70c5ec..00000000000 --- a/core/web/solana_nodes_controller.go +++ /dev/null @@ -1,17 +0,0 @@ -package web - -import ( - "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" - "github.com/smartcontractkit/chainlink/v2/core/services/relay" - "github.com/smartcontractkit/chainlink/v2/core/web/presenters" -) - -// ErrSolanaNotEnabled is returned when Solana.Enabled is not true. -var ErrSolanaNotEnabled = errChainDisabled{name: "Solana", tomlKey: "Solana.Enabled"} - -func NewSolanaNodesController(app chainlink.Application) NodesController { - scopedNodeStatuser := NewNetworkScopedNodeStatuser(app.GetRelayers(), relay.NetworkSolana) - - return newNodesController[presenters.SolanaNodeResource]( - scopedNodeStatuser, ErrSolanaNotEnabled, presenters.NewSolanaNodeResource, app.GetAuditLogger()) -} diff --git a/core/web/solana_transfer_controller.go b/core/web/solana_transfer_controller.go index 07c629a7dd1..5a5f51bc9dd 100644 --- a/core/web/solana_transfer_controller.go +++ b/core/web/solana_transfer_controller.go @@ -22,6 +22,8 @@ type SolanaTransfersController struct { App chainlink.Application } +var ErrSolanaNotEnabled = errChainDisabled{name: "Solana", tomlKey: "Solana.Enabled"} + // Create sends SOL and other native coins from the Chainlink's account to a specified address. func (tc *SolanaTransfersController) Create(c *gin.Context) { relayers := tc.App.GetRelayers().List(chainlink.FilterRelayersByType(relay.NetworkSolana)) diff --git a/core/web/starknet_chains_controller.go b/core/web/starknet_chains_controller.go deleted file mode 100644 index eb79ba3f962..00000000000 --- a/core/web/starknet_chains_controller.go +++ /dev/null @@ -1,17 +0,0 @@ -package web - -import ( - "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" - "github.com/smartcontractkit/chainlink/v2/core/services/relay" - "github.com/smartcontractkit/chainlink/v2/core/web/presenters" -) - -func NewStarkNetChainsController(app chainlink.Application) ChainsController { - return newChainsController( - relay.NetworkStarkNet, - app.GetRelayers().List(chainlink.FilterRelayersByType(relay.NetworkStarkNet)), - ErrStarkNetNotEnabled, - presenters.NewStarkNetChainResource, - app.GetLogger(), - app.GetAuditLogger()) -} diff --git a/core/web/starknet_nodes_controller.go b/core/web/starknet_nodes_controller.go deleted file mode 100644 index 664b89d03ca..00000000000 --- a/core/web/starknet_nodes_controller.go +++ /dev/null @@ -1,17 +0,0 @@ -package web - -import ( - "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" - "github.com/smartcontractkit/chainlink/v2/core/services/relay" - "github.com/smartcontractkit/chainlink/v2/core/web/presenters" -) - -// ErrStarkNetNotEnabled is returned when Starknet.Enabled is not true. -var ErrStarkNetNotEnabled = errChainDisabled{name: "StarkNet", tomlKey: "Starknet.Enabled"} - -func NewStarkNetNodesController(app chainlink.Application) NodesController { - scopedNodeStatuser := NewNetworkScopedNodeStatuser(app.GetRelayers(), relay.NetworkStarkNet) - - return newNodesController[presenters.StarkNetNodeResource]( - scopedNodeStatuser, ErrStarkNetNotEnabled, presenters.NewStarkNetNodeResource, app.GetAuditLogger()) -} diff --git a/deployment/README.md b/deployment/README.md index c6579ca6205..f3bd29b768b 100644 --- a/deployment/README.md +++ b/deployment/README.md @@ -6,14 +6,14 @@ deployment/configuration logic to be tested against ephemeral environments and then exposed for use in persistent environments like testnet/mainnet. ## Table of Contents -- [Address Book](##Address-Book) -- [View](##View) -- [Environment](##Environment) -- [Job Distributor](##Job-Distributor) -- [Changesets](##Changesets) -- [Directory Structure](##Directory-Structure) -- [Integration Testing](##Integration-Testing) -- [FAQ](##FAQ) +- [Address Book](#address-book) +- [View](#view) +- [Environment](#environment) +- [Job Distributor](#job-distributor) +- [Changesets](#changsets) +- [Directory Structure](#directory-structure) +- [Integration Testing](#integration-testing) +- [FAQ](#faq) ## Address Book An [address book](https://github.com/smartcontractkit/chainlink/blob/develop/deployment/address_book.go#L79) represents @@ -100,14 +100,14 @@ TODO: Add various examples in deployment/example. contracts (like MCMS, LinkToken etc) which can be shared by products. -/deployment//internal +/deployment/product/internal - Internal building blocks for changesets -/deployment//view +/deployment/product/view - Hold readonly mappings Go bindings to json marshallable objects. - Used to generate a view of the system. -/deployment//changeset +/deployment/product/changeset - Think of this as the public API for deployment and configuration of your product. - All the changesets should have an associated test using a memory or devenv diff --git a/deployment/address_book.go b/deployment/address_book.go index 6f605013011..3ce0332a4c3 100644 --- a/deployment/address_book.go +++ b/deployment/address_book.go @@ -89,8 +89,10 @@ type AddressBook interface { Remove(ab AddressBook) error } +type AddressesByChain map[uint64]map[string]TypeAndVersion + type AddressBookMap struct { - addressesByChain map[uint64]map[string]TypeAndVersion + addressesByChain AddressesByChain mtx sync.RWMutex } diff --git a/deployment/ccip/changeset/accept_ownership_test.go b/deployment/ccip/changeset/accept_ownership_test.go index 796db6aed09..9b71e0ad5cb 100644 --- a/deployment/ccip/changeset/accept_ownership_test.go +++ b/deployment/ccip/changeset/accept_ownership_test.go @@ -4,24 +4,17 @@ import ( "testing" "github.com/ethereum/go-ethereum/common" - "github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers" - - commonchangeset "github.com/smartcontractkit/chainlink/deployment/common/changeset" - "github.com/smartcontractkit/chainlink/deployment/environment/memory" "github.com/stretchr/testify/require" "golang.org/x/exp/maps" - "github.com/smartcontractkit/chainlink/v2/core/logger" + commonchangeset "github.com/smartcontractkit/chainlink/deployment/common/changeset" + "github.com/smartcontractkit/chainlink/deployment/common/proposalutils" ) func Test_NewAcceptOwnershipChangeset(t *testing.T) { - e := NewMemoryEnvironmentWithJobsAndContracts(t, logger.TestLogger(t), memory.MemoryEnvironmentConfig{ - Chains: 2, - NumOfUsersPerChain: 1, - Nodes: 4, - Bootstraps: 1, - }, &TestConfigs{}) + t.Parallel() + e := NewMemoryEnvironment(t) state, err := LoadOnchainState(e.Env) require.NoError(t, err) @@ -29,9 +22,15 @@ func Test_NewAcceptOwnershipChangeset(t *testing.T) { source := allChains[0] dest := allChains[1] - timelocks := map[uint64]*gethwrappers.RBACTimelock{ - source: state.Chains[source].Timelock, - dest: state.Chains[dest].Timelock, + timelockContracts := map[uint64]*proposalutils.TimelockExecutionContracts{ + source: { + Timelock: state.Chains[source].Timelock, + CallProxy: state.Chains[source].CallProxy, + }, + dest: { + Timelock: state.Chains[dest].Timelock, + CallProxy: state.Chains[dest].CallProxy, + }, } // at this point we have the initial deploys done, now we need to transfer ownership @@ -40,7 +39,7 @@ func Test_NewAcceptOwnershipChangeset(t *testing.T) { require.NoError(t, err) // compose the transfer ownership and accept ownership changesets - _, err = commonchangeset.ApplyChangesets(t, e.Env, timelocks, []commonchangeset.ChangesetApplication{ + _, err = commonchangeset.ApplyChangesets(t, e.Env, timelockContracts, []commonchangeset.ChangesetApplication{ // note this doesn't have proposals. { Changeset: commonchangeset.WrapChangeSet(commonchangeset.TransferToMCMSWithTimelock), diff --git a/deployment/ccip/changeset/cs_active_candidate.go b/deployment/ccip/changeset/cs_active_candidate.go deleted file mode 100644 index ecd87e2d399..00000000000 --- a/deployment/ccip/changeset/cs_active_candidate.go +++ /dev/null @@ -1,323 +0,0 @@ -package changeset - -import ( - "fmt" - "math/big" - - "github.com/ethereum/go-ethereum/common" - "github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers" - "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/mcms" - "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/timelock" - - "github.com/smartcontractkit/chainlink/deployment" - "github.com/smartcontractkit/chainlink/deployment/ccip/changeset/internal" - "github.com/smartcontractkit/chainlink/deployment/common/proposalutils" - cctypes "github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/types" - "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/ccip_home" - "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/capabilities_registry" -) - -var ( - _ deployment.ChangeSet[PromoteAllCandidatesChangesetConfig] = PromoteAllCandidatesChangeset - _ deployment.ChangeSet[AddDonAndSetCandidateChangesetConfig] = SetCandidatePluginChangeset -) - -type PromoteAllCandidatesChangesetConfig struct { - HomeChainSelector uint64 - NewChainSelector uint64 - NodeIDs []string -} - -func (p PromoteAllCandidatesChangesetConfig) Validate(e deployment.Environment, state CCIPOnChainState) (deployment.Nodes, error) { - if p.HomeChainSelector == 0 { - return nil, fmt.Errorf("HomeChainSelector must be set") - } - if p.NewChainSelector == 0 { - return nil, fmt.Errorf("NewChainSelector must be set") - } - if len(p.NodeIDs) == 0 { - return nil, fmt.Errorf("NodeIDs must be set") - } - - nodes, err := deployment.NodeInfo(p.NodeIDs, e.Offchain) - if err != nil { - return nil, fmt.Errorf("fetch node info: %w", err) - } - - donID, err := internal.DonIDForChain( - state.Chains[p.HomeChainSelector].CapabilityRegistry, - state.Chains[p.HomeChainSelector].CCIPHome, - p.NewChainSelector, - ) - if err != nil { - return nil, fmt.Errorf("fetch don id for chain: %w", err) - } - - // check if the DON ID has a candidate digest set that we can promote - for _, pluginType := range []cctypes.PluginType{cctypes.PluginTypeCCIPCommit, cctypes.PluginTypeCCIPExec} { - candidateDigest, err := state.Chains[p.HomeChainSelector].CCIPHome.GetCandidateDigest(nil, donID, uint8(pluginType)) - if err != nil { - return nil, fmt.Errorf("error fetching candidate digest for pluginType(%s): %w", pluginType.String(), err) - } - if candidateDigest == [32]byte{} { - return nil, fmt.Errorf("candidate digest is zero, must be non-zero to promote") - } - } - - return nodes, nil -} - -// PromoteAllCandidatesChangeset generates a proposal to call promoteCandidate on the CCIPHome through CapReg. -// This needs to be called after SetCandidateProposal is executed. -func PromoteAllCandidatesChangeset( - e deployment.Environment, - cfg PromoteAllCandidatesChangesetConfig, -) (deployment.ChangesetOutput, error) { - state, err := LoadOnchainState(e) - if err != nil { - return deployment.ChangesetOutput{}, err - } - - nodes, err := cfg.Validate(e, state) - if err != nil { - return deployment.ChangesetOutput{}, fmt.Errorf("%w: %w", deployment.ErrInvalidConfig, err) - } - - promoteCandidateOps, err := promoteAllCandidatesForChainOps( - state.Chains[cfg.HomeChainSelector].CapabilityRegistry, - state.Chains[cfg.HomeChainSelector].CCIPHome, - cfg.NewChainSelector, - nodes.NonBootstraps(), - ) - if err != nil { - return deployment.ChangesetOutput{}, err - } - - var ( - timelocksPerChain = map[uint64]common.Address{ - cfg.HomeChainSelector: state.Chains[cfg.HomeChainSelector].Timelock.Address(), - } - proposerMCMSes = map[uint64]*gethwrappers.ManyChainMultiSig{ - cfg.HomeChainSelector: state.Chains[cfg.HomeChainSelector].ProposerMcm, - } - ) - prop, err := proposalutils.BuildProposalFromBatches( - timelocksPerChain, - proposerMCMSes, - []timelock.BatchChainOperation{{ - ChainIdentifier: mcms.ChainIdentifier(cfg.HomeChainSelector), - Batch: promoteCandidateOps, - }}, - "promoteCandidate for commit and execution", - 0, // minDelay - ) - if err != nil { - return deployment.ChangesetOutput{}, err - } - return deployment.ChangesetOutput{ - Proposals: []timelock.MCMSWithTimelockProposal{ - *prop, - }, - }, nil -} - -// SetCandidatePluginChangeset calls setCandidate on the CCIPHome for setting up OCR3 exec Plugin config for the new chain. -func SetCandidatePluginChangeset( - e deployment.Environment, - cfg AddDonAndSetCandidateChangesetConfig, -) (deployment.ChangesetOutput, error) { - state, err := LoadOnchainState(e) - if err != nil { - return deployment.ChangesetOutput{}, err - } - - nodes, err := cfg.Validate(e, state) - if err != nil { - return deployment.ChangesetOutput{}, fmt.Errorf("%w: %w", deployment.ErrInvalidConfig, err) - } - - newDONArgs, err := internal.BuildOCR3ConfigForCCIPHome( - e.OCRSecrets, - state.Chains[cfg.NewChainSelector].OffRamp, - e.Chains[cfg.NewChainSelector], - nodes.NonBootstraps(), - state.Chains[cfg.HomeChainSelector].RMNHome.Address(), - cfg.CCIPOCRParams.OCRParameters, - cfg.CCIPOCRParams.CommitOffChainConfig, - cfg.CCIPOCRParams.ExecuteOffChainConfig, - ) - if err != nil { - return deployment.ChangesetOutput{}, err - } - - config, ok := newDONArgs[cfg.PluginType] - if !ok { - return deployment.ChangesetOutput{}, fmt.Errorf("missing %s plugin in ocr3Configs", cfg.PluginType.String()) - } - - setCandidateMCMSOps, err := setCandidateOnExistingDon( - config, - state.Chains[cfg.HomeChainSelector].CapabilityRegistry, - state.Chains[cfg.HomeChainSelector].CCIPHome, - cfg.NewChainSelector, - nodes.NonBootstraps(), - ) - if err != nil { - return deployment.ChangesetOutput{}, err - } - - var ( - timelocksPerChain = map[uint64]common.Address{ - cfg.HomeChainSelector: state.Chains[cfg.HomeChainSelector].Timelock.Address(), - } - proposerMCMSes = map[uint64]*gethwrappers.ManyChainMultiSig{ - cfg.HomeChainSelector: state.Chains[cfg.HomeChainSelector].ProposerMcm, - } - ) - prop, err := proposalutils.BuildProposalFromBatches( - timelocksPerChain, - proposerMCMSes, - []timelock.BatchChainOperation{{ - ChainIdentifier: mcms.ChainIdentifier(cfg.HomeChainSelector), - Batch: setCandidateMCMSOps, - }}, - fmt.Sprintf("SetCandidate for %s plugin", cfg.PluginType.String()), - 0, // minDelay - ) - if err != nil { - return deployment.ChangesetOutput{}, err - } - return deployment.ChangesetOutput{ - Proposals: []timelock.MCMSWithTimelockProposal{ - *prop, - }, - }, nil -} - -// setCandidateOnExistingDon calls setCandidate on CCIPHome contract through the UpdateDON call on CapReg contract -// This proposes to set up OCR3 config for the provided plugin for the DON -func setCandidateOnExistingDon( - pluginConfig ccip_home.CCIPHomeOCR3Config, - capReg *capabilities_registry.CapabilitiesRegistry, - ccipHome *ccip_home.CCIPHome, - chainSelector uint64, - nodes deployment.Nodes, -) ([]mcms.Operation, error) { - // fetch DON ID for the chain - donID, err := internal.DonIDForChain(capReg, ccipHome, chainSelector) - if err != nil { - return nil, fmt.Errorf("fetch don id for chain: %w", err) - } - fmt.Printf("donID: %d", donID) - encodedSetCandidateCall, err := internal.CCIPHomeABI.Pack( - "setCandidate", - donID, - pluginConfig.PluginType, - pluginConfig, - [32]byte{}, - ) - if err != nil { - return nil, fmt.Errorf("pack set candidate call: %w", err) - } - - // set candidate call - updateDonTx, err := capReg.UpdateDON( - deployment.SimTransactOpts(), - donID, - nodes.PeerIDs(), - []capabilities_registry.CapabilitiesRegistryCapabilityConfiguration{ - { - CapabilityId: internal.CCIPCapabilityID, - Config: encodedSetCandidateCall, - }, - }, - false, - nodes.DefaultF(), - ) - if err != nil { - return nil, fmt.Errorf("update don w/ exec config: %w", err) - } - - return []mcms.Operation{{ - To: capReg.Address(), - Data: updateDonTx.Data(), - Value: big.NewInt(0), - }}, nil -} - -// promoteCandidateOp will create the MCMS Operation for `promoteCandidateAndRevokeActive` directed towards the capabilityRegistry -func promoteCandidateOp(donID uint32, pluginType uint8, capReg *capabilities_registry.CapabilitiesRegistry, - ccipHome *ccip_home.CCIPHome, nodes deployment.Nodes) (mcms.Operation, error) { - - allConfigs, err := ccipHome.GetAllConfigs(nil, donID, pluginType) - if err != nil { - return mcms.Operation{}, err - } - - if allConfigs.CandidateConfig.ConfigDigest == [32]byte{} { - return mcms.Operation{}, fmt.Errorf("candidate digest is empty, expected nonempty") - } - fmt.Printf("commit candidate digest after setCandidate: %x\n", allConfigs.CandidateConfig.ConfigDigest) - - encodedPromotionCall, err := internal.CCIPHomeABI.Pack( - "promoteCandidateAndRevokeActive", - donID, - pluginType, - allConfigs.CandidateConfig.ConfigDigest, - allConfigs.ActiveConfig.ConfigDigest, - ) - if err != nil { - return mcms.Operation{}, fmt.Errorf("pack promotion call: %w", err) - } - - updateDonTx, err := capReg.UpdateDON( - deployment.SimTransactOpts(), - donID, - nodes.PeerIDs(), - []capabilities_registry.CapabilitiesRegistryCapabilityConfiguration{ - { - CapabilityId: internal.CCIPCapabilityID, - Config: encodedPromotionCall, - }, - }, - false, - nodes.DefaultF(), - ) - if err != nil { - return mcms.Operation{}, fmt.Errorf("error creating updateDon op for donID(%d) and plugin type (%d): %w", donID, pluginType, err) - } - return mcms.Operation{ - To: capReg.Address(), - Data: updateDonTx.Data(), - Value: big.NewInt(0), - }, nil -} - -// promoteAllCandidatesForChainOps promotes the candidate commit and exec configs to active by calling promoteCandidateAndRevokeActive on CCIPHome through the UpdateDON call on CapReg contract -func promoteAllCandidatesForChainOps( - capReg *capabilities_registry.CapabilitiesRegistry, - ccipHome *ccip_home.CCIPHome, - chainSelector uint64, - nodes deployment.Nodes, -) ([]mcms.Operation, error) { - // fetch DON ID for the chain - donID, err := internal.DonIDForChain(capReg, ccipHome, chainSelector) - if err != nil { - return nil, fmt.Errorf("fetch don id for chain: %w", err) - } - - var mcmsOps []mcms.Operation - updateCommitOp, err := promoteCandidateOp(donID, uint8(cctypes.PluginTypeCCIPCommit), capReg, ccipHome, nodes) - if err != nil { - return nil, fmt.Errorf("promote candidate op: %w", err) - } - mcmsOps = append(mcmsOps, updateCommitOp) - - updateExecOp, err := promoteCandidateOp(donID, uint8(cctypes.PluginTypeCCIPExec), capReg, ccipHome, nodes) - if err != nil { - return nil, fmt.Errorf("promote candidate op: %w", err) - } - mcmsOps = append(mcmsOps, updateExecOp) - - return mcmsOps, nil -} diff --git a/deployment/ccip/changeset/cs_active_candidate_test.go b/deployment/ccip/changeset/cs_active_candidate_test.go deleted file mode 100644 index 0fb29242794..00000000000 --- a/deployment/ccip/changeset/cs_active_candidate_test.go +++ /dev/null @@ -1,246 +0,0 @@ -package changeset - -import ( - "testing" - - "github.com/ethereum/go-ethereum/common" - "github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers" - "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/mcms" - "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/timelock" - "golang.org/x/exp/maps" - - "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/testcontext" - - "github.com/smartcontractkit/chainlink/deployment/ccip/changeset/internal" - "github.com/smartcontractkit/chainlink/deployment/environment/memory" - cctypes "github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/types" - "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/router" - - "github.com/smartcontractkit/chainlink/deployment" - - "github.com/stretchr/testify/require" - - commonchangeset "github.com/smartcontractkit/chainlink/deployment/common/changeset" - "github.com/smartcontractkit/chainlink/deployment/common/proposalutils" - - "github.com/smartcontractkit/chainlink/v2/core/logger" -) - -func TestActiveCandidate(t *testing.T) { - t.Skipf("to be enabled after latest cl-ccip is compatible") - - lggr := logger.TestLogger(t) - tenv := NewMemoryEnvironmentWithJobsAndContracts(t, lggr, memory.MemoryEnvironmentConfig{ - Chains: 3, - NumOfUsersPerChain: 1, - Nodes: 5, - Bootstraps: 1, - }, nil) - e := tenv.Env - state, err := LoadOnchainState(tenv.Env) - require.NoError(t, err) - allChains := maps.Keys(e.Chains) - - // Add all lanes - require.NoError(t, AddLanesForAll(e, state)) - // Need to keep track of the block number for each chain so that event subscription can be done from that block. - startBlocks := make(map[uint64]*uint64) - // Send a message from each chain to every other chain. - expectedSeqNum := make(map[SourceDestPair]uint64) - expectedSeqNumExec := make(map[SourceDestPair][]uint64) - for src := range e.Chains { - for dest, destChain := range e.Chains { - if src == dest { - continue - } - latesthdr, err := destChain.Client.HeaderByNumber(testcontext.Get(t), nil) - require.NoError(t, err) - block := latesthdr.Number.Uint64() - startBlocks[dest] = &block - msgSentEvent := TestSendRequest(t, e, state, src, dest, false, router.ClientEVM2AnyMessage{ - Receiver: common.LeftPadBytes(state.Chains[dest].Receiver.Address().Bytes(), 32), - Data: []byte("hello world"), - TokenAmounts: nil, - FeeToken: common.HexToAddress("0x0"), - ExtraArgs: nil, - }) - expectedSeqNum[SourceDestPair{ - SourceChainSelector: src, - DestChainSelector: dest, - }] = msgSentEvent.SequenceNumber - expectedSeqNumExec[SourceDestPair{ - SourceChainSelector: src, - DestChainSelector: dest, - }] = []uint64{msgSentEvent.SequenceNumber} - } - } - - // Wait for all commit reports to land. - ConfirmCommitForAllWithExpectedSeqNums(t, e, state, expectedSeqNum, startBlocks) - - //After commit is reported on all chains, token prices should be updated in FeeQuoter. - for dest := range e.Chains { - linkAddress := state.Chains[dest].LinkToken.Address() - feeQuoter := state.Chains[dest].FeeQuoter - timestampedPrice, err := feeQuoter.GetTokenPrice(nil, linkAddress) - require.NoError(t, err) - require.Equal(t, MockLinkPrice, timestampedPrice.Value) - } - - //Wait for all exec reports to land - ConfirmExecWithSeqNrsForAll(t, e, state, expectedSeqNumExec, startBlocks) - - // compose the transfer ownership and accept ownership changesets - timelocks := make(map[uint64]*gethwrappers.RBACTimelock) - for _, chain := range allChains { - timelocks[chain] = state.Chains[chain].Timelock - } - _, err = commonchangeset.ApplyChangesets(t, e, timelocks, []commonchangeset.ChangesetApplication{ - // note this doesn't have proposals. - { - Changeset: commonchangeset.WrapChangeSet(commonchangeset.TransferToMCMSWithTimelock), - Config: genTestTransferOwnershipConfig(tenv, allChains, state), - }, - }) - require.NoError(t, err) - // Apply the accept ownership proposal to all the chains. - - err = ConfirmRequestOnSourceAndDest(t, e, state, tenv.HomeChainSel, tenv.FeedChainSel, 2) - require.NoError(t, err) - - // [ACTIVE, CANDIDATE] setup by setting candidate through cap reg - capReg, ccipHome := state.Chains[tenv.HomeChainSel].CapabilityRegistry, state.Chains[tenv.HomeChainSel].CCIPHome - donID, err := internal.DonIDForChain(capReg, ccipHome, tenv.FeedChainSel) - require.NoError(t, err) - donInfo, err := state.Chains[tenv.HomeChainSel].CapabilityRegistry.GetDON(nil, donID) - require.NoError(t, err) - require.Equal(t, 5, len(donInfo.NodeP2PIds)) - require.Equal(t, uint32(4), donInfo.ConfigCount) - - state, err = LoadOnchainState(e) - require.NoError(t, err) - - // delete a non-bootstrap node - nodes, err := deployment.NodeInfo(e.NodeIDs, e.Offchain) - require.NoError(t, err) - var newNodeIDs []string - // make sure we delete a node that is NOT bootstrap. - // we will remove bootstrap later by calling nodes.NonBootstrap() - if nodes[0].IsBootstrap { - newNodeIDs = e.NodeIDs[:len(e.NodeIDs)-1] - } else { - newNodeIDs = e.NodeIDs[1:] - } - nodes, err = deployment.NodeInfo(newNodeIDs, e.Offchain) - require.NoError(t, err) - - // this will construct ocr3 configurations for the - // commit and exec plugin we will be using - rmnHomeAddress := state.Chains[tenv.HomeChainSel].RMNHome.Address() - tokenConfig := NewTestTokenConfig(state.Chains[tenv.FeedChainSel].USDFeeds) - ccipOCRParams := DefaultOCRParams( - tenv.FeedChainSel, - tokenConfig.GetTokenInfo(e.Logger, state.Chains[tenv.FeedChainSel].LinkToken, state.Chains[tenv.FeedChainSel].Weth9), - nil, - ) - ocr3ConfigMap, err := internal.BuildOCR3ConfigForCCIPHome( - e.OCRSecrets, - state.Chains[tenv.FeedChainSel].OffRamp, - e.Chains[tenv.FeedChainSel], - nodes.NonBootstraps(), - rmnHomeAddress, - ccipOCRParams.OCRParameters, - ccipOCRParams.CommitOffChainConfig, - ccipOCRParams.ExecuteOffChainConfig, - ) - require.NoError(t, err) - - var ( - timelocksPerChain = map[uint64]common.Address{ - tenv.HomeChainSel: state.Chains[tenv.HomeChainSel].Timelock.Address(), - } - proposerMCMSes = map[uint64]*gethwrappers.ManyChainMultiSig{ - tenv.HomeChainSel: state.Chains[tenv.HomeChainSel].ProposerMcm, - } - ) - setCommitCandidateOp, err := setCandidateOnExistingDon( - ocr3ConfigMap[cctypes.PluginTypeCCIPCommit], - state.Chains[tenv.HomeChainSel].CapabilityRegistry, - state.Chains[tenv.HomeChainSel].CCIPHome, - tenv.FeedChainSel, - nodes.NonBootstraps(), - ) - require.NoError(t, err) - setCommitCandidateProposal, err := proposalutils.BuildProposalFromBatches(timelocksPerChain, proposerMCMSes, []timelock.BatchChainOperation{{ - ChainIdentifier: mcms.ChainIdentifier(tenv.HomeChainSel), - Batch: setCommitCandidateOp, - }}, "set new candidates on commit plugin", 0) - require.NoError(t, err) - setCommitCandidateSigned := commonchangeset.SignProposal(t, e, setCommitCandidateProposal) - commonchangeset.ExecuteProposal(t, e, setCommitCandidateSigned, state.Chains[tenv.HomeChainSel].Timelock, tenv.HomeChainSel) - - // create the op for the commit plugin as well - setExecCandidateOp, err := setCandidateOnExistingDon( - ocr3ConfigMap[cctypes.PluginTypeCCIPExec], - state.Chains[tenv.HomeChainSel].CapabilityRegistry, - state.Chains[tenv.HomeChainSel].CCIPHome, - tenv.FeedChainSel, - nodes.NonBootstraps(), - ) - require.NoError(t, err) - - setExecCandidateProposal, err := proposalutils.BuildProposalFromBatches(timelocksPerChain, proposerMCMSes, []timelock.BatchChainOperation{{ - ChainIdentifier: mcms.ChainIdentifier(tenv.HomeChainSel), - Batch: setExecCandidateOp, - }}, "set new candidates on commit and exec plugins", 0) - require.NoError(t, err) - setExecCandidateSigned := commonchangeset.SignProposal(t, e, setExecCandidateProposal) - commonchangeset.ExecuteProposal(t, e, setExecCandidateSigned, state.Chains[tenv.HomeChainSel].Timelock, tenv.HomeChainSel) - - // check setup was successful by confirming number of nodes from cap reg - donInfo, err = state.Chains[tenv.HomeChainSel].CapabilityRegistry.GetDON(nil, donID) - require.NoError(t, err) - require.Equal(t, 4, len(donInfo.NodeP2PIds)) - require.Equal(t, uint32(6), donInfo.ConfigCount) - // [ACTIVE, CANDIDATE] done setup - - // [ACTIVE, CANDIDATE] make sure we can still send successful transaction without updating job specs - err = ConfirmRequestOnSourceAndDest(t, e, state, tenv.HomeChainSel, tenv.FeedChainSel, 3) - require.NoError(t, err) - // [ACTIVE, CANDIDATE] done send successful transaction on active - - // [NEW ACTIVE, NO CANDIDATE] promote to active - // confirm by getting old candidate digest and making sure new active matches - oldCandidateDigest, err := state.Chains[tenv.HomeChainSel].CCIPHome.GetCandidateDigest(nil, donID, uint8(cctypes.PluginTypeCCIPExec)) - require.NoError(t, err) - - promoteOps, err := promoteAllCandidatesForChainOps(state.Chains[tenv.HomeChainSel].CapabilityRegistry, state.Chains[tenv.HomeChainSel].CCIPHome, tenv.FeedChainSel, nodes.NonBootstraps()) - require.NoError(t, err) - promoteProposal, err := proposalutils.BuildProposalFromBatches(timelocksPerChain, proposerMCMSes, []timelock.BatchChainOperation{{ - ChainIdentifier: mcms.ChainIdentifier(tenv.HomeChainSel), - Batch: promoteOps, - }}, "promote candidates and revoke actives", 0) - require.NoError(t, err) - promoteSigned := commonchangeset.SignProposal(t, e, promoteProposal) - commonchangeset.ExecuteProposal(t, e, promoteSigned, state.Chains[tenv.HomeChainSel].Timelock, tenv.HomeChainSel) - // [NEW ACTIVE, NO CANDIDATE] done promoting - - // [NEW ACTIVE, NO CANDIDATE] check onchain state - newActiveDigest, err := state.Chains[tenv.HomeChainSel].CCIPHome.GetActiveDigest(nil, donID, uint8(cctypes.PluginTypeCCIPExec)) - require.NoError(t, err) - require.Equal(t, oldCandidateDigest, newActiveDigest) - - newCandidateDigest, err := state.Chains[tenv.HomeChainSel].CCIPHome.GetCandidateDigest(nil, donID, uint8(cctypes.PluginTypeCCIPCommit)) - require.NoError(t, err) - require.Equal(t, newCandidateDigest, [32]byte{}) - // [NEW ACTIVE, NO CANDIDATE] done checking on chain state - - // [NEW ACTIVE, NO CANDIDATE] send successful request on new active - donInfo, err = state.Chains[tenv.HomeChainSel].CapabilityRegistry.GetDON(nil, donID) - require.NoError(t, err) - require.Equal(t, uint32(8), donInfo.ConfigCount) - - err = ConfirmRequestOnSourceAndDest(t, e, state, tenv.HomeChainSel, tenv.FeedChainSel, 4) - require.NoError(t, err) - // [NEW ACTIVE, NO CANDIDATE] done sending successful request -} diff --git a/deployment/ccip/changeset/cs_add_chain.go b/deployment/ccip/changeset/cs_add_chain.go index d589d16b779..ddb6e61d5ba 100644 --- a/deployment/ccip/changeset/cs_add_chain.go +++ b/deployment/ccip/changeset/cs_add_chain.go @@ -8,18 +8,14 @@ import ( "github.com/smartcontractkit/chainlink-ccip/chainconfig" "github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3" - - "github.com/smartcontractkit/chainlink/deployment/ccip/changeset/internal" "github.com/smartcontractkit/chainlink/deployment/common/proposalutils" - "github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/types" - "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/ccip_home" - "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/capabilities_registry" "github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers" "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/mcms" "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/timelock" "github.com/smartcontractkit/chainlink/deployment" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/ccip_home" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/fee_quoter" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/onramp" ) @@ -136,142 +132,6 @@ func NewChainInboundChangeset( }, nil } -type AddDonAndSetCandidateChangesetConfig struct { - HomeChainSelector uint64 - FeedChainSelector uint64 - NewChainSelector uint64 - PluginType types.PluginType - NodeIDs []string - CCIPOCRParams CCIPOCRParams -} - -func (a AddDonAndSetCandidateChangesetConfig) Validate(e deployment.Environment, state CCIPOnChainState) (deployment.Nodes, error) { - if a.HomeChainSelector == 0 { - return nil, fmt.Errorf("HomeChainSelector must be set") - } - if a.FeedChainSelector == 0 { - return nil, fmt.Errorf("FeedChainSelector must be set") - } - if a.NewChainSelector == 0 { - return nil, fmt.Errorf("ocr config chain selector must be set") - } - if a.PluginType != types.PluginTypeCCIPCommit && - a.PluginType != types.PluginTypeCCIPExec { - return nil, fmt.Errorf("PluginType must be set to either CCIPCommit or CCIPExec") - } - // TODO: validate token config - if len(a.NodeIDs) == 0 { - return nil, fmt.Errorf("nodeIDs must be set") - } - nodes, err := deployment.NodeInfo(a.NodeIDs, e.Offchain) - if err != nil { - return nil, fmt.Errorf("get node info: %w", err) - } - - // check that chain config is set up for the new chain - // TODO: feels like we should just have a getter for a particular chain, this pagination - // logic seems a bit out of place here. - allConfigs, err := state.Chains[a.HomeChainSelector].CCIPHome.GetAllChainConfigs(nil, big.NewInt(0), big.NewInt(100)) - if err != nil { - return nil, fmt.Errorf("get all chain configs: %w", err) - } - var found bool - for _, chainConfig := range allConfigs { - if chainConfig.ChainSelector == a.NewChainSelector { - found = true - break - } - } - if !found { - return nil, fmt.Errorf("chain config not set for chain %d", a.NewChainSelector) - } - - err = a.CCIPOCRParams.Validate() - if err != nil { - return nil, fmt.Errorf("invalid ccip ocr params: %w", err) - } - - if e.OCRSecrets.IsEmpty() { - return nil, fmt.Errorf("OCR secrets must be set") - } - - return nodes, nil -} - -// AddDonAndSetCandidateChangeset adds new DON for destination to home chain -// and sets the commit plugin config as candidateConfig for the don. -func AddDonAndSetCandidateChangeset( - e deployment.Environment, - cfg AddDonAndSetCandidateChangesetConfig, -) (deployment.ChangesetOutput, error) { - state, err := LoadOnchainState(e) - if err != nil { - return deployment.ChangesetOutput{}, err - } - - nodes, err := cfg.Validate(e, state) - if err != nil { - return deployment.ChangesetOutput{}, fmt.Errorf("%w: %w", deployment.ErrInvalidConfig, err) - } - - newDONArgs, err := internal.BuildOCR3ConfigForCCIPHome( - e.OCRSecrets, - state.Chains[cfg.NewChainSelector].OffRamp, - e.Chains[cfg.NewChainSelector], - nodes.NonBootstraps(), - state.Chains[cfg.HomeChainSelector].RMNHome.Address(), - cfg.CCIPOCRParams.OCRParameters, - cfg.CCIPOCRParams.CommitOffChainConfig, - cfg.CCIPOCRParams.ExecuteOffChainConfig, - ) - if err != nil { - return deployment.ChangesetOutput{}, err - } - latestDon, err := internal.LatestCCIPDON(state.Chains[cfg.HomeChainSelector].CapabilityRegistry) - if err != nil { - return deployment.ChangesetOutput{}, err - } - commitConfig, ok := newDONArgs[cfg.PluginType] - if !ok { - return deployment.ChangesetOutput{}, fmt.Errorf("missing commit plugin in ocr3Configs") - } - donID := latestDon.Id + 1 - addDonOp, err := newDonWithCandidateOp( - donID, commitConfig, - state.Chains[cfg.HomeChainSelector].CapabilityRegistry, - nodes.NonBootstraps(), - ) - if err != nil { - return deployment.ChangesetOutput{}, err - } - - var ( - timelocksPerChain = map[uint64]common.Address{ - cfg.HomeChainSelector: state.Chains[cfg.HomeChainSelector].Timelock.Address(), - } - proposerMCMSes = map[uint64]*gethwrappers.ManyChainMultiSig{ - cfg.HomeChainSelector: state.Chains[cfg.HomeChainSelector].ProposerMcm, - } - ) - prop, err := proposalutils.BuildProposalFromBatches( - timelocksPerChain, - proposerMCMSes, - []timelock.BatchChainOperation{{ - ChainIdentifier: mcms.ChainIdentifier(cfg.HomeChainSelector), - Batch: []mcms.Operation{addDonOp}, - }}, - "setCandidate for commit and AddDon on new Chain", - 0, // minDelay - ) - if err != nil { - return deployment.ChangesetOutput{}, fmt.Errorf("failed to build proposal from batch: %w", err) - } - - return deployment.ChangesetOutput{ - Proposals: []timelock.MCMSWithTimelockProposal{*prop}, - }, nil -} - func applyChainConfigUpdatesOp( e deployment.Environment, state CCIPOnChainState, @@ -311,38 +171,3 @@ func applyChainConfigUpdatesOp( Value: big.NewInt(0), }, nil } - -// newDonWithCandidateOp sets the candidate commit config by calling setCandidate on CCIPHome contract through the AddDON call on CapReg contract -// This should be done first before calling any other UpdateDON calls -// This proposes to set up OCR3 config for the commit plugin for the DON -func newDonWithCandidateOp( - donID uint32, - pluginConfig ccip_home.CCIPHomeOCR3Config, - capReg *capabilities_registry.CapabilitiesRegistry, - nodes deployment.Nodes, -) (mcms.Operation, error) { - encodedSetCandidateCall, err := internal.CCIPHomeABI.Pack( - "setCandidate", - donID, - pluginConfig.PluginType, - pluginConfig, - [32]byte{}, - ) - if err != nil { - return mcms.Operation{}, fmt.Errorf("pack set candidate call: %w", err) - } - addDonTx, err := capReg.AddDON(deployment.SimTransactOpts(), nodes.PeerIDs(), []capabilities_registry.CapabilitiesRegistryCapabilityConfiguration{ - { - CapabilityId: internal.CCIPCapabilityID, - Config: encodedSetCandidateCall, - }, - }, false, false, nodes.DefaultF()) - if err != nil { - return mcms.Operation{}, fmt.Errorf("could not generate add don tx w/ commit config: %w", err) - } - return mcms.Operation{ - To: capReg.Address(), - Data: addDonTx.Data(), - Value: big.NewInt(0), - }, nil -} diff --git a/deployment/ccip/changeset/cs_add_chain_test.go b/deployment/ccip/changeset/cs_add_chain_test.go index 2873b3bf613..a70ea814881 100644 --- a/deployment/ccip/changeset/cs_add_chain_test.go +++ b/deployment/ccip/changeset/cs_add_chain_test.go @@ -1,16 +1,13 @@ package changeset import ( - "math/big" "testing" "time" - "github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers" - "github.com/smartcontractkit/chainlink/deployment/ccip/changeset/internal" commonchangeset "github.com/smartcontractkit/chainlink/deployment/common/changeset" + "github.com/smartcontractkit/chainlink/deployment/common/proposalutils" commontypes "github.com/smartcontractkit/chainlink/deployment/common/types" - "github.com/smartcontractkit/chainlink/deployment/environment/memory" "github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/types" "github.com/ethereum/go-ethereum/common" @@ -33,13 +30,13 @@ import ( ) func TestAddChainInbound(t *testing.T) { + t.Skipf("Skipping test as it is running into timeout issues, move the test into integration in-memory tests") + t.Parallel() // 4 chains where the 4th is added after initial deployment. - e := NewMemoryEnvironmentWithJobs(t, logger.TestLogger(t), memory.MemoryEnvironmentConfig{ - Chains: 4, - NumOfUsersPerChain: 1, - Nodes: 4, - Bootstraps: 1, - }) + e := NewMemoryEnvironment(t, + WithChains(4), + WithJobsOnly(), + ) state, err := LoadOnchainState(e.Env) require.NoError(t, err) // Take first non-home chain as the new chain. @@ -51,13 +48,7 @@ func TestAddChainInbound(t *testing.T) { require.NoError(t, err) require.NoError(t, e.Env.ExistingAddresses.Merge(newAddresses)) - cfg := commontypes.MCMSWithTimelockConfig{ - Canceller: commonchangeset.SingleGroupMCMS(t), - Bypasser: commonchangeset.SingleGroupMCMS(t), - Proposer: commonchangeset.SingleGroupMCMS(t), - TimelockExecutors: e.Env.AllDeployerKeys(), - TimelockMinDelay: big.NewInt(0), - } + cfg := proposalutils.SingleGroupTimelockConfig(t) e.Env, err = commonchangeset.ApplyChangesets(t, e.Env, nil, []commonchangeset.ChangesetApplication{ { Changeset: commonchangeset.WrapChangeSet(commonchangeset.DeployLinkToken), @@ -158,10 +149,19 @@ func TestAddChainInbound(t *testing.T) { } // transfer ownership to timelock - _, err = commonchangeset.ApplyChangesets(t, e.Env, map[uint64]*gethwrappers.RBACTimelock{ - initialDeploy[0]: state.Chains[initialDeploy[0]].Timelock, - initialDeploy[1]: state.Chains[initialDeploy[1]].Timelock, - initialDeploy[2]: state.Chains[initialDeploy[2]].Timelock, + _, err = commonchangeset.ApplyChangesets(t, e.Env, map[uint64]*proposalutils.TimelockExecutionContracts{ + initialDeploy[0]: { + Timelock: state.Chains[initialDeploy[0]].Timelock, + CallProxy: state.Chains[initialDeploy[0]].CallProxy, + }, + initialDeploy[1]: { + Timelock: state.Chains[initialDeploy[1]].Timelock, + CallProxy: state.Chains[initialDeploy[1]].CallProxy, + }, + initialDeploy[2]: { + Timelock: state.Chains[initialDeploy[2]].Timelock, + CallProxy: state.Chains[initialDeploy[2]].CallProxy, + }, }, []commonchangeset.ChangesetApplication{ { Changeset: commonchangeset.WrapChangeSet(commonchangeset.TransferToMCMSWithTimelock), @@ -180,60 +180,70 @@ func TestAddChainInbound(t *testing.T) { assertTimelockOwnership(t, e, initialDeploy, state) - nodes, err := deployment.NodeInfo(e.Env.NodeIDs, e.Env.Offchain) - require.NoError(t, err) - // TODO This currently is not working - Able to send the request here but request gets stuck in execution // Send a new message and expect that this is delivered once the chain is completely set up as inbound //TestSendRequest(t, e.Env, state, initialDeploy[0], newChain, true) - var nodeIDs []string - for _, node := range nodes { - nodeIDs = append(nodeIDs, node.NodeID) - } - _, err = commonchangeset.ApplyChangesets(t, e.Env, map[uint64]*gethwrappers.RBACTimelock{ - e.HomeChainSel: state.Chains[e.HomeChainSel].Timelock, - newChain: state.Chains[newChain].Timelock, + _, err = commonchangeset.ApplyChangesets(t, e.Env, map[uint64]*proposalutils.TimelockExecutionContracts{ + e.HomeChainSel: { + Timelock: state.Chains[e.HomeChainSel].Timelock, + CallProxy: state.Chains[e.HomeChainSel].CallProxy, + }, + newChain: { + Timelock: state.Chains[newChain].Timelock, + CallProxy: state.Chains[newChain].CallProxy, + }, }, []commonchangeset.ChangesetApplication{ { Changeset: commonchangeset.WrapChangeSet(AddDonAndSetCandidateChangeset), Config: AddDonAndSetCandidateChangesetConfig{ - HomeChainSelector: e.HomeChainSel, - FeedChainSelector: e.FeedChainSel, - NewChainSelector: newChain, - PluginType: types.PluginTypeCCIPCommit, - NodeIDs: nodeIDs, - CCIPOCRParams: DefaultOCRParams( - e.FeedChainSel, - tokenConfig.GetTokenInfo(logger.TestLogger(t), state.Chains[newChain].LinkToken, state.Chains[newChain].Weth9), - nil, - ), + SetCandidateConfigBase: SetCandidateConfigBase{ + HomeChainSelector: e.HomeChainSel, + FeedChainSelector: e.FeedChainSel, + DONChainSelector: newChain, + PluginType: types.PluginTypeCCIPCommit, + CCIPOCRParams: DefaultOCRParams( + e.FeedChainSel, + tokenConfig.GetTokenInfo(logger.TestLogger(t), state.Chains[newChain].LinkToken, state.Chains[newChain].Weth9), + nil, + ), + MCMS: &MCMSConfig{ + MinDelay: 0, + }, + }, }, }, { - Changeset: commonchangeset.WrapChangeSet(SetCandidatePluginChangeset), - Config: AddDonAndSetCandidateChangesetConfig{ - HomeChainSelector: e.HomeChainSel, - FeedChainSelector: e.FeedChainSel, - NewChainSelector: newChain, - PluginType: types.PluginTypeCCIPExec, - NodeIDs: nodeIDs, - CCIPOCRParams: DefaultOCRParams( - e.FeedChainSel, - tokenConfig.GetTokenInfo(logger.TestLogger(t), state.Chains[newChain].LinkToken, state.Chains[newChain].Weth9), - nil, - ), + Changeset: commonchangeset.WrapChangeSet(SetCandidateChangeset), + Config: SetCandidateChangesetConfig{ + SetCandidateConfigBase: SetCandidateConfigBase{ + HomeChainSelector: e.HomeChainSel, + FeedChainSelector: e.FeedChainSel, + DONChainSelector: newChain, + PluginType: types.PluginTypeCCIPExec, + CCIPOCRParams: DefaultOCRParams( + e.FeedChainSel, + tokenConfig.GetTokenInfo(logger.TestLogger(t), state.Chains[newChain].LinkToken, state.Chains[newChain].Weth9), + nil, + ), + MCMS: &MCMSConfig{ + MinDelay: 0, + }, + }, }, }, { Changeset: commonchangeset.WrapChangeSet(PromoteAllCandidatesChangeset), Config: PromoteAllCandidatesChangesetConfig{ HomeChainSelector: e.HomeChainSel, - NewChainSelector: newChain, - NodeIDs: nodeIDs, + DONChainSelector: newChain, + MCMS: &MCMSConfig{ + MinDelay: 0, + }, }, }, }) + require.NoError(t, err) // verify if the configs are updated require.NoError(t, ValidateCCIPHomeConfigSetUp( diff --git a/deployment/ccip/changeset/cs_add_lane_test.go b/deployment/ccip/changeset/cs_add_lane_test.go index fbceeaa8472..5c324c975ef 100644 --- a/deployment/ccip/changeset/cs_add_lane_test.go +++ b/deployment/ccip/changeset/cs_add_lane_test.go @@ -11,18 +11,13 @@ import ( "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/testcontext" "github.com/smartcontractkit/chainlink/deployment" - "github.com/smartcontractkit/chainlink/deployment/environment/memory" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/offramp" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/router" - "github.com/smartcontractkit/chainlink/v2/core/logger" ) func TestAddLanesWithTestRouter(t *testing.T) { - e := NewMemoryEnvironmentWithJobsAndContracts(t, logger.TestLogger(t), memory.MemoryEnvironmentConfig{ - Chains: 2, - Nodes: 4, - Bootstraps: 1, - }, nil) + t.Parallel() + e := NewMemoryEnvironment(t) // Here we have CR + nodes set up, but no CCIP contracts deployed. state, err := LoadOnchainState(e.Env) require.NoError(t, err) @@ -72,11 +67,7 @@ func TestAddLane(t *testing.T) { t.Parallel() // We add more chains to the chainlink nodes than the number of chains where CCIP is deployed. - e := NewMemoryEnvironmentWithJobsAndContracts(t, logger.TestLogger(t), memory.MemoryEnvironmentConfig{ - Chains: 2, - Nodes: 4, - Bootstraps: 1, - }, nil) + e := NewMemoryEnvironment(t) // Here we have CR + nodes set up, but no CCIP contracts deployed. state, err := LoadOnchainState(e.Env) require.NoError(t, err) diff --git a/deployment/ccip/changeset/cs_ccip_home.go b/deployment/ccip/changeset/cs_ccip_home.go new file mode 100644 index 00000000000..f1e860d9d28 --- /dev/null +++ b/deployment/ccip/changeset/cs_ccip_home.go @@ -0,0 +1,890 @@ +package changeset + +import ( + "context" + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers" + "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/mcms" + "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/timelock" + + "github.com/smartcontractkit/chainlink/deployment" + "github.com/smartcontractkit/chainlink/deployment/ccip/changeset/internal" + "github.com/smartcontractkit/chainlink/deployment/common/proposalutils" + "github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/types" + cctypes "github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/types" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/ccip_home" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/capabilities_registry" +) + +var ( + _ deployment.ChangeSet[AddDonAndSetCandidateChangesetConfig] = AddDonAndSetCandidateChangeset + _ deployment.ChangeSet[PromoteAllCandidatesChangesetConfig] = PromoteAllCandidatesChangeset + _ deployment.ChangeSet[SetCandidateChangesetConfig] = SetCandidateChangeset + _ deployment.ChangeSet[RevokeCandidateChangesetConfig] = RevokeCandidateChangeset +) + +type PromoteAllCandidatesChangesetConfig struct { + HomeChainSelector uint64 + + // DONChainSelector is the chain selector of the DON that we want to promote the candidate config of. + // Note that each (chain, ccip capability version) pair has a unique DON ID. + DONChainSelector uint64 + + // MCMS is optional MCMS configuration, if provided the changeset will generate an MCMS proposal. + // If nil, the changeset will execute the commands directly using the deployer key + // of the provided environment. + MCMS *MCMSConfig +} + +func (p PromoteAllCandidatesChangesetConfig) Validate(e deployment.Environment, state CCIPOnChainState) (donID uint32, err error) { + if err := deployment.IsValidChainSelector(p.HomeChainSelector); err != nil { + return 0, fmt.Errorf("home chain selector invalid: %w", err) + } + if err := deployment.IsValidChainSelector(p.DONChainSelector); err != nil { + return 0, fmt.Errorf("don chain selector invalid: %w", err) + } + if len(e.NodeIDs) == 0 { + return 0, fmt.Errorf("NodeIDs must be set") + } + if state.Chains[p.HomeChainSelector].CCIPHome == nil { + return 0, fmt.Errorf("CCIPHome contract does not exist") + } + if state.Chains[p.HomeChainSelector].CapabilityRegistry == nil { + return 0, fmt.Errorf("CapabilityRegistry contract does not exist") + } + if state.Chains[p.DONChainSelector].OffRamp == nil { + // should not be possible, but a defensive check. + return 0, fmt.Errorf("OffRamp contract does not exist") + } + + donID, err = internal.DonIDForChain( + state.Chains[p.HomeChainSelector].CapabilityRegistry, + state.Chains[p.HomeChainSelector].CCIPHome, + p.DONChainSelector, + ) + if err != nil { + return 0, fmt.Errorf("fetch don id for chain: %w", err) + } + if donID == 0 { + return 0, fmt.Errorf("don doesn't exist in CR for chain %d", p.DONChainSelector) + } + + // Check that candidate digest and active digest are not both zero - this is enforced onchain. + commitConfigs, err := state.Chains[p.HomeChainSelector].CCIPHome.GetAllConfigs(&bind.CallOpts{ + Context: context.Background(), + }, donID, uint8(cctypes.PluginTypeCCIPCommit)) + if err != nil { + return 0, fmt.Errorf("fetching commit configs from cciphome: %w", err) + } + + execConfigs, err := state.Chains[p.HomeChainSelector].CCIPHome.GetAllConfigs(&bind.CallOpts{ + Context: context.Background(), + }, donID, uint8(cctypes.PluginTypeCCIPExec)) + if err != nil { + return 0, fmt.Errorf("fetching exec configs from cciphome: %w", err) + } + + if commitConfigs.ActiveConfig.ConfigDigest == [32]byte{} && + commitConfigs.CandidateConfig.ConfigDigest == [32]byte{} { + return 0, fmt.Errorf("commit active and candidate config digests are both zero") + } + + if execConfigs.ActiveConfig.ConfigDigest == [32]byte{} && + execConfigs.CandidateConfig.ConfigDigest == [32]byte{} { + return 0, fmt.Errorf("exec active and candidate config digests are both zero") + } + + return donID, nil +} + +// PromoteAllCandidatesChangeset generates a proposal to call promoteCandidate on the CCIPHome through CapReg. +// Note that a DON must exist prior to being able to use this changeset effectively, +// i.e AddDonAndSetCandidateChangeset must be called first. +// This can also be used to promote a 0x0 candidate config to be the active, effectively shutting down the DON. +// At that point you can call the RemoveDON changeset to remove the DON entirely from the capability registry. +func PromoteAllCandidatesChangeset( + e deployment.Environment, + cfg PromoteAllCandidatesChangesetConfig, +) (deployment.ChangesetOutput, error) { + state, err := LoadOnchainState(e) + if err != nil { + return deployment.ChangesetOutput{}, err + } + + donID, err := cfg.Validate(e, state) + if err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("%w: %w", deployment.ErrInvalidConfig, err) + } + + nodes, err := deployment.NodeInfo(e.NodeIDs, e.Offchain) + if err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("fetch node info: %w", err) + } + + txOpts := e.Chains[cfg.HomeChainSelector].DeployerKey + if cfg.MCMS != nil { + txOpts = deployment.SimTransactOpts() + } + + homeChain := e.Chains[cfg.HomeChainSelector] + + promoteCandidateOps, err := promoteAllCandidatesForChainOps( + txOpts, + homeChain, + state.Chains[cfg.HomeChainSelector].CapabilityRegistry, + state.Chains[cfg.HomeChainSelector].CCIPHome, + nodes.NonBootstraps(), + donID, + cfg.MCMS != nil, + ) + if err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("generating promote candidate ops: %w", err) + } + + // Disabled MCMS means that we already executed the txes, so just return early w/out the proposals. + if cfg.MCMS == nil { + return deployment.ChangesetOutput{}, nil + } + + prop, err := proposalutils.BuildProposalFromBatches( + map[uint64]common.Address{ + cfg.HomeChainSelector: state.Chains[cfg.HomeChainSelector].Timelock.Address(), + }, + map[uint64]*gethwrappers.ManyChainMultiSig{ + cfg.HomeChainSelector: state.Chains[cfg.HomeChainSelector].ProposerMcm, + }, + []timelock.BatchChainOperation{{ + ChainIdentifier: mcms.ChainIdentifier(cfg.HomeChainSelector), + Batch: promoteCandidateOps, + }}, + "promoteCandidate for commit and execution", + cfg.MCMS.MinDelay, + ) + if err != nil { + return deployment.ChangesetOutput{}, err + } + return deployment.ChangesetOutput{ + Proposals: []timelock.MCMSWithTimelockProposal{ + *prop, + }, + }, nil +} + +// SetCandidateConfigBase is a common base config struct for AddDonAndSetCandidateChangesetConfig and SetCandidateChangesetConfig. +// This is extracted to deduplicate most of the validation logic. +// Remaining validation logic is done in the specific config structs that inherit from this. +type SetCandidateConfigBase struct { + HomeChainSelector uint64 + FeedChainSelector uint64 + + // DONChainSelector is the chain selector of the chain where the DON will be added. + DONChainSelector uint64 + + PluginType types.PluginType + // Note that the PluginType field is used to determine which field in CCIPOCRParams is used. + CCIPOCRParams CCIPOCRParams + + // MCMS is optional MCMS configuration, if provided the changeset will generate an MCMS proposal. + // If nil, the changeset will execute the commands directly using the deployer key + // of the provided environment. + MCMS *MCMSConfig +} + +func (s SetCandidateConfigBase) Validate(e deployment.Environment, state CCIPOnChainState) error { + if err := deployment.IsValidChainSelector(s.HomeChainSelector); err != nil { + return fmt.Errorf("home chain selector invalid: %w", err) + } + if err := deployment.IsValidChainSelector(s.FeedChainSelector); err != nil { + return fmt.Errorf("feed chain selector invalid: %w", err) + } + if err := deployment.IsValidChainSelector(s.DONChainSelector); err != nil { + return fmt.Errorf("don chain selector invalid: %w", err) + } + if len(e.NodeIDs) == 0 { + return fmt.Errorf("nodeIDs must be set") + } + if state.Chains[s.HomeChainSelector].CCIPHome == nil { + return fmt.Errorf("CCIPHome contract does not exist") + } + if state.Chains[s.HomeChainSelector].CapabilityRegistry == nil { + return fmt.Errorf("CapabilityRegistry contract does not exist") + } + if state.Chains[s.DONChainSelector].OffRamp == nil { + // should not be possible, but a defensive check. + return fmt.Errorf("OffRamp contract does not exist on don chain selector %d", s.DONChainSelector) + } + if s.PluginType != types.PluginTypeCCIPCommit && + s.PluginType != types.PluginTypeCCIPExec { + return fmt.Errorf("PluginType must be set to either CCIPCommit or CCIPExec") + } + + // no donID check since this config is used for both adding a new DON and updating an existing one. + // see AddDonAndSetCandidateChangesetConfig.Validate and SetCandidateChangesetConfig.Validate + // for these checks. + + // check that chain config is set up for the new chain + chainConfig, err := state.Chains[s.HomeChainSelector].CCIPHome.GetChainConfig(nil, s.DONChainSelector) + if err != nil { + return fmt.Errorf("get all chain configs: %w", err) + } + + // FChain should never be zero if a chain config is set in CCIPHome + if chainConfig.FChain == 0 { + return fmt.Errorf("chain config not set up for new chain %d", s.DONChainSelector) + } + + err = s.CCIPOCRParams.Validate() + if err != nil { + return fmt.Errorf("invalid ccip ocr params: %w", err) + } + + // TODO: validate token config in the commit config, if commit is the plugin. + // TODO: validate gas config in the chain config in cciphome for this DONChainSelector. + + if e.OCRSecrets.IsEmpty() { + return fmt.Errorf("OCR secrets must be set") + } + + return nil +} + +// AddDonAndSetCandidateChangesetConfig is a separate config struct +// because the validation is slightly different from SetCandidateChangesetConfig. +// In particular, we check to make sure we don't already have a DON for the chain. +type AddDonAndSetCandidateChangesetConfig struct { + SetCandidateConfigBase +} + +func (a AddDonAndSetCandidateChangesetConfig) Validate(e deployment.Environment, state CCIPOnChainState) error { + err := a.SetCandidateConfigBase.Validate(e, state) + if err != nil { + return err + } + + // check if a DON already exists for this chain + donID, err := internal.DonIDForChain( + state.Chains[a.HomeChainSelector].CapabilityRegistry, + state.Chains[a.HomeChainSelector].CCIPHome, + a.DONChainSelector, + ) + if err != nil { + return fmt.Errorf("fetch don id for chain: %w", err) + } + if donID != 0 { + return fmt.Errorf("don already exists in CR for chain %d, it has id %d", a.DONChainSelector, donID) + } + + return nil +} + +// AddDonAndSetCandidateChangeset adds new DON for destination to home chain +// and sets the plugin config as candidateConfig for the don. +// +// This is the first step to creating a CCIP DON and must be executed before any +// other changesets (SetCandidateChangeset, PromoteAllCandidatesChangeset) +// can be executed. +// +// Note that these operations must be done together because the createDON call +// in the capability registry calls the capability config contract, so we must +// provide suitable calldata for CCIPHome. +func AddDonAndSetCandidateChangeset( + e deployment.Environment, + cfg AddDonAndSetCandidateChangesetConfig, +) (deployment.ChangesetOutput, error) { + state, err := LoadOnchainState(e) + if err != nil { + return deployment.ChangesetOutput{}, err + } + + err = cfg.Validate(e, state) + if err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("%w: %w", deployment.ErrInvalidConfig, err) + } + + nodes, err := deployment.NodeInfo(e.NodeIDs, e.Offchain) + if err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("get node info: %w", err) + } + + txOpts := e.Chains[cfg.HomeChainSelector].DeployerKey + if cfg.MCMS != nil { + txOpts = deployment.SimTransactOpts() + } + + newDONArgs, err := internal.BuildOCR3ConfigForCCIPHome( + e.OCRSecrets, + state.Chains[cfg.DONChainSelector].OffRamp, + e.Chains[cfg.DONChainSelector], + nodes.NonBootstraps(), + state.Chains[cfg.HomeChainSelector].RMNHome.Address(), + cfg.CCIPOCRParams.OCRParameters, + cfg.CCIPOCRParams.CommitOffChainConfig, + cfg.CCIPOCRParams.ExecuteOffChainConfig, + ) + if err != nil { + return deployment.ChangesetOutput{}, err + } + + latestDon, err := internal.LatestCCIPDON(state.Chains[cfg.HomeChainSelector].CapabilityRegistry) + if err != nil { + return deployment.ChangesetOutput{}, err + } + + pluginOCR3Config, ok := newDONArgs[cfg.PluginType] + if !ok { + return deployment.ChangesetOutput{}, fmt.Errorf("missing commit plugin in ocr3Configs") + } + + expectedDonID := latestDon.Id + 1 + addDonOp, err := newDonWithCandidateOp( + txOpts, + e.Chains[cfg.HomeChainSelector], + expectedDonID, + pluginOCR3Config, + state.Chains[cfg.HomeChainSelector].CapabilityRegistry, + nodes.NonBootstraps(), + cfg.MCMS != nil, + ) + if err != nil { + return deployment.ChangesetOutput{}, err + } + if cfg.MCMS == nil { + return deployment.ChangesetOutput{}, nil + } + + prop, err := proposalutils.BuildProposalFromBatches( + map[uint64]common.Address{ + cfg.HomeChainSelector: state.Chains[cfg.HomeChainSelector].Timelock.Address(), + }, + map[uint64]*gethwrappers.ManyChainMultiSig{ + cfg.HomeChainSelector: state.Chains[cfg.HomeChainSelector].ProposerMcm, + }, + []timelock.BatchChainOperation{{ + ChainIdentifier: mcms.ChainIdentifier(cfg.HomeChainSelector), + Batch: []mcms.Operation{addDonOp}, + }}, + fmt.Sprintf("addDON on new Chain && setCandidate for plugin %s", cfg.PluginType.String()), + cfg.MCMS.MinDelay, + ) + if err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("failed to build proposal from batch: %w", err) + } + + return deployment.ChangesetOutput{ + Proposals: []timelock.MCMSWithTimelockProposal{*prop}, + }, nil +} + +// newDonWithCandidateOp sets the candidate commit config by calling setCandidate on CCIPHome contract through the AddDON call on CapReg contract +// This should be done first before calling any other UpdateDON calls +// This proposes to set up OCR3 config for the commit plugin for the DON +func newDonWithCandidateOp( + txOpts *bind.TransactOpts, + homeChain deployment.Chain, + donID uint32, + pluginConfig ccip_home.CCIPHomeOCR3Config, + capReg *capabilities_registry.CapabilitiesRegistry, + nodes deployment.Nodes, + mcmsEnabled bool, +) (mcms.Operation, error) { + encodedSetCandidateCall, err := internal.CCIPHomeABI.Pack( + "setCandidate", + donID, + pluginConfig.PluginType, + pluginConfig, + [32]byte{}, + ) + if err != nil { + return mcms.Operation{}, fmt.Errorf("pack set candidate call: %w", err) + } + + addDonTx, err := capReg.AddDON( + txOpts, + nodes.PeerIDs(), + []capabilities_registry.CapabilitiesRegistryCapabilityConfiguration{ + { + CapabilityId: internal.CCIPCapabilityID, + Config: encodedSetCandidateCall, + }, + }, + false, // isPublic + false, // acceptsWorkflows + nodes.DefaultF(), + ) + if err != nil { + return mcms.Operation{}, fmt.Errorf("could not generate add don tx w/ commit config: %w", err) + } + if !mcmsEnabled { + _, err = deployment.ConfirmIfNoError(homeChain, addDonTx, err) + if err != nil { + return mcms.Operation{}, fmt.Errorf("error confirming addDon call: %w", err) + } + } + + return mcms.Operation{ + To: capReg.Address(), + Data: addDonTx.Data(), + Value: big.NewInt(0), + }, nil +} + +type SetCandidateChangesetConfig struct { + SetCandidateConfigBase +} + +func (s SetCandidateChangesetConfig) Validate(e deployment.Environment, state CCIPOnChainState) (donID uint32, err error) { + err = s.SetCandidateConfigBase.Validate(e, state) + if err != nil { + return 0, err + } + + donID, err = internal.DonIDForChain( + state.Chains[s.HomeChainSelector].CapabilityRegistry, + state.Chains[s.HomeChainSelector].CCIPHome, + s.DONChainSelector, + ) + if err != nil { + return 0, fmt.Errorf("fetch don id for chain: %w", err) + } + if donID == 0 { + return 0, fmt.Errorf("don doesn't exist in CR for chain %d", s.DONChainSelector) + } + + return donID, nil +} + +// SetCandidateChangeset generates a proposal to call setCandidate on the CCIPHome through the capability registry. +// A DON must exist in order to use this changeset effectively, i.e AddDonAndSetCandidateChangeset must be called first. +func SetCandidateChangeset( + e deployment.Environment, + cfg SetCandidateChangesetConfig, +) (deployment.ChangesetOutput, error) { + state, err := LoadOnchainState(e) + if err != nil { + return deployment.ChangesetOutput{}, err + } + + donID, err := cfg.Validate(e, state) + if err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("%w: %w", deployment.ErrInvalidConfig, err) + } + + nodes, err := deployment.NodeInfo(e.NodeIDs, e.Offchain) + if err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("get node info: %w", err) + } + + txOpts := e.Chains[cfg.HomeChainSelector].DeployerKey + if cfg.MCMS != nil { + txOpts = deployment.SimTransactOpts() + } + + newDONArgs, err := internal.BuildOCR3ConfigForCCIPHome( + e.OCRSecrets, + state.Chains[cfg.DONChainSelector].OffRamp, + e.Chains[cfg.DONChainSelector], + nodes.NonBootstraps(), + state.Chains[cfg.HomeChainSelector].RMNHome.Address(), + cfg.CCIPOCRParams.OCRParameters, + cfg.CCIPOCRParams.CommitOffChainConfig, + cfg.CCIPOCRParams.ExecuteOffChainConfig, + ) + if err != nil { + return deployment.ChangesetOutput{}, err + } + + config, ok := newDONArgs[cfg.PluginType] + if !ok { + return deployment.ChangesetOutput{}, fmt.Errorf("missing %s plugin in ocr3Configs", cfg.PluginType.String()) + } + + setCandidateMCMSOps, err := setCandidateOnExistingDon( + txOpts, + e.Chains[cfg.HomeChainSelector], + state.Chains[cfg.HomeChainSelector].CapabilityRegistry, + nodes.NonBootstraps(), + donID, + config, + cfg.MCMS != nil, + ) + if err != nil { + return deployment.ChangesetOutput{}, err + } + + if cfg.MCMS == nil { + return deployment.ChangesetOutput{}, nil + } + + prop, err := proposalutils.BuildProposalFromBatches( + map[uint64]common.Address{ + cfg.HomeChainSelector: state.Chains[cfg.HomeChainSelector].Timelock.Address(), + }, + map[uint64]*gethwrappers.ManyChainMultiSig{ + cfg.HomeChainSelector: state.Chains[cfg.HomeChainSelector].ProposerMcm, + }, + []timelock.BatchChainOperation{{ + ChainIdentifier: mcms.ChainIdentifier(cfg.HomeChainSelector), + Batch: setCandidateMCMSOps, + }}, + fmt.Sprintf("SetCandidate for %s plugin", cfg.PluginType.String()), + cfg.MCMS.MinDelay, + ) + if err != nil { + return deployment.ChangesetOutput{}, err + } + return deployment.ChangesetOutput{ + Proposals: []timelock.MCMSWithTimelockProposal{ + *prop, + }, + }, nil +} + +// setCandidateOnExistingDon calls setCandidate on CCIPHome contract through the UpdateDON call on CapReg contract +// This proposes to set up OCR3 config for the provided plugin for the DON +func setCandidateOnExistingDon( + txOpts *bind.TransactOpts, + homeChain deployment.Chain, + capReg *capabilities_registry.CapabilitiesRegistry, + nodes deployment.Nodes, + donID uint32, + pluginConfig ccip_home.CCIPHomeOCR3Config, + mcmsEnabled bool, +) ([]mcms.Operation, error) { + if donID == 0 { + return nil, fmt.Errorf("donID is zero") + } + + encodedSetCandidateCall, err := internal.CCIPHomeABI.Pack( + "setCandidate", + donID, + pluginConfig.PluginType, + pluginConfig, + [32]byte{}, + ) + if err != nil { + return nil, fmt.Errorf("pack set candidate call: %w", err) + } + + // set candidate call + updateDonTx, err := capReg.UpdateDON( + txOpts, + donID, + nodes.PeerIDs(), + []capabilities_registry.CapabilitiesRegistryCapabilityConfiguration{ + { + CapabilityId: internal.CCIPCapabilityID, + Config: encodedSetCandidateCall, + }, + }, + false, + nodes.DefaultF(), + ) + if err != nil { + return nil, fmt.Errorf("update don w/ setCandidate call: %w", err) + } + if !mcmsEnabled { + _, err = deployment.ConfirmIfNoError(homeChain, updateDonTx, err) + if err != nil { + return nil, fmt.Errorf("error confirming updateDon call: %w", err) + } + } + if !mcmsEnabled { + _, err = deployment.ConfirmIfNoError(homeChain, updateDonTx, err) + if err != nil { + return nil, fmt.Errorf("error confirming updateDon call: %w", err) + } + } + + return []mcms.Operation{{ + To: capReg.Address(), + Data: updateDonTx.Data(), + Value: big.NewInt(0), + }}, nil +} + +// promoteCandidateOp will create the MCMS Operation for `promoteCandidateAndRevokeActive` directed towards the capabilityRegistry +func promoteCandidateOp( + txOpts *bind.TransactOpts, + homeChain deployment.Chain, + capReg *capabilities_registry.CapabilitiesRegistry, + ccipHome *ccip_home.CCIPHome, + nodes deployment.Nodes, + donID uint32, + pluginType uint8, + mcmsEnabled bool, +) (mcms.Operation, error) { + allConfigs, err := ccipHome.GetAllConfigs(nil, donID, pluginType) + if err != nil { + return mcms.Operation{}, err + } + + encodedPromotionCall, err := internal.CCIPHomeABI.Pack( + "promoteCandidateAndRevokeActive", + donID, + pluginType, + allConfigs.CandidateConfig.ConfigDigest, + allConfigs.ActiveConfig.ConfigDigest, + ) + if err != nil { + return mcms.Operation{}, fmt.Errorf("pack promotion call: %w", err) + } + + updateDonTx, err := capReg.UpdateDON( + txOpts, + donID, + nodes.PeerIDs(), + []capabilities_registry.CapabilitiesRegistryCapabilityConfiguration{ + { + CapabilityId: internal.CCIPCapabilityID, + Config: encodedPromotionCall, + }, + }, + false, + nodes.DefaultF(), + ) + if err != nil { + return mcms.Operation{}, fmt.Errorf("error creating updateDon op for donID(%d) and plugin type (%d): %w", donID, pluginType, err) + } + if !mcmsEnabled { + _, err = deployment.ConfirmIfNoError(homeChain, updateDonTx, err) + if err != nil { + return mcms.Operation{}, fmt.Errorf("error confirming updateDon call for donID(%d) and plugin type (%d): %w", donID, pluginType, err) + } + } + + return mcms.Operation{ + To: capReg.Address(), + Data: updateDonTx.Data(), + Value: big.NewInt(0), + }, nil +} + +// promoteAllCandidatesForChainOps promotes the candidate commit and exec configs to active by calling promoteCandidateAndRevokeActive on CCIPHome through the UpdateDON call on CapReg contract +func promoteAllCandidatesForChainOps( + txOpts *bind.TransactOpts, + homeChain deployment.Chain, + capReg *capabilities_registry.CapabilitiesRegistry, + ccipHome *ccip_home.CCIPHome, + nodes deployment.Nodes, + donID uint32, + mcmsEnabled bool, +) ([]mcms.Operation, error) { + if donID == 0 { + return nil, fmt.Errorf("donID is zero") + } + + var mcmsOps []mcms.Operation + updateCommitOp, err := promoteCandidateOp( + txOpts, + homeChain, + capReg, + ccipHome, + nodes, + donID, + uint8(cctypes.PluginTypeCCIPCommit), + mcmsEnabled, + ) + if err != nil { + return nil, fmt.Errorf("promote candidate op: %w", err) + } + mcmsOps = append(mcmsOps, updateCommitOp) + + updateExecOp, err := promoteCandidateOp( + txOpts, + homeChain, + capReg, + ccipHome, + nodes, + donID, + uint8(cctypes.PluginTypeCCIPExec), + mcmsEnabled, + ) + if err != nil { + return nil, fmt.Errorf("promote candidate op: %w", err) + } + mcmsOps = append(mcmsOps, updateExecOp) + + return mcmsOps, nil +} + +type RevokeCandidateChangesetConfig struct { + HomeChainSelector uint64 + + // DONChainSelector is the chain selector whose candidate config we want to revoke. + DONChainSelector uint64 + PluginType types.PluginType + + // MCMS is optional MCMS configuration, if provided the changeset will generate an MCMS proposal. + // If nil, the changeset will execute the commands directly using the deployer key + // of the provided environment. + MCMS *MCMSConfig +} + +func (r RevokeCandidateChangesetConfig) Validate(e deployment.Environment, state CCIPOnChainState) (donID uint32, err error) { + if err := deployment.IsValidChainSelector(r.HomeChainSelector); err != nil { + return 0, fmt.Errorf("home chain selector invalid: %w", err) + } + if err := deployment.IsValidChainSelector(r.DONChainSelector); err != nil { + return 0, fmt.Errorf("don chain selector invalid: %w", err) + } + if len(e.NodeIDs) == 0 { + return 0, fmt.Errorf("NodeIDs must be set") + } + if state.Chains[r.HomeChainSelector].CCIPHome == nil { + return 0, fmt.Errorf("CCIPHome contract does not exist") + } + if state.Chains[r.HomeChainSelector].CapabilityRegistry == nil { + return 0, fmt.Errorf("CapabilityRegistry contract does not exist") + } + + // check that the don exists for this chain + donID, err = internal.DonIDForChain( + state.Chains[r.HomeChainSelector].CapabilityRegistry, + state.Chains[r.HomeChainSelector].CCIPHome, + r.DONChainSelector, + ) + if err != nil { + return 0, fmt.Errorf("fetch don id for chain: %w", err) + } + if donID == 0 { + return 0, fmt.Errorf("don doesn't exist in CR for chain %d", r.DONChainSelector) + } + + // check that candidate digest is not zero - this is enforced onchain. + candidateDigest, err := state.Chains[r.HomeChainSelector].CCIPHome.GetCandidateDigest(nil, donID, uint8(r.PluginType)) + if err != nil { + return 0, fmt.Errorf("fetching candidate digest from cciphome: %w", err) + } + if candidateDigest == [32]byte{} { + return 0, fmt.Errorf("candidate config digest is zero, can't revoke it") + } + + return donID, nil +} + +func RevokeCandidateChangeset(e deployment.Environment, cfg RevokeCandidateChangesetConfig) (deployment.ChangesetOutput, error) { + state, err := LoadOnchainState(e) + if err != nil { + return deployment.ChangesetOutput{}, err + } + + donID, err := cfg.Validate(e, state) + if err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("%w: %w", deployment.ErrInvalidConfig, err) + } + + nodes, err := deployment.NodeInfo(e.NodeIDs, e.Offchain) + if err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("fetch nodes info: %w", err) + } + + txOpts := e.Chains[cfg.HomeChainSelector].DeployerKey + if cfg.MCMS != nil { + txOpts = deployment.SimTransactOpts() + } + + homeChain := e.Chains[cfg.HomeChainSelector] + ops, err := revokeCandidateOps( + txOpts, + homeChain, + state.Chains[cfg.HomeChainSelector].CapabilityRegistry, + state.Chains[cfg.HomeChainSelector].CCIPHome, + nodes.NonBootstraps(), + donID, + uint8(cfg.PluginType), + cfg.MCMS != nil, + ) + if err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("revoke candidate ops: %w", err) + } + if cfg.MCMS == nil { + return deployment.ChangesetOutput{}, nil + } + + prop, err := proposalutils.BuildProposalFromBatches( + map[uint64]common.Address{ + cfg.HomeChainSelector: state.Chains[cfg.HomeChainSelector].Timelock.Address(), + }, + map[uint64]*gethwrappers.ManyChainMultiSig{ + cfg.HomeChainSelector: state.Chains[cfg.HomeChainSelector].ProposerMcm, + }, + []timelock.BatchChainOperation{{ + ChainIdentifier: mcms.ChainIdentifier(cfg.HomeChainSelector), + Batch: ops, + }}, + fmt.Sprintf("revokeCandidate for don %d", cfg.DONChainSelector), + cfg.MCMS.MinDelay, + ) + if err != nil { + return deployment.ChangesetOutput{}, err + } + + return deployment.ChangesetOutput{ + Proposals: []timelock.MCMSWithTimelockProposal{ + *prop, + }, + }, nil +} + +func revokeCandidateOps( + txOpts *bind.TransactOpts, + homeChain deployment.Chain, + capReg *capabilities_registry.CapabilitiesRegistry, + ccipHome *ccip_home.CCIPHome, + nodes deployment.Nodes, + donID uint32, + pluginType uint8, + mcmsEnabled bool, +) ([]mcms.Operation, error) { + if donID == 0 { + return nil, fmt.Errorf("donID is zero") + } + + candidateDigest, err := ccipHome.GetCandidateDigest(nil, donID, pluginType) + if err != nil { + return nil, fmt.Errorf("fetching candidate digest from cciphome: %w", err) + } + + encodedRevokeCandidateCall, err := internal.CCIPHomeABI.Pack( + "revokeCandidate", + donID, + pluginType, + candidateDigest, + ) + if err != nil { + return nil, fmt.Errorf("pack set candidate call: %w", err) + } + + updateDonTx, err := capReg.UpdateDON( + txOpts, + donID, + nodes.PeerIDs(), + []capabilities_registry.CapabilitiesRegistryCapabilityConfiguration{ + { + CapabilityId: internal.CCIPCapabilityID, + Config: encodedRevokeCandidateCall, + }, + }, + false, // isPublic + nodes.DefaultF(), + ) + if err != nil { + return nil, fmt.Errorf("update don w/ revokeCandidate call: %w", deployment.MaybeDataErr(err)) + } + if !mcmsEnabled { + _, err = deployment.ConfirmIfNoError(homeChain, updateDonTx, err) + if err != nil { + return nil, fmt.Errorf("error confirming updateDon call: %w", err) + } + } + + return []mcms.Operation{{ + To: capReg.Address(), + Data: updateDonTx.Data(), + Value: big.NewInt(0), + }}, nil +} diff --git a/deployment/ccip/changeset/cs_ccip_home_test.go b/deployment/ccip/changeset/cs_ccip_home_test.go new file mode 100644 index 00000000000..b728e7b0c1d --- /dev/null +++ b/deployment/ccip/changeset/cs_ccip_home_test.go @@ -0,0 +1,417 @@ +package changeset + +import ( + "testing" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "golang.org/x/exp/maps" + + "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/testcontext" + + "github.com/smartcontractkit/chainlink/deployment/ccip/changeset/internal" + "github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/types" + "github.com/smartcontractkit/chainlink/v2/core/logger" + + "github.com/stretchr/testify/require" + + commonchangeset "github.com/smartcontractkit/chainlink/deployment/common/changeset" + "github.com/smartcontractkit/chainlink/deployment/common/proposalutils" +) + +func Test_PromoteCandidate(t *testing.T) { + for _, tc := range []struct { + name string + mcmsEnabled bool + }{ + { + name: "MCMS enabled", + mcmsEnabled: true, + }, + { + name: "MCMS disabled", + mcmsEnabled: false, + }, + } { + t.Run(tc.name, func(t *testing.T) { + ctx := testcontext.Get(t) + tenv := NewMemoryEnvironment(t, + WithChains(2), + WithNodes(4)) + state, err := LoadOnchainState(tenv.Env) + require.NoError(t, err) + + // Deploy to all chains. + allChains := maps.Keys(tenv.Env.Chains) + source := allChains[0] + dest := allChains[1] + + if tc.mcmsEnabled { + // Transfer ownership to timelock so that we can promote the zero digest later down the line. + transferToTimelock(t, tenv, state, source, dest) + } + + var ( + capReg = state.Chains[tenv.HomeChainSel].CapabilityRegistry + ccipHome = state.Chains[tenv.HomeChainSel].CCIPHome + ) + donID, err := internal.DonIDForChain(capReg, ccipHome, dest) + require.NoError(t, err) + require.NotEqual(t, uint32(0), donID) + candidateDigestCommitBefore, err := ccipHome.GetCandidateDigest(&bind.CallOpts{ + Context: ctx, + }, donID, uint8(types.PluginTypeCCIPCommit)) + require.NoError(t, err) + require.Equal(t, [32]byte{}, candidateDigestCommitBefore) + candidateDigestExecBefore, err := ccipHome.GetCandidateDigest(&bind.CallOpts{ + Context: ctx, + }, donID, uint8(types.PluginTypeCCIPExec)) + require.NoError(t, err) + require.Equal(t, [32]byte{}, candidateDigestExecBefore) + + var mcmsConfig *MCMSConfig + if tc.mcmsEnabled { + mcmsConfig = &MCMSConfig{ + MinDelay: 0, + } + } + _, err = commonchangeset.ApplyChangesets(t, tenv.Env, map[uint64]*proposalutils.TimelockExecutionContracts{ + tenv.HomeChainSel: { + Timelock: state.Chains[tenv.HomeChainSel].Timelock, + CallProxy: state.Chains[tenv.HomeChainSel].CallProxy, + }, + }, []commonchangeset.ChangesetApplication{ + { + Changeset: commonchangeset.WrapChangeSet(PromoteAllCandidatesChangeset), + Config: PromoteAllCandidatesChangesetConfig{ + HomeChainSelector: tenv.HomeChainSel, + DONChainSelector: dest, + MCMS: mcmsConfig, + }, + }, + }) + require.NoError(t, err) + + // after promoting the zero digest, active digest should also be zero + activeDigestCommit, err := ccipHome.GetActiveDigest(&bind.CallOpts{ + Context: ctx, + }, donID, uint8(types.PluginTypeCCIPCommit)) + require.NoError(t, err) + require.Equal(t, [32]byte{}, activeDigestCommit) + + activeDigestExec, err := ccipHome.GetActiveDigest(&bind.CallOpts{ + Context: ctx, + }, donID, uint8(types.PluginTypeCCIPExec)) + require.NoError(t, err) + require.Equal(t, [32]byte{}, activeDigestExec) + }) + } +} + +func Test_SetCandidate(t *testing.T) { + for _, tc := range []struct { + name string + mcmsEnabled bool + }{ + { + name: "MCMS enabled", + mcmsEnabled: true, + }, + { + name: "MCMS disabled", + mcmsEnabled: false, + }, + } { + t.Run(tc.name, func(t *testing.T) { + ctx := testcontext.Get(t) + tenv := NewMemoryEnvironment(t, + WithChains(2), + WithNodes(4)) + state, err := LoadOnchainState(tenv.Env) + require.NoError(t, err) + + // Deploy to all chains. + allChains := maps.Keys(tenv.Env.Chains) + source := allChains[0] + dest := allChains[1] + + if tc.mcmsEnabled { + // Transfer ownership to timelock so that we can promote the zero digest later down the line. + transferToTimelock(t, tenv, state, source, dest) + } + + var ( + capReg = state.Chains[tenv.HomeChainSel].CapabilityRegistry + ccipHome = state.Chains[tenv.HomeChainSel].CCIPHome + ) + donID, err := internal.DonIDForChain(capReg, ccipHome, dest) + require.NoError(t, err) + require.NotEqual(t, uint32(0), donID) + candidateDigestCommitBefore, err := ccipHome.GetCandidateDigest(&bind.CallOpts{ + Context: ctx, + }, donID, uint8(types.PluginTypeCCIPCommit)) + require.NoError(t, err) + require.Equal(t, [32]byte{}, candidateDigestCommitBefore) + candidateDigestExecBefore, err := ccipHome.GetCandidateDigest(&bind.CallOpts{ + Context: ctx, + }, donID, uint8(types.PluginTypeCCIPExec)) + require.NoError(t, err) + require.Equal(t, [32]byte{}, candidateDigestExecBefore) + + var mcmsConfig *MCMSConfig + if tc.mcmsEnabled { + mcmsConfig = &MCMSConfig{ + MinDelay: 0, + } + } + tokenConfig := NewTestTokenConfig(state.Chains[tenv.FeedChainSel].USDFeeds) + _, err = commonchangeset.ApplyChangesets(t, tenv.Env, map[uint64]*proposalutils.TimelockExecutionContracts{ + tenv.HomeChainSel: { + Timelock: state.Chains[tenv.HomeChainSel].Timelock, + CallProxy: state.Chains[tenv.HomeChainSel].CallProxy, + }, + }, []commonchangeset.ChangesetApplication{ + { + Changeset: commonchangeset.WrapChangeSet(SetCandidateChangeset), + Config: SetCandidateChangesetConfig{ + SetCandidateConfigBase: SetCandidateConfigBase{ + HomeChainSelector: tenv.HomeChainSel, + FeedChainSelector: tenv.FeedChainSel, + DONChainSelector: dest, + PluginType: types.PluginTypeCCIPCommit, + CCIPOCRParams: DefaultOCRParams( + tenv.FeedChainSel, + tokenConfig.GetTokenInfo(logger.TestLogger(t), state.Chains[dest].LinkToken, state.Chains[dest].Weth9), + nil, + ), + MCMS: mcmsConfig, + }, + }, + }, + { + Changeset: commonchangeset.WrapChangeSet(SetCandidateChangeset), + Config: SetCandidateChangesetConfig{ + SetCandidateConfigBase: SetCandidateConfigBase{ + HomeChainSelector: tenv.HomeChainSel, + FeedChainSelector: tenv.FeedChainSel, + DONChainSelector: dest, + PluginType: types.PluginTypeCCIPExec, + CCIPOCRParams: DefaultOCRParams( + tenv.FeedChainSel, + tokenConfig.GetTokenInfo(logger.TestLogger(t), state.Chains[dest].LinkToken, state.Chains[dest].Weth9), + nil, + ), + MCMS: mcmsConfig, + }, + }, + }, + }) + require.NoError(t, err) + + // after setting a new candidate on both plugins, the candidate config digest + // should be nonzero. + candidateDigestCommitAfter, err := ccipHome.GetCandidateDigest(&bind.CallOpts{ + Context: ctx, + }, donID, uint8(types.PluginTypeCCIPCommit)) + require.NoError(t, err) + require.NotEqual(t, [32]byte{}, candidateDigestCommitAfter) + require.NotEqual(t, candidateDigestCommitBefore, candidateDigestCommitAfter) + + candidateDigestExecAfter, err := ccipHome.GetCandidateDigest(&bind.CallOpts{ + Context: ctx, + }, donID, uint8(types.PluginTypeCCIPExec)) + require.NoError(t, err) + require.NotEqual(t, [32]byte{}, candidateDigestExecAfter) + require.NotEqual(t, candidateDigestExecBefore, candidateDigestExecAfter) + }) + } +} + +func Test_RevokeCandidate(t *testing.T) { + for _, tc := range []struct { + name string + mcmsEnabled bool + }{ + { + name: "MCMS enabled", + mcmsEnabled: true, + }, + { + name: "MCMS disabled", + mcmsEnabled: false, + }, + } { + t.Run(tc.name, func(t *testing.T) { + ctx := testcontext.Get(t) + tenv := NewMemoryEnvironment(t, + WithChains(2), + WithNodes(4)) + state, err := LoadOnchainState(tenv.Env) + require.NoError(t, err) + + // Deploy to all chains. + allChains := maps.Keys(tenv.Env.Chains) + source := allChains[0] + dest := allChains[1] + + if tc.mcmsEnabled { + // Transfer ownership to timelock so that we can promote the zero digest later down the line. + transferToTimelock(t, tenv, state, source, dest) + } + + var ( + capReg = state.Chains[tenv.HomeChainSel].CapabilityRegistry + ccipHome = state.Chains[tenv.HomeChainSel].CCIPHome + ) + donID, err := internal.DonIDForChain(capReg, ccipHome, dest) + require.NoError(t, err) + require.NotEqual(t, uint32(0), donID) + candidateDigestCommitBefore, err := ccipHome.GetCandidateDigest(&bind.CallOpts{ + Context: ctx, + }, donID, uint8(types.PluginTypeCCIPCommit)) + require.NoError(t, err) + require.Equal(t, [32]byte{}, candidateDigestCommitBefore) + candidateDigestExecBefore, err := ccipHome.GetCandidateDigest(&bind.CallOpts{ + Context: ctx, + }, donID, uint8(types.PluginTypeCCIPExec)) + require.NoError(t, err) + require.Equal(t, [32]byte{}, candidateDigestExecBefore) + + var mcmsConfig *MCMSConfig + if tc.mcmsEnabled { + mcmsConfig = &MCMSConfig{ + MinDelay: 0, + } + } + tokenConfig := NewTestTokenConfig(state.Chains[tenv.FeedChainSel].USDFeeds) + _, err = commonchangeset.ApplyChangesets(t, tenv.Env, map[uint64]*proposalutils.TimelockExecutionContracts{ + tenv.HomeChainSel: { + Timelock: state.Chains[tenv.HomeChainSel].Timelock, + CallProxy: state.Chains[tenv.HomeChainSel].CallProxy, + }, + }, []commonchangeset.ChangesetApplication{ + { + Changeset: commonchangeset.WrapChangeSet(SetCandidateChangeset), + Config: SetCandidateChangesetConfig{ + SetCandidateConfigBase: SetCandidateConfigBase{ + HomeChainSelector: tenv.HomeChainSel, + FeedChainSelector: tenv.FeedChainSel, + DONChainSelector: dest, + PluginType: types.PluginTypeCCIPCommit, + CCIPOCRParams: DefaultOCRParams( + tenv.FeedChainSel, + tokenConfig.GetTokenInfo(logger.TestLogger(t), state.Chains[dest].LinkToken, state.Chains[dest].Weth9), + nil, + ), + MCMS: mcmsConfig, + }, + }, + }, + { + Changeset: commonchangeset.WrapChangeSet(SetCandidateChangeset), + Config: SetCandidateChangesetConfig{ + SetCandidateConfigBase: SetCandidateConfigBase{ + HomeChainSelector: tenv.HomeChainSel, + FeedChainSelector: tenv.FeedChainSel, + DONChainSelector: dest, + PluginType: types.PluginTypeCCIPExec, + CCIPOCRParams: DefaultOCRParams( + tenv.FeedChainSel, + tokenConfig.GetTokenInfo(logger.TestLogger(t), state.Chains[dest].LinkToken, state.Chains[dest].Weth9), + nil, + ), + MCMS: mcmsConfig, + }, + }, + }, + }) + require.NoError(t, err) + + // after setting a new candidate on both plugins, the candidate config digest + // should be nonzero. + candidateDigestCommitAfter, err := ccipHome.GetCandidateDigest(&bind.CallOpts{ + Context: ctx, + }, donID, uint8(types.PluginTypeCCIPCommit)) + require.NoError(t, err) + require.NotEqual(t, [32]byte{}, candidateDigestCommitAfter) + require.NotEqual(t, candidateDigestCommitBefore, candidateDigestCommitAfter) + + candidateDigestExecAfter, err := ccipHome.GetCandidateDigest(&bind.CallOpts{ + Context: ctx, + }, donID, uint8(types.PluginTypeCCIPExec)) + require.NoError(t, err) + require.NotEqual(t, [32]byte{}, candidateDigestExecAfter) + require.NotEqual(t, candidateDigestExecBefore, candidateDigestExecAfter) + + // next we can revoke candidate - this should set the candidate digest back to zero + _, err = commonchangeset.ApplyChangesets(t, tenv.Env, map[uint64]*proposalutils.TimelockExecutionContracts{ + tenv.HomeChainSel: { + Timelock: state.Chains[tenv.HomeChainSel].Timelock, + CallProxy: state.Chains[tenv.HomeChainSel].CallProxy, + }, + }, []commonchangeset.ChangesetApplication{ + { + Changeset: commonchangeset.WrapChangeSet(RevokeCandidateChangeset), + Config: RevokeCandidateChangesetConfig{ + HomeChainSelector: tenv.HomeChainSel, + DONChainSelector: dest, + PluginType: types.PluginTypeCCIPCommit, + MCMS: mcmsConfig, + }, + }, + { + Changeset: commonchangeset.WrapChangeSet(RevokeCandidateChangeset), + Config: RevokeCandidateChangesetConfig{ + HomeChainSelector: tenv.HomeChainSel, + DONChainSelector: dest, + PluginType: types.PluginTypeCCIPExec, + MCMS: mcmsConfig, + }, + }, + }) + require.NoError(t, err) + + // after revoking the candidate, the candidate digest should be zero + candidateDigestCommitAfterRevoke, err := ccipHome.GetCandidateDigest(&bind.CallOpts{ + Context: ctx, + }, donID, uint8(types.PluginTypeCCIPCommit)) + require.NoError(t, err) + require.Equal(t, [32]byte{}, candidateDigestCommitAfterRevoke) + + candidateDigestExecAfterRevoke, err := ccipHome.GetCandidateDigest(&bind.CallOpts{ + Context: ctx, + }, donID, uint8(types.PluginTypeCCIPExec)) + require.NoError(t, err) + require.Equal(t, [32]byte{}, candidateDigestExecAfterRevoke) + }) + } +} + +func transferToTimelock( + t *testing.T, + tenv DeployedEnv, + state CCIPOnChainState, + source, + dest uint64) { + // Transfer ownership to timelock so that we can promote the zero digest later down the line. + _, err := commonchangeset.ApplyChangesets(t, tenv.Env, map[uint64]*proposalutils.TimelockExecutionContracts{ + source: { + Timelock: state.Chains[source].Timelock, + CallProxy: state.Chains[source].CallProxy, + }, + dest: { + Timelock: state.Chains[dest].Timelock, + CallProxy: state.Chains[dest].CallProxy, + }, + tenv.HomeChainSel: { + Timelock: state.Chains[tenv.HomeChainSel].Timelock, + CallProxy: state.Chains[tenv.HomeChainSel].CallProxy, + }, + }, []commonchangeset.ChangesetApplication{ + { + Changeset: commonchangeset.WrapChangeSet(commonchangeset.TransferToMCMSWithTimelock), + Config: genTestTransferOwnershipConfig(tenv, []uint64{source, dest}, state), + }, + }) + require.NoError(t, err) + assertTimelockOwnership(t, tenv, []uint64{source, dest}, state) +} diff --git a/deployment/ccip/changeset/cs_deploy_chain.go b/deployment/ccip/changeset/cs_deploy_chain.go index de6e4b5f466..065c29755b6 100644 --- a/deployment/ccip/changeset/cs_deploy_chain.go +++ b/deployment/ccip/changeset/cs_deploy_chain.go @@ -19,7 +19,6 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/offramp" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/onramp" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/rmn_home" - "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/rmn_proxy_contract" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/rmn_remote" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/router" ) @@ -31,6 +30,10 @@ var _ deployment.ChangeSet[DeployChainContractsConfig] = DeployChainContracts // DeployChainContracts is idempotent. If there is an error, it will return the successfully deployed addresses and the error so that the caller can call the // changeset again with the same input to retry the failed deployment. // Caller should update the environment's address book with the returned addresses. +// Points to note : +// In case of migrating from legacy ccip to 1.6, the previous RMN address should be set while deploying RMNRemote. +// if there is no existing RMN address found, RMNRemote will be deployed with 0x0 address for previous RMN address +// which will set RMN to 0x0 address immutably in RMNRemote. func DeployChainContracts(env deployment.Environment, c DeployChainContractsConfig) (deployment.ChangesetOutput, error) { if err := c.Validate(); err != nil { return deployment.ChangesetOutput{}, fmt.Errorf("invalid DeployChainContractsConfig: %w", err) @@ -173,6 +176,10 @@ func deployChainContracts( if chainState.Router == nil { return fmt.Errorf("router not found for chain %s, deploy the prerequisites first", chain.String()) } + rmnProxyContract := chainState.RMNProxy + if chainState.RMNProxy == nil { + return fmt.Errorf("rmn proxy not found for chain %s, deploy the prerequisites first", chain.String()) + } if chainState.Receiver == nil { _, err := deployment.DeployContract(e.Logger, chain, ab, func(chain deployment.Chain) deployment.ContractDeploy[*maybe_revert_message_receiver.MaybeRevertMessageReceiver] { @@ -192,6 +199,14 @@ func deployChainContracts( } else { e.Logger.Infow("receiver already deployed", "addr", chainState.Receiver.Address, "chain", chain.String()) } + var rmnLegacyAddr common.Address + if chainState.MockRMN != nil { + rmnLegacyAddr = chainState.MockRMN.Address() + } + // TODO add legacy RMN here when 1.5 contracts are available + if rmnLegacyAddr == (common.Address{}) { + e.Logger.Warnf("No legacy RMN contract found for chain %s, will not setRMN in RMNRemote", chain.String()) + } rmnRemoteContract := chainState.RMNRemote if chainState.RMNRemote == nil { // TODO: Correctly configure RMN remote. @@ -201,8 +216,7 @@ func deployChainContracts( chain.DeployerKey, chain.Client, chain.Selector, - // Indicates no legacy RMN contract - common.HexToAddress("0x0"), + rmnLegacyAddr, ) return deployment.ContractDeploy[*rmn_remote.RMNRemote]{ rmnRemoteAddr, rmnRemote, tx, deployment.NewTypeAndVersion(RMNRemote, deployment.Version1_6_0_dev), err2, @@ -216,6 +230,7 @@ func deployChainContracts( } else { e.Logger.Infow("rmn remote already deployed", "chain", chain.String(), "addr", chainState.RMNRemote.Address) } + activeDigest, err := rmnHome.GetActiveDigest(&bind.CallOpts{}) if err != nil { e.Logger.Errorw("Failed to get active digest", "chain", chain.String(), "err", err) @@ -234,31 +249,6 @@ func deployChainContracts( e.Logger.Errorw("Failed to confirm RMNRemote config", "chain", chain.String(), "err", err) return err } - - // we deploy a new RMNProxy so that RMNRemote can be tested first before pointing it to the main Existing RMNProxy - // To differentiate between the two RMNProxies, we will deploy new one with Version1_6_0_dev - rmnProxyContract := chainState.RMNProxyNew - if chainState.RMNProxyNew == nil { - // we deploy a new rmnproxy contract to test RMNRemote - rmnProxy, err := deployment.DeployContract(e.Logger, chain, ab, - func(chain deployment.Chain) deployment.ContractDeploy[*rmn_proxy_contract.RMNProxyContract] { - rmnProxyAddr, tx, rmnProxy, err2 := rmn_proxy_contract.DeployRMNProxyContract( - chain.DeployerKey, - chain.Client, - rmnRemoteContract.Address(), - ) - return deployment.ContractDeploy[*rmn_proxy_contract.RMNProxyContract]{ - rmnProxyAddr, rmnProxy, tx, deployment.NewTypeAndVersion(ARMProxy, deployment.Version1_6_0_dev), err2, - } - }) - if err != nil { - e.Logger.Errorw("Failed to deploy RMNProxyNew", "chain", chain.String(), "err", err) - return err - } - rmnProxyContract = rmnProxy.Contract - } else { - e.Logger.Infow("rmn proxy already deployed", "chain", chain.String(), "addr", chainState.RMNProxyNew.Address) - } if chainState.TestRouter == nil { _, err := deployment.DeployContract(e.Logger, chain, ab, func(chain deployment.Chain) deployment.ContractDeploy[*router.Router] { @@ -380,10 +370,11 @@ func deployChainContracts( chain.DeployerKey, chain.Client, offramp.OffRampStaticConfig{ - ChainSelector: chain.Selector, - RmnRemote: rmnProxyContract.Address(), - NonceManager: nmContract.Address(), - TokenAdminRegistry: tokenAdminReg.Address(), + ChainSelector: chain.Selector, + GasForCallExactCheck: 5_000, + RmnRemote: rmnProxyContract.Address(), + NonceManager: nmContract.Address(), + TokenAdminRegistry: tokenAdminReg.Address(), }, offramp.OffRampDynamicConfig{ FeeQuoter: feeQuoterContract.Address(), diff --git a/deployment/ccip/changeset/cs_deploy_chain_test.go b/deployment/ccip/changeset/cs_deploy_chain_test.go index 234d73cc4b5..9e1a581112d 100644 --- a/deployment/ccip/changeset/cs_deploy_chain_test.go +++ b/deployment/ccip/changeset/cs_deploy_chain_test.go @@ -3,7 +3,6 @@ package changeset import ( "encoding/json" "fmt" - "math/big" "testing" "github.com/stretchr/testify/require" @@ -11,12 +10,14 @@ import ( "github.com/smartcontractkit/chainlink/deployment" commonchangeset "github.com/smartcontractkit/chainlink/deployment/common/changeset" + "github.com/smartcontractkit/chainlink/deployment/common/proposalutils" commontypes "github.com/smartcontractkit/chainlink/deployment/common/types" "github.com/smartcontractkit/chainlink/deployment/environment/memory" "github.com/smartcontractkit/chainlink/v2/core/logger" ) func TestDeployChainContractsChangeset(t *testing.T) { + t.Parallel() lggr := logger.TestLogger(t) e := memory.NewMemoryEnvironment(t, lggr, zapcore.InfoLevel, memory.MemoryEnvironmentConfig{ Bootstraps: 1, @@ -30,13 +31,7 @@ func TestDeployChainContractsChangeset(t *testing.T) { p2pIds := nodes.NonBootstraps().PeerIDs() cfg := make(map[uint64]commontypes.MCMSWithTimelockConfig) for _, chain := range e.AllChainSelectors() { - cfg[chain] = commontypes.MCMSWithTimelockConfig{ - Canceller: commonchangeset.SingleGroupMCMS(t), - Bypasser: commonchangeset.SingleGroupMCMS(t), - Proposer: commonchangeset.SingleGroupMCMS(t), - TimelockExecutors: e.AllDeployerKeys(), - TimelockMinDelay: big.NewInt(0), - } + cfg[chain] = proposalutils.SingleGroupTimelockConfig(t) } e, err = commonchangeset.ApplyChangesets(t, e, nil, []commonchangeset.ChangesetApplication{ { @@ -99,12 +94,8 @@ func TestDeployChainContractsChangeset(t *testing.T) { } func TestDeployCCIPContracts(t *testing.T) { - lggr := logger.TestLogger(t) - e := NewMemoryEnvironmentWithJobsAndContracts(t, lggr, memory.MemoryEnvironmentConfig{ - Chains: 2, - Nodes: 4, - Bootstraps: 1, - }, nil) + t.Parallel() + e := NewMemoryEnvironment(t) // Deploy all the CCIP contracts. state, err := LoadOnchainState(e.Env) require.NoError(t, err) diff --git a/deployment/ccip/changeset/cs_home_chain.go b/deployment/ccip/changeset/cs_home_chain.go index 750b21229aa..44658d41016 100644 --- a/deployment/ccip/changeset/cs_home_chain.go +++ b/deployment/ccip/changeset/cs_home_chain.go @@ -5,14 +5,18 @@ import ( "context" "encoding/json" "fmt" + "math/big" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/pkg/errors" + "github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers" + "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/mcms" "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/timelock" "golang.org/x/exp/maps" "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink/deployment/common/proposalutils" "github.com/smartcontractkit/chainlink/deployment" "github.com/smartcontractkit/chainlink/deployment/ccip/changeset/internal" @@ -322,3 +326,93 @@ func addNodes( _, err = chain.Confirm(tx) return err } + +type RemoveDONsConfig struct { + HomeChainSel uint64 + DonIDs []uint32 + MCMS *MCMSConfig +} + +func (c RemoveDONsConfig) Validate(homeChain CCIPChainState) error { + if err := deployment.IsValidChainSelector(c.HomeChainSel); err != nil { + return fmt.Errorf("home chain selector must be set %w", err) + } + if len(c.DonIDs) == 0 { + return fmt.Errorf("don ids must be set") + } + // Cap reg must exist + if homeChain.CapabilityRegistry == nil { + return fmt.Errorf("cap reg does not exist") + } + if homeChain.CCIPHome == nil { + return fmt.Errorf("ccip home does not exist") + } + if err := internal.DONIdExists(homeChain.CapabilityRegistry, c.DonIDs); err != nil { + return err + } + return nil +} + +// RemoveDONs removes DONs from the CapabilitiesRegistry contract. +// TODO: Could likely be moved to common, but needs +// a common state struct first. +func RemoveDONs(e deployment.Environment, cfg RemoveDONsConfig) (deployment.ChangesetOutput, error) { + state, err := LoadOnchainState(e) + if err != nil { + return deployment.ChangesetOutput{}, err + } + homeChain, ok := e.Chains[cfg.HomeChainSel] + if !ok { + return deployment.ChangesetOutput{}, fmt.Errorf("home chain %d not found", cfg.HomeChainSel) + } + homeChainState := state.Chains[cfg.HomeChainSel] + if err := cfg.Validate(homeChainState); err != nil { + return deployment.ChangesetOutput{}, err + } + txOpts := homeChain.DeployerKey + if cfg.MCMS != nil { + txOpts = deployment.SimTransactOpts() + } + + tx, err := homeChainState.CapabilityRegistry.RemoveDONs(txOpts, cfg.DonIDs) + if err != nil { + return deployment.ChangesetOutput{}, err + } + if cfg.MCMS == nil { + _, err = homeChain.Confirm(tx) + if err != nil { + return deployment.ChangesetOutput{}, err + } + e.Logger.Infof("Removed dons using deployer key tx %s", tx.Hash().String()) + return deployment.ChangesetOutput{}, nil + } + p, err := proposalutils.BuildProposalFromBatches( + map[uint64]common.Address{ + cfg.HomeChainSel: homeChainState.Timelock.Address(), + }, + map[uint64]*gethwrappers.ManyChainMultiSig{ + cfg.HomeChainSel: homeChainState.ProposerMcm, + }, + []timelock.BatchChainOperation{ + { + ChainIdentifier: mcms.ChainIdentifier(cfg.HomeChainSel), + Batch: []mcms.Operation{ + { + To: homeChainState.CapabilityRegistry.Address(), + Data: tx.Data(), + Value: big.NewInt(0), + }, + }, + }, + }, + "Remove DONs", + cfg.MCMS.MinDelay, + ) + if err != nil { + return deployment.ChangesetOutput{}, err + } + e.Logger.Infof("Created proposal to remove dons") + return deployment.ChangesetOutput{Proposals: []timelock.MCMSWithTimelockProposal{ + *p, + }}, nil +} diff --git a/deployment/ccip/changeset/cs_home_chain_test.go b/deployment/ccip/changeset/cs_home_chain_test.go index a06161f7086..8a2d4f87709 100644 --- a/deployment/ccip/changeset/cs_home_chain_test.go +++ b/deployment/ccip/changeset/cs_home_chain_test.go @@ -3,16 +3,20 @@ package changeset import ( "testing" + "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" "go.uber.org/zap/zapcore" "github.com/smartcontractkit/chainlink/deployment" + commoncs "github.com/smartcontractkit/chainlink/deployment/common/changeset" + "github.com/smartcontractkit/chainlink/deployment/common/proposalutils" "github.com/smartcontractkit/chainlink/deployment/common/view/v1_0" "github.com/smartcontractkit/chainlink/deployment/environment/memory" "github.com/smartcontractkit/chainlink/v2/core/logger" ) func TestDeployHomeChain(t *testing.T) { + t.Parallel() lggr := logger.TestLogger(t) e := memory.NewMemoryEnvironment(t, lggr, zapcore.InfoLevel, memory.MemoryEnvironmentConfig{ Bootstraps: 1, @@ -56,3 +60,114 @@ func TestDeployHomeChain(t *testing.T) { }) require.Len(t, capRegSnap.Nodes, len(p2pIds)) } + +func TestRemoveDonsValidate(t *testing.T) { + e := NewMemoryEnvironment(t) + s, err := LoadOnchainState(e.Env) + require.NoError(t, err) + homeChain := s.Chains[e.HomeChainSel] + var tt = []struct { + name string + config RemoveDONsConfig + expectErr bool + }{ + { + name: "invalid home", + config: RemoveDONsConfig{ + HomeChainSel: 0, + DonIDs: []uint32{1}, + }, + expectErr: true, + }, + { + name: "invalid dons", + config: RemoveDONsConfig{ + HomeChainSel: e.HomeChainSel, + DonIDs: []uint32{1377}, + }, + expectErr: true, + }, + { + name: "no dons", + config: RemoveDONsConfig{ + HomeChainSel: e.HomeChainSel, + DonIDs: []uint32{}, + }, + expectErr: true, + }, + { + name: "success", + config: RemoveDONsConfig{ + HomeChainSel: e.HomeChainSel, + DonIDs: []uint32{1}, + }, + expectErr: false, + }, + } + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + err := tc.config.Validate(homeChain) + if tc.expectErr { + require.Error(t, err) + } else { + require.NoError(t, err) + } + }) + } +} + +func TestRemoveDons(t *testing.T) { + e := NewMemoryEnvironment(t) + s, err := LoadOnchainState(e.Env) + require.NoError(t, err) + homeChain := s.Chains[e.HomeChainSel] + + // Remove a don w/o MCMS + donsBefore, err := homeChain.CapabilityRegistry.GetDONs(nil) + require.NoError(t, err) + e.Env, err = commoncs.ApplyChangesets(t, e.Env, nil, []commoncs.ChangesetApplication{ + { + Changeset: commoncs.WrapChangeSet(RemoveDONs), + Config: RemoveDONsConfig{ + HomeChainSel: e.HomeChainSel, + DonIDs: []uint32{donsBefore[0].Id}, + }, + }, + }) + require.NoError(t, err) + donsAfter, err := homeChain.CapabilityRegistry.GetDONs(nil) + require.NoError(t, err) + require.Len(t, donsAfter, len(donsBefore)-1) + + // Remove a don w/ MCMS + donsBefore, err = homeChain.CapabilityRegistry.GetDONs(nil) + require.NoError(t, err) + e.Env, err = commoncs.ApplyChangesets(t, e.Env, map[uint64]*proposalutils.TimelockExecutionContracts{ + e.HomeChainSel: { + Timelock: s.Chains[e.HomeChainSel].Timelock, + CallProxy: s.Chains[e.HomeChainSel].CallProxy, + }, + }, []commoncs.ChangesetApplication{ + { + Changeset: commoncs.WrapChangeSet(commoncs.TransferToMCMSWithTimelock), + Config: commoncs.TransferToMCMSWithTimelockConfig{ + ContractsByChain: map[uint64][]common.Address{ + e.HomeChainSel: {homeChain.CapabilityRegistry.Address()}, + }, + MinDelay: 0, + }, + }, + { + Changeset: commoncs.WrapChangeSet(RemoveDONs), + Config: RemoveDONsConfig{ + HomeChainSel: e.HomeChainSel, + DonIDs: []uint32{donsBefore[0].Id}, + MCMS: &MCMSConfig{MinDelay: 0}, + }, + }, + }) + require.NoError(t, err) + donsAfter, err = homeChain.CapabilityRegistry.GetDONs(nil) + require.NoError(t, err) + require.Len(t, donsAfter, len(donsBefore)-1) +} diff --git a/deployment/ccip/changeset/cs_initial_add_chain.go b/deployment/ccip/changeset/cs_initial_add_chain.go index 13aee106e5a..4f8b2ac2722 100644 --- a/deployment/ccip/changeset/cs_initial_add_chain.go +++ b/deployment/ccip/changeset/cs_initial_add_chain.go @@ -242,6 +242,20 @@ func setupConfigInfo(chainSelector uint64, readers [][32]byte, fChain uint8, cfg } } +func isChainConfigEqual(a, b ccip_home.CCIPHomeChainConfig) bool { + mapReader := make(map[[32]byte]struct{}) + for i := range a.Readers { + mapReader[a.Readers[i]] = struct{}{} + } + for i := range b.Readers { + if _, ok := mapReader[b.Readers[i]]; !ok { + return false + } + } + return bytes.Equal(a.Config, b.Config) && + a.FChain == b.FChain +} + func addChainConfig( lggr logger.Logger, h deployment.Chain, @@ -259,6 +273,18 @@ func addChainConfig( return ccip_home.CCIPHomeChainConfigArgs{}, err } chainConfig := setupConfigInfo(chainSelector, p2pIDs, uint8(len(p2pIDs)/3), encodedExtraChainConfig) + existingCfg, err := ccipConfig.GetChainConfig(nil, chainSelector) + if err != nil { + return ccip_home.CCIPHomeChainConfigArgs{}, fmt.Errorf("get chain config for selector %d: %w", chainSelector, err) + } + if isChainConfigEqual(existingCfg, chainConfig.ChainConfig) { + lggr.Infow("Chain config already exists, not applying again", + "homeChain", h.String(), + "addedChain", chainSelector, + "chainConfig", chainConfig, + ) + return chainConfig, nil + } tx, err := ccipConfig.ApplyChainConfigUpdates(h.DeployerKey, nil, []ccip_home.CCIPHomeChainConfigArgs{ chainConfig, }) @@ -282,6 +308,15 @@ func createDON( newChainSel uint64, nodes deployment.Nodes, ) error { + donID, err := internal.DonIDForChain(capReg, ccipHome, newChainSel) + if err != nil { + return fmt.Errorf("fetch don id for chain: %w", err) + } + if donID != 0 { + lggr.Infow("DON already exists not adding it again", "donID", donID, "chain", newChainSel) + return ValidateCCIPHomeConfigSetUp(lggr, capReg, ccipHome, newChainSel) + } + commitConfig, ok := ocr3Configs[cctypes.PluginTypeCCIPCommit] if !ok { return fmt.Errorf("missing commit plugin in ocr3Configs") @@ -297,7 +332,7 @@ func createDON( return err } - donID := latestDon.Id + 1 + donID = latestDon.Id + 1 err = internal.SetupCommitDON(lggr, donID, commitConfig, capReg, home, nodes, ccipHome) if err != nil { @@ -350,11 +385,37 @@ func addDON( "chainSelector", dest.Selector, ) + // check if OCR3 config is already set on offramp + ocr3ConfigSet, err := isOCR3ConfigSetOnOffRamp(lggr, dest, offRamp, offrampOCR3Configs) + if err != nil { + return fmt.Errorf("error checking if OCR3 config is set on offramp: %w", err) + } + if ocr3ConfigSet { + lggr.Infow("OCR3 config already set on offramp, not applying again", "chain", dest.String()) + return nil + } tx, err := offRamp.SetOCR3Configs(dest.DeployerKey, offrampOCR3Configs) if _, err := deployment.ConfirmIfNoError(dest, tx, err); err != nil { return err } + lggr.Infow("Set OCR3 Configs", "chain", dest.String()) + // now check if OCR3 config is set on offramp + ocr3ConfigSet, err = isOCR3ConfigSetOnOffRamp(lggr, dest, offRamp, offrampOCR3Configs) + if err != nil { + return fmt.Errorf("error checking if OCR3 config is set on offramp: %w", err) + } + if !ocr3ConfigSet { + return fmt.Errorf("OCR3 config not set on offramp properly, check logs, chain %s", dest.String()) + } + return nil +} +func isOCR3ConfigSetOnOffRamp( + lggr logger.Logger, + chain deployment.Chain, + offRamp *offramp.OffRamp, + offrampOCR3Configs []offramp.MultiOCR3BaseOCRConfigArgs, +) (bool, error) { mapOfframpOCR3Configs := make(map[cctypes.PluginType]offramp.MultiOCR3BaseOCRConfigArgs) for _, config := range offrampOCR3Configs { mapOfframpOCR3Configs[cctypes.PluginType(config.OcrPluginType)] = config @@ -365,7 +426,7 @@ func addDON( Context: context.Background(), }, uint8(pluginType)) if err != nil { - return err + return false, fmt.Errorf("error fetching OCR3 config for plugin %s chain %s: %w", pluginType.String(), chain.String(), err) } lggr.Debugw("Fetched OCR3 Configs", "MultiOCR3BaseOCRConfig.F", ocrConfig.ConfigInfo.F, @@ -374,35 +435,39 @@ func addDON( "Signers", ocrConfig.Signers, "Transmitters", ocrConfig.Transmitters, "configDigest", hex.EncodeToString(ocrConfig.ConfigInfo.ConfigDigest[:]), - "chain", dest.String(), + "chain", chain.String(), ) // TODO: assertions to be done as part of full state // resprentation validation CCIP-3047 if mapOfframpOCR3Configs[pluginType].ConfigDigest != ocrConfig.ConfigInfo.ConfigDigest { - return fmt.Errorf("%s OCR3 config digest mismatch", pluginType.String()) + lggr.Infow("OCR3 config digest mismatch", "pluginType", pluginType.String()) + return false, nil } if mapOfframpOCR3Configs[pluginType].F != ocrConfig.ConfigInfo.F { - return fmt.Errorf("%s OCR3 config F mismatch", pluginType.String()) + lggr.Infow("OCR3 config F mismatch", "pluginType", pluginType.String()) + return false, nil } if mapOfframpOCR3Configs[pluginType].IsSignatureVerificationEnabled != ocrConfig.ConfigInfo.IsSignatureVerificationEnabled { - return fmt.Errorf("%s OCR3 config signature verification mismatch", pluginType.String()) + lggr.Infow("OCR3 config signature verification mismatch", "pluginType", pluginType.String()) + return false, nil } if pluginType == cctypes.PluginTypeCCIPCommit { // only commit will set signers, exec doesn't need them. for i, signer := range mapOfframpOCR3Configs[pluginType].Signers { if !bytes.Equal(signer.Bytes(), ocrConfig.Signers[i].Bytes()) { - return fmt.Errorf("%s OCR3 config signer mismatch", pluginType.String()) + lggr.Infow("OCR3 config signer mismatch", "pluginType", pluginType.String()) + return false, nil } } } for i, transmitter := range mapOfframpOCR3Configs[pluginType].Transmitters { if !bytes.Equal(transmitter.Bytes(), ocrConfig.Transmitters[i].Bytes()) { - return fmt.Errorf("%s OCR3 config transmitter mismatch", pluginType.String()) + lggr.Infow("OCR3 config transmitter mismatch", "pluginType", pluginType.String()) + return false, nil } } } - - return nil + return true, nil } // ValidateCCIPHomeConfigSetUp checks that the commit and exec active and candidate configs are set up correctly @@ -417,6 +482,10 @@ func ValidateCCIPHomeConfigSetUp( if err != nil { return fmt.Errorf("fetch don id for chain: %w", err) } + if donID == 0 { + return fmt.Errorf("don id for chain (%d) does not exist", chainSel) + } + // final sanity checks on configs. commitConfigs, err := ccipHome.GetAllConfigs(&bind.CallOpts{ //Pending: true, diff --git a/deployment/ccip/changeset/cs_initial_add_chain_test.go b/deployment/ccip/changeset/cs_initial_add_chain_test.go new file mode 100644 index 00000000000..7e155b82ed1 --- /dev/null +++ b/deployment/ccip/changeset/cs_initial_add_chain_test.go @@ -0,0 +1,93 @@ +package changeset + +import ( + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/smartcontractkit/chainlink-ccip/pluginconfig" + "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/testcontext" + "github.com/stretchr/testify/require" + + commonchangeset "github.com/smartcontractkit/chainlink/deployment/common/changeset" + "github.com/smartcontractkit/chainlink/deployment/common/proposalutils" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/router" +) + +func TestInitialAddChainAppliedTwice(t *testing.T) { + t.Parallel() + // This already applies the initial add chain changeset. + e := NewMemoryEnvironment(t) + + state, err := LoadOnchainState(e.Env) + require.NoError(t, err) + + // now try to apply it again for the second time + // Build the per chain config. + allChains := e.Env.AllChainSelectors() + tokenConfig := NewTestTokenConfig(state.Chains[e.FeedChainSel].USDFeeds) + chainConfigs := make(map[uint64]CCIPOCRParams) + timelockContractsPerChain := make(map[uint64]*proposalutils.TimelockExecutionContracts) + + for _, chain := range allChains { + timelockContractsPerChain[chain] = &proposalutils.TimelockExecutionContracts{ + Timelock: state.Chains[chain].Timelock, + CallProxy: state.Chains[chain].CallProxy, + } + tokenInfo := tokenConfig.GetTokenInfo(e.Env.Logger, state.Chains[chain].LinkToken, state.Chains[chain].Weth9) + ocrParams := DefaultOCRParams(e.FeedChainSel, tokenInfo, []pluginconfig.TokenDataObserverConfig{}) + chainConfigs[chain] = ocrParams + } + e.Env, err = commonchangeset.ApplyChangesets(t, e.Env, timelockContractsPerChain, []commonchangeset.ChangesetApplication{ + { + Changeset: commonchangeset.WrapChangeSet(ConfigureNewChains), + Config: NewChainsConfig{ + HomeChainSel: e.HomeChainSel, + FeedChainSel: e.FeedChainSel, + ChainConfigByChain: chainConfigs, + }, + }, + }) + require.NoError(t, err) + // send requests + chain1, chain2 := allChains[0], allChains[1] + _, err = AddLanes(e.Env, AddLanesConfig{ + LaneConfigs: []LaneConfig{ + { + SourceSelector: chain1, + DestSelector: chain2, + InitialPricesBySource: DefaultInitialPrices, + FeeQuoterDestChain: DefaultFeeQuoterDestChainConfig(), + TestRouter: true, + }, + }, + }) + require.NoError(t, err) + ReplayLogs(t, e.Env.Offchain, e.ReplayBlocks) + // Need to keep track of the block number for each chain so that event subscription can be done from that block. + startBlocks := make(map[uint64]*uint64) + // Send a message from each chain to every other chain. + expectedSeqNumExec := make(map[SourceDestPair][]uint64) + expectedSeqNum := make(map[SourceDestPair]uint64) + latesthdr, err := e.Env.Chains[chain2].Client.HeaderByNumber(testcontext.Get(t), nil) + require.NoError(t, err) + block := latesthdr.Number.Uint64() + startBlocks[chain2] = &block + msgSentEvent := TestSendRequest(t, e.Env, state, chain1, chain2, true, router.ClientEVM2AnyMessage{ + Receiver: common.LeftPadBytes(state.Chains[chain2].Receiver.Address().Bytes(), 32), + Data: []byte("hello"), + TokenAmounts: nil, + FeeToken: common.HexToAddress("0x0"), + ExtraArgs: nil, + }) + + expectedSeqNum[SourceDestPair{ + SourceChainSelector: chain1, + DestChainSelector: chain2, + }] = msgSentEvent.SequenceNumber + expectedSeqNumExec[SourceDestPair{ + SourceChainSelector: chain1, + DestChainSelector: chain2, + }] = []uint64{msgSentEvent.SequenceNumber} + ConfirmCommitForAllWithExpectedSeqNums(t, e.Env, state, expectedSeqNum, startBlocks) + ConfirmExecWithSeqNrsForAll(t, e.Env, state, expectedSeqNumExec, startBlocks) +} diff --git a/deployment/ccip/changeset/cs_jobspec_test.go b/deployment/ccip/changeset/cs_jobspec_test.go index 21e80e85aa2..a0445b0d5ee 100644 --- a/deployment/ccip/changeset/cs_jobspec_test.go +++ b/deployment/ccip/changeset/cs_jobspec_test.go @@ -13,6 +13,7 @@ import ( ) func TestJobSpecChangeset(t *testing.T) { + t.Parallel() lggr := logger.TestLogger(t) e := memory.NewMemoryEnvironment(t, lggr, zapcore.InfoLevel, memory.MemoryEnvironmentConfig{ Chains: 1, diff --git a/deployment/ccip/changeset/cs_prerequisites.go b/deployment/ccip/changeset/cs_prerequisites.go index 2386d3bb784..95ef923df83 100644 --- a/deployment/ccip/changeset/cs_prerequisites.go +++ b/deployment/ccip/changeset/cs_prerequisites.go @@ -133,15 +133,11 @@ func deployPrerequisiteContracts(e deployment.Environment, ab deployment.Address weth9Contract = chainState.Weth9 tokenAdminReg = chainState.TokenAdminRegistry registryModule = chainState.RegistryModule - rmnProxy = chainState.RMNProxyExisting + rmnProxy = chainState.RMNProxy r = chainState.Router mc3 = chainState.Multicall3 } if rmnProxy == nil { - // we want to replicate the mainnet scenario where RMNProxy is already deployed with some existing RMN - // This will need us to use two different RMNProxy contracts - // 1. RMNProxyNew with RMNRemote - ( deployed later in chain contracts) - // 2. RMNProxyExisting with mockRMN - ( deployed here, replicating the behavior of existing RMNProxy with already set RMN) rmn, err := deployment.DeployContract(lggr, chain, ab, func(chain deployment.Chain) deployment.ContractDeploy[*mock_rmn_contract.MockRMNContract] { rmnAddr, tx2, rmn, err2 := mock_rmn_contract.DeployMockRMNContract( @@ -149,7 +145,7 @@ func deployPrerequisiteContracts(e deployment.Environment, ab deployment.Address chain.Client, ) return deployment.ContractDeploy[*mock_rmn_contract.MockRMNContract]{ - rmnAddr, rmn, tx2, deployment.NewTypeAndVersion(MockRMN, deployment.Version1_0_0), err2, + Address: rmnAddr, Contract: rmn, Tx: tx2, Tv: deployment.NewTypeAndVersion(MockRMN, deployment.Version1_0_0), Err: err2, } }) if err != nil { diff --git a/deployment/ccip/changeset/cs_update_rmn_config.go b/deployment/ccip/changeset/cs_update_rmn_config.go index 7e4d09af20f..96f8eacb4cc 100644 --- a/deployment/ccip/changeset/cs_update_rmn_config.go +++ b/deployment/ccip/changeset/cs_update_rmn_config.go @@ -9,15 +9,148 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers" - mcmsWrappers "github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers" "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/mcms" "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/timelock" + "github.com/smartcontractkit/chainlink/deployment" "github.com/smartcontractkit/chainlink/deployment/common/proposalutils" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/rmn_home" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/rmn_remote" + "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/p2pkey" ) +type SetRMNRemoteOnRMNProxyConfig struct { + ChainSelectors []uint64 + MCMSConfig *MCMSConfig +} + +func (c SetRMNRemoteOnRMNProxyConfig) Validate(state CCIPOnChainState) error { + for _, chain := range c.ChainSelectors { + err := deployment.IsValidChainSelector(chain) + if err != nil { + return err + } + chainState, exists := state.Chains[chain] + if !exists { + return fmt.Errorf("chain %d not found in state", chain) + } + if chainState.RMNRemote == nil { + return fmt.Errorf("RMNRemote not found for chain %d", chain) + } + if chainState.RMNProxy == nil { + return fmt.Errorf("RMNProxy not found for chain %d", chain) + } + } + return nil +} + +func SetRMNRemoteOnRMNProxy(e deployment.Environment, cfg SetRMNRemoteOnRMNProxyConfig) (deployment.ChangesetOutput, error) { + state, err := LoadOnchainState(e) + if err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("failed to load onchain state: %w", err) + } + if err := cfg.Validate(state); err != nil { + return deployment.ChangesetOutput{}, err + } + timelocks, err := state.GetAllTimeLocksForChains(cfg.ChainSelectors) + if err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("failed to get timelocks for chains %v: %w", cfg.ChainSelectors, err) + } + multiSigs, err := state.GetAllProposerMCMSForChains(cfg.ChainSelectors) + if err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("failed to get proposer MCMS for chains %v: %w", cfg.ChainSelectors, err) + } + var timelockBatch []timelock.BatchChainOperation + for _, sel := range cfg.ChainSelectors { + chain, exists := e.Chains[sel] + if !exists { + return deployment.ChangesetOutput{}, fmt.Errorf("chain %d not found", sel) + } + txOpts := chain.DeployerKey + if cfg.MCMSConfig != nil { + txOpts = deployment.SimTransactOpts() + } + mcmsOps, err := setRMNRemoteOnRMNProxyOp(txOpts, chain, state.Chains[sel], cfg.MCMSConfig != nil) + if err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("failed to set RMNRemote on RMNProxy for chain %s: %w", chain.String(), err) + } + if cfg.MCMSConfig != nil { + timelockBatch = append(timelockBatch, timelock.BatchChainOperation{ + ChainIdentifier: mcms.ChainIdentifier(sel), + Batch: []mcms.Operation{mcmsOps}, + }) + } + } + // If we're not using MCMS, we can just return now as we've already confirmed the transactions + if len(timelockBatch) == 0 { + return deployment.ChangesetOutput{}, nil + } + prop, err := proposalutils.BuildProposalFromBatches( + timelocks, + multiSigs, + timelockBatch, + fmt.Sprintf("proposal to set RMNRemote on RMNProxy for chains %v", cfg.ChainSelectors), + cfg.MCMSConfig.MinDelay, + ) + if err != nil { + return deployment.ChangesetOutput{}, err + } + return deployment.ChangesetOutput{ + Proposals: []timelock.MCMSWithTimelockProposal{ + *prop, + }, + }, nil +} + +func setRMNRemoteOnRMNProxyOp(txOpts *bind.TransactOpts, chain deployment.Chain, chainState CCIPChainState, mcmsEnabled bool) (mcms.Operation, error) { + rmnProxy := chainState.RMNProxy + rmnRemoteAddr := chainState.RMNRemote.Address() + setRMNTx, err := rmnProxy.SetARM(txOpts, rmnRemoteAddr) + if err != nil { + return mcms.Operation{}, fmt.Errorf("failed to build call data/transaction to set RMNRemote on RMNProxy for chain %s: %w", chain.String(), err) + } + if !mcmsEnabled { + _, err = deployment.ConfirmIfNoError(chain, setRMNTx, err) + if err != nil { + return mcms.Operation{}, fmt.Errorf("failed to confirm tx to set RMNRemote on RMNProxy for chain %s: %w", chain.String(), deployment.MaybeDataErr(err)) + } + } + return mcms.Operation{ + To: rmnProxy.Address(), + Data: setRMNTx.Data(), + Value: big.NewInt(0), + }, nil +} + +type RMNNopConfig struct { + NodeIndex uint64 + OffchainPublicKey [32]byte + EVMOnChainPublicKey common.Address + PeerId p2pkey.PeerID +} + +func (c RMNNopConfig) ToRMNHomeNode() rmn_home.RMNHomeNode { + return rmn_home.RMNHomeNode{ + PeerId: c.PeerId, + OffchainPublicKey: c.OffchainPublicKey, + } +} + +func (c RMNNopConfig) ToRMNRemoteSigner() rmn_remote.RMNRemoteSigner { + return rmn_remote.RMNRemoteSigner{ + OnchainPublicKey: c.EVMOnChainPublicKey, + NodeIndex: c.NodeIndex, + } +} + +func (c RMNNopConfig) SetBit(bitmap *big.Int, value bool) { + if value { + bitmap.SetBit(bitmap, int(c.NodeIndex), 1) + } else { + bitmap.SetBit(bitmap, int(c.NodeIndex), 0) + } +} + func getDeployer(e deployment.Environment, chain uint64, mcmConfig *MCMSConfig) *bind.TransactOpts { if mcmConfig == nil { return e.Chains[chain].DeployerKey @@ -274,10 +407,13 @@ func NewPromoteCandidateConfigChangeset(e deployment.Environment, config Promote }, nil } -func buildTimelockPerChain(e deployment.Environment, state CCIPOnChainState) map[uint64]*mcmsWrappers.RBACTimelock { - timelocksPerChain := make(map[uint64]*mcmsWrappers.RBACTimelock) +func buildTimelockPerChain(e deployment.Environment, state CCIPOnChainState) map[uint64]*proposalutils.TimelockExecutionContracts { + timelocksPerChain := make(map[uint64]*proposalutils.TimelockExecutionContracts) for _, chain := range e.Chains { - timelocksPerChain[chain.Selector] = state.Chains[chain.Selector].Timelock + timelocksPerChain[chain.Selector] = &proposalutils.TimelockExecutionContracts{ + Timelock: state.Chains[chain.Selector].Timelock, + CallProxy: state.Chains[chain.Selector].CallProxy, + } } return timelocksPerChain } @@ -286,7 +422,7 @@ func buildTimelockAddressPerChain(e deployment.Environment, state CCIPOnChainSta timelocksPerChain := buildTimelockPerChain(e, state) timelockAddressPerChain := make(map[uint64]common.Address) for chain, timelock := range timelocksPerChain { - timelockAddressPerChain[chain] = timelock.Address() + timelockAddressPerChain[chain] = timelock.Timelock.Address() } return timelockAddressPerChain } @@ -307,10 +443,14 @@ func buildRMNRemotePerChain(e deployment.Environment, state CCIPOnChainState) ma return timelocksPerChain } +type RMNRemoteConfig struct { + Signers []rmn_remote.RMNRemoteSigner + F uint64 +} + type SetRMNRemoteConfig struct { HomeChainSelector uint64 - Signers []rmn_remote.RMNRemoteSigner - F uint64 + RMNRemoteConfigs map[uint64]RMNRemoteConfig MCMSConfig *MCMSConfig } @@ -320,14 +460,21 @@ func (c SetRMNRemoteConfig) Validate() error { return err } - for i := 0; i < len(c.Signers)-1; i++ { - if c.Signers[i].NodeIndex >= c.Signers[i+1].NodeIndex { - return fmt.Errorf("signers must be in ascending order of nodeIndex") + for chain, config := range c.RMNRemoteConfigs { + err := deployment.IsValidChainSelector(chain) + if err != nil { + return err + } + + for i := 0; i < len(config.Signers)-1; i++ { + if config.Signers[i].NodeIndex >= config.Signers[i+1].NodeIndex { + return fmt.Errorf("signers must be in ascending order of nodeIndex, but found %d >= %d", config.Signers[i].NodeIndex, config.Signers[i+1].NodeIndex) + } } - } - if len(c.Signers) < 2*int(c.F)+1 { - return fmt.Errorf("signers count must greater than or equal to %d", 2*c.F+1) + if len(config.Signers) < 2*int(config.F)+1 { + return fmt.Errorf("signers count (%d) must be greater than or equal to %d", len(config.Signers), 2*config.F+1) + } } return nil @@ -364,9 +511,10 @@ func NewSetRMNRemoteConfigChangeset(e deployment.Environment, config SetRMNRemot rmnRemotePerChain := buildRMNRemotePerChain(e, state) batches := make([]timelock.BatchChainOperation, 0) - for chain, remote := range rmnRemotePerChain { - if remote == nil { - continue + for chain, remoteConfig := range config.RMNRemoteConfigs { + remote, ok := rmnRemotePerChain[chain] + if !ok { + return deployment.ChangesetOutput{}, fmt.Errorf("RMNRemote contract not found for chain %d", chain) } currentVersionConfig, err := remote.GetVersionedConfig(nil) @@ -376,8 +524,8 @@ func NewSetRMNRemoteConfigChangeset(e deployment.Environment, config SetRMNRemot newConfig := rmn_remote.RMNRemoteConfig{ RmnHomeContractConfigDigest: activeConfig, - Signers: config.Signers, - F: config.F, + Signers: remoteConfig.Signers, + F: remoteConfig.F, } if reflect.DeepEqual(currentVersionConfig.Config, newConfig) { diff --git a/deployment/ccip/changeset/cs_update_rmn_config_test.go b/deployment/ccip/changeset/cs_update_rmn_config_test.go index deae3e2e771..07bf22720c2 100644 --- a/deployment/ccip/changeset/cs_update_rmn_config_test.go +++ b/deployment/ccip/changeset/cs_update_rmn_config_test.go @@ -8,15 +8,37 @@ import ( "github.com/smartcontractkit/chainlink/deployment" commonchangeset "github.com/smartcontractkit/chainlink/deployment/common/changeset" - "github.com/smartcontractkit/chainlink/deployment/environment/memory" + "github.com/smartcontractkit/chainlink/deployment/common/proposalutils" + commontypes "github.com/smartcontractkit/chainlink/deployment/common/types" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/rmn_home" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/rmn_remote" - "github.com/smartcontractkit/chainlink/v2/core/logger" +) + +var ( + rmn_staging_1 = RMNNopConfig{ + NodeIndex: 0, + PeerId: deployment.MustPeerIDFromString("p2p_12D3KooWRXxZq3pd4a3ZGkKj7Nt1SQQrnB8CuvbPnnV9KVeMeWqg"), + OffchainPublicKey: [32]byte(common.FromHex("0xb34944857a42444d1b285d7940d6e06682309e0781e43a69676ee9f85c73c2d1")), + EVMOnChainPublicKey: common.HexToAddress("0x5af8ee32316a6427f169a45fdc1b3a91a85ac459e3c1cb91c69e1c51f0c1fc21"), + } + rmn_staging_2 = RMNNopConfig{ + NodeIndex: 1, + PeerId: deployment.MustPeerIDFromString("p2p_12D3KooWEmdxYQFsRbD9aFczF32zA3CcUwuSiWCk2CrmACo4v9RL"), + OffchainPublicKey: [32]byte(common.FromHex("0x68d9f3f274e3985528a923a9bace3d39c55dd778b187b4120b384cc48c892859")), + EVMOnChainPublicKey: common.HexToAddress("0x858589216956f482a0f68b282a7050af4cd48ed2"), + } + rmn_staging_3 = RMNNopConfig{ + NodeIndex: 2, + PeerId: deployment.MustPeerIDFromString("p2p_12D3KooWJS42cNXKJvj6DeZnxEX7aGxhEuap6uNFrz554AbUDw6Q"), + OffchainPublicKey: [32]byte(common.FromHex("0x5af8ee32316a6427f169a45fdc1b3a91a85ac459e3c1cb91c69e1c51f0c1fc21")), + EVMOnChainPublicKey: common.HexToAddress("0x7c5e94162c6fabbdeb3bfe83ae532846e337bfae"), + } ) type updateRMNConfigTestCase struct { useMCMS bool name string + nops []RMNNopConfig } func TestUpdateRMNConfig(t *testing.T) { @@ -25,26 +47,25 @@ func TestUpdateRMNConfig(t *testing.T) { { useMCMS: true, name: "with MCMS", + nops: []RMNNopConfig{rmn_staging_1, rmn_staging_2, rmn_staging_3}, }, { useMCMS: false, name: "without MCMS", + nops: []RMNNopConfig{rmn_staging_1, rmn_staging_2, rmn_staging_3}, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { + t.Parallel() updateRMNConfig(t, tc) }) } } func updateRMNConfig(t *testing.T, tc updateRMNConfigTestCase) { - e := NewMemoryEnvironmentWithJobsAndContracts(t, logger.TestLogger(t), memory.MemoryEnvironmentConfig{ - Chains: 2, - Nodes: 4, - Bootstraps: 1, - }, nil) + e := NewMemoryEnvironment(t) state, err := LoadOnchainState(e.Env) require.NoError(t, err) @@ -86,10 +107,15 @@ func updateRMNConfig(t *testing.T, tc updateRMNConfigTestCase) { } } + nodes := make([]rmn_home.RMNHomeNode, 0, len(tc.nops)) + for _, nop := range tc.nops { + nodes = append(nodes, nop.ToRMNHomeNode()) + } + setRMNHomeCandidateConfig := SetRMNHomeCandidateConfig{ HomeChainSelector: e.HomeChainSel, RMNStaticConfig: rmn_home.RMNHomeStaticConfig{ - Nodes: []rmn_home.RMNHomeNode{}, + Nodes: nodes, OffchainConfig: []byte(""), }, RMNDynamicConfig: rmn_home.RMNHomeDynamicConfig{ @@ -138,16 +164,25 @@ func updateRMNConfig(t *testing.T, tc updateRMNConfigTestCase) { require.NoError(t, err) require.NotEqual(t, previousActiveDigest, currentActiveDigest) + signers := make([]rmn_remote.RMNRemoteSigner, 0, len(tc.nops)) + for _, nop := range tc.nops { + signers = append(signers, nop.ToRMNRemoteSigner()) + } + + remoteConfigs := make(map[uint64]RMNRemoteConfig, len(e.Env.Chains)) + for _, chain := range e.Env.Chains { + remoteConfig := RMNRemoteConfig{ + Signers: signers, + F: 0, + } + + remoteConfigs[chain.Selector] = remoteConfig + } + setRemoteConfig := SetRMNRemoteConfig{ HomeChainSelector: e.HomeChainSel, - Signers: []rmn_remote.RMNRemoteSigner{ - { - OnchainPublicKey: common.Address{}, - NodeIndex: 0, - }, - }, - F: 0, - MCMSConfig: mcmsConfig, + RMNRemoteConfigs: remoteConfigs, + MCMSConfig: mcmsConfig, } _, err = commonchangeset.ApplyChangesets(t, e.Env, timelocksPerChain, []commonchangeset.ChangesetApplication{ @@ -182,3 +217,86 @@ func buildRMNRemoteAddressPerChain(e deployment.Environment, state CCIPOnChainSt } return rmnRemoteAddressPerChain } + +func TestSetRMNRemoteOnRMNProxy(t *testing.T) { + t.Parallel() + e := NewMemoryEnvironment(t, WithNoJobsAndContracts()) + allChains := e.Env.AllChainSelectors() + mcmsCfg := make(map[uint64]commontypes.MCMSWithTimelockConfig) + var err error + for _, c := range e.Env.AllChainSelectors() { + mcmsCfg[c] = proposalutils.SingleGroupTimelockConfig(t) + } + // Need to deploy prerequisites first so that we can form the USDC config + // no proposals to be made, timelock can be passed as nil here + e.Env, err = commonchangeset.ApplyChangesets(t, e.Env, nil, []commonchangeset.ChangesetApplication{ + { + Changeset: commonchangeset.WrapChangeSet(commonchangeset.DeployLinkToken), + Config: allChains, + }, + { + Changeset: commonchangeset.WrapChangeSet(DeployPrerequisites), + Config: DeployPrerequisiteConfig{ + ChainSelectors: allChains, + }, + }, + { + Changeset: commonchangeset.WrapChangeSet(commonchangeset.DeployMCMSWithTimelock), + Config: mcmsCfg, + }, + }) + require.NoError(t, err) + contractsByChain := make(map[uint64][]common.Address) + state, err := LoadOnchainState(e.Env) + require.NoError(t, err) + for _, chain := range allChains { + rmnProxy := state.Chains[chain].RMNProxy + require.NotNil(t, rmnProxy) + contractsByChain[chain] = []common.Address{rmnProxy.Address()} + } + timelockContractsPerChain := make(map[uint64]*proposalutils.TimelockExecutionContracts) + for _, chain := range allChains { + timelockContractsPerChain[chain] = &proposalutils.TimelockExecutionContracts{ + Timelock: state.Chains[chain].Timelock, + CallProxy: state.Chains[chain].CallProxy, + } + } + e.Env, err = commonchangeset.ApplyChangesets(t, e.Env, timelockContractsPerChain, []commonchangeset.ChangesetApplication{ + // transfer ownership of RMNProxy to timelock + { + Changeset: commonchangeset.WrapChangeSet(commonchangeset.TransferToMCMSWithTimelock), + Config: commonchangeset.TransferToMCMSWithTimelockConfig{ + ContractsByChain: contractsByChain, + MinDelay: 0, + }, + }, + { + Changeset: commonchangeset.WrapChangeSet(DeployChainContracts), + Config: DeployChainContractsConfig{ + ChainSelectors: allChains, + HomeChainSelector: e.HomeChainSel, + }, + }, + { + Changeset: commonchangeset.WrapChangeSet(SetRMNRemoteOnRMNProxy), + Config: SetRMNRemoteOnRMNProxyConfig{ + ChainSelectors: allChains, + MCMSConfig: &MCMSConfig{ + MinDelay: 0, + }, + }, + }, + }) + require.NoError(t, err) + state, err = LoadOnchainState(e.Env) + require.NoError(t, err) + for _, chain := range allChains { + rmnProxy := state.Chains[chain].RMNProxy + proxyOwner, err := rmnProxy.Owner(nil) + require.NoError(t, err) + require.Equal(t, state.Chains[chain].Timelock.Address(), proxyOwner) + rmnAddr, err := rmnProxy.GetARM(nil) + require.NoError(t, err) + require.Equal(t, rmnAddr, state.Chains[chain].RMNRemote.Address()) + } +} diff --git a/deployment/ccip/changeset/internal/deploy_home_chain.go b/deployment/ccip/changeset/internal/deploy_home_chain.go index 6328c329b9a..e4c97cae326 100644 --- a/deployment/ccip/changeset/internal/deploy_home_chain.go +++ b/deployment/ccip/changeset/internal/deploy_home_chain.go @@ -8,10 +8,11 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" - "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/libocr/offchainreporting2plus/confighelper" "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3confighelper" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink-ccip/pluginconfig" "github.com/smartcontractkit/chainlink/deployment" @@ -113,24 +114,38 @@ func LatestCCIPDON(registry *capabilities_registry.CapabilitiesRegistry) (*capab func DonIDForChain(registry *capabilities_registry.CapabilitiesRegistry, ccipHome *ccip_home.CCIPHome, chainSelector uint64) (uint32, error) { dons, err := registry.GetDONs(nil) if err != nil { - return 0, err + return 0, fmt.Errorf("get Dons from capability registry: %w", err) } - // TODO: what happens if there are multiple dons for one chain (accidentally?) + var donIDs []uint32 for _, don := range dons { if len(don.CapabilityConfigurations) == 1 && don.CapabilityConfigurations[0].CapabilityId == CCIPCapabilityID { configs, err := ccipHome.GetAllConfigs(nil, don.Id, uint8(types.PluginTypeCCIPCommit)) if err != nil { - return 0, err + return 0, fmt.Errorf("get all commit configs from cciphome: %w", err) } if configs.ActiveConfig.Config.ChainSelector == chainSelector || configs.CandidateConfig.Config.ChainSelector == chainSelector { - return don.Id, nil + donIDs = append(donIDs, don.Id) } } } - return 0, fmt.Errorf("no DON found for chain %d", chainSelector) + + // more than one DON is an error + if len(donIDs) > 1 { + return 0, fmt.Errorf("more than one DON found for (chain selector %d, ccip capability id %x) pair", chainSelector, CCIPCapabilityID[:]) + } + + // no DON found - don ID of 0 indicates that (this is the case in the CR as well). + if len(donIDs) == 0 { + return 0, nil + } + + // DON found - return it. + return donIDs[0], nil } +// BuildSetOCR3ConfigArgs builds the OCR3 config arguments for the OffRamp contract +// using the donID's OCR3 configs from the CCIPHome contract. func BuildSetOCR3ConfigArgs( donID uint32, ccipHome *ccip_home.CCIPHome, @@ -552,3 +567,24 @@ func BuildOCR3ConfigForCCIPHome( return ocr3Configs, nil } + +func DONIdExists(cr *capabilities_registry.CapabilitiesRegistry, donIDs []uint32) error { + // DON ids must exist + dons, err := cr.GetDONs(nil) + if err != nil { + return fmt.Errorf("failed to get dons: %w", err) + } + for _, donID := range donIDs { + exists := false + for _, don := range dons { + if don.Id == donID { + exists = true + break + } + } + if !exists { + return fmt.Errorf("don id %d does not exist", donID) + } + } + return nil +} diff --git a/deployment/ccip/changeset/solana_state.go b/deployment/ccip/changeset/solana_state.go new file mode 100644 index 00000000000..4e5507cfcd3 --- /dev/null +++ b/deployment/ccip/changeset/solana_state.go @@ -0,0 +1,6 @@ +package changeset + +// SolChainState holds a Go binding for all the currently deployed CCIP programs +// on a chain. If a binding is nil, it means here is no such contract on the chain. +type SolCCIPChainState struct { +} diff --git a/deployment/ccip/changeset/state.go b/deployment/ccip/changeset/state.go index 22ae59fc360..2403f3f7cc2 100644 --- a/deployment/ccip/changeset/state.go +++ b/deployment/ccip/changeset/state.go @@ -3,6 +3,8 @@ package changeset import ( "fmt" + "github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers" + burn_mint_token_pool "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/burn_mint_token_pool_1_4_0" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/shared/generated/erc20" @@ -23,7 +25,6 @@ import ( commoncs "github.com/smartcontractkit/chainlink/deployment/common/changeset" commontypes "github.com/smartcontractkit/chainlink/deployment/common/types" common_v1_0 "github.com/smartcontractkit/chainlink/deployment/common/view/v1_0" - "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/ccip_config" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/commit_store" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/mock_rmn_contract" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/registry_module_owner_custom" @@ -48,33 +49,38 @@ import ( ) var ( + // Legacy + CommitStore deployment.ContractType = "CommitStore" + + // Not legacy MockRMN deployment.ContractType = "MockRMN" RMNRemote deployment.ContractType = "RMNRemote" ARMProxy deployment.ContractType = "ARMProxy" WETH9 deployment.ContractType = "WETH9" Router deployment.ContractType = "Router" - CommitStore deployment.ContractType = "CommitStore" TokenAdminRegistry deployment.ContractType = "TokenAdminRegistry" RegistryModule deployment.ContractType = "RegistryModuleOwnerCustom" NonceManager deployment.ContractType = "NonceManager" FeeQuoter deployment.ContractType = "FeeQuoter" CCIPHome deployment.ContractType = "CCIPHome" - CCIPConfig deployment.ContractType = "CCIPConfig" RMNHome deployment.ContractType = "RMNHome" OnRamp deployment.ContractType = "OnRamp" OffRamp deployment.ContractType = "OffRamp" CapabilitiesRegistry deployment.ContractType = "CapabilitiesRegistry" PriceFeed deployment.ContractType = "PriceFeed" - // Note test router maps to a regular router contract. + + // Test contracts. Note test router maps to a regular router contract. TestRouter deployment.ContractType = "TestRouter" Multicall3 deployment.ContractType = "Multicall3" CCIPReceiver deployment.ContractType = "CCIPReceiver" - BurnMintToken deployment.ContractType = "BurnMintToken" - BurnMintTokenPool deployment.ContractType = "BurnMintTokenPool" - USDCToken deployment.ContractType = "USDCToken" USDCMockTransmitter deployment.ContractType = "USDCMockTransmitter" - USDCTokenMessenger deployment.ContractType = "USDCTokenMessenger" - USDCTokenPool deployment.ContractType = "USDCTokenPool" + + // Pools + BurnMintToken deployment.ContractType = "BurnMintToken" + BurnMintTokenPool deployment.ContractType = "BurnMintTokenPool" + USDCToken deployment.ContractType = "USDCToken" + USDCTokenMessenger deployment.ContractType = "USDCTokenMessenger" + USDCTokenPool deployment.ContractType = "USDCTokenPool" ) // CCIPChainState holds a Go binding for all the currently deployed CCIP contracts @@ -83,18 +89,10 @@ type CCIPChainState struct { commoncs.MCMSWithTimelockState commoncs.LinkTokenState commoncs.StaticLinkTokenState - OnRamp *onramp.OnRamp - OffRamp *offramp.OffRamp - FeeQuoter *fee_quoter.FeeQuoter - // We need 2 RMNProxy contracts because we are in the process of migrating to a new version. - // We will switch to the existing one once the migration is complete. - // This is the new RMNProxy contract that will be used for testing RMNRemote before migration. - // Initially RMNProxyNew will point to RMNRemote - RMNProxyNew *rmn_proxy_contract.RMNProxyContract - // Existing RMNProxy contract that is used in production, This already has existing 1.5 RMN set. - // once RMNRemote is tested with RMNProxyNew, as part of migration - // RMNProxyExisting will point to RMNRemote. This will switch over CCIP 1.5 to 1.6 - RMNProxyExisting *rmn_proxy_contract.RMNProxyContract + OnRamp *onramp.OnRamp + OffRamp *offramp.OffRamp + FeeQuoter *fee_quoter.FeeQuoter + RMNProxy *rmn_proxy_contract.RMNProxyContract NonceManager *nonce_manager.NonceManager TokenAdminRegistry *token_admin_registry.TokenAdminRegistry RegistryModule *registry_module_owner_custom.RegistryModuleOwnerCustom @@ -117,8 +115,6 @@ type CCIPChainState struct { CapabilityRegistry *capabilities_registry.CapabilitiesRegistry CCIPHome *ccip_home.CCIPHome RMNHome *rmn_home.RMNHome - // TODO remove once staging upgraded. - CCIPConfig *ccip_config.CCIPConfig // Test contracts Receiver *maybe_revert_message_receiver.MaybeRevertMessageReceiver @@ -159,6 +155,15 @@ func (c CCIPChainState) GenerateView() (view.ChainView, error) { } chainView.RMN[c.RMNRemote.Address().Hex()] = rmnView } + + if c.RMNHome != nil { + rmnHomeView, err := v1_6.GenerateRMNHomeView(c.RMNHome) + if err != nil { + return chainView, errors.Wrapf(err, "failed to generate rmn home view for rmn home %s", c.RMNHome.Address().String()) + } + chainView.RMNHome[c.RMNHome.Address().Hex()] = rmnHomeView + } + if c.FeeQuoter != nil && c.Router != nil && c.TokenAdminRegistry != nil { fqView, err := v1_6.GenerateFeeQuoterView(c.FeeQuoter, c.Router, c.TokenAdminRegistry) if err != nil { @@ -198,12 +203,19 @@ func (c CCIPChainState) GenerateView() (view.ChainView, error) { chainView.CommitStore[c.CommitStore.Address().Hex()] = commitStoreView } - if c.RMNProxyNew != nil { - rmnProxyView, err := v1_0.GenerateRMNProxyView(c.RMNProxyNew) + if c.RMNProxy != nil { + rmnProxyView, err := v1_0.GenerateRMNProxyView(c.RMNProxy) if err != nil { - return chainView, errors.Wrapf(err, "failed to generate rmn proxy view for rmn proxy %s", c.RMNProxyNew.Address().String()) + return chainView, errors.Wrapf(err, "failed to generate rmn proxy view for rmn proxy %s", c.RMNProxy.Address().String()) } - chainView.RMNProxy[c.RMNProxyNew.Address().Hex()] = rmnProxyView + chainView.RMNProxy[c.RMNProxy.Address().Hex()] = rmnProxyView + } + if c.CCIPHome != nil && c.CapabilityRegistry != nil { + chView, err := v1_6.GenerateCCIPHomeView(c.CapabilityRegistry, c.CCIPHome) + if err != nil { + return chainView, errors.Wrapf(err, "failed to generate CCIP home view for CCIP home %s", c.CCIPHome.Address()) + } + chainView.CCIPHome[c.CCIPHome.Address().Hex()] = chView } if c.CapabilityRegistry != nil { capRegView, err := common_v1_0.GenerateCapabilityRegistryView(c.CapabilityRegistry) @@ -243,7 +255,38 @@ type CCIPOnChainState struct { // Populated go bindings for the appropriate version for all contracts. // We would hold 2 versions of each contract here. Once we upgrade we can phase out the old one. // When generating bindings, make sure the package name corresponds to the version. - Chains map[uint64]CCIPChainState + Chains map[uint64]CCIPChainState + SolChains map[uint64]SolCCIPChainState +} + +func (s CCIPOnChainState) GetAllProposerMCMSForChains(chains []uint64) (map[uint64]*gethwrappers.ManyChainMultiSig, error) { + multiSigs := make(map[uint64]*gethwrappers.ManyChainMultiSig) + for _, chain := range chains { + chainState, ok := s.Chains[chain] + if !ok { + return nil, fmt.Errorf("chain %d not found", chain) + } + if chainState.ProposerMcm == nil { + return nil, fmt.Errorf("proposer mcm not found for chain %d", chain) + } + multiSigs[chain] = chainState.ProposerMcm + } + return multiSigs, nil +} + +func (s CCIPOnChainState) GetAllTimeLocksForChains(chains []uint64) (map[uint64]common.Address, error) { + timelocks := make(map[uint64]common.Address) + for _, chain := range chains { + chainState, ok := s.Chains[chain] + if !ok { + return nil, fmt.Errorf("chain %d not found", chain) + } + if chainState.Timelock == nil { + return nil, fmt.Errorf("timelock not found for chain %d", chain) + } + timelocks[chain] = chainState.Timelock.Address() + } + return timelocks, nil } func (s CCIPOnChainState) View(chains []uint64) (map[string]view.ChainView, error) { @@ -292,13 +335,13 @@ func LoadOnchainState(e deployment.Environment) (CCIPOnChainState, error) { // LoadChainState Loads all state for a chain into state func LoadChainState(chain deployment.Chain, addresses map[string]deployment.TypeAndVersion) (CCIPChainState, error) { var state CCIPChainState - mcmsWithTimelock, err := commoncs.MaybeLoadMCMSWithTimelockState(chain, addresses) + mcmsWithTimelock, err := commoncs.MaybeLoadMCMSWithTimelockChainState(chain, addresses) if err != nil { return state, err } state.MCMSWithTimelockState = *mcmsWithTimelock - linkState, err := commoncs.MaybeLoadLinkTokenState(chain, addresses) + linkState, err := commoncs.MaybeLoadLinkTokenChainState(chain, addresses) if err != nil { return state, err } @@ -311,6 +354,7 @@ func LoadChainState(chain deployment.Chain, addresses map[string]deployment.Type for address, tvStr := range addresses { switch tvStr.String() { case deployment.NewTypeAndVersion(commontypes.RBACTimelock, deployment.Version1_0_0).String(), + deployment.NewTypeAndVersion(commontypes.CallProxy, deployment.Version1_0_0).String(), deployment.NewTypeAndVersion(commontypes.ProposerManyChainMultisig, deployment.Version1_0_0).String(), deployment.NewTypeAndVersion(commontypes.CancellerManyChainMultisig, deployment.Version1_0_0).String(), deployment.NewTypeAndVersion(commontypes.BypasserManyChainMultisig, deployment.Version1_0_0).String(), @@ -341,19 +385,7 @@ func LoadChainState(chain deployment.Chain, addresses map[string]deployment.Type if err != nil { return state, err } - state.RMNProxyExisting = armProxy - case deployment.NewTypeAndVersion(ARMProxy, deployment.Version1_6_0_dev).String(): - armProxy, err := rmn_proxy_contract.NewRMNProxyContract(common.HexToAddress(address), chain.Client) - if err != nil { - return state, err - } - state.RMNProxyNew = armProxy - case deployment.NewTypeAndVersion(ARMProxy, deployment.Version1_6_0_dev).String(): - armProxy, err := rmn_proxy_contract.NewRMNProxyContract(common.HexToAddress(address), chain.Client) - if err != nil { - return state, err - } - state.RMNProxyNew = armProxy + state.RMNProxy = armProxy case deployment.NewTypeAndVersion(MockRMN, deployment.Version1_0_0).String(): mockRMN, err := mock_rmn_contract.NewMockRMNContract(common.HexToAddress(address), chain.Client) if err != nil { @@ -452,13 +484,6 @@ func LoadChainState(chain deployment.Chain, addresses map[string]deployment.Type return state, err } state.CCIPHome = ccipHome - case deployment.NewTypeAndVersion(CCIPConfig, deployment.Version1_0_0).String(): - // TODO: Remove once staging upgraded. - ccipConfig, err := ccip_config.NewCCIPConfig(common.HexToAddress(address), chain.Client) - if err != nil { - return state, err - } - state.CCIPConfig = ccipConfig case deployment.NewTypeAndVersion(CCIPReceiver, deployment.Version1_0_0).String(): mr, err := maybe_revert_message_receiver.NewMaybeRevertMessageReceiver(common.HexToAddress(address), chain.Client) if err != nil { diff --git a/deployment/ccip/changeset/state_test.go b/deployment/ccip/changeset/state_test.go index 6e679c265dc..3587332fff2 100644 --- a/deployment/ccip/changeset/state_test.go +++ b/deployment/ccip/changeset/state_test.go @@ -4,19 +4,10 @@ import ( "testing" "github.com/stretchr/testify/require" - - "github.com/smartcontractkit/chainlink/deployment/environment/memory" - "github.com/smartcontractkit/chainlink/v2/core/logger" ) func TestSmokeState(t *testing.T) { - lggr := logger.TestLogger(t) - tenv := NewMemoryEnvironmentWithJobsAndContracts(t, lggr, memory.MemoryEnvironmentConfig{ - Chains: 3, - Nodes: 4, - Bootstraps: 1, - NumOfUsersPerChain: 1, - }, nil) + tenv := NewMemoryEnvironment(t, WithChains(3)) state, err := LoadOnchainState(tenv.Env) require.NoError(t, err) _, err = state.View(tenv.Env.AllChainSelectors()) diff --git a/deployment/ccip/changeset/test_assertions.go b/deployment/ccip/changeset/test_assertions.go index a7d3ecf61f8..a114e52b361 100644 --- a/deployment/ccip/changeset/test_assertions.go +++ b/deployment/ccip/changeset/test_assertions.go @@ -16,6 +16,7 @@ import ( "github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3" commonutils "github.com/smartcontractkit/chainlink-common/pkg/utils" "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" + "github.com/smartcontractkit/chainlink/deployment/environment/memory" "github.com/smartcontractkit/chainlink/deployment" @@ -220,8 +221,8 @@ func ConfirmCommitForAllWithExpectedSeqNums( return false } }, - 3*time.Minute, - 1*time.Second, + tests.WaitTimeout(t), + 2*time.Second, "all commitments did not confirm", ) } @@ -598,7 +599,7 @@ func RequireConsistently(t *testing.T, condition func() bool, duration time.Dura } } -func SeqNumberRageToSlice(seqRanges map[SourceDestPair]ccipocr3.SeqNumRange) map[SourceDestPair][]uint64 { +func SeqNumberRangeToSlice(seqRanges map[SourceDestPair]ccipocr3.SeqNumRange) map[SourceDestPair][]uint64 { flatten := make(map[SourceDestPair][]uint64) for srcDst, seqRange := range seqRanges { diff --git a/deployment/ccip/changeset/test_environment.go b/deployment/ccip/changeset/test_environment.go new file mode 100644 index 00000000000..cb0760cee1c --- /dev/null +++ b/deployment/ccip/changeset/test_environment.go @@ -0,0 +1,445 @@ +package changeset + +import ( + "context" + "fmt" + "math/big" + "os" + "testing" + "time" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + cciptypes "github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3" + "github.com/smartcontractkit/chainlink-ccip/pluginconfig" + commonconfig "github.com/smartcontractkit/chainlink-common/pkg/config" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + jobv1 "github.com/smartcontractkit/chainlink-protos/job-distributor/v1/job" + "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/testcontext" + "github.com/stretchr/testify/require" + "go.uber.org/zap/zapcore" + + "github.com/smartcontractkit/chainlink/deployment" + commonchangeset "github.com/smartcontractkit/chainlink/deployment/common/changeset" + "github.com/smartcontractkit/chainlink/deployment/common/proposalutils" + commontypes "github.com/smartcontractkit/chainlink/deployment/common/types" + "github.com/smartcontractkit/chainlink/deployment/environment/memory" +) + +type EnvType string + +const ( + Memory EnvType = "in-memory" + Docker EnvType = "docker" + ENVTESTTYPE = "CCIP_V16_TEST_ENV" +) + +type TestConfigs struct { + Type EnvType // set by env var CCIP_V16_TEST_ENV, defaults to Memory + CreateJob bool + CreateJobAndContracts bool + Chains int // only used in memory mode, for docker mode, this is determined by the integration-test config toml input + NumOfUsersPerChain int // only used in memory mode, for docker mode, this is determined by the integration-test config toml input + Nodes int // only used in memory mode, for docker mode, this is determined by the integration-test config toml input + Bootstraps int // only used in memory mode, for docker mode, this is determined by the integration-test config toml input + IsUSDC bool + IsUSDCAttestationMissing bool + IsMultiCall3 bool + OCRConfigOverride func(CCIPOCRParams) CCIPOCRParams + RMNEnabled bool + NumOfRMNNodes int + LinkPrice *big.Int + WethPrice *big.Int +} + +func (tc *TestConfigs) Validate() error { + if tc.Chains < 2 { + return fmt.Errorf("chains must be at least 2") + } + if tc.Nodes < 4 { + return fmt.Errorf("nodes must be at least 4") + } + if tc.Bootstraps < 1 { + return fmt.Errorf("bootstraps must be at least 1") + } + if tc.Type == Memory && tc.RMNEnabled { + return fmt.Errorf("cannot run RMN tests in memory mode") + } + return nil +} + +func (tc *TestConfigs) MustSetEnvTypeOrDefault(t *testing.T) { + envType := os.Getenv(ENVTESTTYPE) + if envType == "" || envType == string(Memory) { + tc.Type = Memory + } else if envType == string(Docker) { + tc.Type = Docker + } else { + t.Fatalf("env var CCIP_V16_TEST_ENV must be either %s or %s, defaults to %s if unset, got: %s", Memory, Docker, Memory, envType) + } +} + +func DefaultTestConfigs() *TestConfigs { + return &TestConfigs{ + Chains: 2, + NumOfUsersPerChain: 1, + Nodes: 4, + Bootstraps: 1, + LinkPrice: MockLinkPrice, + WethPrice: MockWethPrice, + CreateJobAndContracts: true, + } +} + +type TestOps func(testCfg *TestConfigs) + +func WithMultiCall3() TestOps { + return func(testCfg *TestConfigs) { + testCfg.IsMultiCall3 = true + } +} + +func WithJobsOnly() TestOps { + return func(testCfg *TestConfigs) { + testCfg.CreateJobAndContracts = false + testCfg.CreateJob = true + } +} + +func WithNoJobsAndContracts() TestOps { + return func(testCfg *TestConfigs) { + testCfg.CreateJobAndContracts = false + testCfg.CreateJob = false + } +} + +func WithRMNEnabled(numOfNode int) TestOps { + return func(testCfg *TestConfigs) { + testCfg.RMNEnabled = true + testCfg.NumOfRMNNodes = numOfNode + } +} + +func WithOCRConfigOverride(override func(CCIPOCRParams) CCIPOCRParams) TestOps { + return func(testCfg *TestConfigs) { + testCfg.OCRConfigOverride = override + } +} + +func WithUSDCAttestationMissing() TestOps { + return func(testCfg *TestConfigs) { + testCfg.IsUSDCAttestationMissing = true + } +} + +func WithUSDC() TestOps { + return func(testCfg *TestConfigs) { + testCfg.IsUSDC = true + } +} + +func WithChains(numChains int) TestOps { + return func(testCfg *TestConfigs) { + testCfg.Chains = numChains + } +} + +func WithUsersPerChain(numUsers int) TestOps { + return func(testCfg *TestConfigs) { + testCfg.NumOfUsersPerChain = numUsers + } +} + +func WithNodes(numNodes int) TestOps { + return func(testCfg *TestConfigs) { + testCfg.Nodes = numNodes + } +} + +func WithBootstraps(numBootstraps int) TestOps { + return func(testCfg *TestConfigs) { + testCfg.Bootstraps = numBootstraps + } +} + +type TestEnvironment interface { + SetupJobs(t *testing.T) + StartNodes(t *testing.T, tc *TestConfigs, crConfig deployment.CapabilityRegistryConfig) + StartChains(t *testing.T, tc *TestConfigs) + DeployedEnvironment() DeployedEnv + MockUSDCAttestationServer(t *testing.T, isUSDCAttestationMissing bool) string +} + +type DeployedEnv struct { + Env deployment.Environment + HomeChainSel uint64 + FeedChainSel uint64 + ReplayBlocks map[uint64]uint64 + Users map[uint64][]*bind.TransactOpts +} + +func (d *DeployedEnv) SetupJobs(t *testing.T) { + ctx := testcontext.Get(t) + out, err := CCIPCapabilityJobspec(d.Env, struct{}{}) + require.NoError(t, err) + for nodeID, jobs := range out.JobSpecs { + for _, job := range jobs { + // Note these auto-accept + _, err := d.Env.Offchain.ProposeJob(ctx, + &jobv1.ProposeJobRequest{ + NodeId: nodeID, + Spec: job, + }) + require.NoError(t, err) + } + } + // Wait for plugins to register filters? + // TODO: Investigate how to avoid. + time.Sleep(30 * time.Second) + ReplayLogs(t, d.Env.Offchain, d.ReplayBlocks) +} + +type MemoryEnvironment struct { + DeployedEnv + chains map[uint64]deployment.Chain +} + +func (m *MemoryEnvironment) DeployedEnvironment() DeployedEnv { + return m.DeployedEnv +} + +func (m *MemoryEnvironment) StartChains(t *testing.T, tc *TestConfigs) { + ctx := testcontext.Get(t) + chains, users := memory.NewMemoryChains(t, tc.Chains, tc.NumOfUsersPerChain) + m.chains = chains + homeChainSel, feedSel := allocateCCIPChainSelectors(chains) + replayBlocks, err := LatestBlocksByChain(ctx, chains) + require.NoError(t, err) + m.DeployedEnv = DeployedEnv{ + Env: deployment.Environment{ + Chains: m.chains, + }, + HomeChainSel: homeChainSel, + FeedChainSel: feedSel, + ReplayBlocks: replayBlocks, + Users: users, + } +} + +func (m *MemoryEnvironment) StartNodes(t *testing.T, tc *TestConfigs, crConfig deployment.CapabilityRegistryConfig) { + require.NotNil(t, m.chains, "start chains first, chains are empty") + require.NotNil(t, m.DeployedEnv, "start chains and initiate deployed env first before starting nodes") + nodes := memory.NewNodes(t, zapcore.InfoLevel, m.chains, tc.Nodes, tc.Bootstraps, crConfig) + ctx := testcontext.Get(t) + lggr := logger.Test(t) + for _, node := range nodes { + require.NoError(t, node.App.Start(ctx)) + t.Cleanup(func() { + require.NoError(t, node.App.Stop()) + }) + } + m.DeployedEnv.Env = memory.NewMemoryEnvironmentFromChainsNodes(func() context.Context { return ctx }, lggr, m.chains, nodes) +} + +func (m *MemoryEnvironment) MockUSDCAttestationServer(t *testing.T, isUSDCAttestationMissing bool) string { + server := mockAttestationResponse(isUSDCAttestationMissing) + endpoint := server.URL + t.Cleanup(func() { + server.Close() + }) + return endpoint +} + +// NewMemoryEnvironment creates an in-memory environment based on the testconfig requested +func NewMemoryEnvironment(t *testing.T, opts ...TestOps) DeployedEnv { + testCfg := DefaultTestConfigs() + for _, opt := range opts { + opt(testCfg) + } + require.NoError(t, testCfg.Validate(), "invalid test config") + env := &MemoryEnvironment{} + if testCfg.CreateJobAndContracts { + return NewEnvironmentWithJobsAndContracts(t, testCfg, env) + } + if testCfg.CreateJob { + return NewEnvironmentWithJobs(t, testCfg, env) + } + return NewEnvironment(t, testCfg, env) +} + +func NewEnvironment(t *testing.T, tc *TestConfigs, tEnv TestEnvironment) DeployedEnv { + lggr := logger.Test(t) + tEnv.StartChains(t, tc) + dEnv := tEnv.DeployedEnvironment() + require.NotEmpty(t, dEnv.FeedChainSel) + require.NotEmpty(t, dEnv.HomeChainSel) + require.NotEmpty(t, dEnv.Env.Chains) + ab := deployment.NewMemoryAddressBook() + crConfig := DeployTestContracts(t, lggr, ab, dEnv.HomeChainSel, dEnv.FeedChainSel, dEnv.Env.Chains, tc.LinkPrice, tc.WethPrice) + tEnv.StartNodes(t, tc, crConfig) + dEnv = tEnv.DeployedEnvironment() + envNodes, err := deployment.NodeInfo(dEnv.Env.NodeIDs, dEnv.Env.Offchain) + require.NoError(t, err) + dEnv.Env.ExistingAddresses = ab + _, err = deployHomeChain(lggr, dEnv.Env, dEnv.Env.ExistingAddresses, dEnv.Env.Chains[dEnv.HomeChainSel], + NewTestRMNStaticConfig(), + NewTestRMNDynamicConfig(), + NewTestNodeOperator(dEnv.Env.Chains[dEnv.HomeChainSel].DeployerKey.From), + map[string][][32]byte{ + "NodeOperator": envNodes.NonBootstraps().PeerIDs(), + }, + ) + require.NoError(t, err) + + return dEnv +} + +func NewEnvironmentWithJobsAndContracts(t *testing.T, tc *TestConfigs, tEnv TestEnvironment) DeployedEnv { + var err error + e := NewEnvironment(t, tc, tEnv) + allChains := e.Env.AllChainSelectors() + mcmsCfg := make(map[uint64]commontypes.MCMSWithTimelockConfig) + + for _, c := range e.Env.AllChainSelectors() { + mcmsCfg[c] = proposalutils.SingleGroupTimelockConfig(t) + } + var ( + usdcChains []uint64 + isMulticall3 bool + ) + if tc != nil { + if tc.IsUSDC { + usdcChains = allChains + } + isMulticall3 = tc.IsMultiCall3 + } + // Need to deploy prerequisites first so that we can form the USDC config + // no proposals to be made, timelock can be passed as nil here + e.Env, err = commonchangeset.ApplyChangesets(t, e.Env, nil, []commonchangeset.ChangesetApplication{ + { + Changeset: commonchangeset.WrapChangeSet(commonchangeset.DeployLinkToken), + Config: allChains, + }, + { + Changeset: commonchangeset.WrapChangeSet(DeployPrerequisites), + Config: DeployPrerequisiteConfig{ + ChainSelectors: allChains, + Opts: []PrerequisiteOpt{ + WithUSDCChains(usdcChains), + WithMulticall3(isMulticall3), + }, + }, + }, + { + Changeset: commonchangeset.WrapChangeSet(commonchangeset.DeployMCMSWithTimelock), + Config: mcmsCfg, + }, + { + Changeset: commonchangeset.WrapChangeSet(DeployChainContracts), + Config: DeployChainContractsConfig{ + ChainSelectors: allChains, + HomeChainSelector: e.HomeChainSel, + }, + }, + { + Changeset: commonchangeset.WrapChangeSet(SetRMNRemoteOnRMNProxy), + Config: SetRMNRemoteOnRMNProxyConfig{ + ChainSelectors: allChains, + }, + }, + }) + require.NoError(t, err) + + state, err := LoadOnchainState(e.Env) + require.NoError(t, err) + // Assert USDC set up as expected. + for _, chain := range usdcChains { + require.NotNil(t, state.Chains[chain].MockUSDCTokenMessenger) + require.NotNil(t, state.Chains[chain].MockUSDCTransmitter) + require.NotNil(t, state.Chains[chain].USDCTokenPool) + } + // Assert link present + require.NotNil(t, state.Chains[e.FeedChainSel].LinkToken) + require.NotNil(t, state.Chains[e.FeedChainSel].Weth9) + + tokenConfig := NewTestTokenConfig(state.Chains[e.FeedChainSel].USDFeeds) + var tokenDataProviders []pluginconfig.TokenDataObserverConfig + if len(usdcChains) > 0 { + endpoint := tEnv.MockUSDCAttestationServer(t, tc.IsUSDCAttestationMissing) + cctpContracts := make(map[cciptypes.ChainSelector]pluginconfig.USDCCCTPTokenConfig) + for _, usdcChain := range usdcChains { + cctpContracts[cciptypes.ChainSelector(usdcChain)] = pluginconfig.USDCCCTPTokenConfig{ + SourcePoolAddress: state.Chains[usdcChain].USDCTokenPool.Address().String(), + SourceMessageTransmitterAddr: state.Chains[usdcChain].MockUSDCTransmitter.Address().String(), + } + } + tokenDataProviders = append(tokenDataProviders, pluginconfig.TokenDataObserverConfig{ + Type: pluginconfig.USDCCCTPHandlerType, + Version: "1.0", + USDCCCTPObserverConfig: &pluginconfig.USDCCCTPObserverConfig{ + Tokens: cctpContracts, + AttestationAPI: endpoint, + AttestationAPITimeout: commonconfig.MustNewDuration(time.Second), + AttestationAPIInterval: commonconfig.MustNewDuration(500 * time.Millisecond), + }}) + } + // Build the per chain config. + chainConfigs := make(map[uint64]CCIPOCRParams) + timelockContractsPerChain := make(map[uint64]*proposalutils.TimelockExecutionContracts) + for _, chain := range allChains { + timelockContractsPerChain[chain] = &proposalutils.TimelockExecutionContracts{ + Timelock: state.Chains[chain].Timelock, + CallProxy: state.Chains[chain].CallProxy, + } + tokenInfo := tokenConfig.GetTokenInfo(e.Env.Logger, state.Chains[chain].LinkToken, state.Chains[chain].Weth9) + ocrParams := DefaultOCRParams(e.FeedChainSel, tokenInfo, tokenDataProviders) + if tc.OCRConfigOverride != nil { + ocrParams = tc.OCRConfigOverride(ocrParams) + } + chainConfigs[chain] = ocrParams + } + // Deploy second set of changesets to deploy and configure the CCIP contracts. + e.Env, err = commonchangeset.ApplyChangesets(t, e.Env, timelockContractsPerChain, []commonchangeset.ChangesetApplication{ + { + Changeset: commonchangeset.WrapChangeSet(ConfigureNewChains), + Config: NewChainsConfig{ + HomeChainSel: e.HomeChainSel, + FeedChainSel: e.FeedChainSel, + ChainConfigByChain: chainConfigs, + }, + }, + { + Changeset: commonchangeset.WrapChangeSet(CCIPCapabilityJobspec), + }, + }) + require.NoError(t, err) + + ReplayLogs(t, e.Env.Offchain, e.ReplayBlocks) + + state, err = LoadOnchainState(e.Env) + require.NoError(t, err) + require.NotNil(t, state.Chains[e.HomeChainSel].CapabilityRegistry) + require.NotNil(t, state.Chains[e.HomeChainSel].CCIPHome) + require.NotNil(t, state.Chains[e.HomeChainSel].RMNHome) + for _, chain := range allChains { + require.NotNil(t, state.Chains[chain].LinkToken) + require.NotNil(t, state.Chains[chain].Weth9) + require.NotNil(t, state.Chains[chain].TokenAdminRegistry) + require.NotNil(t, state.Chains[chain].RegistryModule) + require.NotNil(t, state.Chains[chain].Router) + require.NotNil(t, state.Chains[chain].RMNRemote) + require.NotNil(t, state.Chains[chain].TestRouter) + require.NotNil(t, state.Chains[chain].NonceManager) + require.NotNil(t, state.Chains[chain].FeeQuoter) + require.NotNil(t, state.Chains[chain].OffRamp) + require.NotNil(t, state.Chains[chain].OnRamp) + } + return e +} + +// NewEnvironmentWithJobs creates a new CCIP environment +// with capreg, fee tokens, feeds, nodes and jobs set up. +func NewEnvironmentWithJobs(t *testing.T, tc *TestConfigs, tEnv TestEnvironment) DeployedEnv { + e := NewEnvironment(t, tc, tEnv) + e.SetupJobs(t) + return e +} diff --git a/deployment/ccip/changeset/test_helpers.go b/deployment/ccip/changeset/test_helpers.go index 49edb275526..75801d99cff 100644 --- a/deployment/ccip/changeset/test_helpers.go +++ b/deployment/ccip/changeset/test_helpers.go @@ -13,29 +13,19 @@ import ( "golang.org/x/sync/errgroup" - mapset "github.com/deckarep/golang-set/v2" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/types" "github.com/pkg/errors" - "github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers" - "github.com/smartcontractkit/chainlink-ccip/pluginconfig" - commonconfig "github.com/smartcontractkit/chainlink-common/pkg/config" - - commonchangeset "github.com/smartcontractkit/chainlink/deployment/common/changeset" - commontypes "github.com/smartcontractkit/chainlink/deployment/common/types" "github.com/smartcontractkit/chainlink/v2/core/services/relay" "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" - "go.uber.org/multierr" - "go.uber.org/zap/zapcore" - chainsel "github.com/smartcontractkit/chain-selectors" + "go.uber.org/multierr" - jobv1 "github.com/smartcontractkit/chainlink-protos/job-distributor/v1/job" "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/testcontext" "github.com/smartcontractkit/chainlink-ccip/pkg/reader" @@ -89,35 +79,6 @@ func Context(tb testing.TB) context.Context { return ctx } -type DeployedEnv struct { - Env deployment.Environment - HomeChainSel uint64 - FeedChainSel uint64 - ReplayBlocks map[uint64]uint64 - Users map[uint64][]*bind.TransactOpts -} - -func (e *DeployedEnv) SetupJobs(t *testing.T) { - ctx := testcontext.Get(t) - out, err := CCIPCapabilityJobspec(e.Env, struct{}{}) - require.NoError(t, err) - for nodeID, jobs := range out.JobSpecs { - for _, job := range jobs { - // Note these auto-accept - _, err := e.Env.Offchain.ProposeJob(ctx, - &jobv1.ProposeJobRequest{ - NodeId: nodeID, - Spec: job, - }) - require.NoError(t, err) - } - } - // Wait for plugins to register filters? - // TODO: Investigate how to avoid. - time.Sleep(30 * time.Second) - ReplayLogs(t, e.Env.Offchain, e.ReplayBlocks) -} - func ReplayLogs(t *testing.T, oc deployment.OffchainClient, replayBlocks map[uint64]uint64) { switch oc := oc.(type) { case *memory.JobClient: @@ -185,62 +146,6 @@ func allocateCCIPChainSelectors(chains map[uint64]deployment.Chain) (homeChainSe return chainSels[HomeChainIndex], chainSels[FeedChainIndex] } -// NewMemoryEnvironment creates a new CCIP environment -// with capreg, fee tokens, feeds and nodes set up. -func NewMemoryEnvironment( - t *testing.T, - lggr logger.Logger, - config memory.MemoryEnvironmentConfig, - linkPrice *big.Int, - wethPrice *big.Int) DeployedEnv { - require.GreaterOrEqual(t, config.Chains, 2, "numChains must be at least 2 for home and feed chains") - require.GreaterOrEqual(t, config.Nodes, 4, "numNodes must be at least 4") - ctx := testcontext.Get(t) - chains, users := memory.NewMemoryChains(t, config.Chains, config.NumOfUsersPerChain) - homeChainSel, feedSel := allocateCCIPChainSelectors(chains) - replayBlocks, err := LatestBlocksByChain(ctx, chains) - require.NoError(t, err) - - ab := deployment.NewMemoryAddressBook() - crConfig := DeployTestContracts(t, lggr, ab, homeChainSel, feedSel, chains, linkPrice, wethPrice) - nodes := memory.NewNodes(t, zapcore.InfoLevel, chains, config.Nodes, config.Bootstraps, crConfig) - for _, node := range nodes { - require.NoError(t, node.App.Start(ctx)) - t.Cleanup(func() { - require.NoError(t, node.App.Stop()) - }) - } - e := memory.NewMemoryEnvironmentFromChainsNodes(func() context.Context { return ctx }, lggr, chains, nodes) - envNodes, err := deployment.NodeInfo(e.NodeIDs, e.Offchain) - require.NoError(t, err) - e.ExistingAddresses = ab - _, err = deployHomeChain(lggr, e, e.ExistingAddresses, chains[homeChainSel], - NewTestRMNStaticConfig(), - NewTestRMNDynamicConfig(), - NewTestNodeOperator(chains[homeChainSel].DeployerKey.From), - map[string][][32]byte{ - "NodeOperator": envNodes.NonBootstraps().PeerIDs(), - }, - ) - require.NoError(t, err) - - return DeployedEnv{ - Env: e, - HomeChainSel: homeChainSel, - FeedChainSel: feedSel, - ReplayBlocks: replayBlocks, - Users: users, - } -} - -// NewMemoryEnvironmentWithJobs creates a new CCIP environment -// with capreg, fee tokens, feeds, nodes and jobs set up. -func NewMemoryEnvironmentWithJobs(t *testing.T, lggr logger.Logger, config memory.MemoryEnvironmentConfig) DeployedEnv { - e := NewMemoryEnvironment(t, lggr, config, MockLinkPrice, MockWethPrice) - e.SetupJobs(t) - return e -} - // mockAttestationResponse mocks the USDC attestation server, it returns random Attestation. // We don't need to return exactly the same attestation, because our Mocked USDC contract doesn't rely on any specific // value, but instead of that it just checks if the attestation is present. Therefore, it makes the test a bit simpler @@ -265,150 +170,6 @@ func mockAttestationResponse(isFaulty bool) *httptest.Server { return server } -type TestConfigs struct { - IsUSDC bool - IsUSDCAttestationMissing bool - IsMultiCall3 bool - OCRConfigOverride func(CCIPOCRParams) CCIPOCRParams -} - -func NewMemoryEnvironmentWithJobsAndContracts(t *testing.T, lggr logger.Logger, config memory.MemoryEnvironmentConfig, tCfg *TestConfigs) DeployedEnv { - var err error - e := NewMemoryEnvironment(t, lggr, config, MockLinkPrice, MockWethPrice) - allChains := e.Env.AllChainSelectors() - mcmsCfg := make(map[uint64]commontypes.MCMSWithTimelockConfig) - for _, c := range e.Env.AllChainSelectors() { - mcmsCfg[c] = commontypes.MCMSWithTimelockConfig{ - Canceller: commonchangeset.SingleGroupMCMS(t), - Bypasser: commonchangeset.SingleGroupMCMS(t), - Proposer: commonchangeset.SingleGroupMCMS(t), - TimelockExecutors: e.Env.AllDeployerKeys(), - TimelockMinDelay: big.NewInt(0), - } - } - var ( - usdcChains []uint64 - isMulticall3 bool - ) - if tCfg != nil { - if tCfg.IsUSDC { - usdcChains = allChains - } - isMulticall3 = tCfg.IsMultiCall3 - } - // Need to deploy prerequisites first so that we can form the USDC config - // no proposals to be made, timelock can be passed as nil here - e.Env, err = commonchangeset.ApplyChangesets(t, e.Env, nil, []commonchangeset.ChangesetApplication{ - { - Changeset: commonchangeset.WrapChangeSet(commonchangeset.DeployLinkToken), - Config: allChains, - }, - { - Changeset: commonchangeset.WrapChangeSet(DeployPrerequisites), - Config: DeployPrerequisiteConfig{ - ChainSelectors: allChains, - Opts: []PrerequisiteOpt{ - WithUSDCChains(usdcChains), - WithMulticall3(isMulticall3), - }, - }, - }, - { - Changeset: commonchangeset.WrapChangeSet(commonchangeset.DeployMCMSWithTimelock), - Config: mcmsCfg, - }, - { - Changeset: commonchangeset.WrapChangeSet(DeployChainContracts), - Config: DeployChainContractsConfig{ - ChainSelectors: allChains, - HomeChainSelector: e.HomeChainSel, - }, - }, - }) - require.NoError(t, err) - - state, err := LoadOnchainState(e.Env) - require.NoError(t, err) - // Assert USDC set up as expected. - for _, chain := range usdcChains { - require.NotNil(t, state.Chains[chain].MockUSDCTokenMessenger) - require.NotNil(t, state.Chains[chain].MockUSDCTransmitter) - require.NotNil(t, state.Chains[chain].USDCTokenPool) - } - // Assert link present - require.NotNil(t, state.Chains[e.FeedChainSel].LinkToken) - require.NotNil(t, state.Chains[e.FeedChainSel].Weth9) - - tokenConfig := NewTestTokenConfig(state.Chains[e.FeedChainSel].USDFeeds) - var tokenDataProviders []pluginconfig.TokenDataObserverConfig - if len(usdcChains) > 0 { - server := mockAttestationResponse(tCfg.IsUSDCAttestationMissing) - endpoint := server.URL - t.Cleanup(func() { - server.Close() - }) - cctpContracts := make(map[cciptypes.ChainSelector]pluginconfig.USDCCCTPTokenConfig) - for _, usdcChain := range usdcChains { - cctpContracts[cciptypes.ChainSelector(usdcChain)] = pluginconfig.USDCCCTPTokenConfig{ - SourcePoolAddress: state.Chains[usdcChain].USDCTokenPool.Address().String(), - SourceMessageTransmitterAddr: state.Chains[usdcChain].MockUSDCTransmitter.Address().String(), - } - } - tokenDataProviders = append(tokenDataProviders, pluginconfig.TokenDataObserverConfig{ - Type: pluginconfig.USDCCCTPHandlerType, - Version: "1.0", - USDCCCTPObserverConfig: &pluginconfig.USDCCCTPObserverConfig{ - Tokens: cctpContracts, - AttestationAPI: endpoint, - AttestationAPITimeout: commonconfig.MustNewDuration(time.Second), - AttestationAPIInterval: commonconfig.MustNewDuration(500 * time.Millisecond), - }}) - } - // Build the per chain config. - chainConfigs := make(map[uint64]CCIPOCRParams) - timelocksPerChain := make(map[uint64]*gethwrappers.RBACTimelock) - for _, chain := range allChains { - timelocksPerChain[chain] = state.Chains[chain].Timelock - tokenInfo := tokenConfig.GetTokenInfo(e.Env.Logger, state.Chains[chain].LinkToken, state.Chains[chain].Weth9) - chainConfigs[chain] = DefaultOCRParams(e.FeedChainSel, tokenInfo, tokenDataProviders) - } - // Deploy second set of changesets to deploy and configure the CCIP contracts. - e.Env, err = commonchangeset.ApplyChangesets(t, e.Env, timelocksPerChain, []commonchangeset.ChangesetApplication{ - { - Changeset: commonchangeset.WrapChangeSet(ConfigureNewChains), - Config: NewChainsConfig{ - HomeChainSel: e.HomeChainSel, - FeedChainSel: e.FeedChainSel, - ChainConfigByChain: chainConfigs, - }, - }, - { - Changeset: commonchangeset.WrapChangeSet(CCIPCapabilityJobspec), - }, - }) - require.NoError(t, err) - - state, err = LoadOnchainState(e.Env) - require.NoError(t, err) - require.NotNil(t, state.Chains[e.HomeChainSel].CapabilityRegistry) - require.NotNil(t, state.Chains[e.HomeChainSel].CCIPHome) - require.NotNil(t, state.Chains[e.HomeChainSel].RMNHome) - for _, chain := range allChains { - require.NotNil(t, state.Chains[chain].LinkToken) - require.NotNil(t, state.Chains[chain].Weth9) - require.NotNil(t, state.Chains[chain].TokenAdminRegistry) - require.NotNil(t, state.Chains[chain].RegistryModule) - require.NotNil(t, state.Chains[chain].Router) - require.NotNil(t, state.Chains[chain].RMNRemote) - require.NotNil(t, state.Chains[chain].TestRouter) - require.NotNil(t, state.Chains[chain].NonceManager) - require.NotNil(t, state.Chains[chain].FeeQuoter) - require.NotNil(t, state.Chains[chain].OffRamp) - require.NotNil(t, state.Chains[chain].OnRamp) - } - return e -} - func CCIPSendRequest( e deployment.Environment, state CCIPOnChainState, @@ -805,35 +566,6 @@ func ConfirmRequestOnSourceAndDest(t *testing.T, env deployment.Environment, sta return nil } -// TODO: Remove this to replace with ApplyChangeset -func ProcessChangeset(t *testing.T, e deployment.Environment, c deployment.ChangesetOutput) { - - // TODO: Add support for jobspecs as well - - // sign and execute all proposals provided - if len(c.Proposals) != 0 { - state, err := LoadOnchainState(e) - require.NoError(t, err) - for _, prop := range c.Proposals { - chains := mapset.NewSet[uint64]() - for _, op := range prop.Transactions { - chains.Add(uint64(op.ChainIdentifier)) - } - - signed := commonchangeset.SignProposal(t, e, &prop) - for _, sel := range chains.ToSlice() { - commonchangeset.ExecuteProposal(t, e, signed, state.Chains[sel].Timelock, sel) - } - } - } - - // merge address books - if c.AddressBook != nil { - err := e.ExistingAddresses.Merge(c.AddressBook) - require.NoError(t, err) - } -} - func DeployTransferableToken( lggr logger.Logger, chains map[uint64]deployment.Chain, diff --git a/deployment/ccip/changeset/view_test.go b/deployment/ccip/changeset/view_test.go index 934b937f7b5..35193979849 100644 --- a/deployment/ccip/changeset/view_test.go +++ b/deployment/ccip/changeset/view_test.go @@ -4,19 +4,11 @@ import ( "testing" "github.com/stretchr/testify/require" - - "github.com/smartcontractkit/chainlink/deployment/environment/memory" - "github.com/smartcontractkit/chainlink/v2/core/logger" ) func TestSmokeView(t *testing.T) { - lggr := logger.TestLogger(t) - tenv := NewMemoryEnvironmentWithJobsAndContracts(t, lggr, memory.MemoryEnvironmentConfig{ - Chains: 3, - Nodes: 4, - Bootstraps: 1, - NumOfUsersPerChain: 1, - }, nil) + t.Parallel() + tenv := NewMemoryEnvironment(t, WithChains(3)) _, err := ViewCCIP(tenv.Env) require.NoError(t, err) } diff --git a/deployment/ccip/view/v1_2/price_registry.go b/deployment/ccip/view/v1_2/price_registry.go new file mode 100644 index 00000000000..ee0f1067b6c --- /dev/null +++ b/deployment/ccip/view/v1_2/price_registry.go @@ -0,0 +1,45 @@ +package v1_2 + +import ( + "fmt" + + "github.com/ethereum/go-ethereum/common" + + "github.com/smartcontractkit/chainlink/deployment/common/view/types" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/price_registry_1_2_0" +) + +type PriceRegistryView struct { + types.ContractMetaData + FeeTokens []common.Address `json:"feeTokens"` + StalenessThreshold string `json:"stalenessThreshold"` + Updaters []common.Address `json:"updaters"` +} + +func GeneratePriceRegistryView(pr *price_registry_1_2_0.PriceRegistry) (PriceRegistryView, error) { + if pr == nil { + return PriceRegistryView{}, fmt.Errorf("cannot generate view for nil PriceRegistry") + } + meta, err := types.NewContractMetaData(pr, pr.Address()) + if err != nil { + return PriceRegistryView{}, fmt.Errorf("failed to generate contract metadata for PriceRegistry %s: %w", pr.Address(), err) + } + ft, err := pr.GetFeeTokens(nil) + if err != nil { + return PriceRegistryView{}, fmt.Errorf("failed to get fee tokens %s: %w", pr.Address(), err) + } + st, err := pr.GetStalenessThreshold(nil) + if err != nil { + return PriceRegistryView{}, fmt.Errorf("failed to get staleness threshold %s: %w", pr.Address(), err) + } + updaters, err := pr.GetPriceUpdaters(nil) + if err != nil { + return PriceRegistryView{}, fmt.Errorf("failed to get price updaters %s: %w", pr.Address(), err) + } + return PriceRegistryView{ + ContractMetaData: meta, + FeeTokens: ft, + StalenessThreshold: st.String(), + Updaters: updaters, + }, nil +} diff --git a/deployment/ccip/view/v1_2/price_registry_test.go b/deployment/ccip/view/v1_2/price_registry_test.go new file mode 100644 index 00000000000..cbcdbe253ce --- /dev/null +++ b/deployment/ccip/view/v1_2/price_registry_test.go @@ -0,0 +1,38 @@ +package v1_2 + +import ( + "encoding/json" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap/zapcore" + + "github.com/smartcontractkit/chainlink/deployment" + "github.com/smartcontractkit/chainlink/deployment/environment/memory" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/price_registry_1_2_0" + "github.com/smartcontractkit/chainlink/v2/core/logger" +) + +func TestGeneratePriceRegistryView(t *testing.T) { + e := memory.NewMemoryEnvironment(t, logger.TestLogger(t), zapcore.InfoLevel, memory.MemoryEnvironmentConfig{ + Chains: 1, + }) + chain := e.Chains[e.AllChainSelectors()[0]] + f1, f2 := common.HexToAddress("0x1"), common.HexToAddress("0x2") + _, tx, c, err := price_registry_1_2_0.DeployPriceRegistry( + chain.DeployerKey, chain.Client, []common.Address{chain.DeployerKey.From}, []common.Address{f1, f2}, uint32(10)) + _, err = deployment.ConfirmIfNoError(chain, tx, err) + require.NoError(t, err) + + v, err := GeneratePriceRegistryView(c) + require.NoError(t, err) + assert.Equal(t, v.Owner, chain.DeployerKey.From) + assert.Equal(t, v.TypeAndVersion, "PriceRegistry 1.2.0") + assert.Equal(t, v.FeeTokens, []common.Address{f1, f2}) + assert.Equal(t, v.StalenessThreshold, "10") + assert.Equal(t, v.Updaters, []common.Address{chain.DeployerKey.From}) + _, err = json.MarshalIndent(v, "", " ") + require.NoError(t, err) +} diff --git a/deployment/ccip/view/v1_5/offramp.go b/deployment/ccip/view/v1_5/offramp.go new file mode 100644 index 00000000000..95e40d9da27 --- /dev/null +++ b/deployment/ccip/view/v1_5/offramp.go @@ -0,0 +1,40 @@ +package v1_5 + +import ( + "fmt" + + "github.com/smartcontractkit/chainlink/deployment/common/view/types" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/evm_2_evm_offramp" +) + +type OffRampView struct { + types.ContractMetaData + StaticConfig evm_2_evm_offramp.EVM2EVMOffRampStaticConfig `json:"staticConfig"` + DynamicConfig evm_2_evm_offramp.EVM2EVMOffRampDynamicConfig `json:"dynamicConfig"` +} + +func GenerateOffRampView(r *evm_2_evm_offramp.EVM2EVMOffRamp) (OffRampView, error) { + if r == nil { + return OffRampView{}, fmt.Errorf("cannot generate view for nil OffRamp") + } + meta, err := types.NewContractMetaData(r, r.Address()) + if err != nil { + return OffRampView{}, fmt.Errorf("failed to generate contract metadata for OffRamp %s: %w", r.Address(), err) + } + staticConfig, err := r.GetStaticConfig(nil) + if err != nil { + return OffRampView{}, fmt.Errorf("failed to get static config for OffRamp %s: %w", r.Address(), err) + } + dynamicConfig, err := r.GetDynamicConfig(nil) + if err != nil { + return OffRampView{}, fmt.Errorf("failed to get dynamic config for OffRamp %s: %w", r.Address(), err) + } + + // TODO: If needed, we can filter logs to get the OCR config. + // May not be required for the legacy contracts. + return OffRampView{ + ContractMetaData: meta, + StaticConfig: staticConfig, + DynamicConfig: dynamicConfig, + }, nil +} diff --git a/deployment/ccip/view/v1_5/offramp_test.go b/deployment/ccip/view/v1_5/offramp_test.go new file mode 100644 index 00000000000..d6539fe2ba5 --- /dev/null +++ b/deployment/ccip/view/v1_5/offramp_test.go @@ -0,0 +1,60 @@ +package v1_5 + +import ( + "encoding/json" + "testing" + + "github.com/ethereum/go-ethereum/common" + chainsel "github.com/smartcontractkit/chain-selectors" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap/zapcore" + + "math/big" + + "github.com/smartcontractkit/chainlink/deployment" + "github.com/smartcontractkit/chainlink/deployment/environment/memory" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/commit_store" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/evm_2_evm_offramp" + "github.com/smartcontractkit/chainlink/v2/core/logger" +) + +func TestOffRampView(t *testing.T) { + e := memory.NewMemoryEnvironment(t, logger.TestLogger(t), zapcore.InfoLevel, memory.MemoryEnvironmentConfig{ + Chains: 1, + }) + chain := e.Chains[e.AllChainSelectors()[0]] + _, tx, c, err := commit_store.DeployCommitStore( + chain.DeployerKey, chain.Client, commit_store.CommitStoreStaticConfig{ + ChainSelector: chainsel.TEST_90000002.Selector, + SourceChainSelector: chainsel.TEST_90000001.Selector, + OnRamp: common.HexToAddress("0x4"), + RmnProxy: common.HexToAddress("0x1"), + }) + _, err = deployment.ConfirmIfNoError(chain, tx, err) + require.NoError(t, err) + sc := evm_2_evm_offramp.EVM2EVMOffRampStaticConfig{ + ChainSelector: chainsel.TEST_90000002.Selector, + SourceChainSelector: chainsel.TEST_90000001.Selector, + RmnProxy: common.HexToAddress("0x1"), + CommitStore: c.Address(), + TokenAdminRegistry: common.HexToAddress("0x3"), + OnRamp: common.HexToAddress("0x4"), + } + rl := evm_2_evm_offramp.RateLimiterConfig{ + IsEnabled: true, + Capacity: big.NewInt(100), + Rate: big.NewInt(10), + } + _, tx, c2, err := evm_2_evm_offramp.DeployEVM2EVMOffRamp( + chain.DeployerKey, chain.Client, sc, rl) + _, err = deployment.ConfirmIfNoError(chain, tx, err) + require.NoError(t, err) + + v, err := GenerateOffRampView(c2) + require.NoError(t, err) + assert.Equal(t, v.StaticConfig, sc) + assert.Equal(t, v.TypeAndVersion, "EVM2EVMOffRamp 1.5.0") + _, err = json.MarshalIndent(v, "", " ") + require.NoError(t, err) +} diff --git a/deployment/ccip/view/v1_5/onramp.go b/deployment/ccip/view/v1_5/onramp.go new file mode 100644 index 00000000000..d679f6c14c0 --- /dev/null +++ b/deployment/ccip/view/v1_5/onramp.go @@ -0,0 +1,39 @@ +package v1_5 + +import ( + "fmt" + + "github.com/smartcontractkit/chainlink/deployment/common/view/types" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/evm_2_evm_onramp" +) + +type OnRampView struct { + types.ContractMetaData + StaticConfig evm_2_evm_onramp.EVM2EVMOnRampStaticConfig `json:"staticConfig"` + DynamicConfig evm_2_evm_onramp.EVM2EVMOnRampDynamicConfig `json:"dynamicConfig"` +} + +func GenerateOnRampView(r *evm_2_evm_onramp.EVM2EVMOnRamp) (OnRampView, error) { + if r == nil { + return OnRampView{}, fmt.Errorf("cannot generate view for nil OnRamp") + } + meta, err := types.NewContractMetaData(r, r.Address()) + if err != nil { + return OnRampView{}, fmt.Errorf("failed to generate contract metadata for OnRamp %s: %w", r.Address(), err) + } + staticConfig, err := r.GetStaticConfig(nil) + if err != nil { + return OnRampView{}, fmt.Errorf("failed to get static config for OnRamp %s: %w", r.Address(), err) + } + dynamicConfig, err := r.GetDynamicConfig(nil) + if err != nil { + return OnRampView{}, fmt.Errorf("failed to get dynamic config for OnRamp %s: %w", r.Address(), err) + } + + // Add billing if needed, maybe not required for legacy contract? + return OnRampView{ + ContractMetaData: meta, + StaticConfig: staticConfig, + DynamicConfig: dynamicConfig, + }, nil +} diff --git a/deployment/ccip/view/v1_5/onramp_test.go b/deployment/ccip/view/v1_5/onramp_test.go new file mode 100644 index 00000000000..4d7ef0225a6 --- /dev/null +++ b/deployment/ccip/view/v1_5/onramp_test.go @@ -0,0 +1,71 @@ +package v1_5 + +import ( + "encoding/json" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap/zapcore" + + "github.com/smartcontractkit/chainlink/deployment" + "github.com/smartcontractkit/chainlink/deployment/environment/memory" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/evm_2_evm_onramp" + "github.com/smartcontractkit/chainlink/v2/core/logger" +) + +func TestOnRampView(t *testing.T) { + e := memory.NewMemoryEnvironment(t, logger.TestLogger(t), zapcore.InfoLevel, memory.MemoryEnvironmentConfig{ + Chains: 1, + }) + chain := e.Chains[e.AllChainSelectors()[0]] + _, tx, c, err := evm_2_evm_onramp.DeployEVM2EVMOnRamp( + chain.DeployerKey, chain.Client, + evm_2_evm_onramp.EVM2EVMOnRampStaticConfig{ + LinkToken: common.HexToAddress("0x1"), + ChainSelector: chain.Selector, + DestChainSelector: 100, + DefaultTxGasLimit: 10, + MaxNopFeesJuels: big.NewInt(10), + PrevOnRamp: common.Address{}, + RmnProxy: common.HexToAddress("0x2"), + TokenAdminRegistry: common.HexToAddress("0x3"), + }, + evm_2_evm_onramp.EVM2EVMOnRampDynamicConfig{ + Router: common.HexToAddress("0x4"), + MaxNumberOfTokensPerMsg: 0, + DestGasOverhead: 0, + DestGasPerPayloadByte: 0, + DestDataAvailabilityOverheadGas: 0, + DestGasPerDataAvailabilityByte: 0, + DestDataAvailabilityMultiplierBps: 0, + PriceRegistry: common.HexToAddress("0x5"), + MaxDataBytes: 0, + MaxPerMsgGasLimit: 0, + DefaultTokenFeeUSDCents: 0, + DefaultTokenDestGasOverhead: 0, + EnforceOutOfOrder: false, + }, + evm_2_evm_onramp.RateLimiterConfig{ + IsEnabled: true, + Capacity: big.NewInt(100), + Rate: big.NewInt(10), + }, + []evm_2_evm_onramp.EVM2EVMOnRampFeeTokenConfigArgs{}, + []evm_2_evm_onramp.EVM2EVMOnRampTokenTransferFeeConfigArgs{}, + []evm_2_evm_onramp.EVM2EVMOnRampNopAndWeight{}, + ) + _, err = deployment.ConfirmIfNoError(chain, tx, err) + require.NoError(t, err) + v, err := GenerateOnRampView(c) + require.NoError(t, err) + // Check a few fields. + assert.Equal(t, v.StaticConfig.ChainSelector, chain.Selector) + assert.Equal(t, v.DynamicConfig.Router, common.HexToAddress("0x4")) + assert.Equal(t, v.TypeAndVersion, "EVM2EVMOnRamp 1.5.0") + _, err = json.MarshalIndent(v, "", " ") + require.NoError(t, err) + +} diff --git a/deployment/ccip/view/v1_5/rmn.go b/deployment/ccip/view/v1_5/rmn.go new file mode 100644 index 00000000000..cef55460446 --- /dev/null +++ b/deployment/ccip/view/v1_5/rmn.go @@ -0,0 +1,31 @@ +package v1_5 + +import ( + "fmt" + + "github.com/smartcontractkit/chainlink/deployment/common/view/types" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/rmn_contract" +) + +type RMNView struct { + types.ContractMetaData + ConfigDetails rmn_contract.GetConfigDetails `json:"configDetails"` +} + +func GenerateRMNView(r *rmn_contract.RMNContract) (RMNView, error) { + if r == nil { + return RMNView{}, fmt.Errorf("cannot generate view for nil RMN") + } + meta, err := types.NewContractMetaData(r, r.Address()) + if err != nil { + return RMNView{}, fmt.Errorf("failed to generate contract metadata for RMN: %w", err) + } + config, err := r.GetConfigDetails(nil) + if err != nil { + return RMNView{}, fmt.Errorf("failed to get config details for RMN: %w", err) + } + return RMNView{ + ContractMetaData: meta, + ConfigDetails: config, + }, nil +} diff --git a/deployment/ccip/view/v1_5/rmn_test.go b/deployment/ccip/view/v1_5/rmn_test.go new file mode 100644 index 00000000000..3ec7d7a9cc9 --- /dev/null +++ b/deployment/ccip/view/v1_5/rmn_test.go @@ -0,0 +1,53 @@ +package v1_5 + +import ( + "encoding/json" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap/zapcore" + + "github.com/smartcontractkit/chainlink/deployment/environment/memory" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/rmn_contract" + "github.com/smartcontractkit/chainlink/v2/core/logger" +) + +func TestGenerateRMNView(t *testing.T) { + e := memory.NewMemoryEnvironment(t, logger.TestLogger(t), zapcore.InfoLevel, memory.MemoryEnvironmentConfig{ + Chains: 1, + }) + chain := e.Chains[e.AllChainSelectors()[0]] + cfg := rmn_contract.RMNConfig{ + Voters: []rmn_contract.RMNVoter{ + { + BlessVoteAddr: chain.DeployerKey.From, + CurseVoteAddr: common.HexToAddress("0x3"), + BlessWeight: 1, + CurseWeight: 1, + }, + { + BlessVoteAddr: common.HexToAddress("0x1"), + CurseVoteAddr: common.HexToAddress("0x2"), + BlessWeight: 1, + CurseWeight: 1, + }, + }, + BlessWeightThreshold: uint16(2), + CurseWeightThreshold: uint16(1), + } + _, tx, c, err := rmn_contract.DeployRMNContract( + chain.DeployerKey, chain.Client, cfg) + require.NoError(t, err) + _, err = chain.Confirm(tx) + require.NoError(t, err) + v, err := GenerateRMNView(c) + require.NoError(t, err) + assert.Equal(t, v.Owner, chain.DeployerKey.From) + assert.Equal(t, v.TypeAndVersion, "RMN 1.5.0") + assert.Equal(t, v.ConfigDetails.Version, uint32(1)) + assert.Equal(t, v.ConfigDetails.Config, cfg) + _, err = json.MarshalIndent(v, "", " ") + require.NoError(t, err) +} diff --git a/deployment/ccip/view/v1_6/capreg.go b/deployment/ccip/view/v1_6/capreg.go deleted file mode 100644 index 26ec545d98e..00000000000 --- a/deployment/ccip/view/v1_6/capreg.go +++ /dev/null @@ -1,45 +0,0 @@ -package v1_6 - -import ( - "github.com/ethereum/go-ethereum/common" - - "github.com/smartcontractkit/chainlink/deployment/common/view/types" - "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/capabilities_registry" -) - -// CapRegView denotes a view of the capabilities registry contract. -// Note that the contract itself is 1.0.0 versioned, but we're releasing it first -// as part of 1.6 for CCIP. -type CapRegView struct { - types.ContractMetaData - Capabilities []CapabilityView `json:"capabilities,omitempty"` -} - -type CapabilityView struct { - LabelledName string `json:"labelledName"` - Version string `json:"version"` - ConfigContract common.Address `json:"configContract"` -} - -func GenerateCapRegView(capReg *capabilities_registry.CapabilitiesRegistry) (CapRegView, error) { - tv, err := types.NewContractMetaData(capReg, capReg.Address()) - if err != nil { - return CapRegView{}, err - } - caps, err := capReg.GetCapabilities(nil) - if err != nil { - return CapRegView{}, err - } - var capViews []CapabilityView - for _, capability := range caps { - capViews = append(capViews, CapabilityView{ - LabelledName: capability.LabelledName, - Version: capability.Version, - ConfigContract: capability.ConfigurationContract, - }) - } - return CapRegView{ - ContractMetaData: tv, - Capabilities: capViews, - }, nil -} diff --git a/deployment/ccip/view/v1_6/ccip_home.go b/deployment/ccip/view/v1_6/ccip_home.go new file mode 100644 index 00000000000..ac1e23179c4 --- /dev/null +++ b/deployment/ccip/view/v1_6/ccip_home.go @@ -0,0 +1,85 @@ +package v1_6 + +import ( + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum/common" + + "github.com/smartcontractkit/chainlink/deployment/common/view/types" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/ccip_home" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/capabilities_registry" +) + +// https://github.com/smartcontractkit/chainlink/blob/develop/contracts/src/v0.8/ccip/libraries/Internal.sol#L190 +const ( + CommitPluginType = 0 + ExecPluginType = 1 +) + +type DonView struct { + DonID uint32 `json:"donID"` + // TODO: find a way to hexify the bytes here + CommitConfigs ccip_home.GetAllConfigs `json:"commitConfigs"` + ExecConfigs ccip_home.GetAllConfigs `json:"execConfigs"` +} + +type CCIPHomeView struct { + types.ContractMetaData + ChainConfigs []ccip_home.CCIPHomeChainConfigArgs `json:"chainConfigs"` + CapabilityRegistry common.Address `json:"capabilityRegistry"` + Dons []DonView `json:"dons"` +} + +func GenerateCCIPHomeView(cr *capabilities_registry.CapabilitiesRegistry, ch *ccip_home.CCIPHome) (CCIPHomeView, error) { + if ch == nil { + return CCIPHomeView{}, fmt.Errorf("cannot generate view for nil CCIPHome") + } + meta, err := types.NewContractMetaData(ch, ch.Address()) + if err != nil { + return CCIPHomeView{}, fmt.Errorf("failed to generate contract metadata for CCIPHome %s: %w", ch.Address(), err) + } + numChains, err := ch.GetNumChainConfigurations(nil) + if err != nil { + return CCIPHomeView{}, fmt.Errorf("failed to get number of chain configurations for CCIPHome %s: %w", ch.Address(), err) + } + // Pagination shouldn't be required here, but we can add it if needed. + chains, err := ch.GetAllChainConfigs(nil, big.NewInt(0), numChains) + if err != nil { + return CCIPHomeView{}, fmt.Errorf("failed to get all chain configs for CCIPHome %s: %w", ch.Address(), err) + } + crAddr, err := ch.GetCapabilityRegistry(nil) + if err != nil { + return CCIPHomeView{}, fmt.Errorf("failed to get capability registry for CCIPHome %s: %w", ch.Address(), err) + } + if crAddr != cr.Address() { + return CCIPHomeView{}, fmt.Errorf("capability registry address mismatch for CCIPHome %s: %w", ch.Address(), err) + } + dons, err := cr.GetDONs(nil) + if err != nil { + return CCIPHomeView{}, fmt.Errorf("failed to get DONs for CCIPHome %s: %w", ch.Address(), err) + } + // Get every don's configuration. + var dvs []DonView + for _, d := range dons { + commitConfigs, err := ch.GetAllConfigs(nil, d.Id, CommitPluginType) + if err != nil { + return CCIPHomeView{}, fmt.Errorf("failed to get active commit config for CCIPHome %s: %w", ch.Address(), err) + } + execConfigs, err := ch.GetAllConfigs(nil, d.Id, ExecPluginType) + if err != nil { + return CCIPHomeView{}, fmt.Errorf("failed to get active commit config for CCIPHome %s: %w", ch.Address(), err) + } + dvs = append(dvs, DonView{ + DonID: d.Id, + CommitConfigs: commitConfigs, + ExecConfigs: execConfigs, + }) + } + return CCIPHomeView{ + ContractMetaData: meta, + ChainConfigs: chains, + CapabilityRegistry: crAddr, + Dons: dvs, + }, nil +} diff --git a/deployment/ccip/view/v1_6/ccip_home_test.go b/deployment/ccip/view/v1_6/ccip_home_test.go new file mode 100644 index 00000000000..26f6f50aed5 --- /dev/null +++ b/deployment/ccip/view/v1_6/ccip_home_test.go @@ -0,0 +1,40 @@ +package v1_6 + +import ( + "encoding/json" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap/zapcore" + + "github.com/smartcontractkit/chainlink/deployment" + "github.com/smartcontractkit/chainlink/deployment/environment/memory" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/ccip_home" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/capabilities_registry" + "github.com/smartcontractkit/chainlink/v2/core/logger" +) + +func TestCCIPHomeView(t *testing.T) { + e := memory.NewMemoryEnvironment(t, logger.TestLogger(t), zapcore.InfoLevel, memory.MemoryEnvironmentConfig{ + Chains: 1, + }) + chain := e.Chains[e.AllChainSelectors()[0]] + _, tx, cr, err := capabilities_registry.DeployCapabilitiesRegistry( + chain.DeployerKey, chain.Client) + require.NoError(t, err) + _, err = deployment.ConfirmIfNoError(chain, tx, err) + require.NoError(t, err) + + _, tx, ch, err := ccip_home.DeployCCIPHome( + chain.DeployerKey, chain.Client, cr.Address()) + _, err = deployment.ConfirmIfNoError(chain, tx, err) + require.NoError(t, err) + + v, err := GenerateCCIPHomeView(cr, ch) + require.NoError(t, err) + assert.Equal(t, v.TypeAndVersion, "CCIPHome 1.6.0-dev") + + _, err = json.MarshalIndent(v, "", " ") + require.NoError(t, err) +} diff --git a/deployment/ccip/view/v1_6/rmnhome.go b/deployment/ccip/view/v1_6/rmnhome.go new file mode 100644 index 00000000000..82d39074d6f --- /dev/null +++ b/deployment/ccip/view/v1_6/rmnhome.go @@ -0,0 +1,214 @@ +package v1_6 + +import ( + "encoding/hex" + "encoding/json" + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/smartcontractkit/chainlink/deployment/common/view/types" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/rmn_home" +) + +type RMNHomeView struct { + types.ContractMetaData + CandidateConfig *RMNHomeVersionedConfig `json:"candidateConfig,omitempty"` + ActiveConfig *RMNHomeVersionedConfig `json:"activeConfig,omitempty"` +} + +type RMNHomeVersionedConfig struct { + Version uint32 `json:"version"` + StaticConfig RMNHomeStaticConfig `json:"staticConfig"` + DynamicConfig RMNHomeDynamicConfig `json:"dynamicConfig"` + Digest [32]byte `json:"digest"` +} + +func decodeHexString(hexStr string, expectedLength int) ([]byte, error) { + bytes, err := hex.DecodeString(hexStr) + if err != nil { + return nil, err + } + if len(bytes) != expectedLength { + return nil, fmt.Errorf("invalid length: expected %d, got %d", expectedLength, len(bytes)) + } + return bytes, nil +} + +func (c RMNHomeVersionedConfig) MarshalJSON() ([]byte, error) { + type Alias RMNHomeVersionedConfig + return json.Marshal(&struct { + Digest string `json:"digest"` + *Alias + }{ + Digest: hex.EncodeToString(c.Digest[:]), + Alias: (*Alias)(&c), + }) +} + +func (c *RMNHomeVersionedConfig) UnmarshalJSON(data []byte) error { + type Alias RMNHomeVersionedConfig + aux := &struct { + Digest string `json:"digest"` + *Alias + }{ + Alias: (*Alias)(c), + } + + if err := json.Unmarshal(data, &aux); err != nil { + return err + } + + digestBytes, err := decodeHexString(aux.Digest, 32) + if err != nil { + return err + } + copy(c.Digest[:], digestBytes) + return nil +} + +type RMNHomeStaticConfig struct { + Nodes []RMNHomeNode `json:"nodes"` +} + +type RMNHomeDynamicConfig struct { + SourceChains []RMNHomeSourceChain `json:"sourceChains"` +} + +type RMNHomeSourceChain struct { + ChainSelector uint64 `json:"selector"` + F uint64 `json:"f"` + ObserverNodesBitmap *big.Int `json:"observerNodesBitmap"` +} + +type RMNHomeNode struct { + PeerId [32]byte `json:"peerId"` + OffchainPublicKey [32]byte `json:"offchainPublicKey"` +} + +func (n RMNHomeNode) MarshalJSON() ([]byte, error) { + type Alias RMNHomeNode + return json.Marshal(&struct { + PeerId string `json:"peerId"` + OffchainPublicKey string `json:"offchainPublicKey"` + *Alias + }{ + PeerId: hex.EncodeToString(n.PeerId[:]), + OffchainPublicKey: hex.EncodeToString(n.OffchainPublicKey[:]), + Alias: (*Alias)(&n), + }) +} + +func (n *RMNHomeNode) UnmarshalJSON(data []byte) error { + type Alias RMNHomeNode + aux := &struct { + PeerId string `json:"peerId"` + OffchainPublicKey string `json:"offchainPublicKey"` + *Alias + }{ + Alias: (*Alias)(n), + } + if err := json.Unmarshal(data, &aux); err != nil { + return err + } + + peerIdBytes, err := decodeHexString(aux.PeerId, 32) + if err != nil { + return err + } + copy(n.PeerId[:], peerIdBytes) + + offchainPublicKeyBytes, err := decodeHexString(aux.OffchainPublicKey, 32) + if err != nil { + return err + } + copy(n.OffchainPublicKey[:], offchainPublicKeyBytes) + + return nil +} + +type DigestFunc func(*bind.CallOpts) ([32]byte, error) + +func mapNodes(nodes []rmn_home.RMNHomeNode) []RMNHomeNode { + result := make([]RMNHomeNode, len(nodes)) + for i, node := range nodes { + result[i] = RMNHomeNode{ + PeerId: node.PeerId, + OffchainPublicKey: node.OffchainPublicKey, + } + } + return result +} + +func mapSourceChains(chains []rmn_home.RMNHomeSourceChain) []RMNHomeSourceChain { + result := make([]RMNHomeSourceChain, len(chains)) + for i, chain := range chains { + result[i] = RMNHomeSourceChain{ + ChainSelector: chain.ChainSelector, + F: chain.F, + ObserverNodesBitmap: chain.ObserverNodesBitmap, + } + } + return result +} + +func generateRmnHomeVersionedConfig(reader *rmn_home.RMNHome, digestFunc DigestFunc) (*RMNHomeVersionedConfig, error) { + address := reader.Address() + digest, err := digestFunc(nil) + if err != nil { + return nil, fmt.Errorf("failed to get digest for contract %s: %w", address, err) + } + + if digest == [32]byte{} { + return nil, nil + } + + config, err := reader.GetConfig(nil, digest) + if err != nil { + return nil, fmt.Errorf("failed to get config for contract %s: %w", address, err) + } + + staticConfig := RMNHomeStaticConfig{ + Nodes: mapNodes(config.VersionedConfig.StaticConfig.Nodes), + } + + dynamicConfig := RMNHomeDynamicConfig{ + SourceChains: mapSourceChains(config.VersionedConfig.DynamicConfig.SourceChains), + } + + return &RMNHomeVersionedConfig{ + Version: config.VersionedConfig.Version, + Digest: config.VersionedConfig.ConfigDigest, + StaticConfig: staticConfig, + DynamicConfig: dynamicConfig, + }, nil +} + +func GenerateRMNHomeView(rmnReader *rmn_home.RMNHome) (RMNHomeView, error) { + if rmnReader == nil { + return RMNHomeView{}, nil + } + + address := rmnReader.Address() + + activeConfig, err := generateRmnHomeVersionedConfig(rmnReader, rmnReader.GetActiveDigest) + if err != nil { + return RMNHomeView{}, fmt.Errorf("failed to generate active config for contract %s: %w", address, err) + } + + candidateConfig, err := generateRmnHomeVersionedConfig(rmnReader, rmnReader.GetCandidateDigest) + if err != nil { + return RMNHomeView{}, fmt.Errorf("failed to generate candidate config for contract %s: %w", address, err) + } + + contractMetaData, err := types.NewContractMetaData(rmnReader, rmnReader.Address()) + if err != nil { + return RMNHomeView{}, fmt.Errorf("failed to create contract metadata for contract %s: %w", address, err) + } + + return RMNHomeView{ + ContractMetaData: contractMetaData, + CandidateConfig: candidateConfig, + ActiveConfig: activeConfig, + }, nil +} diff --git a/deployment/ccip/view/view.go b/deployment/ccip/view/view.go index 1cacd58cc2b..4f216d13008 100644 --- a/deployment/ccip/view/view.go +++ b/deployment/ccip/view/view.go @@ -20,11 +20,15 @@ type ChainView struct { TokenAdminRegistry map[string]v1_5.TokenAdminRegistryView `json:"tokenAdminRegistry,omitempty"` CommitStore map[string]v1_5.CommitStoreView `json:"commitStore,omitempty"` // v1.6 - FeeQuoter map[string]v1_6.FeeQuoterView `json:"feeQuoter,omitempty"` - NonceManager map[string]v1_6.NonceManagerView `json:"nonceManager,omitempty"` - RMN map[string]v1_6.RMNRemoteView `json:"rmn,omitempty"` - OnRamp map[string]v1_6.OnRampView `json:"onRamp,omitempty"` - OffRamp map[string]v1_6.OffRampView `json:"offRamp,omitempty"` + FeeQuoter map[string]v1_6.FeeQuoterView `json:"feeQuoter,omitempty"` + NonceManager map[string]v1_6.NonceManagerView `json:"nonceManager,omitempty"` + RMNHome map[string]v1_6.RMNHomeView `json:"rmnHome,omitempty"` + RMN map[string]v1_6.RMNRemoteView `json:"rmn,omitempty"` + OnRamp map[string]v1_6.OnRampView `json:"onRamp,omitempty"` + OffRamp map[string]v1_6.OffRampView `json:"offRamp,omitempty"` + // TODO: Perhaps restrict to one CCIPHome/CR? Shouldn't + // be more than one per env. + CCIPHome map[string]v1_6.CCIPHomeView `json:"ccipHome,omitempty"` CapabilityRegistry map[string]common_v1_0.CapabilityRegistryView `json:"capabilityRegistry,omitempty"` MCMSWithTimelock common_v1_0.MCMSWithTimelockView `json:"mcmsWithTimelock,omitempty"` LinkToken common_v1_0.LinkTokenView `json:"linkToken,omitempty"` @@ -43,11 +47,15 @@ func NewChain() ChainView { // v1.6 FeeQuoter: make(map[string]v1_6.FeeQuoterView), NonceManager: make(map[string]v1_6.NonceManagerView), + RMNHome: make(map[string]v1_6.RMNHomeView), RMN: make(map[string]v1_6.RMNRemoteView), OnRamp: make(map[string]v1_6.OnRampView), OffRamp: make(map[string]v1_6.OffRampView), CapabilityRegistry: make(map[string]common_v1_0.CapabilityRegistryView), + CCIPHome: make(map[string]v1_6.CCIPHomeView), MCMSWithTimelock: common_v1_0.MCMSWithTimelockView{}, + LinkToken: common_v1_0.LinkTokenView{}, + StaticLinkToken: common_v1_0.StaticLinkTokenView{}, } } diff --git a/deployment/common/changeset/deploy_link_token.go b/deployment/common/changeset/deploy_link_token.go index 292c07c93df..c115a7ee083 100644 --- a/deployment/common/changeset/deploy_link_token.go +++ b/deployment/common/changeset/deploy_link_token.go @@ -12,7 +12,7 @@ import ( var _ deployment.ChangeSet[[]uint64] = DeployLinkToken -// DeployLinkToken deploys a link token contract to the chain identified by the chainSelector. +// DeployLinkToken deploys a link token contract to the chain identified by the ChainSelector. func DeployLinkToken(e deployment.Environment, chains []uint64) (deployment.ChangesetOutput, error) { for _, chain := range chains { _, ok := e.Chains[chain] diff --git a/deployment/common/changeset/deploy_link_token_test.go b/deployment/common/changeset/deploy_link_token_test.go index a61743e9bf4..bc472d2a247 100644 --- a/deployment/common/changeset/deploy_link_token_test.go +++ b/deployment/common/changeset/deploy_link_token_test.go @@ -27,7 +27,7 @@ func TestDeployLinkToken(t *testing.T) { require.NoError(t, err) addrs, err := e.ExistingAddresses.AddressesForChain(chain1) require.NoError(t, err) - state, err := changeset.MaybeLoadLinkTokenState(e.Chains[chain1], addrs) + state, err := changeset.MaybeLoadLinkTokenChainState(e.Chains[chain1], addrs) require.NoError(t, err) // View itself already unit tested _, err = state.GenerateLinkView() diff --git a/deployment/common/changeset/example/add_mint_burners_link.go b/deployment/common/changeset/example/add_mint_burners_link.go new file mode 100644 index 00000000000..7322f99dd60 --- /dev/null +++ b/deployment/common/changeset/example/add_mint_burners_link.go @@ -0,0 +1,70 @@ +package example + +import ( + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + + "github.com/smartcontractkit/chainlink/deployment" + "github.com/smartcontractkit/chainlink/deployment/common/changeset" +) + +type AddMintersBurnersLinkConfig struct { + ChainSelector uint64 + Minters []common.Address + Burners []common.Address +} + +var _ deployment.ChangeSet[*AddMintersBurnersLinkConfig] = AddMintersBurnersLink + +// AddMintersBurnersLink grants the minter / burner role to the provided addresses. +func AddMintersBurnersLink(e deployment.Environment, cfg *AddMintersBurnersLinkConfig) (deployment.ChangesetOutput, error) { + + chain := e.Chains[cfg.ChainSelector] + addresses, err := e.ExistingAddresses.AddressesForChain(cfg.ChainSelector) + if err != nil { + return deployment.ChangesetOutput{}, err + } + linkState, err := changeset.MaybeLoadLinkTokenChainState(chain, addresses) + if err != nil { + return deployment.ChangesetOutput{}, err + } + + for _, minter := range cfg.Minters { + // check if minter is already a minter + isMinter, err := linkState.LinkToken.IsMinter(&bind.CallOpts{Context: e.GetContext()}, minter) + if err != nil { + return deployment.ChangesetOutput{}, err + } + if isMinter { + continue + } + tx, err := linkState.LinkToken.GrantMintRole(chain.DeployerKey, minter) + if err != nil { + return deployment.ChangesetOutput{}, err + } + _, err = deployment.ConfirmIfNoError(chain, tx, err) + if err != nil { + return deployment.ChangesetOutput{}, err + } + } + for _, burner := range cfg.Burners { + // check if burner is already a burner + isBurner, err := linkState.LinkToken.IsBurner(&bind.CallOpts{Context: e.GetContext()}, burner) + if err != nil { + return deployment.ChangesetOutput{}, err + } + if isBurner { + continue + } + tx, err := linkState.LinkToken.GrantBurnRole(chain.DeployerKey, burner) + if err != nil { + return deployment.ChangesetOutput{}, err + } + _, err = deployment.ConfirmIfNoError(chain, tx, err) + if err != nil { + return deployment.ChangesetOutput{}, err + } + } + return deployment.ChangesetOutput{}, nil + +} diff --git a/deployment/common/changeset/example/add_mint_burners_link_test.go b/deployment/common/changeset/example/add_mint_burners_link_test.go new file mode 100644 index 00000000000..4dbfddc0b30 --- /dev/null +++ b/deployment/common/changeset/example/add_mint_burners_link_test.go @@ -0,0 +1,50 @@ +package example_test + +import ( + "context" + "testing" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink/deployment/common/changeset" + "github.com/smartcontractkit/chainlink/deployment/common/changeset/example" +) + +// TestAddMintersBurnersLink tests the AddMintersBurnersLink changeset +func TestAddMintersBurnersLink(t *testing.T) { + t.Parallel() + ctx := context.Background() + // Deploy Link Token and Timelock contracts and add addresses to environment + env := setupLinkTransferTestEnv(t) + + chainSelector := env.AllChainSelectors()[0] + chain := env.Chains[chainSelector] + addrs, err := env.ExistingAddresses.AddressesForChain(chainSelector) + require.NoError(t, err) + require.Len(t, addrs, 6) + + mcmsState, err := changeset.MaybeLoadMCMSWithTimelockChainState(chain, addrs) + require.NoError(t, err) + linkState, err := changeset.MaybeLoadLinkTokenChainState(chain, addrs) + require.NoError(t, err) + + timelockAddress := mcmsState.Timelock.Address() + + // Mint some funds + _, err = example.AddMintersBurnersLink(env, &example.AddMintersBurnersLinkConfig{ + ChainSelector: chainSelector, + Minters: []common.Address{timelockAddress}, + Burners: []common.Address{timelockAddress}, + }) + require.NoError(t, err) + + // check timelock balance + isMinter, err := linkState.LinkToken.IsMinter(&bind.CallOpts{Context: ctx}, timelockAddress) + require.NoError(t, err) + require.True(t, isMinter) + isBurner, err := linkState.LinkToken.IsBurner(&bind.CallOpts{Context: ctx}, timelockAddress) + require.NoError(t, err) + require.True(t, isBurner) +} diff --git a/deployment/common/changeset/example/link_transfer.go b/deployment/common/changeset/example/link_transfer.go new file mode 100644 index 00000000000..2e3be48a4d1 --- /dev/null +++ b/deployment/common/changeset/example/link_transfer.go @@ -0,0 +1,239 @@ +package example + +import ( + "errors" + "fmt" + "math/big" + "time" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + ethTypes "github.com/ethereum/go-ethereum/core/types" + owner_helpers "github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers" + chain_selectors "github.com/smartcontractkit/chain-selectors" + + "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/mcms" + "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/timelock" + + "github.com/smartcontractkit/chainlink/deployment" + "github.com/smartcontractkit/chainlink/deployment/common/changeset" + "github.com/smartcontractkit/chainlink/deployment/common/proposalutils" + "github.com/smartcontractkit/chainlink/deployment/common/types" +) + +const MaxTimelockDelay = 24 * 7 * time.Hour + +type TransferConfig struct { + To common.Address + Value *big.Int +} + +type MCMSConfig struct { + MinDelay time.Duration // delay for timelock worker to execute the transfers. + OverrideRoot bool +} + +type LinkTransferConfig struct { + Transfers map[uint64][]TransferConfig + From common.Address + McmsConfig *MCMSConfig +} + +var _ deployment.ChangeSet[*LinkTransferConfig] = LinkTransfer + +func getDeployer(e deployment.Environment, chain uint64, mcmConfig *MCMSConfig) *bind.TransactOpts { + if mcmConfig == nil { + return e.Chains[chain].DeployerKey + } + + return deployment.SimTransactOpts() +} + +// Validate checks that the LinkTransferConfig is valid. +func (cfg LinkTransferConfig) Validate(e deployment.Environment) error { + ctx := e.GetContext() + // Check that Transfers map has at least one chainSel + if len(cfg.Transfers) == 0 { + return errors.New("transfers map must have at least one chainSel") + } + + // Check transfers config values. + for chainSel, transfers := range cfg.Transfers { + selector, err := chain_selectors.GetSelectorFamily(chainSel) + if err != nil { + return fmt.Errorf("invalid chain selector: %w", err) + } + if selector != chain_selectors.FamilyEVM { + return fmt.Errorf("chain selector %d is not an EVM chain", chainSel) + } + chain, ok := e.Chains[chainSel] + if !ok { + return fmt.Errorf("chain with selector %d not found", chainSel) + } + addrs, err := e.ExistingAddresses.AddressesForChain(chainSel) + if err != nil { + return fmt.Errorf("error getting addresses for chain %d: %w", chainSel, err) + } + if len(transfers) == 0 { + return fmt.Errorf("transfers for chainSel %d must have at least one LinkTransfer", chainSel) + } + totalAmount := big.NewInt(0) + linkState, err := changeset.MaybeLoadLinkTokenChainState(chain, addrs) + if err != nil { + return fmt.Errorf("error loading link token state during validation: %w", err) + } + for _, transfer := range transfers { + if transfer.To == (common.Address{}) { + return errors.New("'to' address for transfers must be set") + } + if transfer.Value == nil { + return errors.New("value for transfers must be set") + } + if transfer.Value.Cmp(big.NewInt(0)) == 0 { + return errors.New("value for transfers must be non-zero") + } + if transfer.Value.Cmp(big.NewInt(0)) == -1 { + return errors.New("value for transfers must be positive") + } + totalAmount.Add(totalAmount, transfer.Value) + } + // check that from address has enough funds for the transfers + balance, err := linkState.LinkToken.BalanceOf(&bind.CallOpts{Context: ctx}, cfg.From) + if balance.Cmp(totalAmount) < 0 { + return fmt.Errorf("sender does not have enough funds for transfers for chain selector %d, required: %s, available: %s", chainSel, totalAmount.String(), balance.String()) + } + } + + if cfg.McmsConfig == nil { + return nil + } + + // Upper bound for min delay (7 days) + if cfg.McmsConfig.MinDelay > MaxTimelockDelay { + return errors.New("minDelay must be less than 7 days") + } + + return nil +} + +// initStatePerChain initializes the state for each chain selector on the provided config +func initStatePerChain(cfg *LinkTransferConfig, e deployment.Environment) ( + linkStatePerChain map[uint64]*changeset.LinkTokenState, + mcmsStatePerChain map[uint64]*changeset.MCMSWithTimelockState, + err error) { + linkStatePerChain = map[uint64]*changeset.LinkTokenState{} + mcmsStatePerChain = map[uint64]*changeset.MCMSWithTimelockState{} + // Load state for each chain + chainSelectors := []uint64{} + for chainSelector := range cfg.Transfers { + chainSelectors = append(chainSelectors, chainSelector) + } + linkStatePerChain, err = changeset.MaybeLoadLinkTokenState(e, chainSelectors) + if err != nil { + return nil, nil, err + } + mcmsStatePerChain, err = changeset.MaybeLoadMCMSWithTimelockState(e, chainSelectors) + if err != nil { + return nil, nil, err + + } + return linkStatePerChain, mcmsStatePerChain, nil +} + +// transferOrBuildTx transfers the LINK tokens or builds the tx for the MCMS proposal +func transferOrBuildTx( + e deployment.Environment, + linkState *changeset.LinkTokenState, + transfer TransferConfig, + opts *bind.TransactOpts, + chain deployment.Chain, + mcmsConfig *MCMSConfig) (*ethTypes.Transaction, error) { + tx, err := linkState.LinkToken.Transfer(opts, transfer.To, transfer.Value) + if err != nil { + return nil, fmt.Errorf("error packing transfer tx data: %w", err) + } + // only wait for tx if we are not using MCMS + if mcmsConfig == nil { + if _, err := deployment.ConfirmIfNoError(chain, tx, err); err != nil { + e.Logger.Errorw("Failed to confirm transfer tx", "chain", chain.String(), "err", err) + return nil, err + } + } + return tx, nil + +} + +// LinkTransfer takes the given link transfers and executes them or creates an MCMS proposal for them. +func LinkTransfer(e deployment.Environment, cfg *LinkTransferConfig) (deployment.ChangesetOutput, error) { + + err := cfg.Validate(e) + if err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("invalid LinkTransferConfig: %w", err) + } + chainSelectors := []uint64{} + for chainSelector := range cfg.Transfers { + chainSelectors = append(chainSelectors, chainSelector) + } + mcmsPerChain := map[uint64]*owner_helpers.ManyChainMultiSig{} + + timelockAddresses := map[uint64]common.Address{} + // Initialize state for each chain + linkStatePerChain, mcmsStatePerChain, err := initStatePerChain(cfg, e) + + allBatches := []timelock.BatchChainOperation{} + for chainSelector := range cfg.Transfers { + chainID := mcms.ChainIdentifier(chainSelector) + chain := e.Chains[chainSelector] + linkAddress := linkStatePerChain[chainSelector].LinkToken.Address() + mcmsState := mcmsStatePerChain[chainSelector] + linkState := linkStatePerChain[chainSelector] + + timelockAddress := mcmsState.Timelock.Address() + + mcmsPerChain[uint64(chainID)] = mcmsState.ProposerMcm + + timelockAddresses[chainSelector] = timelockAddress + batch := timelock.BatchChainOperation{ + ChainIdentifier: chainID, + Batch: []mcms.Operation{}, + } + + opts := getDeployer(e, chainSelector, cfg.McmsConfig) + totalAmount := big.NewInt(0) + for _, transfer := range cfg.Transfers[chainSelector] { + tx, err := transferOrBuildTx(e, linkState, transfer, opts, chain, cfg.McmsConfig) + if err != nil { + return deployment.ChangesetOutput{}, err + } + op := mcms.Operation{ + To: linkAddress, + Data: tx.Data(), + Value: big.NewInt(0), + ContractType: string(types.LinkToken), + } + batch.Batch = append(batch.Batch, op) + totalAmount.Add(totalAmount, transfer.Value) + } + + allBatches = append(allBatches, batch) + } + + if cfg.McmsConfig != nil { + proposal, err := proposalutils.BuildProposalFromBatches( + timelockAddresses, + mcmsPerChain, + allBatches, + "LINK Value transfer proposal", + cfg.McmsConfig.MinDelay, + ) + if err != nil { + return deployment.ChangesetOutput{}, err + } + + return deployment.ChangesetOutput{ + Proposals: []timelock.MCMSWithTimelockProposal{*proposal}, + }, nil + } + + return deployment.ChangesetOutput{}, nil +} diff --git a/deployment/common/changeset/example/link_transfer_test.go b/deployment/common/changeset/example/link_transfer_test.go new file mode 100644 index 00000000000..eecfbd37c95 --- /dev/null +++ b/deployment/common/changeset/example/link_transfer_test.go @@ -0,0 +1,373 @@ +package example_test + +import ( + "context" + "math/big" + "testing" + "time" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + chain_selectors "github.com/smartcontractkit/chain-selectors" + + "github.com/smartcontractkit/chainlink/deployment/common/changeset/example" + "github.com/smartcontractkit/chainlink/deployment/common/proposalutils" + "github.com/smartcontractkit/chainlink/v2/core/logger" + + "github.com/stretchr/testify/require" + "go.uber.org/zap/zapcore" + + "github.com/smartcontractkit/chainlink/deployment" + "github.com/smartcontractkit/chainlink/deployment/common/changeset" + "github.com/smartcontractkit/chainlink/deployment/common/types" + "github.com/smartcontractkit/chainlink/deployment/environment/memory" +) + +// setupLinkTransferContracts deploys all required contracts for the link transfer tests and returns the updated env. +func setupLinkTransferTestEnv(t *testing.T) deployment.Environment { + + lggr := logger.TestLogger(t) + cfg := memory.MemoryEnvironmentConfig{ + Nodes: 1, + Chains: 2, + } + env := memory.NewMemoryEnvironment(t, lggr, zapcore.DebugLevel, cfg) + chainSelector := env.AllChainSelectors()[0] + config := proposalutils.SingleGroupMCMS(t) + + // Deploy MCMS and Timelock + env, err := changeset.ApplyChangesets(t, env, nil, []changeset.ChangesetApplication{ + { + Changeset: changeset.WrapChangeSet(changeset.DeployLinkToken), + Config: []uint64{chainSelector}, + }, + { + Changeset: changeset.WrapChangeSet(changeset.DeployMCMSWithTimelock), + Config: map[uint64]types.MCMSWithTimelockConfig{ + chainSelector: { + Canceller: config, + Bypasser: config, + Proposer: config, + TimelockMinDelay: big.NewInt(0), + }, + }, + }, + }) + require.NoError(t, err) + return env +} + +// TestLinkTransferMCMS tests the LinkTransfer changeset by sending LINK from a timelock contract +// to the deployer key via mcms proposal. +func TestLinkTransferMCMS(t *testing.T) { + t.Parallel() + ctx := context.Background() + + env := setupLinkTransferTestEnv(t) + chainSelector := env.AllChainSelectors()[0] + chain := env.Chains[chainSelector] + addrs, err := env.ExistingAddresses.AddressesForChain(chainSelector) + require.NoError(t, err) + require.Len(t, addrs, 6) + + mcmsState, err := changeset.MaybeLoadMCMSWithTimelockChainState(chain, addrs) + require.NoError(t, err) + linkState, err := changeset.MaybeLoadLinkTokenChainState(chain, addrs) + require.NoError(t, err) + timelockAddress := mcmsState.Timelock.Address() + + // Mint some funds + // grant minter permissions + tx, err := linkState.LinkToken.GrantMintRole(chain.DeployerKey, chain.DeployerKey.From) + require.NoError(t, err) + _, err = deployment.ConfirmIfNoError(chain, tx, err) + require.NoError(t, err) + + tx, err = linkState.LinkToken.Mint(chain.DeployerKey, timelockAddress, big.NewInt(750)) + require.NoError(t, err) + _, err = deployment.ConfirmIfNoError(chain, tx, err) + require.NoError(t, err) + + timelocks := map[uint64]*proposalutils.TimelockExecutionContracts{ + chainSelector: { + Timelock: mcmsState.Timelock, + CallProxy: mcmsState.CallProxy, + }, + } + // Apply the changeset + _, err = changeset.ApplyChangesets(t, env, timelocks, []changeset.ChangesetApplication{ + // the changeset produces proposals, ApplyChangesets will sign & execute them. + // in practice, signing and executing are separated processes. + { + Changeset: changeset.WrapChangeSet(example.LinkTransfer), + Config: &example.LinkTransferConfig{ + From: timelockAddress, + Transfers: map[uint64][]example.TransferConfig{ + chainSelector: { + { + To: chain.DeployerKey.From, + Value: big.NewInt(500), + }, + }, + }, + McmsConfig: &example.MCMSConfig{ + MinDelay: 0, + OverrideRoot: true, + }, + }, + }, + }) + require.NoError(t, err) + + // Check new balances + endBalance, err := linkState.LinkToken.BalanceOf(&bind.CallOpts{Context: ctx}, chain.DeployerKey.From) + require.NoError(t, err) + expectedBalance := big.NewInt(500) + require.Equal(t, expectedBalance, endBalance) + + // check timelock balance + endBalance, err = linkState.LinkToken.BalanceOf(&bind.CallOpts{Context: ctx}, timelockAddress) + require.NoError(t, err) + expectedBalance = big.NewInt(250) + require.Equal(t, expectedBalance, endBalance) +} + +// TestLinkTransfer tests the LinkTransfer changeset by sending LINK from a timelock contract to the deployer key. +func TestLinkTransfer(t *testing.T) { + t.Parallel() + ctx := context.Background() + + env := setupLinkTransferTestEnv(t) + chainSelector := env.AllChainSelectors()[0] + chain := env.Chains[chainSelector] + addrs, err := env.ExistingAddresses.AddressesForChain(chainSelector) + require.NoError(t, err) + require.Len(t, addrs, 6) + + mcmsState, err := changeset.MaybeLoadMCMSWithTimelockChainState(chain, addrs) + require.NoError(t, err) + linkState, err := changeset.MaybeLoadLinkTokenChainState(chain, addrs) + require.NoError(t, err) + timelockAddress := mcmsState.Timelock.Address() + + // Mint some funds + // grant minter permissions + tx, err := linkState.LinkToken.GrantMintRole(chain.DeployerKey, chain.DeployerKey.From) + require.NoError(t, err) + _, err = deployment.ConfirmIfNoError(chain, tx, err) + require.NoError(t, err) + + tx, err = linkState.LinkToken.Mint(chain.DeployerKey, chain.DeployerKey.From, big.NewInt(750)) + require.NoError(t, err) + _, err = deployment.ConfirmIfNoError(chain, tx, err) + require.NoError(t, err) + + timelocks := map[uint64]*proposalutils.TimelockExecutionContracts{ + chainSelector: { + Timelock: mcmsState.Timelock, + CallProxy: mcmsState.CallProxy, + }, + } + + // Apply the changeset + _, err = changeset.ApplyChangesets(t, env, timelocks, []changeset.ChangesetApplication{ + // the changeset produces proposals, ApplyChangesets will sign & execute them. + // in practice, signing and executing are separated processes. + { + Changeset: changeset.WrapChangeSet(example.LinkTransfer), + Config: &example.LinkTransferConfig{ + From: chain.DeployerKey.From, + Transfers: map[uint64][]example.TransferConfig{ + chainSelector: { + { + To: timelockAddress, + Value: big.NewInt(500), + }, + }, + }, + // No MCMSConfig here means we'll execute the txs directly. + }, + }, + }) + require.NoError(t, err) + + // Check new balances + endBalance, err := linkState.LinkToken.BalanceOf(&bind.CallOpts{Context: ctx}, chain.DeployerKey.From) + require.NoError(t, err) + expectedBalance := big.NewInt(250) + require.Equal(t, expectedBalance, endBalance) + + // check timelock balance + endBalance, err = linkState.LinkToken.BalanceOf(&bind.CallOpts{Context: ctx}, timelockAddress) + require.NoError(t, err) + expectedBalance = big.NewInt(500) + require.Equal(t, expectedBalance, endBalance) +} + +func TestValidate(t *testing.T) { + env := setupLinkTransferTestEnv(t) + chainSelector := env.AllChainSelectors()[0] + chain := env.Chains[chainSelector] + addrs, err := env.ExistingAddresses.AddressesForChain(chainSelector) + require.NoError(t, err) + require.Len(t, addrs, 6) + mcmsState, err := changeset.MaybeLoadMCMSWithTimelockChainState(chain, addrs) + require.NoError(t, err) + linkState, err := changeset.MaybeLoadLinkTokenChainState(chain, addrs) + require.NoError(t, err) + tx, err := linkState.LinkToken.GrantMintRole(chain.DeployerKey, chain.DeployerKey.From) + require.NoError(t, err) + _, err = deployment.ConfirmIfNoError(chain, tx, err) + require.NoError(t, err) + tx, err = linkState.LinkToken.Mint(chain.DeployerKey, chain.DeployerKey.From, big.NewInt(750)) + require.NoError(t, err) + _, err = deployment.ConfirmIfNoError(chain, tx, err) + + require.NoError(t, err) + tests := []struct { + name string + cfg example.LinkTransferConfig + errorMsg string + }{ + { + name: "valid config", + cfg: example.LinkTransferConfig{ + Transfers: map[uint64][]example.TransferConfig{ + chainSelector: {{To: mcmsState.Timelock.Address(), Value: big.NewInt(100)}}}, + From: chain.DeployerKey.From, + McmsConfig: &example.MCMSConfig{ + MinDelay: time.Hour, + }, + }, + }, + { + name: "valid non mcms config", + cfg: example.LinkTransferConfig{ + Transfers: map[uint64][]example.TransferConfig{ + chainSelector: {{To: mcmsState.Timelock.Address(), Value: big.NewInt(100)}}}, + From: chain.DeployerKey.From, + }, + }, + { + name: "insufficient funds", + cfg: example.LinkTransferConfig{ + Transfers: map[uint64][]example.TransferConfig{ + chainSelector: { + {To: chain.DeployerKey.From, Value: big.NewInt(100)}, + {To: chain.DeployerKey.From, Value: big.NewInt(500)}, + {To: chain.DeployerKey.From, Value: big.NewInt(1250)}, + }, + }, + From: mcmsState.Timelock.Address(), + McmsConfig: &example.MCMSConfig{ + MinDelay: time.Hour, + }, + }, + errorMsg: "sender does not have enough funds for transfers for chain selector 909606746561742123, required: 1850, available: 0", + }, + { + name: "invalid config: empty transfers", + cfg: example.LinkTransferConfig{Transfers: map[uint64][]example.TransferConfig{}}, + errorMsg: "transfers map must have at least one chainSel", + }, + { + name: "invalid chain selector", + cfg: example.LinkTransferConfig{ + Transfers: map[uint64][]example.TransferConfig{ + 1: {{To: common.Address{}, Value: big.NewInt(100)}}}, + }, + errorMsg: "invalid chain selector: unknown chain selector 1", + }, + { + name: "chain selector not found", + cfg: example.LinkTransferConfig{ + Transfers: map[uint64][]example.TransferConfig{ + chain_selectors.ETHEREUM_TESTNET_GOERLI_ARBITRUM_1.Selector: {{To: common.Address{}, Value: big.NewInt(100)}}}, + }, + errorMsg: "chain with selector 6101244977088475029 not found", + }, + { + name: "empty transfer list", + cfg: example.LinkTransferConfig{ + Transfers: map[uint64][]example.TransferConfig{ + chainSelector: {}, + }, + }, + errorMsg: "transfers for chainSel 909606746561742123 must have at least one LinkTransfer", + }, + { + name: "empty value", + cfg: example.LinkTransferConfig{ + Transfers: map[uint64][]example.TransferConfig{ + chainSelector: { + {To: chain.DeployerKey.From, Value: nil}, + }, + }, + }, + errorMsg: "value for transfers must be set", + }, + { + name: "zero value", + cfg: example.LinkTransferConfig{ + Transfers: map[uint64][]example.TransferConfig{ + chainSelector: { + {To: chain.DeployerKey.From, Value: big.NewInt(0)}, + }, + }, + }, + errorMsg: "value for transfers must be non-zero", + }, + { + name: "negative value", + cfg: example.LinkTransferConfig{ + Transfers: map[uint64][]example.TransferConfig{ + chainSelector: { + {To: chain.DeployerKey.From, Value: big.NewInt(-5)}, + }, + }, + }, + errorMsg: "value for transfers must be positive", + }, + { + name: "non-evm-chain", + cfg: example.LinkTransferConfig{ + Transfers: map[uint64][]example.TransferConfig{ + chain_selectors.APTOS_MAINNET.Selector: {{To: mcmsState.Timelock.Address(), Value: big.NewInt(100)}}}, + From: chain.DeployerKey.From, + }, + errorMsg: "chain selector 4741433654826277614 is not an EVM chain", + }, + { + name: "delay greater than max allowed", + cfg: example.LinkTransferConfig{ + Transfers: map[uint64][]example.TransferConfig{ + chainSelector: {{To: mcmsState.Timelock.Address(), Value: big.NewInt(100)}}}, + From: chain.DeployerKey.From, + McmsConfig: &example.MCMSConfig{ + MinDelay: time.Hour * 24 * 10, + }, + }, + errorMsg: "minDelay must be less than 7 days", + }, + { + name: "invalid config: transfer to address missing", + cfg: example.LinkTransferConfig{ + Transfers: map[uint64][]example.TransferConfig{ + chainSelector: {{To: common.Address{}, Value: big.NewInt(100)}}}, + }, + errorMsg: "'to' address for transfers must be set", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := tt.cfg.Validate(env) + if tt.errorMsg != "" { + require.Error(t, err) + require.Contains(t, err.Error(), tt.errorMsg) + } else { + require.NoError(t, err) + } + }) + } +} diff --git a/deployment/common/changeset/example/mint_link.go b/deployment/common/changeset/example/mint_link.go new file mode 100644 index 00000000000..dc50f8a1a27 --- /dev/null +++ b/deployment/common/changeset/example/mint_link.go @@ -0,0 +1,43 @@ +package example + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/common" + + "github.com/smartcontractkit/chainlink/deployment" + "github.com/smartcontractkit/chainlink/deployment/common/changeset" +) + +type MintLinkConfig struct { + Amount *big.Int + ChainSelector uint64 + To common.Address +} + +var _ deployment.ChangeSet[*MintLinkConfig] = MintLink + +// MintLink mints LINK to the provided contract. +func MintLink(e deployment.Environment, cfg *MintLinkConfig) (deployment.ChangesetOutput, error) { + + chain := e.Chains[cfg.ChainSelector] + addresses, err := e.ExistingAddresses.AddressesForChain(cfg.ChainSelector) + if err != nil { + return deployment.ChangesetOutput{}, err + } + linkState, err := changeset.MaybeLoadLinkTokenChainState(chain, addresses) + if err != nil { + return deployment.ChangesetOutput{}, err + } + + tx, err := linkState.LinkToken.Mint(chain.DeployerKey, cfg.To, cfg.Amount) + if err != nil { + return deployment.ChangesetOutput{}, err + } + _, err = deployment.ConfirmIfNoError(chain, tx, err) + if err != nil { + return deployment.ChangesetOutput{}, err + } + return deployment.ChangesetOutput{}, nil + +} diff --git a/deployment/common/changeset/example/mint_link_test.go b/deployment/common/changeset/example/mint_link_test.go new file mode 100644 index 00000000000..1c60c3221de --- /dev/null +++ b/deployment/common/changeset/example/mint_link_test.go @@ -0,0 +1,58 @@ +package example_test + +import ( + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink/deployment/common/changeset" + "github.com/smartcontractkit/chainlink/deployment/common/changeset/example" +) + +// TestMintLink tests the MintLink changeset +func TestMintLink(t *testing.T) { + t.Parallel() + env := setupLinkTransferTestEnv(t) + ctx := env.GetContext() + chainSelector := env.AllChainSelectors()[0] + chain := env.Chains[chainSelector] + + addrs, err := env.ExistingAddresses.AddressesForChain(chainSelector) + require.NoError(t, err) + require.Len(t, addrs, 6) + + mcmsState, err := changeset.MaybeLoadMCMSWithTimelockChainState(chain, addrs) + require.NoError(t, err) + linkState, err := changeset.MaybeLoadLinkTokenChainState(chain, addrs) + require.NoError(t, err) + + _, err = changeset.ApplyChangesets(t, env, nil, []changeset.ChangesetApplication{ + { + Changeset: changeset.WrapChangeSet(example.AddMintersBurnersLink), + Config: &example.AddMintersBurnersLinkConfig{ + ChainSelector: chainSelector, + Minters: []common.Address{chain.DeployerKey.From}, + }, + }, + }) + require.NoError(t, err) + + timelockAddress := mcmsState.Timelock.Address() + + // Mint some funds + _, err = example.MintLink(env, &example.MintLinkConfig{ + ChainSelector: chainSelector, + To: timelockAddress, + Amount: big.NewInt(7568), + }) + require.NoError(t, err) + + // check timelock balance + endBalance, err := linkState.LinkToken.BalanceOf(&bind.CallOpts{Context: ctx}, timelockAddress) + require.NoError(t, err) + expectedBalance := big.NewInt(7568) + require.Equal(t, expectedBalance, endBalance) +} diff --git a/deployment/common/changeset/internal/mcms.go b/deployment/common/changeset/internal/mcms.go index 281f43924f4..baa82d77c8f 100644 --- a/deployment/common/changeset/internal/mcms.go +++ b/deployment/common/changeset/internal/mcms.go @@ -55,6 +55,7 @@ type MCMSWithTimelockDeploy struct { Bypasser *deployment.ContractDeploy[*owner_helpers.ManyChainMultiSig] Proposer *deployment.ContractDeploy[*owner_helpers.ManyChainMultiSig] Timelock *deployment.ContractDeploy[*owner_helpers.RBACTimelock] + CallProxy *deployment.ContractDeploy[*owner_helpers.CallProxy] } func DeployMCMSWithTimelockContractsBatch( @@ -106,10 +107,12 @@ func DeployMCMSWithTimelockContracts( // TODO: Could expose this as config? // Or keep this enforced to follow the same pattern? chain.DeployerKey.From, - []common.Address{proposer.Address}, // proposers - config.TimelockExecutors, //executors - []common.Address{canceller.Address}, // cancellers - []common.Address{bypasser.Address}, // bypassers + []common.Address{proposer.Address}, // proposers + // Executors field is empty here because we grant the executor role to the call proxy later + // and the call proxy cannot be deployed before the timelock. + []common.Address{}, + []common.Address{canceller.Address, proposer.Address, bypasser.Address}, // cancellers + []common.Address{bypasser.Address}, // bypassers ) return deployment.ContractDeploy[*owner_helpers.RBACTimelock]{ timelock, cc, tx2, deployment.NewTypeAndVersion(types.RBACTimelock, deployment.Version1_0_0), err2, @@ -119,6 +122,37 @@ func DeployMCMSWithTimelockContracts( lggr.Errorw("Failed to deploy timelock", "chain", chain.String(), "err", err) return nil, err } + + callProxy, err := deployment.DeployContract(lggr, chain, ab, + func(chain deployment.Chain) deployment.ContractDeploy[*owner_helpers.CallProxy] { + callProxy, tx2, cc, err2 := owner_helpers.DeployCallProxy( + chain.DeployerKey, + chain.Client, + timelock.Address, + ) + return deployment.ContractDeploy[*owner_helpers.CallProxy]{ + callProxy, cc, tx2, deployment.NewTypeAndVersion(types.CallProxy, deployment.Version1_0_0), err2, + } + }) + if err != nil { + lggr.Errorw("Failed to deploy call proxy", "chain", chain.String(), "err", err) + return nil, err + } + + grantRoleTx, err := timelock.Contract.GrantRole( + chain.DeployerKey, + v1_0.EXECUTOR_ROLE.ID, + callProxy.Address, + ) + if err != nil { + lggr.Errorw("Failed to grant timelock executor role", "chain", chain.String(), "err", err) + return nil, err + } + + if _, err := deployment.ConfirmIfNoError(chain, grantRoleTx, err); err != nil { + lggr.Errorw("Failed to grant timelock executor role", "chain", chain.String(), "err", err) + return nil, err + } // We grant the timelock the admin role on the MCMS contracts. tx, err := timelock.Contract.GrantRole(chain.DeployerKey, v1_0.ADMIN_ROLE.ID, timelock.Address) @@ -133,5 +167,6 @@ func DeployMCMSWithTimelockContracts( Bypasser: bypasser, Proposer: proposer, Timelock: timelock, + CallProxy: callProxy, }, nil } diff --git a/deployment/common/changeset/internal/mcms_test.go b/deployment/common/changeset/internal/mcms_test.go index 2269911f4cd..ff013717d30 100644 --- a/deployment/common/changeset/internal/mcms_test.go +++ b/deployment/common/changeset/internal/mcms_test.go @@ -2,16 +2,15 @@ package internal_test import ( "encoding/json" - "math/big" "testing" - "github.com/ethereum/go-ethereum/common" chainsel "github.com/smartcontractkit/chain-selectors" "github.com/stretchr/testify/require" "github.com/smartcontractkit/chainlink/deployment" "github.com/smartcontractkit/chainlink/deployment/common/changeset" "github.com/smartcontractkit/chainlink/deployment/common/changeset/internal" + "github.com/smartcontractkit/chainlink/deployment/common/proposalutils" "github.com/smartcontractkit/chainlink/deployment/common/types" "github.com/smartcontractkit/chainlink/deployment/environment/memory" "github.com/smartcontractkit/chainlink/v2/core/logger" @@ -24,7 +23,7 @@ func TestDeployMCMSWithConfig(t *testing.T) { }) ab := deployment.NewMemoryAddressBook() _, err := internal.DeployMCMSWithConfig(types.ProposerManyChainMultisig, - lggr, chains[chainsel.TEST_90000001.Selector], ab, changeset.SingleGroupMCMS(t)) + lggr, chains[chainsel.TEST_90000001.Selector], ab, proposalutils.SingleGroupMCMS(t)) require.NoError(t, err) } @@ -36,20 +35,12 @@ func TestDeployMCMSWithTimelockContracts(t *testing.T) { ab := deployment.NewMemoryAddressBook() _, err := internal.DeployMCMSWithTimelockContracts(lggr, chains[chainsel.TEST_90000001.Selector], - ab, types.MCMSWithTimelockConfig{ - Canceller: changeset.SingleGroupMCMS(t), - Bypasser: changeset.SingleGroupMCMS(t), - Proposer: changeset.SingleGroupMCMS(t), - TimelockExecutors: []common.Address{ - chains[chainsel.TEST_90000001.Selector].DeployerKey.From, - }, - TimelockMinDelay: big.NewInt(0), - }) + ab, proposalutils.SingleGroupTimelockConfig(t)) require.NoError(t, err) addresses, err := ab.AddressesForChain(chainsel.TEST_90000001.Selector) require.NoError(t, err) - require.Len(t, addresses, 4) - mcmsState, err := changeset.MaybeLoadMCMSWithTimelockState(chains[chainsel.TEST_90000001.Selector], addresses) + require.Len(t, addresses, 5) + mcmsState, err := changeset.MaybeLoadMCMSWithTimelockChainState(chains[chainsel.TEST_90000001.Selector], addresses) require.NoError(t, err) v, err := mcmsState.GenerateMCMSWithTimelockView() b, err := json.MarshalIndent(v, "", " ") diff --git a/deployment/common/changeset/state.go b/deployment/common/changeset/state.go index 0055c908f8d..0db34abad71 100644 --- a/deployment/common/changeset/state.go +++ b/deployment/common/changeset/state.go @@ -8,6 +8,7 @@ import ( owner_helpers "github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers" "github.com/smartcontractkit/chainlink/deployment" + "github.com/smartcontractkit/chainlink/deployment/common/proposalutils" "github.com/smartcontractkit/chainlink/deployment/common/types" "github.com/smartcontractkit/chainlink/deployment/common/view/v1_0" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/link_token_interface" @@ -19,28 +20,7 @@ import ( // It is public for use in product specific packages. // Either all fields are nil or all fields are non-nil. type MCMSWithTimelockState struct { - CancellerMcm *owner_helpers.ManyChainMultiSig - BypasserMcm *owner_helpers.ManyChainMultiSig - ProposerMcm *owner_helpers.ManyChainMultiSig - Timelock *owner_helpers.RBACTimelock -} - -// Validate checks that all fields are non-nil, ensuring it's ready -// for use generating views or interactions. -func (state MCMSWithTimelockState) Validate() error { - if state.Timelock == nil { - return errors.New("timelock not found") - } - if state.CancellerMcm == nil { - return errors.New("canceller not found") - } - if state.ProposerMcm == nil { - return errors.New("proposer not found") - } - if state.BypasserMcm == nil { - return errors.New("bypasser not found") - } - return nil + *proposalutils.MCMSWithTimelockContracts } func (state MCMSWithTimelockState) GenerateMCMSWithTimelockView() (v1_0.MCMSWithTimelockView, error) { @@ -51,6 +31,10 @@ func (state MCMSWithTimelockState) GenerateMCMSWithTimelockView() (v1_0.MCMSWith if err != nil { return v1_0.MCMSWithTimelockView{}, nil } + callProxyView, err := v1_0.GenerateCallProxyView(*state.CallProxy) + if err != nil { + return v1_0.MCMSWithTimelockView{}, nil + } bypasserView, err := v1_0.GenerateMCMSView(*state.BypasserMcm) if err != nil { return v1_0.MCMSWithTimelockView{}, nil @@ -68,20 +52,45 @@ func (state MCMSWithTimelockState) GenerateMCMSWithTimelockView() (v1_0.MCMSWith Bypasser: bypasserView, Proposer: proposerView, Canceller: cancellerView, + CallProxy: callProxyView, }, nil } -// MaybeLoadMCMSWithTimelockState looks for the addresses corresponding to +// MaybeLoadMCMSWithTimelockState loads the MCMSWithTimelockState state for each chain in the given environment. +func MaybeLoadMCMSWithTimelockState(env deployment.Environment, chainSelectors []uint64) (map[uint64]*MCMSWithTimelockState, error) { + result := map[uint64]*MCMSWithTimelockState{} + for _, chainSelector := range chainSelectors { + chain, ok := env.Chains[chainSelector] + if !ok { + return nil, fmt.Errorf("chain %d not found", chainSelector) + } + addressesChain, err := env.ExistingAddresses.AddressesForChain(chainSelector) + if err != nil { + return nil, err + } + state, err := MaybeLoadMCMSWithTimelockChainState(chain, addressesChain) + if err != nil { + return nil, err + } + result[chainSelector] = state + } + return result, nil +} + +// MaybeLoadMCMSWithTimelockChainState looks for the addresses corresponding to // contracts deployed with DeployMCMSWithTimelock and loads them into a // MCMSWithTimelockState struct. If none of the contracts are found, the state struct will be nil. // An error indicates: // - Found but was unable to load a contract // - It only found part of the bundle of contracts // - If found more than one instance of a contract (we expect one bundle in the given addresses) -func MaybeLoadMCMSWithTimelockState(chain deployment.Chain, addresses map[string]deployment.TypeAndVersion) (*MCMSWithTimelockState, error) { - state := MCMSWithTimelockState{} +func MaybeLoadMCMSWithTimelockChainState(chain deployment.Chain, addresses map[string]deployment.TypeAndVersion) (*MCMSWithTimelockState, error) { + state := MCMSWithTimelockState{ + MCMSWithTimelockContracts: &proposalutils.MCMSWithTimelockContracts{}, + } // We expect one of each contract on the chain. timelock := deployment.NewTypeAndVersion(types.RBACTimelock, deployment.Version1_0_0) + callProxy := deployment.NewTypeAndVersion(types.CallProxy, deployment.Version1_0_0) proposer := deployment.NewTypeAndVersion(types.ProposerManyChainMultisig, deployment.Version1_0_0) canceller := deployment.NewTypeAndVersion(types.CancellerManyChainMultisig, deployment.Version1_0_0) bypasser := deployment.NewTypeAndVersion(types.BypasserManyChainMultisig, deployment.Version1_0_0) @@ -89,7 +98,7 @@ func MaybeLoadMCMSWithTimelockState(chain deployment.Chain, addresses map[string // Ensure we either have the bundle or not. _, err := deployment.AddressesContainBundle(addresses, map[deployment.TypeAndVersion]struct{}{ - timelock: {}, proposer: {}, canceller: {}, bypasser: {}, + timelock: {}, proposer: {}, canceller: {}, bypasser: {}, callProxy: {}, }) if err != nil { return nil, fmt.Errorf("unable to check MCMS contracts on chain %s error: %w", chain.Name(), err) @@ -103,6 +112,12 @@ func MaybeLoadMCMSWithTimelockState(chain deployment.Chain, addresses map[string return nil, err } state.Timelock = tl + case callProxy: + cp, err := owner_helpers.NewCallProxy(common.HexToAddress(address), chain.Client) + if err != nil { + return nil, err + } + state.CallProxy = cp case proposer: mcms, err := owner_helpers.NewManyChainMultiSig(common.HexToAddress(address), chain.Client) if err != nil { @@ -137,7 +152,28 @@ func (s LinkTokenState) GenerateLinkView() (v1_0.LinkTokenView, error) { return v1_0.GenerateLinkTokenView(s.LinkToken) } -func MaybeLoadLinkTokenState(chain deployment.Chain, addresses map[string]deployment.TypeAndVersion) (*LinkTokenState, error) { +// MaybeLoadLinkTokenState loads the LinkTokenState state for each chain in the given environment. +func MaybeLoadLinkTokenState(env deployment.Environment, chainSelectors []uint64) (map[uint64]*LinkTokenState, error) { + result := map[uint64]*LinkTokenState{} + for _, chainSelector := range chainSelectors { + chain, ok := env.Chains[chainSelector] + if !ok { + return nil, fmt.Errorf("chain %d not found", chainSelector) + } + addressesChain, err := env.ExistingAddresses.AddressesForChain(chainSelector) + if err != nil { + return nil, err + } + state, err := MaybeLoadLinkTokenChainState(chain, addressesChain) + if err != nil { + return nil, err + } + result[chainSelector] = state + } + return result, nil +} + +func MaybeLoadLinkTokenChainState(chain deployment.Chain, addresses map[string]deployment.TypeAndVersion) (*LinkTokenState, error) { state := LinkTokenState{} linkToken := deployment.NewTypeAndVersion(types.LinkToken, deployment.Version1_0_0) // Perhaps revisit if we have a use case for multiple. diff --git a/deployment/common/changeset/test_helpers.go b/deployment/common/changeset/test_helpers.go index 2d5295282f5..e92b36e5b55 100644 --- a/deployment/common/changeset/test_helpers.go +++ b/deployment/common/changeset/test_helpers.go @@ -5,11 +5,11 @@ import ( "testing" mapset "github.com/deckarep/golang-set/v2" - "github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers" jobv1 "github.com/smartcontractkit/chainlink-protos/job-distributor/v1/job" "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/testcontext" "github.com/smartcontractkit/chainlink/deployment" + "github.com/smartcontractkit/chainlink/deployment/common/proposalutils" ) type ChangesetApplication struct { @@ -33,7 +33,7 @@ func WrapChangeSet[C any](fn deployment.ChangeSet[C]) func(e deployment.Environm } // ApplyChangesets applies the changeset applications to the environment and returns the updated environment. -func ApplyChangesets(t *testing.T, e deployment.Environment, timelocksPerChain map[uint64]*gethwrappers.RBACTimelock, changesetApplications []ChangesetApplication) (deployment.Environment, error) { +func ApplyChangesets(t *testing.T, e deployment.Environment, timelockContractsPerChain map[uint64]*proposalutils.TimelockExecutionContracts, changesetApplications []ChangesetApplication) (deployment.Environment, error) { currentEnv := e for i, csa := range changesetApplications { out, err := csa.Changeset(currentEnv, csa.Config) @@ -73,13 +73,14 @@ func ApplyChangesets(t *testing.T, e deployment.Environment, timelocksPerChain m chains.Add(uint64(op.ChainIdentifier)) } - signed := SignProposal(t, e, &prop) + signed := proposalutils.SignProposal(t, e, &prop) for _, sel := range chains.ToSlice() { - timelock, ok := timelocksPerChain[sel] - if !ok || timelock == nil { - return deployment.Environment{}, fmt.Errorf("timelock not found for chain %d", sel) + timelockContracts, ok := timelockContractsPerChain[sel] + if !ok || timelockContracts == nil { + return deployment.Environment{}, fmt.Errorf("timelock contracts not found for chain %d", sel) } - ExecuteProposal(t, e, signed, timelock, sel) + + proposalutils.ExecuteProposal(t, e, signed, timelockContracts, sel) } } } @@ -91,6 +92,7 @@ func ApplyChangesets(t *testing.T, e deployment.Environment, timelocksPerChain m NodeIDs: e.NodeIDs, Offchain: e.Offchain, OCRSecrets: e.OCRSecrets, + GetContext: e.GetContext, } } return currentEnv, nil diff --git a/deployment/common/changeset/transfer_to_mcms_with_timelock.go b/deployment/common/changeset/transfer_to_mcms_with_timelock.go index e48d29af92b..afba1afa48b 100644 --- a/deployment/common/changeset/transfer_to_mcms_with_timelock.go +++ b/deployment/common/changeset/transfer_to_mcms_with_timelock.go @@ -1,6 +1,7 @@ package changeset import ( + "encoding/binary" "fmt" "math/big" "time" @@ -142,3 +143,102 @@ func TransferToMCMSWithTimelock( return deployment.ChangesetOutput{Proposals: []timelock.MCMSWithTimelockProposal{*proposal}}, nil } + +var _ deployment.ChangeSet[TransferToDeployerConfig] = TransferToDeployer + +type TransferToDeployerConfig struct { + ContractAddress common.Address + ChainSel uint64 +} + +// TransferToDeployer relies on the deployer key +// still being a timelock admin and transfers the ownership of a contract +// back to the deployer key. It's effectively the rollback function of transferring +// to the timelock. +func TransferToDeployer(e deployment.Environment, cfg TransferToDeployerConfig) (deployment.ChangesetOutput, error) { + owner, ownable, err := LoadOwnableContract(cfg.ContractAddress, e.Chains[cfg.ChainSel].Client) + if err != nil { + return deployment.ChangesetOutput{}, err + } + if owner == e.Chains[cfg.ChainSel].DeployerKey.From { + e.Logger.Infof("Contract %s already owned by deployer", cfg.ContractAddress) + return deployment.ChangesetOutput{}, nil + } + tx, err := ownable.TransferOwnership(deployment.SimTransactOpts(), e.Chains[cfg.ChainSel].DeployerKey.From) + if err != nil { + return deployment.ChangesetOutput{}, err + } + addrs, err := e.ExistingAddresses.AddressesForChain(cfg.ChainSel) + if err != nil { + return deployment.ChangesetOutput{}, err + } + tls, err := MaybeLoadMCMSWithTimelockChainState(e.Chains[cfg.ChainSel], addrs) + if err != nil { + return deployment.ChangesetOutput{}, err + } + calls := []owner_helpers.RBACTimelockCall{ + { + Target: ownable.Address(), + Data: tx.Data(), + Value: big.NewInt(0), + }, + } + var salt [32]byte + binary.BigEndian.PutUint32(salt[:], uint32(time.Now().Unix())) + tx, err = tls.Timelock.ScheduleBatch(e.Chains[cfg.ChainSel].DeployerKey, calls, [32]byte{}, salt, big.NewInt(0)) + if _, err = deployment.ConfirmIfNoError(e.Chains[cfg.ChainSel], tx, err); err != nil { + return deployment.ChangesetOutput{}, err + } + e.Logger.Infof("scheduled transfer ownership batch with tx %s", tx.Hash().Hex()) + timelockExecutorProxy, err := owner_helpers.NewRBACTimelock(tls.CallProxy.Address(), e.Chains[cfg.ChainSel].Client) + if err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("error creating timelock executor proxy: %w", err) + } + tx, err = timelockExecutorProxy.ExecuteBatch( + e.Chains[cfg.ChainSel].DeployerKey, calls, [32]byte{}, salt) + if err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("error executing batch: %w", err) + } + if _, err = deployment.ConfirmIfNoError(e.Chains[cfg.ChainSel], tx, err); err != nil { + return deployment.ChangesetOutput{}, err + } + e.Logger.Infof("executed transfer ownership to deployer key with tx %s", tx.Hash().Hex()) + + tx, err = ownable.AcceptOwnership(e.Chains[cfg.ChainSel].DeployerKey) + if _, err = deployment.ConfirmIfNoError(e.Chains[cfg.ChainSel], tx, err); err != nil { + return deployment.ChangesetOutput{}, err + } + e.Logger.Infof("deployer key accepted ownership tx %s", tx.Hash().Hex()) + return deployment.ChangesetOutput{}, nil +} + +var _ deployment.ChangeSet[RenounceTimelockDeployerConfig] = RenounceTimelockDeployer + +type RenounceTimelockDeployerConfig struct { + ChainSel uint64 +} + +// RenounceTimelockDeployer revokes the deployer key from administering the contract. +func RenounceTimelockDeployer(e deployment.Environment, cfg RenounceTimelockDeployerConfig) (deployment.ChangesetOutput, error) { + contracts, err := MaybeLoadMCMSWithTimelockState(e, []uint64{cfg.ChainSel}) + if err != nil { + return deployment.ChangesetOutput{}, err + } + tl := contracts[cfg.ChainSel].Timelock + if tl == nil { + return deployment.ChangesetOutput{}, fmt.Errorf("timelock not found on chain %d", cfg.ChainSel) + } + admin, err := tl.ADMINROLE(&bind.CallOpts{}) + if err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("failed to get admin role: %w", err) + } + tx, err := tl.RenounceRole(e.Chains[cfg.ChainSel].DeployerKey, admin, e.Chains[cfg.ChainSel].DeployerKey.From) + if err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("failed to revoke deployer key: %w", err) + } + if _, err := deployment.ConfirmIfNoError(e.Chains[cfg.ChainSel], tx, err); err != nil { + return deployment.ChangesetOutput{}, err + } + e.Logger.Infof("revoked deployer key from owning contract %s", tl.Address().Hex()) + return deployment.ChangesetOutput{}, nil +} diff --git a/deployment/common/changeset/transfer_to_mcms_with_timelock_test.go b/deployment/common/changeset/transfer_to_mcms_with_timelock_test.go index 6cdff286707..49c46aab5f0 100644 --- a/deployment/common/changeset/transfer_to_mcms_with_timelock_test.go +++ b/deployment/common/changeset/transfer_to_mcms_with_timelock_test.go @@ -1,14 +1,14 @@ package changeset import ( + "math/big" "testing" + "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" - owner_helpers "github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers" "github.com/stretchr/testify/require" - "math/big" - + "github.com/smartcontractkit/chainlink/deployment/common/proposalutils" "github.com/smartcontractkit/chainlink/deployment/common/types" "github.com/smartcontractkit/chainlink/deployment/environment/memory" "github.com/smartcontractkit/chainlink/v2/core/logger" @@ -29,25 +29,22 @@ func TestTransferToMCMSWithTimelock(t *testing.T) { { Changeset: WrapChangeSet(DeployMCMSWithTimelock), Config: map[uint64]types.MCMSWithTimelockConfig{ - chain1: { - Canceller: SingleGroupMCMS(t), - Bypasser: SingleGroupMCMS(t), - Proposer: SingleGroupMCMS(t), - TimelockExecutors: e.AllDeployerKeys(), - TimelockMinDelay: big.NewInt(0), - }, + chain1: proposalutils.SingleGroupTimelockConfig(t), }, }, }) require.NoError(t, err) addrs, err := e.ExistingAddresses.AddressesForChain(chain1) require.NoError(t, err) - state, err := MaybeLoadMCMSWithTimelockState(e.Chains[chain1], addrs) + state, err := MaybeLoadMCMSWithTimelockChainState(e.Chains[chain1], addrs) require.NoError(t, err) - link, err := MaybeLoadLinkTokenState(e.Chains[chain1], addrs) + link, err := MaybeLoadLinkTokenChainState(e.Chains[chain1], addrs) require.NoError(t, err) - e, err = ApplyChangesets(t, e, map[uint64]*owner_helpers.RBACTimelock{ - chain1: state.Timelock, + e, err = ApplyChangesets(t, e, map[uint64]*proposalutils.TimelockExecutionContracts{ + chain1: { + Timelock: state.Timelock, + CallProxy: state.CallProxy, + }, }, []ChangesetApplication{ { Changeset: WrapChangeSet(TransferToMCMSWithTimelock), @@ -61,9 +58,81 @@ func TestTransferToMCMSWithTimelock(t *testing.T) { }) require.NoError(t, err) // We expect now that the link token is owned by the MCMS timelock. - link, err = MaybeLoadLinkTokenState(e.Chains[chain1], addrs) + link, err = MaybeLoadLinkTokenChainState(e.Chains[chain1], addrs) require.NoError(t, err) o, err := link.LinkToken.Owner(nil) require.NoError(t, err) require.Equal(t, state.Timelock.Address(), o) + + // Try a rollback to the deployer. + e, err = ApplyChangesets(t, e, nil, []ChangesetApplication{ + { + Changeset: WrapChangeSet(TransferToDeployer), + Config: TransferToDeployerConfig{ + ContractAddress: link.LinkToken.Address(), + ChainSel: chain1, + }, + }, + }) + require.NoError(t, err) + + o, err = link.LinkToken.Owner(nil) + require.NoError(t, err) + require.Equal(t, e.Chains[chain1].DeployerKey.From, o) +} + +func TestRenounceTimelockDeployer(t *testing.T) { + lggr := logger.TestLogger(t) + e := memory.NewMemoryEnvironment(t, lggr, 0, memory.MemoryEnvironmentConfig{ + Chains: 1, + Nodes: 1, + }) + chain1 := e.AllChainSelectors()[0] + e, err := ApplyChangesets(t, e, nil, []ChangesetApplication{ + { + Changeset: WrapChangeSet(DeployMCMSWithTimelock), + Config: map[uint64]types.MCMSWithTimelockConfig{ + chain1: proposalutils.SingleGroupTimelockConfig(t), + }, + }, + }) + require.NoError(t, err) + addrs, err := e.ExistingAddresses.AddressesForChain(chain1) + require.NoError(t, err) + + state, err := MaybeLoadMCMSWithTimelockChainState(e.Chains[chain1], addrs) + require.NoError(t, err) + + tl := state.Timelock + require.NotNil(t, tl) + + adminRole, err := tl.ADMINROLE(nil) + require.NoError(t, err) + + r, err := tl.GetRoleMemberCount(&bind.CallOpts{}, adminRole) + require.NoError(t, err) + require.Equal(t, int64(2), r.Int64()) + + // Revoke Deployer + e, err = ApplyChangesets(t, e, nil, []ChangesetApplication{ + { + Changeset: WrapChangeSet(RenounceTimelockDeployer), + Config: RenounceTimelockDeployerConfig{ + ChainSel: chain1, + }, + }, + }) + require.NoError(t, err) + + // Check that the deployer is no longer an admin + r, err = tl.GetRoleMemberCount(&bind.CallOpts{}, adminRole) + require.NoError(t, err) + require.Equal(t, int64(1), r.Int64()) + + // Retrieve the admin address + admin, err := tl.GetRoleMember(&bind.CallOpts{}, adminRole, big.NewInt(0)) + require.NoError(t, err) + + // Check that the admin is the timelock + require.Equal(t, tl.Address(), admin) } diff --git a/deployment/common/proposalutils/mcms_helpers.go b/deployment/common/proposalutils/mcms_helpers.go new file mode 100644 index 00000000000..51a720a4389 --- /dev/null +++ b/deployment/common/proposalutils/mcms_helpers.go @@ -0,0 +1,275 @@ +package proposalutils + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + owner_helpers "github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers" + "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/mcms" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink/deployment" + "github.com/smartcontractkit/chainlink/deployment/common/types" +) + +// TimelockExecutionContracts is a helper struct for executing timelock proposals. it contains +// the timelock and call proxy contracts. +type TimelockExecutionContracts struct { + Timelock *owner_helpers.RBACTimelock + CallProxy *owner_helpers.CallProxy +} + +// NewTimelockExecutionContracts creates a new TimelockExecutionContracts struct. +// If there are multiple timelocks or call proxy on the chain, an error is returned. +// If there is a missing timelocks or call proxy on the chain, an error is returned. +func NewTimelockExecutionContracts(env deployment.Environment, chainSelector uint64) (*TimelockExecutionContracts, error) { + addrTypeVer, err := env.ExistingAddresses.AddressesForChain(chainSelector) + if err != nil { + return nil, fmt.Errorf("error getting addresses for chain: %w", err) + } + var timelock *owner_helpers.RBACTimelock + var callProxy *owner_helpers.CallProxy + for addr, tv := range addrTypeVer { + if tv.Type == types.RBACTimelock { + if timelock != nil { + return nil, fmt.Errorf("multiple timelocks found on chain %d", chainSelector) + } + var err error + timelock, err = owner_helpers.NewRBACTimelock(common.HexToAddress(addr), env.Chains[chainSelector].Client) + if err != nil { + return nil, fmt.Errorf("error creating timelock: %w", err) + } + } + if tv.Type == types.CallProxy { + if callProxy != nil { + return nil, fmt.Errorf("multiple call proxies found on chain %d", chainSelector) + } + var err error + callProxy, err = owner_helpers.NewCallProxy(common.HexToAddress(addr), env.Chains[chainSelector].Client) + if err != nil { + return nil, fmt.Errorf("error creating call proxy: %w", err) + } + } + } + if timelock == nil || callProxy == nil { + return nil, fmt.Errorf("missing timelock (%T) or call proxy(%T) on chain %d", timelock == nil, callProxy == nil, chainSelector) + } + return &TimelockExecutionContracts{ + Timelock: timelock, + CallProxy: callProxy, + }, nil +} + +type RunTimelockExecutorConfig struct { + Executor *mcms.Executor + TimelockContracts *TimelockExecutionContracts + ChainSelector uint64 + // BlockStart is optional. It filter the timelock scheduled events. + // If not provided, the executor assumes that the operations have not been executed yet + // executes all the operations for the given chain. + BlockStart *uint64 + BlockEnd *uint64 +} + +func (cfg RunTimelockExecutorConfig) Validate() error { + if cfg.Executor == nil { + return fmt.Errorf("executor is nil") + } + if cfg.TimelockContracts == nil { + return fmt.Errorf("timelock contracts is nil") + } + if cfg.ChainSelector == 0 { + return fmt.Errorf("chain selector is 0") + } + if cfg.BlockStart != nil && cfg.BlockEnd == nil { + if *cfg.BlockStart > *cfg.BlockEnd { + return fmt.Errorf("block start is greater than block end") + } + } + if cfg.BlockStart == nil && cfg.BlockEnd != nil { + return fmt.Errorf("block start must not be nil when block end is not nil") + } + + if len(cfg.Executor.Operations[mcms.ChainIdentifier(cfg.ChainSelector)]) == 0 { + return fmt.Errorf("no operations for chain %d", cfg.ChainSelector) + } + return nil +} + +// RunTimelockExecutor runs the scheduled operations for the given chain. +// If the block start is not provided, it assumes that the operations have not been scheduled yet +// and executes all the operations for the given chain. +// It is an error if there are no operations for the given chain. +func RunTimelockExecutor(env deployment.Environment, cfg RunTimelockExecutorConfig) error { + // TODO: This sort of helper probably should move to the MCMS lib. + // Execute all the transactions in the proposal which are for this chain. + if err := cfg.Validate(); err != nil { + return fmt.Errorf("error validating config: %w", err) + } + for _, chainOp := range cfg.Executor.Operations[mcms.ChainIdentifier(cfg.ChainSelector)] { + for idx, op := range cfg.Executor.ChainAgnosticOps { + start := cfg.BlockStart + end := cfg.BlockEnd + if bytes.Equal(op.Data, chainOp.Data) && op.To == chainOp.To { + if start == nil { + opTx, err2 := cfg.Executor.ExecuteOnChain(env.Chains[cfg.ChainSelector].Client, env.Chains[cfg.ChainSelector].DeployerKey, idx) + if err2 != nil { + return fmt.Errorf("error executing on chain: %w", err2) + } + block, err2 := env.Chains[cfg.ChainSelector].Confirm(opTx) + if err2 != nil { + return fmt.Errorf("error confirming on chain: %w", err2) + } + start = &block + end = &block + } + + it, err2 := cfg.TimelockContracts.Timelock.FilterCallScheduled(&bind.FilterOpts{ + Start: *start, + End: end, + Context: env.GetContext(), + }, nil, nil) + if err2 != nil { + return fmt.Errorf("error filtering call scheduled: %w", err2) + } + var calls []owner_helpers.RBACTimelockCall + var pred, salt [32]byte + for it.Next() { + // Note these are the same for the whole batch, can overwrite + pred = it.Event.Predecessor + salt = it.Event.Salt + verboseDebug(env.Logger, it.Event) + env.Logger.Info("scheduled", "event", it.Event) + calls = append(calls, owner_helpers.RBACTimelockCall{ + Target: it.Event.Target, + Data: it.Event.Data, + Value: it.Event.Value, + }) + } + if len(calls) == 0 { + return fmt.Errorf("no calls found for chain %d in blocks [%d, %d]", cfg.ChainSelector, *start, *end) + } + timelockExecutorProxy, err := owner_helpers.NewRBACTimelock(cfg.TimelockContracts.CallProxy.Address(), env.Chains[cfg.ChainSelector].Client) + if err != nil { + return fmt.Errorf("error creating timelock executor proxy: %w", err) + } + tx, err := timelockExecutorProxy.ExecuteBatch( + env.Chains[cfg.ChainSelector].DeployerKey, calls, pred, salt) + if err != nil { + return fmt.Errorf("error executing batch: %w", err) + } + _, err = env.Chains[cfg.ChainSelector].Confirm(tx) + if err != nil { + return fmt.Errorf("error confirming batch: %w", err) + } + } + } + } + return nil +} + +func verboseDebug(lggr logger.Logger, event *owner_helpers.RBACTimelockCallScheduled) { + b, err := json.Marshal(event) + if err != nil { + panic(err) + } + lggr.Debug("scheduled", "event", string(b)) +} + +// MCMSWithTimelockContracts holds the Go bindings +// for a MCMSWithTimelock contract deployment. +// It is public for use in product specific packages. +// Either all fields are nil or all fields are non-nil. +type MCMSWithTimelockContracts struct { + CancellerMcm *owner_helpers.ManyChainMultiSig + BypasserMcm *owner_helpers.ManyChainMultiSig + ProposerMcm *owner_helpers.ManyChainMultiSig + Timelock *owner_helpers.RBACTimelock + CallProxy *owner_helpers.CallProxy +} + +// Validate checks that all fields are non-nil, ensuring it's ready +// for use generating views or interactions. +func (state MCMSWithTimelockContracts) Validate() error { + if state.Timelock == nil { + return errors.New("timelock not found") + } + if state.CancellerMcm == nil { + return errors.New("canceller not found") + } + if state.ProposerMcm == nil { + return errors.New("proposer not found") + } + if state.BypasserMcm == nil { + return errors.New("bypasser not found") + } + if state.CallProxy == nil { + return errors.New("call proxy not found") + } + return nil +} + +// MaybeLoadMCMSWithTimelockContracts looks for the addresses corresponding to +// contracts deployed with DeployMCMSWithTimelock and loads them into a +// MCMSWithTimelockState struct. If none of the contracts are found, the state struct will be nil. +// An error indicates: +// - Found but was unable to load a contract +// - It only found part of the bundle of contracts +// - If found more than one instance of a contract (we expect one bundle in the given addresses) +func MaybeLoadMCMSWithTimelockContracts(chain deployment.Chain, addresses map[string]deployment.TypeAndVersion) (*MCMSWithTimelockContracts, error) { + state := MCMSWithTimelockContracts{} + // We expect one of each contract on the chain. + timelock := deployment.NewTypeAndVersion(types.RBACTimelock, deployment.Version1_0_0) + callProxy := deployment.NewTypeAndVersion(types.CallProxy, deployment.Version1_0_0) + proposer := deployment.NewTypeAndVersion(types.ProposerManyChainMultisig, deployment.Version1_0_0) + canceller := deployment.NewTypeAndVersion(types.CancellerManyChainMultisig, deployment.Version1_0_0) + bypasser := deployment.NewTypeAndVersion(types.BypasserManyChainMultisig, deployment.Version1_0_0) + + // Ensure we either have the bundle or not. + _, err := deployment.AddressesContainBundle(addresses, + map[deployment.TypeAndVersion]struct{}{ + timelock: {}, proposer: {}, canceller: {}, bypasser: {}, callProxy: {}, + }) + if err != nil { + return nil, fmt.Errorf("unable to check MCMS contracts on chain %s error: %w", chain.Name(), err) + } + + for address, tvStr := range addresses { + switch tvStr { + case timelock: + tl, err := owner_helpers.NewRBACTimelock(common.HexToAddress(address), chain.Client) + if err != nil { + return nil, err + } + state.Timelock = tl + case callProxy: + cp, err := owner_helpers.NewCallProxy(common.HexToAddress(address), chain.Client) + if err != nil { + return nil, err + } + state.CallProxy = cp + case proposer: + mcms, err := owner_helpers.NewManyChainMultiSig(common.HexToAddress(address), chain.Client) + if err != nil { + return nil, err + } + state.ProposerMcm = mcms + case bypasser: + mcms, err := owner_helpers.NewManyChainMultiSig(common.HexToAddress(address), chain.Client) + if err != nil { + return nil, err + } + state.BypasserMcm = mcms + case canceller: + mcms, err := owner_helpers.NewManyChainMultiSig(common.HexToAddress(address), chain.Client) + if err != nil { + return nil, err + } + state.CancellerMcm = mcms + } + } + return &state, nil +} diff --git a/deployment/common/changeset/mcms_test_helpers.go b/deployment/common/proposalutils/mcms_test_helpers.go similarity index 59% rename from deployment/common/changeset/mcms_test_helpers.go rename to deployment/common/proposalutils/mcms_test_helpers.go index 3951149815c..610fe84f34c 100644 --- a/deployment/common/changeset/mcms_test_helpers.go +++ b/deployment/common/proposalutils/mcms_test_helpers.go @@ -1,22 +1,21 @@ -package changeset +package proposalutils import ( - "bytes" - "context" "crypto/ecdsa" + "math/big" "testing" - "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/smartcontractkit/ccip-owner-contracts/pkg/config" - owner_helpers "github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers" "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/mcms" "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/timelock" chainsel "github.com/smartcontractkit/chain-selectors" "github.com/stretchr/testify/require" "github.com/smartcontractkit/chainlink/deployment" + commontypes "github.com/smartcontractkit/chainlink/deployment/common/types" + // "github.com/smartcontractkit/chainlink/deployment/common/proposalutils" ) var ( @@ -65,51 +64,29 @@ func SignProposal(t *testing.T, env deployment.Environment, proposal *timelock.M } func ExecuteProposal(t *testing.T, env deployment.Environment, executor *mcms.Executor, - timelock *owner_helpers.RBACTimelock, sel uint64) { + timelockContracts *TimelockExecutionContracts, sel uint64) { t.Log("Executing proposal on chain", sel) // Set the root. tx, err2 := executor.SetRootOnChain(env.Chains[sel].Client, env.Chains[sel].DeployerKey, mcms.ChainIdentifier(sel)) if err2 != nil { require.NoError(t, deployment.MaybeDataErr(err2)) } + _, err2 = env.Chains[sel].Confirm(tx) require.NoError(t, err2) + cfg := RunTimelockExecutorConfig{ + Executor: executor, + TimelockContracts: timelockContracts, + ChainSelector: sel, + } + require.NoError(t, RunTimelockExecutor(env, cfg)) +} - // TODO: This sort of helper probably should move to the MCMS lib. - // Execute all the transactions in the proposal which are for this chain. - for _, chainOp := range executor.Operations[mcms.ChainIdentifier(sel)] { - for idx, op := range executor.ChainAgnosticOps { - if bytes.Equal(op.Data, chainOp.Data) && op.To == chainOp.To { - opTx, err3 := executor.ExecuteOnChain(env.Chains[sel].Client, env.Chains[sel].DeployerKey, idx) - require.NoError(t, err3) - block, err3 := env.Chains[sel].Confirm(opTx) - require.NoError(t, err3) - t.Log("executed", chainOp) - it, err3 := timelock.FilterCallScheduled(&bind.FilterOpts{ - Start: block, - End: &block, - Context: context.Background(), - }, nil, nil) - require.NoError(t, err3) - var calls []owner_helpers.RBACTimelockCall - var pred, salt [32]byte - for it.Next() { - // Note these are the same for the whole batch, can overwrite - pred = it.Event.Predecessor - salt = it.Event.Salt - t.Log("scheduled", it.Event) - calls = append(calls, owner_helpers.RBACTimelockCall{ - Target: it.Event.Target, - Data: it.Event.Data, - Value: it.Event.Value, - }) - } - tx, err := timelock.ExecuteBatch( - env.Chains[sel].DeployerKey, calls, pred, salt) - require.NoError(t, err) - _, err = env.Chains[sel].Confirm(tx) - require.NoError(t, err) - } - } +func SingleGroupTimelockConfig(t *testing.T) commontypes.MCMSWithTimelockConfig { + return commontypes.MCMSWithTimelockConfig{ + Canceller: SingleGroupMCMS(t), + Bypasser: SingleGroupMCMS(t), + Proposer: SingleGroupMCMS(t), + TimelockMinDelay: big.NewInt(0), } } diff --git a/deployment/common/proposalutils/propose.go b/deployment/common/proposalutils/propose.go index f525c0b6643..32a5bcdfda2 100644 --- a/deployment/common/proposalutils/propose.go +++ b/deployment/common/proposalutils/propose.go @@ -11,7 +11,12 @@ import ( "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/timelock" ) -func buildProposalMetadata( +const ( + DefaultValidUntil = 72 * time.Hour +) + + +func BuildProposalMetadata( chainSelectors []uint64, proposerMcmsesPerChain map[uint64]*gethwrappers.ManyChainMultiSig, ) (map[mcms.ChainIdentifier]mcms.ChainMetadata, error) { @@ -52,7 +57,7 @@ func BuildProposalFromBatches( chains.Add(uint64(op.ChainIdentifier)) } - mcmsMd, err := buildProposalMetadata(chains.ToSlice(), proposerMcmsesPerChain) + mcmsMd, err := BuildProposalMetadata(chains.ToSlice(), proposerMcmsesPerChain) if err != nil { return nil, err } @@ -61,10 +66,10 @@ func BuildProposalFromBatches( for chainId, tl := range timelocksPerChain { tlsPerChainId[mcms.ChainIdentifier(chainId)] = tl } - + validUntil := time.Now().Unix() + int64(DefaultValidUntil.Seconds()) return timelock.NewMCMSWithTimelockProposal( "1", - 2004259681, // TODO: should be parameterized and based on current block timestamp. + uint32(validUntil), []mcms.Signature{}, false, mcmsMd, diff --git a/deployment/common/types/types.go b/deployment/common/types/types.go index 386ef8fbb36..0f04421af43 100644 --- a/deployment/common/types/types.go +++ b/deployment/common/types/types.go @@ -5,7 +5,6 @@ import ( "math/big" "time" - "github.com/ethereum/go-ethereum/common" "github.com/smartcontractkit/ccip-owner-contracts/pkg/config" "github.com/smartcontractkit/chainlink/deployment" @@ -16,6 +15,7 @@ const ( CancellerManyChainMultisig deployment.ContractType = "CancellerManyChainMultiSig" ProposerManyChainMultisig deployment.ContractType = "ProposerManyChainMultiSig" RBACTimelock deployment.ContractType = "RBACTimelock" + CallProxy deployment.ContractType = "CallProxy" // LinkToken is the burn/mint link token. It should be used everywhere for // new deployments. Corresponds to // https://github.com/smartcontractkit/chainlink/blob/develop/core/gethwrappers/shared/generated/link_token/link_token.go#L34 @@ -29,11 +29,10 @@ const ( ) type MCMSWithTimelockConfig struct { - Canceller config.Config - Bypasser config.Config - Proposer config.Config - TimelockExecutors []common.Address - TimelockMinDelay *big.Int + Canceller config.Config + Bypasser config.Config + Proposer config.Config + TimelockMinDelay *big.Int } type OCRParameters struct { diff --git a/deployment/common/view/v1_0/mcms.go b/deployment/common/view/v1_0/mcms.go index 25ca614a553..bc971623545 100644 --- a/deployment/common/view/v1_0/mcms.go +++ b/deployment/common/view/v1_0/mcms.go @@ -107,11 +107,24 @@ func GenerateTimelockView(tl owner_helpers.RBACTimelock) (TimelockView, error) { }, nil } +type CallProxyView struct { + types.ContractMetaData +} + +func GenerateCallProxyView(cp owner_helpers.CallProxy) (CallProxyView, error) { + return CallProxyView{ + ContractMetaData: types.ContractMetaData{ + Address: cp.Address(), + }, + }, nil +} + type MCMSWithTimelockView struct { - Bypasser MCMSView `json:"bypasser"` - Canceller MCMSView `json:"canceller"` - Proposer MCMSView `json:"proposer"` - Timelock TimelockView `json:"timelock"` + Bypasser MCMSView `json:"bypasser"` + Canceller MCMSView `json:"canceller"` + Proposer MCMSView `json:"proposer"` + Timelock TimelockView `json:"timelock"` + CallProxy CallProxyView `json:"callProxy"` } func GenerateMCMSWithTimelockView( @@ -124,6 +137,10 @@ func GenerateMCMSWithTimelockView( if err != nil { return MCMSWithTimelockView{}, nil } + callProxyView, err := GenerateCallProxyView(owner_helpers.CallProxy{}) + if err != nil { + return MCMSWithTimelockView{}, nil + } bypasserView, err := GenerateMCMSView(bypasser) if err != nil { return MCMSWithTimelockView{}, nil @@ -142,5 +159,6 @@ func GenerateMCMSWithTimelockView( Bypasser: bypasserView, Proposer: proposerView, Canceller: cancellerView, + CallProxy: callProxyView, }, nil } diff --git a/deployment/environment.go b/deployment/environment.go index 3d120adbbf1..0823404da2d 100644 --- a/deployment/environment.go +++ b/deployment/environment.go @@ -95,6 +95,7 @@ type Environment struct { Logger logger.Logger ExistingAddresses AddressBook Chains map[uint64]Chain + SolChains map[uint64]SolChain NodeIDs []string Offchain OffchainClient GetContext func() context.Context @@ -180,7 +181,7 @@ func MaybeDataErr(err error) error { var d rpc.DataError ok := errors.As(err, &d) if ok { - return d + return fmt.Errorf("%s: %v", d.Error(), d.ErrorData()) } return err } @@ -331,7 +332,6 @@ func NodeInfo(nodeIDs []string, oc NodeChainConfigsLister) (Nodes, error) { Enabled: 1, Ids: nodeIDs, } - } nodesFromJD, err := oc.ListNodes(context.Background(), &nodev1.ListNodesRequest{ Filter: filter, diff --git a/deployment/environment/crib/ccip_deployer.go b/deployment/environment/crib/ccip_deployer.go new file mode 100644 index 00000000000..aea7ad0cb8f --- /dev/null +++ b/deployment/environment/crib/ccip_deployer.go @@ -0,0 +1,136 @@ +package crib + +import ( + "context" + "errors" + "fmt" + "github.com/ethereum/go-ethereum/common" + "github.com/smartcontractkit/ccip-owner-contracts/pkg/config" + commonchangeset "github.com/smartcontractkit/chainlink/deployment/common/changeset" + commontypes "github.com/smartcontractkit/chainlink/deployment/common/types" + "github.com/smartcontractkit/chainlink/deployment/environment/devenv" + "github.com/smartcontractkit/chainlink/v2/core/services/relay" + "math/big" + + "github.com/smartcontractkit/chainlink/deployment" + "github.com/smartcontractkit/chainlink/deployment/ccip/changeset" + "github.com/smartcontractkit/chainlink/v2/core/logger" +) + +// DeployHomeChainContracts deploys the home chain contracts so that the chainlink nodes can be started with the CR address in Capabilities.ExternalRegistry +// DeployHomeChainContracts is to 1. Set up crib with chains and chainlink nodes ( cap reg is not known yet so not setting the config with capreg address) +// Call DeployHomeChain changeset with nodeinfo ( the peer id and all) +func DeployHomeChainContracts(ctx context.Context, lggr logger.Logger, envConfig devenv.EnvironmentConfig, homeChainSel uint64, feedChainSel uint64) (deployment.CapabilityRegistryConfig, deployment.AddressBook, error) { + e, _, err := devenv.NewEnvironment(func() context.Context { return ctx }, lggr, envConfig) + if err != nil { + return deployment.CapabilityRegistryConfig{}, nil, err + } + if e == nil { + return deployment.CapabilityRegistryConfig{}, nil, errors.New("environment is nil") + } + + nodes, err := deployment.NodeInfo(e.NodeIDs, e.Offchain) + if err != nil { + return deployment.CapabilityRegistryConfig{}, e.ExistingAddresses, fmt.Errorf("failed to get node info from env: %w", err) + } + p2pIds := nodes.NonBootstraps().PeerIDs() + *e, err = commonchangeset.ApplyChangesets(nil, *e, nil, []commonchangeset.ChangesetApplication{ + { + Changeset: commonchangeset.WrapChangeSet(changeset.DeployHomeChain), + Config: changeset.DeployHomeChainConfig{ + HomeChainSel: homeChainSel, + RMNStaticConfig: changeset.NewTestRMNStaticConfig(), + RMNDynamicConfig: changeset.NewTestRMNDynamicConfig(), + NodeOperators: changeset.NewTestNodeOperator(e.Chains[homeChainSel].DeployerKey.From), + NodeP2PIDsPerNodeOpAdmin: map[string][][32]byte{ + "NodeOperator": p2pIds, + }, + }, + }, + }) + + state, err := changeset.LoadOnchainState(*e) + if err != nil { + return deployment.CapabilityRegistryConfig{}, e.ExistingAddresses, fmt.Errorf("failed to load on chain state: %w", err) + } + capRegAddr := state.Chains[homeChainSel].CapabilityRegistry.Address() + if capRegAddr == common.HexToAddress("0x") { + return deployment.CapabilityRegistryConfig{}, e.ExistingAddresses, fmt.Errorf("cap Reg address not found: %w", err) + } + capRegConfig := deployment.CapabilityRegistryConfig{ + EVMChainID: homeChainSel, + Contract: state.Chains[homeChainSel].CapabilityRegistry.Address(), + NetworkType: relay.NetworkEVM, + } + return capRegConfig, e.ExistingAddresses, nil +} + +func DeployCCIPAndAddLanes(ctx context.Context, lggr logger.Logger, envConfig devenv.EnvironmentConfig, homeChainSel, feedChainSel uint64, ab deployment.AddressBook) (DeployCCIPOutput, error) { + e, _, err := devenv.NewEnvironment(func() context.Context { return ctx }, lggr, envConfig) + if err != nil { + return DeployCCIPOutput{}, fmt.Errorf("failed to initiate new environment: %w", err) + } + e.ExistingAddresses = ab + allChainIds := e.AllChainSelectors() + cfg := make(map[uint64]commontypes.MCMSWithTimelockConfig) + for _, chain := range e.AllChainSelectors() { + mcmsConfig, err := config.NewConfig(1, []common.Address{e.Chains[chain].DeployerKey.From}, []config.Config{}) + if err != nil { + return DeployCCIPOutput{}, fmt.Errorf("failed to create mcms config: %w", err) + } + cfg[chain] = commontypes.MCMSWithTimelockConfig{ + Canceller: *mcmsConfig, + Bypasser: *mcmsConfig, + Proposer: *mcmsConfig, + TimelockMinDelay: big.NewInt(0), + } + } + + // This will not apply any proposals because we pass nil to testing. + // However, setup is ok because we only need to deploy the contracts and distribute job specs + *e, err = commonchangeset.ApplyChangesets(nil, *e, nil, []commonchangeset.ChangesetApplication{ + { + Changeset: commonchangeset.WrapChangeSet(commonchangeset.DeployLinkToken), + Config: allChainIds, + }, + { + Changeset: commonchangeset.WrapChangeSet(changeset.DeployPrerequisites), + Config: changeset.DeployPrerequisiteConfig{ + ChainSelectors: allChainIds, + }, + }, + { + Changeset: commonchangeset.WrapChangeSet(commonchangeset.DeployMCMSWithTimelock), + Config: cfg, + }, + { + Changeset: commonchangeset.WrapChangeSet(changeset.DeployChainContracts), + Config: changeset.DeployChainContractsConfig{ + ChainSelectors: allChainIds, + HomeChainSelector: homeChainSel, + }, + }, + { + Changeset: commonchangeset.WrapChangeSet(changeset.CCIPCapabilityJobspec), + Config: struct{}{}, + }, + }) + state, err := changeset.LoadOnchainState(*e) + if err != nil { + return DeployCCIPOutput{}, fmt.Errorf("failed to load onchain state: %w", err) + } + // Add all lanes + err = changeset.AddLanesForAll(*e, state) + if err != nil { + return DeployCCIPOutput{}, fmt.Errorf("failed to add lanes: %w", err) + } + + addresses, err := e.ExistingAddresses.Addresses() + if err != nil { + return DeployCCIPOutput{}, fmt.Errorf("failed to get convert address book to address book map: %w", err) + } + return DeployCCIPOutput{ + AddressBook: *deployment.NewMemoryAddressBookFromMap(addresses), + NodeIDs: e.NodeIDs, + }, err +} diff --git a/deployment/environment/crib/data.go b/deployment/environment/crib/data.go new file mode 100644 index 00000000000..b9197691613 --- /dev/null +++ b/deployment/environment/crib/data.go @@ -0,0 +1,81 @@ +package crib + +import ( + "encoding/json" + "fmt" + "io" + "os" + + "github.com/smartcontractkit/chainlink/deployment" + "github.com/smartcontractkit/chainlink/deployment/environment/devenv" +) + +type OutputReader struct { + outputDir string +} + +func NewOutputReader(outputDir string) *OutputReader { + return &OutputReader{outputDir: outputDir} +} + +func (r *OutputReader) ReadNodesDetails() NodesDetails { + byteValue := r.readFile(NodesDetailsFileName) + + var result NodesDetails + + // Unmarshal the JSON into the map + err := json.Unmarshal(byteValue, &result) + if err != nil { + fmt.Println("Error unmarshalling JSON:", err) + panic(err) + } + + return result +} + +func (r *OutputReader) ReadChainConfigs() []devenv.ChainConfig { + byteValue := r.readFile(ChainsConfigsFileName) + + var result []devenv.ChainConfig + + // Unmarshal the JSON into the map + err := json.Unmarshal(byteValue, &result) + if err != nil { + fmt.Println("Error unmarshalling JSON:", err) + panic(err) + } + + return result +} + +func (r *OutputReader) ReadAddressBook() *deployment.AddressBookMap { + byteValue := r.readFile(AddressBookFileName) + + var result map[uint64]map[string]deployment.TypeAndVersion + + // Unmarshal the JSON into the map + err := json.Unmarshal(byteValue, &result) + if err != nil { + fmt.Println("Error unmarshalling JSON:", err) + panic(err) + } + + return deployment.NewMemoryAddressBookFromMap(result) +} + +func (r *OutputReader) readFile(fileName string) []byte { + file, err := os.Open(fmt.Sprintf("%s/%s", r.outputDir, fileName)) + if err != nil { + fmt.Println("Error opening file:", err) + panic(err) + } + defer file.Close() + + // Read the file's content into a byte slice + byteValue, err := io.ReadAll(file) + if err != nil { + fmt.Println("Error reading file:", err) + panic(err) + } + return byteValue +} diff --git a/deployment/environment/crib/env.go b/deployment/environment/crib/env.go new file mode 100644 index 00000000000..3af1acaf754 --- /dev/null +++ b/deployment/environment/crib/env.go @@ -0,0 +1,45 @@ +package crib + +const ( + AddressBookFileName = "ccip-v2-scripts-address-book.json" + NodesDetailsFileName = "ccip-v2-scripts-nodes-details.json" + ChainsConfigsFileName = "ccip-v2-scripts-chains-details.json" +) + +type CRIBEnv struct { + envStateDir string +} + +func NewDevspaceEnvFromStateDir(envStateDir string) CRIBEnv { + return CRIBEnv{ + envStateDir: envStateDir, + } +} + +func (c CRIBEnv) GetConfig() DeployOutput { + reader := NewOutputReader(c.envStateDir) + nodesDetails := reader.ReadNodesDetails() + chainConfigs := reader.ReadChainConfigs() + return DeployOutput{ + AddressBook: reader.ReadAddressBook(), + NodeIDs: nodesDetails.NodeIDs, + Chains: chainConfigs, + } +} + +type RPC struct { + External *string + Internal *string +} + +type ChainConfig struct { + ChainID uint64 // chain id as per EIP-155, mainly applicable for EVM chains + ChainName string // name of the chain populated from chainselector repo + ChainType string // should denote the chain family. Acceptable values are EVM, COSMOS, SOLANA, STARKNET, APTOS etc + WSRPCs []RPC // websocket rpcs to connect to the chain + HTTPRPCs []RPC // http rpcs to connect to the chain +} + +type NodesDetails struct { + NodeIDs []string +} diff --git a/deployment/environment/crib/env_test.go b/deployment/environment/crib/env_test.go new file mode 100644 index 00000000000..262a2540923 --- /dev/null +++ b/deployment/environment/crib/env_test.go @@ -0,0 +1,18 @@ +package crib + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestShouldProvideEnvironmentConfig(t *testing.T) { + t.Parallel() + env := NewDevspaceEnvFromStateDir("testdata/lanes-deployed-state") + config := env.GetConfig() + require.NotNil(t, config) + assert.NotEmpty(t, config.NodeIDs) + assert.NotNil(t, config.AddressBook) + assert.NotEmpty(t, config.Chains) +} diff --git a/deployment/environment/crib/testdata/lanes-deployed-state/ccip-v2-scripts-address-book.json b/deployment/environment/crib/testdata/lanes-deployed-state/ccip-v2-scripts-address-book.json new file mode 100644 index 00000000000..e4b2672cb5f --- /dev/null +++ b/deployment/environment/crib/testdata/lanes-deployed-state/ccip-v2-scripts-address-book.json @@ -0,0 +1 @@ +{"12922642891491394802":{"0x05Aa229Aec102f78CE0E852A812a388F076Aa555":{"Type":"CancellerManyChainMultiSig","Version":"1.0.0"},"0x0D4ff719551E23185Aeb16FFbF2ABEbB90635942":{"Type":"TestRouter","Version":"1.2.0"},"0x0f5D1ef48f12b6f691401bfe88c2037c690a6afe":{"Type":"ProposerManyChainMultiSig","Version":"1.0.0"},"0x2dE080e97B0caE9825375D31f5D0eD5751fDf16D":{"Type":"CCIPReceiver","Version":"1.0.0"},"0x2fc631e4B3018258759C52AF169200213e84ABab":{"Type":"OnRamp","Version":"1.6.0-dev"},"0x5C7c905B505f0Cf40Ab6600d05e677F717916F6B":{"Type":"Router","Version":"1.2.0"},"0x63cf2Cd54fE91e3545D1379abf5bfd194545259d":{"Type":"OffRamp","Version":"1.6.0-dev"},"0x712516e61C8B383dF4A63CFe83d7701Bce54B03e":{"Type":"LinkToken","Version":"1.0.0"},"0x71C95911E9a5D330f4D621842EC243EE1343292e":{"Type":"PriceFeed","Version":"1.0.0"},"0x73eccD6288e117cAcA738BDAD4FEC51312166C1A":{"Type":"RMNRemote","Version":"1.6.0-dev"},"0x8464135c8F25Da09e49BC8782676a84730C318bC":{"Type":"PriceFeed","Version":"1.0.0"},"0x85C5Dd61585773423e378146D4bEC6f8D149E248":{"Type":"TokenAdminRegistry","Version":"1.5.0"},"0x948B3c65b89DF0B4894ABE91E6D02FE579834F8F":{"Type":"WETH9","Version":"1.0.0"},"0xAfe1b5bdEbD4ae65AF2024738bf0735fbb65d44b":{"Type":"FeeQuoter","Version":"1.6.0-dev"},"0xC6bA8C3233eCF65B761049ef63466945c362EdD2":{"Type":"BypasserManyChainMultiSig","Version":"1.0.0"},"0xbCF26943C0197d2eE0E5D05c716Be60cc2761508":{"Type":"AdminManyChainMultiSig","Version":"1.0.0"},"0xcA03Dc4665A8C3603cb4Fd5Ce71Af9649dC00d44":{"Type":"RBACTimelock","Version":"1.0.0"},"0xe6b98F104c1BEf218F3893ADab4160Dc73Eb8367":{"Type":"ARMProxy","Version":"1.0.0"},"0xfbAb4aa40C202E4e80390171E82379824f7372dd":{"Type":"NonceManager","Version":"1.6.0-dev"}},"3379446385462418246":{"0x09635F643e140090A9A8Dcd712eD6285858ceBef":{"Type":"RMNRemote","Version":"1.6.0-dev"},"0x0B306BF915C4d645ff596e518fAf3F9669b97016":{"Type":"LinkToken","Version":"1.0.0"},"0x1613beB3B2C4f22Ee086B2b38C1476A3cE7f78E8":{"Type":"OnRamp","Version":"1.6.0-dev"},"0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6":{"Type":"CCIPHome","Version":"1.6.0-dev"},"0x322813Fd9A801c5507c9de605d63CEA4f2CE6c44":{"Type":"ProposerManyChainMultiSig","Version":"1.0.0"},"0x3Aa5ebB10DC797CAC828524e59A333d0A371443c":{"Type":"BypasserManyChainMultiSig","Version":"1.0.0"},"0x4A679253410272dd5232B3Ff7cF5dbB88f295319":{"Type":"RBACTimelock","Version":"1.0.0"},"0x59b670e9fA9D0A427751Af201D676719a970857b":{"Type":"CancellerManyChainMultiSig","Version":"1.0.0"},"0x67d269191c92Caf3cD7723F116c85e6E9bf55933":{"Type":"ARMProxy","Version":"1.0.0"},"0x7a2088a1bFc9d81c55368AE168C2C02570cB814F":{"Type":"CCIPReceiver","Version":"1.0.0"},"0x84eA74d481Ee0A5332c457a4d796187F6Ba67fEB":{"Type":"TokenAdminRegistry","Version":"1.5.0"},"0x851356ae760d987E095750cCeb3bC6014560891C":{"Type":"OffRamp","Version":"1.6.0-dev"},"0x8A791620dd6260079BF849Dc5567aDC3F2FdC318":{"Type":"RMNHome","Version":"1.6.0-dev"},"0x9A676e781A523b5d0C0e43731313A708CB607508":{"Type":"WETH9","Version":"1.0.0"},"0x9A9f2CCfdE556A7E9Ff0848998Aa4a0CFD8863AE":{"Type":"AdminManyChainMultiSig","Version":"1.0.0"},"0x9E545E3C0baAB3E08CdfD552C960A1050f373042":{"Type":"NonceManager","Version":"1.6.0-dev"},"0xE6E340D132b5f46d1e472DebcD681B2aBc16e57E":{"Type":"Router","Version":"1.2.0"},"0xa513E6E4b8f2a923D98304ec87F64353C4D5C853":{"Type":"CapabilitiesRegistry","Version":"1.0.0"},"0xa82fF9aFd8f496c3d6ac40E2a0F282E47488CFc9":{"Type":"FeeQuoter","Version":"1.6.0-dev"},"0xc3e53F4d16Ae77Db1c982e75a937B9f60FE63690":{"Type":"TestRouter","Version":"1.2.0"}}} diff --git a/deployment/environment/crib/testdata/lanes-deployed-state/ccip-v2-scripts-chains-details.json b/deployment/environment/crib/testdata/lanes-deployed-state/ccip-v2-scripts-chains-details.json new file mode 100644 index 00000000000..f93ea4ce231 --- /dev/null +++ b/deployment/environment/crib/testdata/lanes-deployed-state/ccip-v2-scripts-chains-details.json @@ -0,0 +1,24 @@ +[ + { + "ChainID": 1337, + "ChainName": "alpha", + "ChainType": "EVM", + "WSRPCs": [ + "wss://crib-local-geth-1337-ws.local:443" + ], + "HTTPRPCs": [ + "https://crib-local-geth-1337-ws.local:443" + ] + }, + { + "ChainID": 2337, + "ChainName": "alpha", + "ChainType": "EVM", + "WSRPCs": [ + "wss://crib-local-geth-2337-ws.local:443" + ], + "HTTPRPCs": [ + "https://crib-local-geth-2337-ws.local:443" + ] + } +] diff --git a/deployment/environment/crib/testdata/lanes-deployed-state/ccip-v2-scripts-nodes-details.json b/deployment/environment/crib/testdata/lanes-deployed-state/ccip-v2-scripts-nodes-details.json new file mode 100644 index 00000000000..477ae0527b1 --- /dev/null +++ b/deployment/environment/crib/testdata/lanes-deployed-state/ccip-v2-scripts-nodes-details.json @@ -0,0 +1 @@ +{"NodeIDs":["node_2URuou3RXmtZu5gLQX8qd","node_m9TTQbUxBx3WjDEjmpVDL","node_4FiKVPtuQjCTvHnS7QpES","node_A4VTgecDwMoG2YYicyjuG","node_jQFpzXDadzaADq147nThS"]} diff --git a/deployment/environment/crib/types.go b/deployment/environment/crib/types.go new file mode 100644 index 00000000000..d19c8424443 --- /dev/null +++ b/deployment/environment/crib/types.go @@ -0,0 +1,39 @@ +package crib + +import ( + "context" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink/deployment" + "github.com/smartcontractkit/chainlink/deployment/environment/devenv" +) + +const ( + CRIB_ENV_NAME = "Crib Environment" +) + +type DeployOutput struct { + NodeIDs []string + Chains []devenv.ChainConfig // chain selector -> Chain Config + AddressBook deployment.AddressBook // Addresses of all contracts +} + +type DeployCCIPOutput struct { + AddressBook deployment.AddressBookMap + NodeIDs []string +} + +func NewDeployEnvironmentFromCribOutput(lggr logger.Logger, output DeployOutput) (*deployment.Environment, error) { + chains, err := devenv.NewChains(lggr, output.Chains) + if err != nil { + return nil, err + } + return deployment.NewEnvironment( + CRIB_ENV_NAME, + lggr, + output.AddressBook, + chains, + output.NodeIDs, + nil, // todo: populate the offchain client using output.DON + func() context.Context { return context.Background() }, deployment.XXXGenerateTestOCRSecrets(), + ), nil +} diff --git a/deployment/environment/devenv/don.go b/deployment/environment/devenv/don.go index 05a3d5bea08..76f6ee92b68 100644 --- a/deployment/environment/devenv/don.go +++ b/deployment/environment/devenv/don.go @@ -2,7 +2,9 @@ package devenv import ( "context" + "errors" "fmt" + chainsel "github.com/smartcontractkit/chain-selectors" "strconv" "strings" "time" @@ -10,8 +12,6 @@ import ( "github.com/hashicorp/go-multierror" "github.com/rs/zerolog" "github.com/sethvargo/go-retry" - chainsel "github.com/smartcontractkit/chain-selectors" - nodev1 "github.com/smartcontractkit/chainlink-protos/job-distributor/v1/node" clclient "github.com/smartcontractkit/chainlink/deployment/environment/nodeclient" "github.com/smartcontractkit/chainlink/deployment/environment/web/sdk/client" @@ -185,7 +185,7 @@ type JDChainConfigInput struct { // It expects bootstrap nodes to have label with key "type" and value as "bootstrap". // It fetches the account address, peer id, and OCR2 key bundle id and creates the JobDistributorChainConfig. func (n *Node) CreateCCIPOCRSupportedChains(ctx context.Context, chains []JDChainConfigInput, jd JobDistributor) error { - for i, chain := range chains { + for _, chain := range chains { chainId := strconv.FormatUint(chain.ChainID, 10) var account string switch chain.ChainType { @@ -239,35 +239,51 @@ func (n *Node) CreateCCIPOCRSupportedChains(ctx context.Context, chains []JDChai break } } - // JD silently fails to update nodeChainConfig. Therefore, we fetch the node config and - // if it's not updated , throw an error - _, err = n.gqlClient.CreateJobDistributorChainConfig(ctx, client.JobDistributorChainConfigInput{ - JobDistributorID: n.JDId, - ChainID: chainId, - ChainType: chain.ChainType, - AccountAddr: account, - AdminAddr: n.adminAddr, - Ocr2Enabled: true, - Ocr2IsBootstrap: isBootstrap, - Ocr2Multiaddr: n.multiAddr, - Ocr2P2PPeerID: value(peerID), - Ocr2KeyBundleID: ocr2BundleId, - Ocr2Plugins: `{"commit":true,"execute":true,"median":false,"mercury":false}`, + + // retry twice with 5 seconds interval to create JobDistributorChainConfig + err = retry.Do(ctx, retry.WithMaxDuration(10*time.Second, retry.NewConstant(3*time.Second)), func(ctx context.Context) error { + // check the node chain config to see if this chain already exists + nodeChainConfigs, err := jd.ListNodeChainConfigs(context.Background(), &nodev1.ListNodeChainConfigsRequest{ + Filter: &nodev1.ListNodeChainConfigsRequest_Filter{ + NodeIds: []string{n.NodeId}, + }}) + if err != nil { + return retry.RetryableError(fmt.Errorf("failed to list node chain configs for node %s, retrying..: %w", n.Name, err)) + } + if nodeChainConfigs != nil { + for _, chainConfig := range nodeChainConfigs.ChainConfigs { + if chainConfig.Chain.Id == chainId { + return nil + } + } + } + + // JD silently fails to update nodeChainConfig. Therefore, we fetch the node config and + // if it's not updated , throw an error + _, err = n.gqlClient.CreateJobDistributorChainConfig(ctx, client.JobDistributorChainConfigInput{ + JobDistributorID: n.JDId, + ChainID: chainId, + ChainType: chain.ChainType, + AccountAddr: account, + AdminAddr: n.adminAddr, + Ocr2Enabled: true, + Ocr2IsBootstrap: isBootstrap, + Ocr2Multiaddr: n.multiAddr, + Ocr2P2PPeerID: value(peerID), + Ocr2KeyBundleID: ocr2BundleId, + Ocr2Plugins: `{"commit":true,"execute":true,"median":false,"mercury":false}`, + }) + // todo: add a check if the chain config failed because of a duplicate in that case, should we update or return success? + if err != nil { + return fmt.Errorf("failed to create CCIPOCR2SupportedChains for node %s: %w", n.Name, err) + } + + return retry.RetryableError(errors.New("retrying CreateChainConfig in JD")) }) + if err != nil { return fmt.Errorf("failed to create CCIPOCR2SupportedChains for node %s: %w", n.Name, err) } - // query the node chain config to check if it's created - nodeChainConfigs, err := jd.ListNodeChainConfigs(context.Background(), &nodev1.ListNodeChainConfigsRequest{ - Filter: &nodev1.ListNodeChainConfigsRequest_Filter{ - NodeIds: []string{n.NodeId}, - }}) - if err != nil { - return fmt.Errorf("failed to list node chain configs for node %s: %w", n.Name, err) - } - if nodeChainConfigs == nil || len(nodeChainConfigs.ChainConfigs) < i+1 { - return fmt.Errorf("failed to create chain config for node %s", n.Name) - } } return nil } @@ -377,6 +393,17 @@ func (n *Node) CreateJobDistributor(ctx context.Context, jd JobDistributor) (str return "", err } // create the job distributor in the node with the csa key + resp, err := n.gqlClient.ListJobDistributors(ctx) + if err != nil { + return "", fmt.Errorf("could not list job distrubutors: %w", err) + } + if len(resp.FeedsManagers.Results) > 0 { + for _, fm := range resp.FeedsManagers.Results { + if fm.GetPublicKey() == csaKey { + return fm.GetId(), nil + } + } + } return n.gqlClient.CreateJobDistributor(ctx, client.JobDistributorInput{ Name: "Job Distributor", Uri: jd.WSRPC, @@ -394,8 +421,9 @@ func (n *Node) SetUpAndLinkJobDistributor(ctx context.Context, jd JobDistributor } // now create the job distributor in the node id, err := n.CreateJobDistributor(ctx, jd) - if err != nil && !strings.Contains(err.Error(), "DuplicateFeedsManagerError") { - return err + if err != nil && + (!strings.Contains(err.Error(), "only a single feeds manager is supported") || !strings.Contains(err.Error(), "DuplicateFeedsManagerError")) { + return fmt.Errorf("failed to create job distributor in node %s: %w", n.Name, err) } // wait for the node to connect to the job distributor err = retry.Do(ctx, retry.WithMaxDuration(1*time.Minute, retry.NewFibonacci(1*time.Second)), func(ctx context.Context) error { diff --git a/deployment/environment/devenv/rmn.go b/deployment/environment/devenv/rmn.go index 63f27f1e422..3e0c6efe0cd 100644 --- a/deployment/environment/devenv/rmn.go +++ b/deployment/environment/devenv/rmn.go @@ -22,7 +22,6 @@ import ( "github.com/smartcontractkit/chainlink-testing-framework/lib/docker" "github.com/smartcontractkit/chainlink-testing-framework/lib/docker/test_env" "github.com/smartcontractkit/chainlink-testing-framework/lib/logging" - "github.com/smartcontractkit/chainlink-testing-framework/lib/logstream" p2ptypes "github.com/smartcontractkit/chainlink/v2/core/services/p2p/types" ) @@ -51,7 +50,6 @@ func NewRage2ProxyComponent( imageVersion string, local ProxyLocalConfig, shared ProxySharedConfig, - logStream *logstream.LogStream, ) (*RageProxy, error) { rageName := fmt.Sprintf("%s-proxy-%s", name, uuid.NewString()[0:8]) @@ -71,7 +69,6 @@ func NewRage2ProxyComponent( ContainerImage: imageName, ContainerVersion: imageVersion, Networks: networks, - LogStream: logStream, }, Passphrase: DefaultAFNPassphrase, proxyListenerPort: listenPort, @@ -193,8 +190,7 @@ func NewAFN2ProxyComponent( imageName, imageVersion string, shared SharedConfig, - local LocalConfig, - logStream *logstream.LogStream) (*AFN2Proxy, error) { + local LocalConfig) (*AFN2Proxy, error) { afnName := fmt.Sprintf("%s-%s", name, uuid.NewString()[0:8]) rmn := &AFN2Proxy{ EnvComponent: test_env.EnvComponent{ @@ -202,7 +198,6 @@ func NewAFN2ProxyComponent( ContainerImage: imageName, ContainerVersion: imageVersion, Networks: networks, - LogStream: logStream, }, AFNPassphrase: DefaultAFNPassphrase, Shared: shared, @@ -343,7 +338,6 @@ func NewRMNCluster( proxyVersion string, rmnImage string, rmnVersion string, - logStream *logstream.LogStream, ) (*RMNCluster, error) { rmn := &RMNCluster{ t: t, @@ -351,7 +345,7 @@ func NewRMNCluster( Nodes: make(map[string]RMNNode), } for name, rmnConfig := range config { - proxy, err := NewRage2ProxyComponent(networks, name, proxyImage, proxyVersion, rmnConfig.ProxyLocal, rmnConfig.ProxyShared, logStream) + proxy, err := NewRage2ProxyComponent(networks, name, proxyImage, proxyVersion, rmnConfig.ProxyLocal, rmnConfig.ProxyShared) if err != nil { return nil, err } @@ -371,7 +365,7 @@ func NewRMNCluster( return nil, err } rmnConfig.Local.Networking.RageProxy = strings.TrimPrefix(fmt.Sprintf("%s:%s", proxyName, port), "/") - afn, err := NewAFN2ProxyComponent(networks, name, rmnImage, rmnVersion, rmnConfig.Shared, rmnConfig.Local, logStream) + afn, err := NewAFN2ProxyComponent(networks, name, rmnImage, rmnVersion, rmnConfig.Shared, rmnConfig.Local) if err != nil { return nil, err } diff --git a/deployment/environment/memory/node.go b/deployment/environment/memory/node.go index fd08d3cf17b..84f0d2e443f 100644 --- a/deployment/environment/memory/node.go +++ b/deployment/environment/memory/node.go @@ -286,6 +286,8 @@ func CreateKeys(t *testing.T, } backend := chain.Client.(*Backend).Sim fundAddress(t, chain.DeployerKey, transmitters[evmChainID], assets.Ether(1000).ToInt(), backend) + // need to look more into it, but it seems like with sim chains nodes are sending txs with 0x from address + fundAddress(t, chain.DeployerKey, common.Address{}, assets.Ether(1000).ToInt(), backend) } return Keys{ diff --git a/deployment/environment/web/sdk/client/client.go b/deployment/environment/web/sdk/client/client.go index 5472591ef94..e0a56b9e642 100644 --- a/deployment/environment/web/sdk/client/client.go +++ b/deployment/environment/web/sdk/client/client.go @@ -4,10 +4,11 @@ import ( "context" "encoding/json" "fmt" + "github.com/Khan/genqlient/graphql" + "github.com/sethvargo/go-retry" "net/http" "strings" - - "github.com/Khan/genqlient/graphql" + "time" "github.com/smartcontractkit/chainlink/deployment/environment/web/sdk/client/doer" "github.com/smartcontractkit/chainlink/deployment/environment/web/sdk/internal/generated" @@ -60,8 +61,15 @@ func New(baseURI string, creds Credentials) (Client, error) { endpoints: ep, credentials: creds, } - - if err := c.login(); err != nil { + + err := retry.Do(context.Background(), retry.WithMaxDuration(10*time.Second, retry.NewFibonacci(2*time.Second)), func(ctx context.Context) error { + err := c.login() + if err != nil { + return retry.RetryableError(fmt.Errorf("retrying login to node: %w", err)) + } + return nil + }) + if err != nil { return nil, fmt.Errorf("failed to login to node: %w", err) } diff --git a/deployment/go.mod b/deployment/go.mod index 058df5d2a29..e5307c9da60 100644 --- a/deployment/go.mod +++ b/deployment/go.mod @@ -7,6 +7,10 @@ toolchain go1.23.4 // Make sure we're working with the latest chainlink libs replace github.com/smartcontractkit/chainlink/v2 => ../ +// Using a separate inline `require` here to avoid surrounding line changes +// creating potential merge conflicts. +require github.com/smartcontractkit/chainlink/v2 v2.0.0-20241206210521-125d98cdaf66 + require ( github.com/Khan/genqlient v0.7.0 github.com/Masterminds/semver/v3 v3.3.0 @@ -22,13 +26,12 @@ require ( github.com/pkg/errors v0.9.1 github.com/rs/zerolog v1.33.0 github.com/sethvargo/go-retry v0.2.4 - github.com/smartcontractkit/ccip-owner-contracts v0.0.0-20240926212305-a6deabdfce86 + github.com/smartcontractkit/ccip-owner-contracts v0.0.0-salt-fix github.com/smartcontractkit/chain-selectors v1.0.34 - github.com/smartcontractkit/chainlink-ccip v0.0.0-20241204015713-8956bb614e9e - github.com/smartcontractkit/chainlink-common v0.3.1-0.20241209151352-70300ddcc776 + github.com/smartcontractkit/chainlink-ccip v0.0.0-20241213122413-5e8f65dd6b1b + github.com/smartcontractkit/chainlink-common v0.3.1-0.20241214155818-b403079b2805 github.com/smartcontractkit/chainlink-protos/job-distributor v0.6.0 github.com/smartcontractkit/chainlink-testing-framework/lib v1.50.13 - github.com/smartcontractkit/chainlink/v2 v2.0.0-00010101000000-000000000000 github.com/smartcontractkit/libocr v0.0.0-20241007185508-adbe57025f12 github.com/stretchr/testify v1.9.0 github.com/test-go/testify v1.1.4 @@ -37,7 +40,7 @@ require ( go.uber.org/zap v1.27.0 golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c golang.org/x/oauth2 v0.23.0 - golang.org/x/sync v0.8.0 + golang.org/x/sync v0.10.0 google.golang.org/grpc v1.67.1 google.golang.org/protobuf v1.35.1 gopkg.in/guregu/null.v4 v4.0.0 @@ -79,7 +82,7 @@ require ( github.com/alecthomas/units v0.0.0-20240626203959-61d1e3462e30 // indirect github.com/alexflint/go-arg v1.4.2 // indirect github.com/alexflint/go-scalar v1.0.0 // indirect - github.com/andybalholm/brotli v1.1.0 // indirect + github.com/andybalholm/brotli v1.1.1 // indirect github.com/armon/go-metrics v0.4.1 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect github.com/atombender/go-jsonschema v0.16.1-0.20240916205339-a74cd4e2851c // indirect @@ -220,7 +223,7 @@ require ( github.com/go-viper/mapstructure/v2 v2.1.0 // indirect github.com/go-webauthn/webauthn v0.9.4 // indirect github.com/go-webauthn/x v0.1.5 // indirect - github.com/goccy/go-json v0.10.2 // indirect + github.com/goccy/go-json v0.10.3 // indirect github.com/goccy/go-yaml v1.12.0 // indirect github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect github.com/gofrs/flock v0.8.1 // indirect @@ -312,8 +315,8 @@ require ( github.com/json-iterator/go v1.1.12 // indirect github.com/julienschmidt/httprouter v1.3.0 // indirect github.com/kelseyhightower/envconfig v1.4.0 // indirect - github.com/klauspost/compress v1.17.9 // indirect - github.com/klauspost/cpuid/v2 v2.2.7 // indirect + github.com/klauspost/compress v1.17.11 // indirect + github.com/klauspost/cpuid/v2 v2.2.8 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect github.com/kylelemons/godebug v1.1.0 // indirect @@ -404,10 +407,10 @@ require ( github.com/sirupsen/logrus v1.9.3 // indirect github.com/smartcontractkit/chainlink-automation v0.8.1 // indirect github.com/smartcontractkit/chainlink-cosmos v0.5.2-0.20241202195413-82468150ac1e // indirect - github.com/smartcontractkit/chainlink-data-streams v0.1.1-0.20241202141438-a90db35252db // indirect + github.com/smartcontractkit/chainlink-data-streams v0.1.1-0.20241216163550-fa030d178ba3 // indirect github.com/smartcontractkit/chainlink-feeds v0.1.1 // indirect - github.com/smartcontractkit/chainlink-protos/orchestrator v0.3.2 // indirect - github.com/smartcontractkit/chainlink-solana v1.1.1-0.20241204153209-c3a71b0eef99 // indirect + github.com/smartcontractkit/chainlink-protos/orchestrator v0.4.0 // indirect + github.com/smartcontractkit/chainlink-solana v1.1.1-0.20241210172617-6fd1891d0fbc // indirect github.com/smartcontractkit/chainlink-starknet/relayer v0.1.1-0.20241202202529-2033490e77b8 // indirect github.com/smartcontractkit/chainlink-testing-framework/lib/grafana v1.50.0 // indirect github.com/smartcontractkit/chainlink-testing-framework/seth v1.50.5 // indirect @@ -489,12 +492,12 @@ require ( go.uber.org/ratelimit v0.3.1 // indirect go4.org/netipx v0.0.0-20230125063823-8449b0a6169f // indirect golang.org/x/arch v0.11.0 // indirect - golang.org/x/crypto v0.28.0 // indirect + golang.org/x/crypto v0.31.0 // indirect golang.org/x/mod v0.21.0 // indirect golang.org/x/net v0.30.0 // indirect - golang.org/x/sys v0.26.0 // indirect - golang.org/x/term v0.25.0 // indirect - golang.org/x/text v0.19.0 // indirect + golang.org/x/sys v0.28.0 // indirect + golang.org/x/term v0.27.0 // indirect + golang.org/x/text v0.21.0 // indirect golang.org/x/time v0.7.0 // indirect golang.org/x/tools v0.26.0 // indirect golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect diff --git a/deployment/go.sum b/deployment/go.sum index 00e0aab077b..f988f4f37bc 100644 --- a/deployment/go.sum +++ b/deployment/go.sum @@ -184,9 +184,11 @@ github.com/allegro/bigcache v1.2.1/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2uc github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129/go.mod h1:rFgpPQZYZ8vdbc+48xibu8ALc3yeyd64IhHS+PU6Yyg= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= -github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= -github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= +github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA= +github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/apache/arrow-go/v18 v18.0.0 h1:1dBDaSbH3LtulTyOVYaBCHO3yVRwjV+TZaqn3g6V7ZM= +github.com/apache/arrow-go/v18 v18.0.0/go.mod h1:t6+cWRSmKgdQ6HsxisQjok+jBpKGhRDiqcf3p0p/F+A= github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q= github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= @@ -548,8 +550,8 @@ github.com/gedex/inflector v0.0.0-20170307190818-16278e9db813/go.mod h1:P+oSoE9y github.com/getsentry/sentry-go v0.27.0 h1:Pv98CIbtB3LkMWmXi4Joa5OOcwbmnX88sF5qbK3r3Ps= github.com/getsentry/sentry-go v0.27.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/gin-contrib/cors v1.5.0 h1:DgGKV7DDoOn36DFkNtbHrjoRiT5ExCe+PC9/xp7aKvk= -github.com/gin-contrib/cors v1.5.0/go.mod h1:TvU7MAZ3EwrPLI2ztzTt3tqgvBCq+wn8WpZmfADjupI= +github.com/gin-contrib/cors v1.7.2 h1:oLDHxdg8W/XDoN/8zamqk/Drgt4oVZDvaV0YmvVICQw= +github.com/gin-contrib/cors v1.7.2/go.mod h1:SUJVARKgQ40dmrzgXEVxj2m7Ig1v1qIboQkPDTQ9t2E= github.com/gin-contrib/expvar v0.0.1 h1:IuU5ArEgihz50vG8Onrwz22kJr7Mcvgv9xSSpfU5g+w= github.com/gin-contrib/expvar v0.0.1/go.mod h1:8o2CznfQi1JjktORdHr2/abg3wSV6OCnXh0yGypvvVw= github.com/gin-contrib/sessions v0.0.5 h1:CATtfHmLMQrMNpJRgzjWXD7worTh7g7ritsQfmF+0jE= @@ -638,8 +640,8 @@ github.com/go-webauthn/x v0.1.5 h1:V2TCzDU2TGLd0kSZOXdrqDVV5JB9ILnKxA9S53CSBw0= github.com/go-webauthn/x v0.1.5/go.mod h1:qbzWwcFcv4rTwtCLOZd+icnr6B7oSsAGZJqlt8cukqY= github.com/go-zookeeper/zk v1.0.3 h1:7M2kwOsc//9VeeFiPtf+uSJlVpU66x9Ba5+8XK7/TDg= github.com/go-zookeeper/zk v1.0.3/go.mod h1:nOB03cncLtlp4t+UAkGSV+9beXP/akpekBwL+UX1Qcw= -github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= -github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= +github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/goccy/go-yaml v1.12.0 h1:/1WHjnMsI1dlIBQutrvSMGZRQufVO3asrHfTwfACoPM= github.com/goccy/go-yaml v1.12.0/go.mod h1:wKnAMd44+9JAAnGQpWVEgBzGt3YuTaQ4uXoHvE4m7WU= github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 h1:ZpnhV/YsD2/4cESfV5+Hoeu/iUR3ruzNvZ+yQfO03a0= @@ -707,6 +709,8 @@ github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Z github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= +github.com/google/flatbuffers v24.3.25+incompatible h1:CX395cjN9Kke9mmalRoL3d81AtFUxJM+yDthflgJGkI= +github.com/google/flatbuffers v24.3.25+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 h1:0VpGH+cDhbDtdcweoyCVsF3fhN8kejK6rFe/2FFX2nU= github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49/go.mod h1:BkkQ4L1KS1xMt2aWSPStnn55ChGC0DPOn2FQYj+f25M= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -1032,11 +1036,11 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= -github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= -github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= +github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= -github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM= +github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b h1:udzkj9S/zlT5X367kqJis0QP7YMxobob6zhzq6Yre00= github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b/go.mod h1:pcaDhQK0/NJZEvtCO0qQPPropqV0sJOJ6YW7X+9kRwM= @@ -1091,6 +1095,8 @@ github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYt github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg= github.com/manyminds/api2go v0.0.0-20171030193247-e7b693844a6f h1:tVvGiZQFjOXP+9YyGqSA6jE55x1XVxmoPYudncxrZ8U= github.com/manyminds/api2go v0.0.0-20171030193247-e7b693844a6f/go.mod h1:Z60vy0EZVSu0bOugCHdcN5ZxFMKSpjRgsnh0XKPFqqk= +github.com/marcboeker/go-duckdb v1.8.3 h1:ZkYwiIZhbYsT6MmJsZ3UPTHrTZccDdM4ztoqSlEMXiQ= +github.com/marcboeker/go-duckdb v1.8.3/go.mod h1:C9bYRE1dPYb1hhfu/SSomm78B0FXmNgRvv6YBW/Hooc= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= @@ -1262,8 +1268,8 @@ github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5/go.mod h1:jvVRKCrJTQWu0XVbaOlby/2lO20uSCHEMzzplHXte1o= github.com/petermattis/goid v0.0.0-20230317030725-371a4b8eda08 h1:hDSdbBuw3Lefr6R18ax0tZ2BJeNB3NehB3trOwYBsdU= github.com/petermattis/goid v0.0.0-20230317030725-371a4b8eda08/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= -github.com/pierrec/lz4/v4 v4.1.18 h1:xaKrnTkyoqfh1YItXl56+6KJNVYWlEEPuAQW9xsplYQ= -github.com/pierrec/lz4/v4 v4.1.18/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= +github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= +github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= @@ -1403,28 +1409,28 @@ github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrf github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/smartcontractkit/ccip-owner-contracts v0.0.0-20240926212305-a6deabdfce86 h1:qQH6fZZe31nBAG6INHph3z5ysDTPptyu0TR9uoJ1+ok= -github.com/smartcontractkit/ccip-owner-contracts v0.0.0-20240926212305-a6deabdfce86/go.mod h1:WtWOoVQQEHxRHL2hNmuRrvDfYfQG/CioFNoa9Rr2mBE= +github.com/smartcontractkit/ccip-owner-contracts v0.0.0-salt-fix h1:DPJD++yKLSx0EfT+U14P8vLVxjXFmoIETiCO9lVwQo8= +github.com/smartcontractkit/ccip-owner-contracts v0.0.0-salt-fix/go.mod h1:NnT6w4Kj42OFFXhSx99LvJZWPpMjmo4+CpDEWfw61xY= github.com/smartcontractkit/chain-selectors v1.0.34 h1:MJ17OGu8+jjl426pcKrJkCf3fePb3eCreuAnUA3RBj4= github.com/smartcontractkit/chain-selectors v1.0.34/go.mod h1:xsKM0aN3YGcQKTPRPDDtPx2l4mlTN1Djmg0VVXV40b8= github.com/smartcontractkit/chainlink-automation v0.8.1 h1:sTc9LKpBvcKPc1JDYAmgBc2xpDKBco/Q4h4ydl6+UUU= github.com/smartcontractkit/chainlink-automation v0.8.1/go.mod h1:Iij36PvWZ6blrdC5A/nrQUBuf3MH3JvsBB9sSyc9W08= -github.com/smartcontractkit/chainlink-ccip v0.0.0-20241204015713-8956bb614e9e h1:GnM6ZWV6vlk2+n6c6o+v/R1LtXzBGVVx7r37nt/h6Uc= -github.com/smartcontractkit/chainlink-ccip v0.0.0-20241204015713-8956bb614e9e/go.mod h1:80vGBbOfertJig0xFKsRfm+i17FkjdKkk1dAaGE45Os= -github.com/smartcontractkit/chainlink-common v0.3.1-0.20241209151352-70300ddcc776 h1:NATQA1LfrEPXCdtEed9/G4SxaVuF8EZp5O2ucOK5C98= -github.com/smartcontractkit/chainlink-common v0.3.1-0.20241209151352-70300ddcc776/go.mod h1:bQktEJf7sJ0U3SmIcXvbGUox7SmXcnSEZ4kUbT8R5Nk= +github.com/smartcontractkit/chainlink-ccip v0.0.0-20241213122413-5e8f65dd6b1b h1:iSQJ6ng4FhEswf8SXunGkaJlVP3E3JlgLB8Oo2f3Ud4= +github.com/smartcontractkit/chainlink-ccip v0.0.0-20241213122413-5e8f65dd6b1b/go.mod h1:F8xQAIW0ymb2BZhqn89sWZLXreJhM5KDVF6Qb4y44N0= +github.com/smartcontractkit/chainlink-common v0.3.1-0.20241214155818-b403079b2805 h1:Pz8jB/6qe10xT10h2S3LFYJrnebNpG5rJ/w16HZGwPQ= +github.com/smartcontractkit/chainlink-common v0.3.1-0.20241214155818-b403079b2805/go.mod h1:yti7e1+G9hhkYhj+L5sVUULn9Bn3bBL5/AxaNqdJ5YQ= github.com/smartcontractkit/chainlink-cosmos v0.5.2-0.20241202195413-82468150ac1e h1:PRoeby6ZlTuTkv2f+7tVU4+zboTfRzI+beECynF4JQ0= github.com/smartcontractkit/chainlink-cosmos v0.5.2-0.20241202195413-82468150ac1e/go.mod h1:mUh5/woemsVaHgTorA080hrYmO3syBCmPdnWc/5dOqk= -github.com/smartcontractkit/chainlink-data-streams v0.1.1-0.20241202141438-a90db35252db h1:N1RH1hSr2ACzOFc9hkCcjE8pRBTdcU3p8nsTJByaLes= -github.com/smartcontractkit/chainlink-data-streams v0.1.1-0.20241202141438-a90db35252db/go.mod h1:yjb9d4q7+m8aGbjfTbkNoNuA4PeSxcUszsSZHDrvS0E= +github.com/smartcontractkit/chainlink-data-streams v0.1.1-0.20241216163550-fa030d178ba3 h1:aeiBdBHGY8QNftps+VqrIk6OnfeeOD5z4jrAabW4ZSc= +github.com/smartcontractkit/chainlink-data-streams v0.1.1-0.20241216163550-fa030d178ba3/go.mod h1:AS6zY2BkcRwfiGzNabGbHhfrLSrXrcI/GmjnT4jQ5/s= 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/job-distributor v0.6.0 h1:0ewLMbAz3rZrovdRUCgd028yOXX8KigB4FndAUdI2kM= github.com/smartcontractkit/chainlink-protos/job-distributor v0.6.0/go.mod h1:/dVVLXrsp+V0AbcYGJo3XMzKg3CkELsweA/TTopCsKE= -github.com/smartcontractkit/chainlink-protos/orchestrator v0.3.2 h1:onBe3DqNrbtOAzKS4PrPIiJX65BGo1aYiYZxFVEW+jc= -github.com/smartcontractkit/chainlink-protos/orchestrator v0.3.2/go.mod h1:m/A3lqD7ms/RsQ9BT5P2uceYY0QX5mIt4KQxT2G6qEo= -github.com/smartcontractkit/chainlink-solana v1.1.1-0.20241204153209-c3a71b0eef99 h1:lvn9Yxah+QD1/PcgijLO0dNRa28HuQWZl8Kkxh46KJc= -github.com/smartcontractkit/chainlink-solana v1.1.1-0.20241204153209-c3a71b0eef99/go.mod h1:p8aUDfJeley6oer7y+Ucd3edOtRlMTnWg3mN6rhaLWo= +github.com/smartcontractkit/chainlink-protos/orchestrator v0.4.0 h1:ZBat8EBvE2LpSQR9U1gEbRV6PfAkiFdINmQ8nVnXIAQ= +github.com/smartcontractkit/chainlink-protos/orchestrator v0.4.0/go.mod h1:m/A3lqD7ms/RsQ9BT5P2uceYY0QX5mIt4KQxT2G6qEo= +github.com/smartcontractkit/chainlink-solana v1.1.1-0.20241210172617-6fd1891d0fbc h1:dssRwJhmzJkUN/OajaDj2GsxBn+Tupk3bI1BkPEoJg0= +github.com/smartcontractkit/chainlink-solana v1.1.1-0.20241210172617-6fd1891d0fbc/go.mod h1:p8aUDfJeley6oer7y+Ucd3edOtRlMTnWg3mN6rhaLWo= github.com/smartcontractkit/chainlink-starknet/relayer v0.1.1-0.20241202202529-2033490e77b8 h1:tNS7U9lrxkFvEuyxQv11HHOiV9LPDGC9wYEy+yM/Jv4= github.com/smartcontractkit/chainlink-starknet/relayer v0.1.1-0.20241202202529-2033490e77b8/go.mod h1:EBrEgcdIbwepqguClkv8Ohy7CbyWSJaE4EC9aBJlQK0= github.com/smartcontractkit/chainlink-testing-framework/lib v1.50.13 h1:T0kbw07Vb6xUyA9MIJZfErMgWseWi1zf7cYvRpoq7ug= @@ -1589,6 +1595,8 @@ github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= +github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= +github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -1600,6 +1608,8 @@ github.com/yuin/gopher-lua v1.1.0 h1:BojcDhfyDWgU2f2TOzYK/g5p2gxMrku8oupLDqlnSqE github.com/yuin/gopher-lua v1.1.0/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0= +github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= github.com/zondax/hid v0.9.2 h1:WCJFnEDMiqGF64nlZz28E9qLVZ0KSJ7xpc5DLEyma2U= github.com/zondax/hid v0.9.2/go.mod h1:l5wttcP0jwtdLjqjMMWFVEE7d1zO0jvSPA9OPZxWpEM= @@ -1746,8 +1756,8 @@ golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0 golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= -golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= -golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= +golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= +golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -1877,8 +1887,8 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1972,8 +1982,8 @@ golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= -golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -1984,8 +1994,8 @@ golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= -golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= -golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= +golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= +golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -2000,8 +2010,8 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= -golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= diff --git a/deployment/helpers.go b/deployment/helpers.go index 50f4c404b09..dfbbccc2698 100644 --- a/deployment/helpers.go +++ b/deployment/helpers.go @@ -146,7 +146,7 @@ func DeployContract[C any]( lggr.Errorw("Failed to confirm deployment", "chain", chain.String(), "Contract", contractDeploy.Tv.String(), "err", err) return nil, err } - lggr.Infow("Deployed contract", "Contract", contractDeploy.Tv.String(), "addr", contractDeploy.Address, "chain", chain.Selector) + lggr.Infow("Deployed contract", "Contract", contractDeploy.Tv.String(), "addr", contractDeploy.Address, "chain", chain.String()) err = addressBook.Save(chain.Selector, contractDeploy.Address.String(), contractDeploy.Tv) if err != nil { lggr.Errorw("Failed to save contract address", "Contract", contractDeploy.Tv.String(), "addr", contractDeploy.Address, "chain", chain.String(), "err", err) diff --git a/deployment/keystone/capability_management.go b/deployment/keystone/capability_management.go index 888cba5b931..7e502d4f8ea 100644 --- a/deployment/keystone/capability_management.go +++ b/deployment/keystone/capability_management.go @@ -2,7 +2,9 @@ package keystone import ( "fmt" + "math/big" + "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/mcms" "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/timelock" "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink/deployment" @@ -10,12 +12,11 @@ import ( ) // AddCapabilities adds the capabilities to the registry -// it tries to add all capabilities in one go, if that fails, it falls back to adding them one by one - -func AddCapabilities(lggr logger.Logger, registry *kcr.CapabilitiesRegistry, chain deployment.Chain, capabilities []kcr.CapabilitiesRegistryCapability, useMCMS bool) ([]timelock.MCMSWithTimelockProposal, error) { +func AddCapabilities(lggr logger.Logger, contractSet *ContractSet, chain deployment.Chain, capabilities []kcr.CapabilitiesRegistryCapability, useMCMS bool) (*timelock.BatchChainOperation, error) { if len(capabilities) == 0 { return nil, nil } + registry := contractSet.CapabilitiesRegistry deduped, err := dedupCapabilities(registry, capabilities) if err != nil { return nil, fmt.Errorf("failed to dedup capabilities: %w", err) @@ -29,7 +30,7 @@ func AddCapabilities(lggr logger.Logger, registry *kcr.CapabilitiesRegistry, cha err = DecodeErr(kcr.CapabilitiesRegistryABI, err) return nil, fmt.Errorf("failed to add capabilities: %w", err) } - var proposals []timelock.MCMSWithTimelockProposal + var batch *timelock.BatchChainOperation if !useMCMS { _, err = chain.Confirm(tx) if err != nil { @@ -37,9 +38,18 @@ func AddCapabilities(lggr logger.Logger, registry *kcr.CapabilitiesRegistry, cha } lggr.Info("registered capabilities", "capabilities", deduped) } else { - // TODO + batch = &timelock.BatchChainOperation{ + ChainIdentifier: mcms.ChainIdentifier(chain.Selector), + Batch: []mcms.Operation{ + { + To: registry.Address(), + Data: tx.Data(), + Value: big.NewInt(0), + }, + }, + } } - return proposals, nil + return batch, nil } // CapabilityID returns a unique id for the capability diff --git a/deployment/keystone/changeset/accept_ownership_test.go b/deployment/keystone/changeset/accept_ownership_test.go index f205adda496..d949e63c7aa 100644 --- a/deployment/keystone/changeset/accept_ownership_test.go +++ b/deployment/keystone/changeset/accept_ownership_test.go @@ -1,16 +1,15 @@ package changeset_test import ( - "math/big" "testing" - owner_helpers "github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers" "github.com/stretchr/testify/require" "go.uber.org/zap/zapcore" "github.com/smartcontractkit/chainlink-common/pkg/logger" commonchangeset "github.com/smartcontractkit/chainlink/deployment/common/changeset" + "github.com/smartcontractkit/chainlink/deployment/common/proposalutils" "github.com/smartcontractkit/chainlink/deployment/common/types" "github.com/smartcontractkit/chainlink/deployment/environment/memory" "github.com/smartcontractkit/chainlink/deployment/keystone/changeset" @@ -38,27 +37,28 @@ func TestAcceptAllOwnership(t *testing.T) { Changeset: commonchangeset.WrapChangeSet(changeset.DeployForwarder), Config: registrySel, }, + { + Changeset: commonchangeset.WrapChangeSet(changeset.DeployFeedsConsumer), + Config: &changeset.DeployFeedsConsumerRequest{ChainSelector: registrySel}, + }, { Changeset: commonchangeset.WrapChangeSet(commonchangeset.DeployMCMSWithTimelock), Config: map[uint64]types.MCMSWithTimelockConfig{ - registrySel: { - Canceller: commonchangeset.SingleGroupMCMS(t), - Bypasser: commonchangeset.SingleGroupMCMS(t), - Proposer: commonchangeset.SingleGroupMCMS(t), - TimelockExecutors: env.AllDeployerKeys(), - TimelockMinDelay: big.NewInt(0), - }, + registrySel: proposalutils.SingleGroupTimelockConfig(t), }, }, }) require.NoError(t, err) addrs, err := env.ExistingAddresses.AddressesForChain(registrySel) require.NoError(t, err) - timelock, err := commonchangeset.MaybeLoadMCMSWithTimelockState(env.Chains[registrySel], addrs) + timelock, err := commonchangeset.MaybeLoadMCMSWithTimelockChainState(env.Chains[registrySel], addrs) require.NoError(t, err) - _, err = commonchangeset.ApplyChangesets(t, env, map[uint64]*owner_helpers.RBACTimelock{ - registrySel: timelock.Timelock, + _, err = commonchangeset.ApplyChangesets(t, env, map[uint64]*proposalutils.TimelockExecutionContracts{ + registrySel: &proposalutils.TimelockExecutionContracts{ + Timelock: timelock.Timelock, + CallProxy: timelock.CallProxy, + }, }, []commonchangeset.ChangesetApplication{ { Changeset: commonchangeset.WrapChangeSet(changeset.AcceptAllOwnershipsProposal), diff --git a/deployment/keystone/changeset/append_node_capbilities.go b/deployment/keystone/changeset/append_node_capabilities.go similarity index 56% rename from deployment/keystone/changeset/append_node_capbilities.go rename to deployment/keystone/changeset/append_node_capabilities.go index 974c4970c51..688d4fd8d2f 100644 --- a/deployment/keystone/changeset/append_node_capbilities.go +++ b/deployment/keystone/changeset/append_node_capabilities.go @@ -3,7 +3,11 @@ package changeset import ( "fmt" + "github.com/ethereum/go-ethereum/common" + "github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers" + "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/timelock" "github.com/smartcontractkit/chainlink/deployment" + "github.com/smartcontractkit/chainlink/deployment/common/proposalutils" kslib "github.com/smartcontractkit/chainlink/deployment/keystone" "github.com/smartcontractkit/chainlink/deployment/keystone/changeset/internal" ) @@ -16,15 +20,39 @@ type AppendNodeCapabilitiesRequest = MutateNodeCapabilitiesRequest // AppendNodeCapabilities adds any new capabilities to the registry, merges the new capabilities with the existing capabilities // of the node, and updates the nodes in the registry host the union of the new and existing capabilities. func AppendNodeCapabilities(env deployment.Environment, req *AppendNodeCapabilitiesRequest) (deployment.ChangesetOutput, error) { - cfg, err := req.convert(env) + c, err := req.convert(env) if err != nil { return deployment.ChangesetOutput{}, err } - _, err = internal.AppendNodeCapabilitiesImpl(env.Logger, cfg) + r, err := internal.AppendNodeCapabilitiesImpl(env.Logger, c) if err != nil { return deployment.ChangesetOutput{}, err } - return deployment.ChangesetOutput{}, nil + out := deployment.ChangesetOutput{} + if req.UseMCMS() { + if r.Ops == nil { + return out, fmt.Errorf("expected MCMS operation to be non-nil") + } + timelocksPerChain := map[uint64]common.Address{ + c.Chain.Selector: c.ContractSet.Timelock.Address(), + } + proposerMCMSes := map[uint64]*gethwrappers.ManyChainMultiSig{ + c.Chain.Selector: c.ContractSet.ProposerMcm, + } + + proposal, err := proposalutils.BuildProposalFromBatches( + timelocksPerChain, + proposerMCMSes, + []timelock.BatchChainOperation{*r.Ops}, + "proposal to set update node capabilities", + req.MCMSConfig.MinDuration, + ) + if err != nil { + return out, fmt.Errorf("failed to build proposal: %w", err) + } + out.Proposals = []timelock.MCMSWithTimelockProposal{*proposal} + } + return out, nil } func (req *AppendNodeCapabilitiesRequest) convert(e deployment.Environment) (*internal.AppendNodeCapabilitiesRequest, error) { @@ -35,21 +63,19 @@ func (req *AppendNodeCapabilitiesRequest) convert(e deployment.Environment) (*in if !ok { return nil, fmt.Errorf("registry chain selector %d does not exist in environment", req.RegistryChainSel) } - contracts, err := kslib.GetContractSets(e.Logger, &kslib.GetContractSetsRequest{ + resp, err := kslib.GetContractSets(e.Logger, &kslib.GetContractSetsRequest{ Chains: map[uint64]deployment.Chain{req.RegistryChainSel: registryChain}, - AddressBook: req.AddressBook, + AddressBook: e.ExistingAddresses, }) if err != nil { return nil, fmt.Errorf("failed to get contract sets: %w", err) } - registry := contracts.ContractSets[req.RegistryChainSel].CapabilitiesRegistry - if registry == nil { - return nil, fmt.Errorf("capabilities registry not found for chain %d", req.RegistryChainSel) - } + contracts := resp.ContractSets[req.RegistryChainSel] return &internal.AppendNodeCapabilitiesRequest{ Chain: registryChain, - Registry: registry, + ContractSet: &contracts, P2pToCapabilities: req.P2pToCapabilities, + UseMCMS: req.UseMCMS(), }, nil } diff --git a/deployment/keystone/changeset/append_node_capabilities_test.go b/deployment/keystone/changeset/append_node_capabilities_test.go new file mode 100644 index 00000000000..bfc01b309f5 --- /dev/null +++ b/deployment/keystone/changeset/append_node_capabilities_test.go @@ -0,0 +1,133 @@ +package changeset_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "golang.org/x/exp/maps" + + commonchangeset "github.com/smartcontractkit/chainlink/deployment/common/changeset" + "github.com/smartcontractkit/chainlink/deployment/common/proposalutils" + "github.com/smartcontractkit/chainlink/deployment/keystone/changeset" + kcr "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/capabilities_registry" + "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/p2pkey" +) + +func TestAppendNodeCapabilities(t *testing.T) { + t.Parallel() + + var ( + capA = kcr.CapabilitiesRegistryCapability{ + LabelledName: "capA", + Version: "0.4.2", + } + capB = kcr.CapabilitiesRegistryCapability{ + LabelledName: "capB", + Version: "3.16.0", + } + caps = []kcr.CapabilitiesRegistryCapability{capA, capB} + ) + t.Run("no mcms", func(t *testing.T) { + te := SetupTestEnv(t, TestConfig{ + WFDonConfig: DonConfig{N: 4}, + AssetDonConfig: DonConfig{N: 4}, + WriterDonConfig: DonConfig{N: 4}, + NumChains: 1, + }) + + newCapabilities := make(map[p2pkey.PeerID][]kcr.CapabilitiesRegistryCapability) + for id, _ := range te.WFNodes { + k, err := p2pkey.MakePeerID(id) + require.NoError(t, err) + newCapabilities[k] = caps + } + + t.Run("succeeds if existing capabilities not explicit", func(t *testing.T) { + cfg := changeset.AppendNodeCapabilitiesRequest{ + RegistryChainSel: te.RegistrySelector, + P2pToCapabilities: newCapabilities, + } + + csOut, err := changeset.AppendNodeCapabilities(te.Env, &cfg) + require.NoError(t, err) + require.Len(t, csOut.Proposals, 0) + require.Nil(t, csOut.AddressBook) + + validateCapabilityAppends(t, te, newCapabilities) + }) + }) + t.Run("with mcms", func(t *testing.T) { + te := SetupTestEnv(t, TestConfig{ + WFDonConfig: DonConfig{N: 4}, + AssetDonConfig: DonConfig{N: 4}, + WriterDonConfig: DonConfig{N: 4}, + NumChains: 1, + UseMCMS: true, + }) + + newCapabilities := make(map[p2pkey.PeerID][]kcr.CapabilitiesRegistryCapability) + for id, _ := range te.WFNodes { + k, err := p2pkey.MakePeerID(id) + require.NoError(t, err) + newCapabilities[k] = caps + } + + cfg := changeset.AppendNodeCapabilitiesRequest{ + RegistryChainSel: te.RegistrySelector, + P2pToCapabilities: newCapabilities, + MCMSConfig: &changeset.MCMSConfig{MinDuration: 0}, + } + + csOut, err := changeset.AppendNodeCapabilities(te.Env, &cfg) + require.NoError(t, err) + require.Len(t, csOut.Proposals, 1) + require.Len(t, csOut.Proposals[0].Transactions, 1) + require.Len(t, csOut.Proposals[0].Transactions[0].Batch, 2) // add capabilities, update nodes + require.Nil(t, csOut.AddressBook) + + // now apply the changeset such that the proposal is signed and execed + contracts := te.ContractSets()[te.RegistrySelector] + timelockContracts := map[uint64]*proposalutils.TimelockExecutionContracts{ + te.RegistrySelector: { + Timelock: contracts.Timelock, + CallProxy: contracts.CallProxy, + }, + } + + _, err = commonchangeset.ApplyChangesets(t, te.Env, timelockContracts, []commonchangeset.ChangesetApplication{ + { + Changeset: commonchangeset.WrapChangeSet(changeset.AppendNodeCapabilities), + Config: &cfg, + }, + }) + require.NoError(t, err) + validateCapabilityAppends(t, te, newCapabilities) + }) + +} + +// validateUpdate checks reads nodes from the registry and checks they have the expected updates +func validateCapabilityAppends(t *testing.T, te TestEnv, appended map[p2pkey.PeerID][]kcr.CapabilitiesRegistryCapability) { + registry := te.ContractSets()[te.RegistrySelector].CapabilitiesRegistry + wfP2PIDs := p2pIDs(t, maps.Keys(te.WFNodes)) + nodes, err := registry.GetNodesByP2PIds(nil, wfP2PIDs) + require.NoError(t, err) + require.Len(t, nodes, len(wfP2PIDs)) + for _, node := range nodes { + want := appended[node.P2pId] + require.NotNil(t, want) + assertContainsCapabilities(t, registry, want, node) + } +} + +func assertContainsCapabilities(t *testing.T, registry *kcr.CapabilitiesRegistry, want []kcr.CapabilitiesRegistryCapability, got kcr.INodeInfoProviderNodeInfo) { + wantHashes := make([][32]byte, len(want)) + for i, c := range want { + h, err := registry.GetHashedCapabilityId(nil, c.LabelledName, c.Version) + require.NoError(t, err) + wantHashes[i] = h + assert.Contains(t, got.HashedCapabilityIds, h, "missing capability %v", c) + } + assert.LessOrEqual(t, len(want), len(got.HashedCapabilityIds)) +} diff --git a/deployment/keystone/changeset/deploy_forwarder.go b/deployment/keystone/changeset/deploy_forwarder.go index cf116decd54..1e4066770bd 100644 --- a/deployment/keystone/changeset/deploy_forwarder.go +++ b/deployment/keystone/changeset/deploy_forwarder.go @@ -3,7 +3,11 @@ package changeset import ( "fmt" + "github.com/ethereum/go-ethereum/common" + "github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers" + "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/timelock" "github.com/smartcontractkit/chainlink/deployment" + "github.com/smartcontractkit/chainlink/deployment/common/proposalutils" kslib "github.com/smartcontractkit/chainlink/deployment/keystone" ) @@ -35,7 +39,8 @@ type ConfigureForwardContractsRequest struct { WFNodeIDs []string RegistryChainSel uint64 - UseMCMS bool + // MCMSConfig is optional. If non-nil, the changes will be proposed using MCMS. + MCMSConfig *MCMSConfig } func (r ConfigureForwardContractsRequest) Validate() error { @@ -45,6 +50,10 @@ func (r ConfigureForwardContractsRequest) Validate() error { return nil } +func (r ConfigureForwardContractsRequest) UseMCMS() bool { + return r.MCMSConfig != nil +} + func ConfigureForwardContracts(env deployment.Environment, req ConfigureForwardContractsRequest) (deployment.ChangesetOutput, error) { wfDon, err := kslib.NewRegisteredDon(env, kslib.RegisteredDonConfig{ NodeIDs: req.WFNodeIDs, @@ -56,12 +65,46 @@ func ConfigureForwardContracts(env deployment.Environment, req ConfigureForwardC } r, err := kslib.ConfigureForwardContracts(&env, kslib.ConfigureForwarderContractsRequest{ Dons: []kslib.RegisteredDon{*wfDon}, - UseMCMS: req.UseMCMS, + UseMCMS: req.UseMCMS(), }) if err != nil { return deployment.ChangesetOutput{}, fmt.Errorf("failed to configure forward contracts: %w", err) } - return deployment.ChangesetOutput{ - Proposals: r.Proposals, - }, nil + + cresp, err := kslib.GetContractSets(env.Logger, &kslib.GetContractSetsRequest{ + Chains: env.Chains, + AddressBook: env.ExistingAddresses, + }) + if err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("failed to get contract sets: %w", err) + } + + var out deployment.ChangesetOutput + if req.UseMCMS() { + if len(r.OpsPerChain) == 0 { + return out, fmt.Errorf("expected MCMS operation to be non-nil") + } + for chainSelector, op := range r.OpsPerChain { + contracts := cresp.ContractSets[chainSelector] + timelocksPerChain := map[uint64]common.Address{ + chainSelector: contracts.Timelock.Address(), + } + proposerMCMSes := map[uint64]*gethwrappers.ManyChainMultiSig{ + chainSelector: contracts.ProposerMcm, + } + + proposal, err := proposalutils.BuildProposalFromBatches( + timelocksPerChain, + proposerMCMSes, + []timelock.BatchChainOperation{op}, + "proposal to set update nodes", + req.MCMSConfig.MinDuration, + ) + if err != nil { + return out, fmt.Errorf("failed to build proposal: %w", err) + } + out.Proposals = append(out.Proposals, *proposal) + } + } + return out, nil } diff --git a/deployment/keystone/changeset/deploy_forwarder_test.go b/deployment/keystone/changeset/deploy_forwarder_test.go index 32a53f1cf08..e04bac6d264 100644 --- a/deployment/keystone/changeset/deploy_forwarder_test.go +++ b/deployment/keystone/changeset/deploy_forwarder_test.go @@ -8,10 +8,10 @@ import ( "github.com/stretchr/testify/require" - "github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers" "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink/deployment" commonchangeset "github.com/smartcontractkit/chainlink/deployment/common/changeset" + "github.com/smartcontractkit/chainlink/deployment/common/proposalutils" "github.com/smartcontractkit/chainlink/deployment/environment/memory" "github.com/smartcontractkit/chainlink/deployment/keystone/changeset" ) @@ -110,19 +110,23 @@ func TestConfigureForwarders(t *testing.T) { WFDonName: "test-wf-don", WFNodeIDs: wfNodes, RegistryChainSel: te.RegistrySelector, - UseMCMS: true, + MCMSConfig: &changeset.MCMSConfig{MinDuration: 0}, } csOut, err := changeset.ConfigureForwardContracts(te.Env, cfg) require.NoError(t, err) require.Len(t, csOut.Proposals, nChains) require.Nil(t, csOut.AddressBook) - timelocks := make(map[uint64]*gethwrappers.RBACTimelock) + timelockContracts := make(map[uint64]*proposalutils.TimelockExecutionContracts) for selector, contractSet := range te.ContractSets() { require.NotNil(t, contractSet.Timelock) - timelocks[selector] = contractSet.Timelock + require.NotNil(t, contractSet.CallProxy) + timelockContracts[selector] = &proposalutils.TimelockExecutionContracts{ + Timelock: contractSet.Timelock, + CallProxy: contractSet.CallProxy, + } } - _, err = commonchangeset.ApplyChangesets(t, te.Env, timelocks, []commonchangeset.ChangesetApplication{ + _, err = commonchangeset.ApplyChangesets(t, te.Env, timelockContracts, []commonchangeset.ChangesetApplication{ { Changeset: commonchangeset.WrapChangeSet(changeset.ConfigureForwardContracts), Config: cfg, diff --git a/deployment/keystone/changeset/deploy_ocr3.go b/deployment/keystone/changeset/deploy_ocr3.go index 0ce3d02844b..4dfed1e292c 100644 --- a/deployment/keystone/changeset/deploy_ocr3.go +++ b/deployment/keystone/changeset/deploy_ocr3.go @@ -5,9 +5,12 @@ import ( "fmt" "io" + "github.com/ethereum/go-ethereum/common" + "github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers" "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/timelock" "github.com/smartcontractkit/chainlink/deployment" + "github.com/smartcontractkit/chainlink/deployment/common/proposalutils" kslib "github.com/smartcontractkit/chainlink/deployment/keystone" ) @@ -38,7 +41,12 @@ type ConfigureOCR3Config struct { DryRun bool WriteGeneratedConfig io.Writer // if not nil, write the generated config to this writer as JSON [OCR2OracleConfig] - UseMCMS bool + // MCMSConfig is optional. If non-nil, the changes will be proposed using MCMS. + MCMSConfig *MCMSConfig +} + +func (cfg ConfigureOCR3Config) UseMCMS() bool { + return cfg.MCMSConfig != nil } func ConfigureOCR3Contract(env deployment.Environment, cfg ConfigureOCR3Config) (deployment.ChangesetOutput, error) { @@ -47,7 +55,7 @@ func ConfigureOCR3Contract(env deployment.Environment, cfg ConfigureOCR3Config) NodeIDs: cfg.NodeIDs, OCR3Config: cfg.OCR3Config, DryRun: cfg.DryRun, - UseMCMS: cfg.UseMCMS, + UseMCMS: cfg.UseMCMS(), }) if err != nil { return deployment.ChangesetOutput{}, fmt.Errorf("failed to configure OCR3Capability: %w", err) @@ -67,11 +75,38 @@ func ConfigureOCR3Contract(env deployment.Environment, cfg ConfigureOCR3Config) } } // does not create any new addresses - var proposals []timelock.MCMSWithTimelockProposal - if cfg.UseMCMS { - proposals = append(proposals, *resp.Proposal) + var out deployment.ChangesetOutput + if cfg.UseMCMS() { + if resp.Ops == nil { + return out, fmt.Errorf("expected MCMS operation to be non-nil") + } + r, err := kslib.GetContractSets(env.Logger, &kslib.GetContractSetsRequest{ + Chains: env.Chains, + AddressBook: env.ExistingAddresses, + }) + if err != nil { + return out, fmt.Errorf("failed to get contract sets: %w", err) + } + contracts := r.ContractSets[cfg.ChainSel] + timelocksPerChain := map[uint64]common.Address{ + cfg.ChainSel: contracts.Timelock.Address(), + } + proposerMCMSes := map[uint64]*gethwrappers.ManyChainMultiSig{ + cfg.ChainSel: contracts.ProposerMcm, + } + + proposal, err := proposalutils.BuildProposalFromBatches( + timelocksPerChain, + proposerMCMSes, + []timelock.BatchChainOperation{*resp.Ops}, + "proposal to set update nodes", + cfg.MCMSConfig.MinDuration, + ) + if err != nil { + return out, fmt.Errorf("failed to build proposal: %w", err) + } + out.Proposals = []timelock.MCMSWithTimelockProposal{*proposal} + } - return deployment.ChangesetOutput{ - Proposals: proposals, - }, nil + return out, nil } diff --git a/deployment/keystone/changeset/deploy_ocr3_test.go b/deployment/keystone/changeset/deploy_ocr3_test.go index ae00f19fc22..7a276886242 100644 --- a/deployment/keystone/changeset/deploy_ocr3_test.go +++ b/deployment/keystone/changeset/deploy_ocr3_test.go @@ -10,10 +10,10 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers" "github.com/smartcontractkit/chainlink-common/pkg/logger" commonchangeset "github.com/smartcontractkit/chainlink/deployment/common/changeset" + "github.com/smartcontractkit/chainlink/deployment/common/proposalutils" "github.com/smartcontractkit/chainlink/deployment/environment/memory" kslib "github.com/smartcontractkit/chainlink/deployment/keystone" "github.com/smartcontractkit/chainlink/deployment/keystone/changeset" @@ -72,7 +72,6 @@ func TestConfigureOCR3(t *testing.T) { NodeIDs: wfNodes, OCR3Config: &c, WriteGeneratedConfig: w, - UseMCMS: false, } csOut, err := changeset.ConfigureOCR3Contract(te.Env, cfg) @@ -105,7 +104,7 @@ func TestConfigureOCR3(t *testing.T) { NodeIDs: wfNodes, OCR3Config: &c, WriteGeneratedConfig: w, - UseMCMS: true, + MCMSConfig: &changeset.MCMSConfig{MinDuration: 0}, } csOut, err := changeset.ConfigureOCR3Contract(te.Env, cfg) @@ -119,13 +118,18 @@ func TestConfigureOCR3(t *testing.T) { t.Logf("got: %v", csOut.Proposals[0]) contracts := te.ContractSets()[te.RegistrySelector] - var timelocks = map[uint64]*gethwrappers.RBACTimelock{ - te.RegistrySelector: contracts.Timelock, + require.NoError(t, err) + var timelockContracts = map[uint64]*proposalutils.TimelockExecutionContracts{ + te.RegistrySelector: { + Timelock: contracts.Timelock, + CallProxy: contracts.CallProxy, + }, } + // now apply the changeset such that the proposal is signed and execed w2 := &bytes.Buffer{} cfg.WriteGeneratedConfig = w2 - _, err = commonchangeset.ApplyChangesets(t, te.Env, timelocks, []commonchangeset.ChangesetApplication{ + _, err = commonchangeset.ApplyChangesets(t, te.Env, timelockContracts, []commonchangeset.ChangesetApplication{ { Changeset: commonchangeset.WrapChangeSet(changeset.ConfigureOCR3Contract), Config: cfg, diff --git a/deployment/keystone/changeset/helpers_test.go b/deployment/keystone/changeset/helpers_test.go index a4e98efd550..f51a4ed610c 100644 --- a/deployment/keystone/changeset/helpers_test.go +++ b/deployment/keystone/changeset/helpers_test.go @@ -8,11 +8,9 @@ import ( "errors" "fmt" "math" - "math/big" "sort" "testing" - "github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers" "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" "github.com/smartcontractkit/chainlink-protos/job-distributor/v1/node" @@ -22,6 +20,7 @@ import ( "github.com/smartcontractkit/chainlink/deployment" commonchangeset "github.com/smartcontractkit/chainlink/deployment/common/changeset" + "github.com/smartcontractkit/chainlink/deployment/common/proposalutils" commontypes "github.com/smartcontractkit/chainlink/deployment/common/types" "github.com/smartcontractkit/chainlink/deployment/environment/memory" "github.com/smartcontractkit/chainlink/deployment/keystone" @@ -42,7 +41,7 @@ func TestSetupTestEnv(t *testing.T) { NumChains: 3, UseMCMS: useMCMS, }) - t.Run(fmt.Sprintf("set up test env using MCMS: %T", useMCMS), func(t *testing.T) { + t.Run(fmt.Sprintf("set up test env using MCMS: %t", useMCMS), func(t *testing.T) { require.NotNil(t, te.Env.ExistingAddresses) require.Len(t, te.Env.Chains, 3) require.NotEmpty(t, te.RegistrySelector) @@ -259,13 +258,7 @@ func SetupTestEnv(t *testing.T, c TestConfig) TestEnv { timelockCfgs := make(map[uint64]commontypes.MCMSWithTimelockConfig) for sel := range env.Chains { t.Logf("Enabling MCMS on chain %d", sel) - timelockCfgs[sel] = commontypes.MCMSWithTimelockConfig{ - Canceller: commonchangeset.SingleGroupMCMS(t), - Bypasser: commonchangeset.SingleGroupMCMS(t), - Proposer: commonchangeset.SingleGroupMCMS(t), - TimelockExecutors: env.AllDeployerKeys(), - TimelockMinDelay: big.NewInt(0), - } + timelockCfgs[sel] = proposalutils.SingleGroupTimelockConfig(t) } env, err = commonchangeset.ApplyChangesets(t, env, nil, []commonchangeset.ChangesetApplication{ { @@ -286,7 +279,7 @@ func SetupTestEnv(t *testing.T, c TestConfig) TestEnv { require.NoError(t, mcms.Validate()) // transfer ownership of all contracts to the MCMS - env, err = commonchangeset.ApplyChangesets(t, env, map[uint64]*gethwrappers.RBACTimelock{sel: mcms.Timelock}, []commonchangeset.ChangesetApplication{ + env, err = commonchangeset.ApplyChangesets(t, env, map[uint64]*proposalutils.TimelockExecutionContracts{sel: {Timelock: mcms.Timelock, CallProxy: mcms.CallProxy}}, []commonchangeset.ChangesetApplication{ { Changeset: commonchangeset.WrapChangeSet(kschangeset.AcceptAllOwnershipsProposal), Config: &kschangeset.AcceptAllOwnershipRequest{ diff --git a/deployment/keystone/changeset/internal/append_node_capabilities.go b/deployment/keystone/changeset/internal/append_node_capabilities.go index 6168356c351..892aa4c1e16 100644 --- a/deployment/keystone/changeset/internal/append_node_capabilities.go +++ b/deployment/keystone/changeset/internal/append_node_capabilities.go @@ -11,8 +11,8 @@ import ( ) type AppendNodeCapabilitiesRequest struct { - Chain deployment.Chain - Registry *kcr.CapabilitiesRegistry + Chain deployment.Chain + ContractSet *kslib.ContractSet P2pToCapabilities map[p2pkey.PeerID][]kcr.CapabilitiesRegistryCapability UseMCMS bool @@ -22,7 +22,7 @@ func (req *AppendNodeCapabilitiesRequest) Validate() error { if len(req.P2pToCapabilities) == 0 { return fmt.Errorf("p2pToCapabilities is empty") } - if req.Registry == nil { + if req.ContractSet.CapabilitiesRegistry == nil { return fmt.Errorf("registry is nil") } return nil @@ -32,36 +32,37 @@ func AppendNodeCapabilitiesImpl(lggr logger.Logger, req *AppendNodeCapabilitiesR if err := req.Validate(); err != nil { return nil, fmt.Errorf("failed to validate request: %w", err) } - // collect all the capabilities and add them to the registry - var capabilities []kcr.CapabilitiesRegistryCapability - for _, cap := range req.P2pToCapabilities { - capabilities = append(capabilities, cap...) - } - proposals, err := kslib.AddCapabilities(lggr, req.Registry, req.Chain, capabilities, req.UseMCMS) - if err != nil { - return nil, fmt.Errorf("failed to add capabilities: %w", err) - } // for each node, merge the new capabilities with the existing ones and update the node updatesByPeer := make(map[p2pkey.PeerID]NodeUpdate) for p2pID, caps := range req.P2pToCapabilities { - caps, err := AppendCapabilities(lggr, req.Registry, req.Chain, []p2pkey.PeerID{p2pID}, caps) + caps, err := AppendCapabilities(lggr, req.ContractSet.CapabilitiesRegistry, req.Chain, []p2pkey.PeerID{p2pID}, caps) if err != nil { return nil, fmt.Errorf("failed to append capabilities for p2p %s: %w", p2pID, err) } updatesByPeer[p2pID] = NodeUpdate{Capabilities: caps[p2pID]} } + // collect all the capabilities and add them to the registry + var capabilities []kcr.CapabilitiesRegistryCapability + for _, cap := range req.P2pToCapabilities { + capabilities = append(capabilities, cap...) + } + op, err := kslib.AddCapabilities(lggr, req.ContractSet, req.Chain, capabilities, req.UseMCMS) + if err != nil { + return nil, fmt.Errorf("failed to add capabilities: %w", err) + } + updateNodesReq := &UpdateNodesRequest{ Chain: req.Chain, - Registry: req.Registry, + ContractSet: req.ContractSet, P2pToUpdates: updatesByPeer, UseMCMS: req.UseMCMS, + Ops: op, } resp, err := UpdateNodes(lggr, updateNodesReq) if err != nil { return nil, fmt.Errorf("failed to update nodes: %w", err) } - resp.Proposals = append(proposals, resp.Proposals...) return resp, nil } diff --git a/deployment/keystone/changeset/internal/append_node_capabilities_test.go b/deployment/keystone/changeset/internal/append_node_capabilities_test.go index d28dcd73230..f2ec2bf5c8f 100644 --- a/deployment/keystone/changeset/internal/append_node_capabilities_test.go +++ b/deployment/keystone/changeset/internal/append_node_capabilities_test.go @@ -94,8 +94,8 @@ func TestAppendNodeCapabilities(t *testing.T) { t.Run(tt.name, func(t *testing.T) { setupResp := kstest.SetupTestRegistry(t, lggr, tt.args.initialState) - tt.args.req.Registry = setupResp.Registry tt.args.req.Chain = setupResp.Chain + tt.args.req.ContractSet = setupResp.ContractSet got, err := internal.AppendNodeCapabilitiesImpl(tt.args.lggr, tt.args.req) if (err != nil) != tt.wantErr { diff --git a/deployment/keystone/changeset/internal/test/utils.go b/deployment/keystone/changeset/internal/test/utils.go index a7aed2c9cb1..0a23f7e60a7 100644 --- a/deployment/keystone/changeset/internal/test/utils.go +++ b/deployment/keystone/changeset/internal/test/utils.go @@ -40,6 +40,7 @@ type SetupTestRegistryResponse struct { Registry *kcr.CapabilitiesRegistry Chain deployment.Chain RegistrySelector uint64 + ContractSet *kslib.ContractSet } func SetupTestRegistry(t *testing.T, lggr logger.Logger, req *SetupTestRegistryRequest) *SetupTestRegistryResponse { @@ -100,6 +101,9 @@ func SetupTestRegistry(t *testing.T, lggr logger.Logger, req *SetupTestRegistryR Registry: registry, Chain: chain, RegistrySelector: chain.Selector, + ContractSet: &kslib.ContractSet{ + CapabilitiesRegistry: registry, + }, } } diff --git a/deployment/keystone/changeset/internal/update_don.go b/deployment/keystone/changeset/internal/update_don.go index d56f77c1c78..fc7e410e540 100644 --- a/deployment/keystone/changeset/internal/update_don.go +++ b/deployment/keystone/changeset/internal/update_don.go @@ -6,9 +6,11 @@ import ( "encoding/hex" "encoding/json" "fmt" + "math/big" "sort" "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/mcms" "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/timelock" "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink/deployment" @@ -27,8 +29,8 @@ type CapabilityConfig struct { } type UpdateDonRequest struct { - Registry *kcr.CapabilitiesRegistry - Chain deployment.Chain + Chain deployment.Chain + ContractSet *kslib.ContractSet // contract set for the given chain P2PIDs []p2pkey.PeerID // this is the unique identifier for the don CapabilityConfigs []CapabilityConfig // if Config subfield is nil, a default config is used @@ -36,10 +38,10 @@ type UpdateDonRequest struct { UseMCMS bool } -func (r *UpdateDonRequest) appendNodeCapabilitiesRequest() *AppendNodeCapabilitiesRequest { +func (r *UpdateDonRequest) AppendNodeCapabilitiesRequest() *AppendNodeCapabilitiesRequest { out := &AppendNodeCapabilitiesRequest{ Chain: r.Chain, - Registry: r.Registry, + ContractSet: r.ContractSet, P2pToCapabilities: make(map[p2pkey.PeerID][]kcr.CapabilitiesRegistryCapability), UseMCMS: r.UseMCMS, } @@ -55,7 +57,7 @@ func (r *UpdateDonRequest) appendNodeCapabilitiesRequest() *AppendNodeCapabiliti } func (r *UpdateDonRequest) Validate() error { - if r.Registry == nil { + if r.ContractSet.CapabilitiesRegistry == nil { return fmt.Errorf("registry is required") } if len(r.P2PIDs) == 0 { @@ -65,8 +67,8 @@ func (r *UpdateDonRequest) Validate() error { } type UpdateDonResponse struct { - DonInfo kcr.CapabilitiesRegistryDONInfo - Proposals []timelock.MCMSWithTimelockProposal + DonInfo kcr.CapabilitiesRegistryDONInfo + Ops *timelock.BatchChainOperation } func UpdateDon(lggr logger.Logger, req *UpdateDonRequest) (*UpdateDonResponse, error) { @@ -74,7 +76,8 @@ func UpdateDon(lggr logger.Logger, req *UpdateDonRequest) (*UpdateDonResponse, e return nil, fmt.Errorf("failed to validate request: %w", err) } - getDonsResp, err := req.Registry.GetDONs(&bind.CallOpts{}) + registry := req.ContractSet.CapabilitiesRegistry + getDonsResp, err := registry.GetDONs(&bind.CallOpts{}) if err != nil { return nil, fmt.Errorf("failed to get Dons: %w", err) } @@ -83,29 +86,42 @@ func UpdateDon(lggr logger.Logger, req *UpdateDonRequest) (*UpdateDonResponse, e if err != nil { return nil, fmt.Errorf("failed to lookup don by p2pIDs: %w", err) } - cfgs, err := computeConfigs(req.Registry, req.CapabilityConfigs, don) + cfgs, err := computeConfigs(registry, req.CapabilityConfigs, don) if err != nil { return nil, fmt.Errorf("failed to compute configs: %w", err) } - _, err = AppendNodeCapabilitiesImpl(lggr, req.appendNodeCapabilitiesRequest()) - if err != nil { - return nil, fmt.Errorf("failed to append node capabilities: %w", err) + txOpts := req.Chain.DeployerKey + if req.UseMCMS { + txOpts = deployment.SimTransactOpts() } - - tx, err := req.Registry.UpdateDON(req.Chain.DeployerKey, don.Id, don.NodeP2PIds, cfgs, don.IsPublic, don.F) + tx, err := registry.UpdateDON(txOpts, don.Id, don.NodeP2PIds, cfgs, don.IsPublic, don.F) if err != nil { err = kslib.DecodeErr(kcr.CapabilitiesRegistryABI, err) return nil, fmt.Errorf("failed to call UpdateDON: %w", err) } - - _, err = req.Chain.Confirm(tx) - if err != nil { - return nil, fmt.Errorf("failed to confirm UpdateDON transaction %s: %w", tx.Hash().String(), err) + var ops *timelock.BatchChainOperation + if !req.UseMCMS { + _, err = req.Chain.Confirm(tx) + if err != nil { + return nil, fmt.Errorf("failed to confirm UpdateDON transaction %s: %w", tx.Hash().String(), err) + } + } else { + ops = &timelock.BatchChainOperation{ + ChainIdentifier: mcms.ChainIdentifier(req.Chain.Selector), + Batch: []mcms.Operation{ + { + To: registry.Address(), + Data: tx.Data(), + Value: big.NewInt(0), + }, + }, + } } + out := don out.CapabilityConfigurations = cfgs - return &UpdateDonResponse{DonInfo: out}, nil + return &UpdateDonResponse{DonInfo: out, Ops: ops}, nil } func PeerIDsToBytes(p2pIDs []p2pkey.PeerID) [][32]byte { diff --git a/deployment/keystone/changeset/internal/update_don_test.go b/deployment/keystone/changeset/internal/update_don_test.go index e500ade60d7..93857b26f78 100644 --- a/deployment/keystone/changeset/internal/update_don_test.go +++ b/deployment/keystone/changeset/internal/update_don_test.go @@ -83,13 +83,13 @@ func TestUpdateDon(t *testing.T) { admin: admin_4, }) // capabilities - cap_A = kcr.CapabilitiesRegistryCapability{ + initialCap = kcr.CapabilitiesRegistryCapability{ LabelledName: "test", Version: "1.0.0", CapabilityType: 0, } - cap_B = kcr.CapabilitiesRegistryCapability{ + capToAdd = kcr.CapabilitiesRegistryCapability{ LabelledName: "cap b", Version: "1.0.0", CapabilityType: 1, @@ -104,7 +104,7 @@ func TestUpdateDon(t *testing.T) { { Name: "don 1", Nodes: []deployment.Node{node_1, node_2, node_3, node_4}, - Capabilities: []kcr.CapabilitiesRegistryCapability{cap_A}, + Capabilities: []kcr.CapabilitiesRegistryCapability{initialCap}, }, }, nops: []keystone.NOP{ @@ -115,14 +115,26 @@ func TestUpdateDon(t *testing.T) { }, } - testCfg := setupUpdateDonTest(t, lggr, cfg) + testCfg := registerTestDon(t, lggr, cfg) + // add the new capabilities to registry + m := make(map[p2pkey.PeerID][]kcr.CapabilitiesRegistryCapability) + for _, node := range cfg.dons[0].Nodes { + m[node.PeerID] = append(m[node.PeerID], capToAdd) + } + + _, err := internal.AppendNodeCapabilitiesImpl(lggr, &internal.AppendNodeCapabilitiesRequest{ + Chain: testCfg.Chain, + ContractSet: testCfg.ContractSet, + P2pToCapabilities: m, + }) + require.NoError(t, err) req := &internal.UpdateDonRequest{ - Registry: testCfg.Registry, - Chain: testCfg.Chain, - P2PIDs: []p2pkey.PeerID{p2p_1.PeerID(), p2p_2.PeerID(), p2p_3.PeerID(), p2p_4.PeerID()}, + ContractSet: testCfg.ContractSet, + Chain: testCfg.Chain, + P2PIDs: []p2pkey.PeerID{p2p_1.PeerID(), p2p_2.PeerID(), p2p_3.PeerID(), p2p_4.PeerID()}, CapabilityConfigs: []internal.CapabilityConfig{ - {Capability: cap_A}, {Capability: cap_B}, + {Capability: initialCap}, {Capability: capToAdd}, }, } want := &internal.UpdateDonResponse{ @@ -131,8 +143,8 @@ func TestUpdateDon(t *testing.T) { ConfigCount: 1, NodeP2PIds: internal.PeerIDsToBytes([]p2pkey.PeerID{p2p_1.PeerID(), p2p_2.PeerID(), p2p_3.PeerID(), p2p_4.PeerID()}), CapabilityConfigurations: []kcr.CapabilitiesRegistryCapabilityConfiguration{ - {CapabilityId: kstest.MustCapabilityId(t, testCfg.Registry, cap_A)}, - {CapabilityId: kstest.MustCapabilityId(t, testCfg.Registry, cap_B)}, + {CapabilityId: kstest.MustCapabilityId(t, testCfg.Registry, initialCap)}, + {CapabilityId: kstest.MustCapabilityId(t, testCfg.Registry, capToAdd)}, }, }, } @@ -220,10 +232,11 @@ type setupUpdateDonTestResult struct { chain deployment.Chain } -func setupUpdateDonTest(t *testing.T, lggr logger.Logger, cfg setupUpdateDonTestConfig) *kstest.SetupTestRegistryResponse { +func registerTestDon(t *testing.T, lggr logger.Logger, cfg setupUpdateDonTestConfig) *kstest.SetupTestRegistryResponse { t.Helper() req := newSetupTestRegistryRequest(t, cfg.dons, cfg.nops) return kstest.SetupTestRegistry(t, lggr, req) + } func newSetupTestRegistryRequest(t *testing.T, dons []kslib.DonInfo, nops []keystone.NOP) *kstest.SetupTestRegistryRequest { diff --git a/deployment/keystone/changeset/internal/update_node_capabilities.go b/deployment/keystone/changeset/internal/update_node_capabilities.go index 72e6f99ee49..fe101c90296 100644 --- a/deployment/keystone/changeset/internal/update_node_capabilities.go +++ b/deployment/keystone/changeset/internal/update_node_capabilities.go @@ -11,9 +11,8 @@ import ( ) type UpdateNodeCapabilitiesImplRequest struct { - Chain deployment.Chain - Registry *kcr.CapabilitiesRegistry - + Chain deployment.Chain + ContractSet *kslib.ContractSet P2pToCapabilities map[p2pkey.PeerID][]kcr.CapabilitiesRegistryCapability UseMCMS bool @@ -23,7 +22,7 @@ func (req *UpdateNodeCapabilitiesImplRequest) Validate() error { if len(req.P2pToCapabilities) == 0 { return fmt.Errorf("p2pToCapabilities is empty") } - if req.Registry == nil { + if req.ContractSet == nil { return fmt.Errorf("registry is nil") } @@ -39,7 +38,7 @@ func UpdateNodeCapabilitiesImpl(lggr logger.Logger, req *UpdateNodeCapabilitiesI for _, cap := range req.P2pToCapabilities { capabilities = append(capabilities, cap...) } - proposals, err := kslib.AddCapabilities(lggr, req.Registry, req.Chain, capabilities, req.UseMCMS) + op, err := kslib.AddCapabilities(lggr, req.ContractSet, req.Chain, capabilities, req.UseMCMS) if err != nil { return nil, fmt.Errorf("failed to add capabilities: %w", err) } @@ -51,14 +50,14 @@ func UpdateNodeCapabilitiesImpl(lggr logger.Logger, req *UpdateNodeCapabilitiesI updateNodesReq := &UpdateNodesRequest{ Chain: req.Chain, - Registry: req.Registry, P2pToUpdates: p2pToUpdates, + ContractSet: req.ContractSet, + Ops: op, UseMCMS: req.UseMCMS, } resp, err := UpdateNodes(lggr, updateNodesReq) if err != nil { return nil, fmt.Errorf("failed to update nodes: %w", err) } - resp.Proposals = append(proposals, resp.Proposals...) return resp, nil } diff --git a/deployment/keystone/changeset/internal/update_node_capabilities_test.go b/deployment/keystone/changeset/internal/update_node_capabilities_test.go index 0346ff20dd6..ac39e57b32d 100644 --- a/deployment/keystone/changeset/internal/update_node_capabilities_test.go +++ b/deployment/keystone/changeset/internal/update_node_capabilities_test.go @@ -92,8 +92,8 @@ func TestUpdateNodeCapabilities(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { setupResp := kstest.SetupTestRegistry(t, lggr, tt.args.initialState) - tt.args.req.Registry = setupResp.Registry tt.args.req.Chain = setupResp.Chain + tt.args.req.ContractSet = setupResp.ContractSet got, err := kslib.UpdateNodeCapabilitiesImpl(tt.args.lggr, tt.args.req) if (err != nil) != tt.wantErr { diff --git a/deployment/keystone/changeset/internal/update_nodes.go b/deployment/keystone/changeset/internal/update_nodes.go index 2eba6d063df..3480f39b084 100644 --- a/deployment/keystone/changeset/internal/update_nodes.go +++ b/deployment/keystone/changeset/internal/update_nodes.go @@ -9,9 +9,7 @@ import ( "sort" "github.com/ethereum/go-ethereum/accounts/abi/bind" - "github.com/ethereum/go-ethereum/common" - "github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers" "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/mcms" "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/timelock" "github.com/smartcontractkit/chainlink-common/pkg/logger" @@ -19,7 +17,6 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/p2pkey" "github.com/smartcontractkit/chainlink/deployment" - "github.com/smartcontractkit/chainlink/deployment/common/proposalutils" kslib "github.com/smartcontractkit/chainlink/deployment/keystone" ) @@ -32,17 +29,19 @@ type NodeUpdate struct { } type UpdateNodesRequest struct { - Chain deployment.Chain - Registry *kcr.CapabilitiesRegistry + Chain deployment.Chain + ContractSet *kslib.ContractSet // contract set for the given chain P2pToUpdates map[p2pkey.PeerID]NodeUpdate - ContractSet kslib.ContractSet // contract set for the given chain - UseMCMS bool + UseMCMS bool + // If UseMCMS is true, and Ops is not nil then the UpdateNodes contract operation + // will be added to the Ops.Batch + Ops *timelock.BatchChainOperation } func (req *UpdateNodesRequest) NodeParams() ([]kcr.CapabilitiesRegistryNodeParams, error) { - return makeNodeParams(req.Registry, req.P2pToUpdates) + return makeNodeParams(req.ContractSet.CapabilitiesRegistry, req.P2pToUpdates) } // P2PSignerEnc represent the key fields in kcr.CapabilitiesRegistryNodeParams @@ -80,7 +79,7 @@ func (req *UpdateNodesRequest) Validate() error { } } - if req.Registry == nil { + if req.ContractSet.CapabilitiesRegistry == nil { return errors.New("registry is nil") } @@ -89,7 +88,9 @@ func (req *UpdateNodesRequest) Validate() error { type UpdateNodesResponse struct { NodeParams []kcr.CapabilitiesRegistryNodeParams - Proposals []timelock.MCMSWithTimelockProposal + // MCMS operation to update the nodes + // The operation is added to the Batch of the given Ops if not nil + Ops *timelock.BatchChainOperation } // UpdateNodes updates the nodes in the registry @@ -109,50 +110,39 @@ func UpdateNodes(lggr logger.Logger, req *UpdateNodesRequest) (*UpdateNodesRespo if req.UseMCMS { txOpts = deployment.SimTransactOpts() } - tx, err := req.Registry.UpdateNodes(txOpts, params) + registry := req.ContractSet.CapabilitiesRegistry + tx, err := registry.UpdateNodes(txOpts, params) if err != nil { err = kslib.DecodeErr(kcr.CapabilitiesRegistryABI, err) return nil, fmt.Errorf("failed to call UpdateNodes: %w", err) } - var proposals []timelock.MCMSWithTimelockProposal + ops := req.Ops if !req.UseMCMS { _, err = req.Chain.Confirm(tx) if err != nil { return nil, fmt.Errorf("failed to confirm UpdateNodes confirm transaction %s: %w", tx.Hash().String(), err) } } else { - ops := timelock.BatchChainOperation{ - ChainIdentifier: mcms.ChainIdentifier(req.Chain.Selector), - Batch: []mcms.Operation{ - { - To: req.Registry.Address(), - Data: tx.Data(), - Value: big.NewInt(0), - }, - }, - } - timelocksPerChain := map[uint64]common.Address{ - req.Chain.Selector: req.ContractSet.Timelock.Address(), - } - proposerMCMSes := map[uint64]*gethwrappers.ManyChainMultiSig{ - req.Chain.Selector: req.ContractSet.ProposerMcm, + op := mcms.Operation{ + To: registry.Address(), + Data: tx.Data(), + Value: big.NewInt(0), } - proposal, err := proposalutils.BuildProposalFromBatches( - timelocksPerChain, - proposerMCMSes, - []timelock.BatchChainOperation{ops}, - "proposal to set update nodes", - 0, - ) - if err != nil { - return nil, fmt.Errorf("failed to build proposal: %w", err) + if ops == nil { + ops = &timelock.BatchChainOperation{ + ChainIdentifier: mcms.ChainIdentifier(req.Chain.Selector), + Batch: []mcms.Operation{ + op, + }, + } + } else { + ops.Batch = append(ops.Batch, op) } - proposals = append(proposals, *proposal) } - return &UpdateNodesResponse{NodeParams: params, Proposals: proposals}, nil + return &UpdateNodesResponse{NodeParams: params, Ops: ops}, nil } // AppendCapabilities appends the capabilities to the existing capabilities of the nodes listed in p2pIds in the registry diff --git a/deployment/keystone/changeset/internal/update_nodes_test.go b/deployment/keystone/changeset/internal/update_nodes_test.go index b167f210811..e730668f806 100644 --- a/deployment/keystone/changeset/internal/update_nodes_test.go +++ b/deployment/keystone/changeset/internal/update_nodes_test.go @@ -30,7 +30,7 @@ func Test_UpdateNodesRequest_validate(t *testing.T) { p2pToUpdates map[p2pkey.PeerID]internal.NodeUpdate nopToNodes map[kcr.CapabilitiesRegistryNodeOperator][]*internal.P2PSignerEnc chain deployment.Chain - registry *kcr.CapabilitiesRegistry + contractSet *kslib.ContractSet } tests := []struct { name string @@ -43,7 +43,7 @@ func Test_UpdateNodesRequest_validate(t *testing.T) { p2pToUpdates: map[p2pkey.PeerID]internal.NodeUpdate{}, nopToNodes: nil, chain: deployment.Chain{}, - registry: nil, + contractSet: nil, }, wantErr: true, }, @@ -55,9 +55,9 @@ func Test_UpdateNodesRequest_validate(t *testing.T) { EncryptionPublicKey: "jk", }, }, - nopToNodes: nil, - chain: deployment.Chain{}, - registry: nil, + nopToNodes: nil, + chain: deployment.Chain{}, + contractSet: nil, }, wantErr: true, }, @@ -69,9 +69,9 @@ func Test_UpdateNodesRequest_validate(t *testing.T) { EncryptionPublicKey: "aabb", }, }, - nopToNodes: nil, - chain: deployment.Chain{}, - registry: nil, + nopToNodes: nil, + chain: deployment.Chain{}, + contractSet: nil, }, wantErr: true, }, @@ -81,7 +81,7 @@ func Test_UpdateNodesRequest_validate(t *testing.T) { req := &internal.UpdateNodesRequest{ P2pToUpdates: tt.fields.p2pToUpdates, Chain: tt.fields.chain, - Registry: tt.fields.registry, + ContractSet: tt.fields.contractSet, } if err := req.Validate(); (err != nil) != tt.wantErr { t.Errorf("internal.UpdateNodesRequest.validate() error = %v, wantErr %v", err, tt.wantErr) @@ -130,8 +130,7 @@ func TestUpdateNodes(t *testing.T) { }, }, }, - Chain: chain, - Registry: nil, // set in test to ensure no conflicts + Chain: chain, }, nopsToNodes: map[kcr.CapabilitiesRegistryNodeOperator][]*internal.P2PSignerEnc{ testNop(t, "nop1"): []*internal.P2PSignerEnc{ @@ -177,8 +176,7 @@ func TestUpdateNodes(t *testing.T) { }, }, }, - Chain: chain, - Registry: nil, // set in test to ensure no conflicts + Chain: chain, }, nopsToNodes: map[kcr.CapabilitiesRegistryNodeOperator][]*internal.P2PSignerEnc{ testNop(t, "nop1"): []*internal.P2PSignerEnc{ @@ -235,8 +233,7 @@ func TestUpdateNodes(t *testing.T) { }, }, }, - Chain: chain, - Registry: nil, // set in test to ensure no conflicts + Chain: chain, }, nopsToNodes: map[kcr.CapabilitiesRegistryNodeOperator][]*internal.P2PSignerEnc{ testNop(t, "nopA"): []*internal.P2PSignerEnc{ @@ -300,8 +297,7 @@ func TestUpdateNodes(t *testing.T) { }, }, }, - Chain: chain, - Registry: nil, // set in test to ensure no conflicts + Chain: chain, }, nopsToNodes: map[kcr.CapabilitiesRegistryNodeOperator][]*internal.P2PSignerEnc{ testNop(t, "nopA"): []*internal.P2PSignerEnc{ @@ -350,8 +346,8 @@ func TestUpdateNodes(t *testing.T) { EncryptionPublicKey: newKeyStr, }, }, - Chain: chain, - Registry: nil, // set in test to ensure no conflicts + Chain: chain, + ContractSet: nil, // set in test to ensure no conflicts }, nopsToNodes: map[kcr.CapabilitiesRegistryNodeOperator][]*internal.P2PSignerEnc{ testNop(t, "nop1"): []*internal.P2PSignerEnc{ @@ -385,8 +381,8 @@ func TestUpdateNodes(t *testing.T) { Signer: [32]byte{0: 2, 1: 3}, }, }, - Chain: chain, - Registry: nil, // set in test to ensure no conflicts + Chain: chain, + ContractSet: nil, // set in test to ensure no conflicts }, nopsToNodes: map[kcr.CapabilitiesRegistryNodeOperator][]*internal.P2PSignerEnc{ testNop(t, "nop1"): []*internal.P2PSignerEnc{ @@ -420,8 +416,7 @@ func TestUpdateNodes(t *testing.T) { NodeOperatorID: 2, }, }, - Chain: chain, - Registry: nil, // set in test to ensure no conflicts + Chain: chain, }, nopsToNodes: map[kcr.CapabilitiesRegistryNodeOperator][]*internal.P2PSignerEnc{ testNop(t, "nop1"): []*internal.P2PSignerEnc{ @@ -463,7 +458,7 @@ func TestUpdateNodes(t *testing.T) { NopToNodes: tt.args.nopsToNodes, }) registry := setupResp.Registry - tt.args.req.Registry = setupResp.Registry + tt.args.req.ContractSet = setupResp.ContractSet tt.args.req.Chain = setupResp.Chain id, err := registry.GetHashedCapabilityId(&bind.CallOpts{}, phonyCap.LabelledName, phonyCap.Version) @@ -581,8 +576,8 @@ func TestUpdateNodes(t *testing.T) { Capabilities: toRegister, }, }, - Chain: chain, - Registry: registry, + Chain: chain, + ContractSet: setupResp.ContractSet, } _, err = internal.UpdateNodes(lggr, req) require.NoError(t, err) diff --git a/deployment/keystone/changeset/update_don.go b/deployment/keystone/changeset/update_don.go index 1ab40d5a935..3f43ea513be 100644 --- a/deployment/keystone/changeset/update_don.go +++ b/deployment/keystone/changeset/update_don.go @@ -4,8 +4,11 @@ import ( "fmt" "github.com/smartcontractkit/chainlink/deployment" + kslib "github.com/smartcontractkit/chainlink/deployment/keystone" + "github.com/smartcontractkit/chainlink/deployment/keystone/changeset/internal" kcr "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/capabilities_registry" + "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/p2pkey" ) var _ deployment.ChangeSet[*UpdateDonRequest] = UpdateDon @@ -13,7 +16,28 @@ var _ deployment.ChangeSet[*UpdateDonRequest] = UpdateDon // CapabilityConfig is a struct that holds a capability and its configuration type CapabilityConfig = internal.CapabilityConfig -type UpdateDonRequest = internal.UpdateDonRequest +type UpdateDonRequest struct { + RegistryChainSel uint64 + P2PIDs []p2pkey.PeerID // this is the unique identifier for the don + CapabilityConfigs []CapabilityConfig // if Config subfield is nil, a default config is used + + // MCMSConfig is optional. If non-nil, the changes will be proposed using MCMS. + MCMSConfig *MCMSConfig +} + +func (r *UpdateDonRequest) Validate() error { + if len(r.P2PIDs) == 0 { + return fmt.Errorf("p2pIDs is required") + } + if len(r.CapabilityConfigs) == 0 { + return fmt.Errorf("capabilityConfigs is required") + } + return nil +} + +func (r UpdateDonRequest) UseMCMS() bool { + return r.MCMSConfig != nil +} type UpdateDonResponse struct { DonInfo kcr.CapabilitiesRegistryDONInfo @@ -23,9 +47,73 @@ type UpdateDonResponse struct { // This a complex action in practice that involves registering missing capabilities, adding the nodes, and updating // the capabilities of the DON func UpdateDon(env deployment.Environment, req *UpdateDonRequest) (deployment.ChangesetOutput, error) { - _, err := internal.UpdateDon(env.Logger, req) + appendResult, err := AppendNodeCapabilities(env, appendRequest(req)) + if err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("failed to append node capabilities: %w", err) + } + + ur, err := updateDonRequest(env, req) + if err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("failed to create update don request: %w", err) + } + updateResult, err := internal.UpdateDon(env.Logger, ur) if err != nil { return deployment.ChangesetOutput{}, fmt.Errorf("failed to update don: %w", err) } - return deployment.ChangesetOutput{}, nil + + out := deployment.ChangesetOutput{} + if req.UseMCMS() { + if updateResult.Ops == nil { + return out, fmt.Errorf("expected MCMS operation to be non-nil") + } + if len(appendResult.Proposals) == 0 { + return out, fmt.Errorf("expected append node capabilities to return proposals") + } + + out.Proposals = appendResult.Proposals + + // add the update don to the existing batch + // this makes the proposal all-or-nothing because all the operations are in the same batch, there is only one tr + // transaction and only one proposal + out.Proposals[0].Transactions[0].Batch = append(out.Proposals[0].Transactions[0].Batch, updateResult.Ops.Batch...) + + } + return out, nil + +} + +func appendRequest(r *UpdateDonRequest) *AppendNodeCapabilitiesRequest { + out := &AppendNodeCapabilitiesRequest{ + RegistryChainSel: r.RegistryChainSel, + P2pToCapabilities: make(map[p2pkey.PeerID][]kcr.CapabilitiesRegistryCapability), + MCMSConfig: r.MCMSConfig, + } + for _, p2pid := range r.P2PIDs { + if _, exists := out.P2pToCapabilities[p2pid]; !exists { + out.P2pToCapabilities[p2pid] = make([]kcr.CapabilitiesRegistryCapability, 0) + } + for _, cc := range r.CapabilityConfigs { + out.P2pToCapabilities[p2pid] = append(out.P2pToCapabilities[p2pid], cc.Capability) + } + } + return out +} + +func updateDonRequest(env deployment.Environment, r *UpdateDonRequest) (*internal.UpdateDonRequest, error) { + resp, err := kslib.GetContractSets(env.Logger, &kslib.GetContractSetsRequest{ + Chains: env.Chains, + AddressBook: env.ExistingAddresses, + }) + if err != nil { + return nil, fmt.Errorf("failed to get contract sets: %w", err) + } + contractSet := resp.ContractSets[r.RegistryChainSel] + + return &internal.UpdateDonRequest{ + Chain: env.Chains[r.RegistryChainSel], + ContractSet: &contractSet, + P2PIDs: r.P2PIDs, + CapabilityConfigs: r.CapabilityConfigs, + UseMCMS: r.UseMCMS(), + }, nil } diff --git a/deployment/keystone/changeset/update_don_test.go b/deployment/keystone/changeset/update_don_test.go new file mode 100644 index 00000000000..012111c4e62 --- /dev/null +++ b/deployment/keystone/changeset/update_don_test.go @@ -0,0 +1,158 @@ +package changeset_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + commonchangeset "github.com/smartcontractkit/chainlink/deployment/common/changeset" + "github.com/smartcontractkit/chainlink/deployment/common/proposalutils" + "github.com/smartcontractkit/chainlink/deployment/keystone/changeset" + "github.com/smartcontractkit/chainlink/deployment/keystone/changeset/internal" + kcr "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/capabilities_registry" + "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/p2pkey" +) + +func TestUpdateDon(t *testing.T) { + t.Parallel() + + var ( + capA = kcr.CapabilitiesRegistryCapability{ + LabelledName: "capA", + Version: "0.4.2", + } + capB = kcr.CapabilitiesRegistryCapability{ + LabelledName: "capB", + Version: "3.16.0", + } + caps = []kcr.CapabilitiesRegistryCapability{capA, capB} + ) + t.Run("no mcms", func(t *testing.T) { + te := SetupTestEnv(t, TestConfig{ + WFDonConfig: DonConfig{N: 4}, + AssetDonConfig: DonConfig{N: 4}, + WriterDonConfig: DonConfig{N: 4}, + NumChains: 1, + }) + + // contract set is already deployed with capabilities + // we have to keep track of the existing capabilities to add to the new ones + var p2pIDs []p2pkey.PeerID + newCapabilities := make(map[p2pkey.PeerID][]kcr.CapabilitiesRegistryCapability) + for id, _ := range te.WFNodes { + k, err := p2pkey.MakePeerID(id) + require.NoError(t, err) + p2pIDs = append(p2pIDs, k) + newCapabilities[k] = caps + } + + t.Run("succeeds if update sets new and existing capabilities", func(t *testing.T) { + cfg := changeset.UpdateDonRequest{ + RegistryChainSel: te.RegistrySelector, + P2PIDs: p2pIDs, + CapabilityConfigs: []changeset.CapabilityConfig{ + { + Capability: capA, + }, + { + Capability: capB, + }, + }, + } + + csOut, err := changeset.UpdateDon(te.Env, &cfg) + require.NoError(t, err) + require.Len(t, csOut.Proposals, 0) + require.Nil(t, csOut.AddressBook) + + assertDonContainsCapabilities(t, te.ContractSets()[te.RegistrySelector].CapabilitiesRegistry, caps, p2pIDs) + }) + }) + t.Run("with mcms", func(t *testing.T) { + te := SetupTestEnv(t, TestConfig{ + WFDonConfig: DonConfig{N: 4}, + AssetDonConfig: DonConfig{N: 4}, + WriterDonConfig: DonConfig{N: 4}, + NumChains: 1, + UseMCMS: true, + }) + + // contract set is already deployed with capabilities + // we have to keep track of the existing capabilities to add to the new ones + var p2pIDs []p2pkey.PeerID + for id, _ := range te.WFNodes { + k, err := p2pkey.MakePeerID(id) + require.NoError(t, err) + p2pIDs = append(p2pIDs, k) + } + + cfg := changeset.UpdateDonRequest{ + RegistryChainSel: te.RegistrySelector, + P2PIDs: p2pIDs, + CapabilityConfigs: []changeset.CapabilityConfig{ + { + Capability: capA, + }, + { + Capability: capB, + }, + }, + MCMSConfig: &changeset.MCMSConfig{MinDuration: 0}, + } + + csOut, err := changeset.UpdateDon(te.Env, &cfg) + require.NoError(t, err) + + require.Len(t, csOut.Proposals, 1) + require.Len(t, csOut.Proposals[0].Transactions, 1) // append node capabilties cs, update don + require.Len(t, csOut.Proposals[0].Transactions[0].Batch, 3) // add capabilities, update nodes, update don + require.Nil(t, csOut.AddressBook) + + // now apply the changeset such that the proposal is signed and execed + contracts := te.ContractSets()[te.RegistrySelector] + timelockContracts := map[uint64]*proposalutils.TimelockExecutionContracts{ + te.RegistrySelector: { + Timelock: contracts.Timelock, + CallProxy: contracts.CallProxy, + }, + } + _, err = commonchangeset.ApplyChangesets(t, te.Env, timelockContracts, []commonchangeset.ChangesetApplication{ + { + Changeset: commonchangeset.WrapChangeSet(changeset.UpdateDon), + Config: &cfg, + }, + }) + require.NoError(t, err) + assertDonContainsCapabilities(t, te.ContractSets()[te.RegistrySelector].CapabilitiesRegistry, caps, p2pIDs) + }) +} + +func assertDonContainsCapabilities(t *testing.T, registry *kcr.CapabilitiesRegistry, want []kcr.CapabilitiesRegistryCapability, p2pIDs []p2pkey.PeerID) { + dons, err := registry.GetDONs(nil) + require.NoError(t, err) + var got *kcr.CapabilitiesRegistryDONInfo + for i, don := range dons { + if internal.SortedHash(internal.PeerIDsToBytes(p2pIDs)) == internal.SortedHash(don.NodeP2PIds) { + got = &dons[i] + break + } + } + require.NotNil(t, got, "missing don with p2pIDs %v", p2pIDs) + wantHashes := make([][32]byte, len(want)) + for i, c := range want { + h, err := registry.GetHashedCapabilityId(nil, c.LabelledName, c.Version) + require.NoError(t, err) + wantHashes[i] = h + assert.Contains(t, capIDsFromCapCfgs(got.CapabilityConfigurations), h, "missing capability %v", c) + } + assert.LessOrEqual(t, len(want), len(got.CapabilityConfigurations), "too many capabilities") +} + +func capIDsFromCapCfgs(cfgs []kcr.CapabilitiesRegistryCapabilityConfiguration) [][32]byte { + out := make([][32]byte, len(cfgs)) + for i, c := range cfgs { + out[i] = c.CapabilityId + } + return out +} diff --git a/deployment/keystone/changeset/update_node_capabilities.go b/deployment/keystone/changeset/update_node_capabilities.go index 655614ed7f0..9c9d5585fc2 100644 --- a/deployment/keystone/changeset/update_node_capabilities.go +++ b/deployment/keystone/changeset/update_node_capabilities.go @@ -1,13 +1,16 @@ package changeset import ( - "encoding/json" "fmt" "strconv" + "github.com/ethereum/go-ethereum/common" chainsel "github.com/smartcontractkit/chain-selectors" + "github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers" + "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/timelock" "github.com/smartcontractkit/chainlink/deployment" + "github.com/smartcontractkit/chainlink/deployment/common/proposalutils" kslib "github.com/smartcontractkit/chainlink/deployment/keystone" "github.com/smartcontractkit/chainlink/deployment/keystone/changeset/internal" @@ -50,17 +53,14 @@ type UpdateNodeCapabilitiesRequest = MutateNodeCapabilitiesRequest // MutateNodeCapabilitiesRequest is a request to change the capabilities of nodes in the registry type MutateNodeCapabilitiesRequest struct { - AddressBook deployment.AddressBook - RegistryChainSel uint64 - + RegistryChainSel uint64 P2pToCapabilities map[p2pkey.PeerID][]kcr.CapabilitiesRegistryCapability - UseMCMS bool + + // MCMSConfig is optional. If non-nil, the changes will be proposed using MCMS. + MCMSConfig *MCMSConfig } func (req *MutateNodeCapabilitiesRequest) Validate() error { - if req.AddressBook == nil { - return fmt.Errorf("address book is nil") - } if len(req.P2pToCapabilities) == 0 { return fmt.Errorf("p2pToCapabilities is empty") } @@ -72,6 +72,10 @@ func (req *MutateNodeCapabilitiesRequest) Validate() error { return nil } +func (req *MutateNodeCapabilitiesRequest) UseMCMS() bool { + return req.MCMSConfig != nil +} + func (req *MutateNodeCapabilitiesRequest) updateNodeCapabilitiesImplRequest(e deployment.Environment) (*internal.UpdateNodeCapabilitiesImplRequest, error) { if err := req.Validate(); err != nil { return nil, fmt.Errorf("failed to validate UpdateNodeCapabilitiesRequest: %w", err) @@ -80,39 +84,61 @@ func (req *MutateNodeCapabilitiesRequest) updateNodeCapabilitiesImplRequest(e de if !ok { return nil, fmt.Errorf("registry chain selector %d does not exist in environment", req.RegistryChainSel) } - contracts, err := kslib.GetContractSets(e.Logger, &kslib.GetContractSetsRequest{ + resp, err := kslib.GetContractSets(e.Logger, &kslib.GetContractSetsRequest{ Chains: map[uint64]deployment.Chain{req.RegistryChainSel: registryChain}, - AddressBook: req.AddressBook, + AddressBook: e.ExistingAddresses, }) if err != nil { return nil, fmt.Errorf("failed to get contract sets: %w", err) } - registry := contracts.ContractSets[req.RegistryChainSel].CapabilitiesRegistry - if registry == nil { - return nil, fmt.Errorf("capabilities registry not found for chain %d", req.RegistryChainSel) + contractSet, exists := resp.ContractSets[req.RegistryChainSel] + if !exists { + return nil, fmt.Errorf("contract set not found for chain %d", req.RegistryChainSel) } return &internal.UpdateNodeCapabilitiesImplRequest{ Chain: registryChain, - Registry: registry, + ContractSet: &contractSet, P2pToCapabilities: req.P2pToCapabilities, - UseMCMS: req.UseMCMS, + UseMCMS: req.UseMCMS(), }, nil } // UpdateNodeCapabilities updates the capabilities of nodes in the registry -func UpdateNodeCapabilities(env deployment.Environment, req *MutateNodeCapabilitiesRequest) (deployment.ChangesetOutput, error) { +func UpdateNodeCapabilities(env deployment.Environment, req *UpdateNodeCapabilitiesRequest) (deployment.ChangesetOutput, error) { c, err := req.updateNodeCapabilitiesImplRequest(env) if err != nil { return deployment.ChangesetOutput{}, fmt.Errorf("failed to convert request: %w", err) } r, err := internal.UpdateNodeCapabilitiesImpl(env.Logger, c) - if err == nil { - b, err2 := json.Marshal(r) - if err2 != nil { - env.Logger.Debugf("Updated node capabilities '%s'", b) + if err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("failed to update nodes: %w", err) + } + + out := deployment.ChangesetOutput{} + if req.UseMCMS() { + if r.Ops == nil { + return out, fmt.Errorf("expected MCMS operation to be non-nil") + } + timelocksPerChain := map[uint64]common.Address{ + c.Chain.Selector: c.ContractSet.Timelock.Address(), + } + proposerMCMSes := map[uint64]*gethwrappers.ManyChainMultiSig{ + c.Chain.Selector: c.ContractSet.ProposerMcm, + } + + proposal, err := proposalutils.BuildProposalFromBatches( + timelocksPerChain, + proposerMCMSes, + []timelock.BatchChainOperation{*r.Ops}, + "proposal to set update node capabilities", + req.MCMSConfig.MinDuration, + ) + if err != nil { + return out, fmt.Errorf("failed to build proposal: %w", err) } + out.Proposals = []timelock.MCMSWithTimelockProposal{*proposal} } - return deployment.ChangesetOutput{}, err + return out, nil } diff --git a/deployment/keystone/changeset/update_node_capabilities_test.go b/deployment/keystone/changeset/update_node_capabilities_test.go new file mode 100644 index 00000000000..87b49acf614 --- /dev/null +++ b/deployment/keystone/changeset/update_node_capabilities_test.go @@ -0,0 +1,207 @@ +package changeset_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "golang.org/x/exp/maps" + + commonchangeset "github.com/smartcontractkit/chainlink/deployment/common/changeset" + "github.com/smartcontractkit/chainlink/deployment/common/proposalutils" + "github.com/smartcontractkit/chainlink/deployment/keystone/changeset" + kcr "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/capabilities_registry" + "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/p2pkey" +) + +func TestUpdateNodeCapabilities(t *testing.T) { + t.Parallel() + + var ( + capA = kcr.CapabilitiesRegistryCapability{ + LabelledName: "capA", + Version: "0.4.2", + } + capB = kcr.CapabilitiesRegistryCapability{ + LabelledName: "capB", + Version: "3.16.0", + } + caps = []kcr.CapabilitiesRegistryCapability{capA, capB} + ) + t.Run("no mcms", func(t *testing.T) { + te := SetupTestEnv(t, TestConfig{ + WFDonConfig: DonConfig{N: 4}, + AssetDonConfig: DonConfig{N: 4}, + WriterDonConfig: DonConfig{N: 4}, + NumChains: 1, + }) + + // contract set is already deployed with capabilities + // we have to keep track of the existing capabilities to add to the new ones + var p2pIDs []p2pkey.PeerID + newCapabilities := make(map[p2pkey.PeerID][]kcr.CapabilitiesRegistryCapability) + for id, _ := range te.WFNodes { + k, err := p2pkey.MakePeerID(id) + require.NoError(t, err) + p2pIDs = append(p2pIDs, k) + newCapabilities[k] = caps + } + + t.Run("fails if update drops existing capabilities", func(t *testing.T) { + + cfg := changeset.UpdateNodeCapabilitiesRequest{ + RegistryChainSel: te.RegistrySelector, + P2pToCapabilities: newCapabilities, + } + + _, err := changeset.UpdateNodeCapabilities(te.Env, &cfg) + require.Error(t, err) + assert.Contains(t, err.Error(), "CapabilityRequiredByDON") + }) + t.Run("succeeds if update sets new and existing capabilities", func(t *testing.T) { + existing := getNodeCapabilities(te.ContractSets()[te.RegistrySelector].CapabilitiesRegistry, p2pIDs) + + capabiltiesToSet := existing + for k, v := range newCapabilities { + capabiltiesToSet[k] = append(capabiltiesToSet[k], v...) + } + cfg := changeset.UpdateNodeCapabilitiesRequest{ + RegistryChainSel: te.RegistrySelector, + P2pToCapabilities: capabiltiesToSet, + } + + csOut, err := changeset.UpdateNodeCapabilities(te.Env, &cfg) + require.NoError(t, err) + require.Len(t, csOut.Proposals, 0) + require.Nil(t, csOut.AddressBook) + + validateCapabilityUpdates(t, te, capabiltiesToSet) + }) + }) + t.Run("with mcms", func(t *testing.T) { + te := SetupTestEnv(t, TestConfig{ + WFDonConfig: DonConfig{N: 4}, + AssetDonConfig: DonConfig{N: 4}, + WriterDonConfig: DonConfig{N: 4}, + NumChains: 1, + UseMCMS: true, + }) + + // contract set is already deployed with capabilities + // we have to keep track of the existing capabilities to add to the new ones + var p2pIDs []p2pkey.PeerID + newCapabilities := make(map[p2pkey.PeerID][]kcr.CapabilitiesRegistryCapability) + for id, _ := range te.WFNodes { + k, err := p2pkey.MakePeerID(id) + require.NoError(t, err) + p2pIDs = append(p2pIDs, k) + newCapabilities[k] = caps + } + + existing := getNodeCapabilities(te.ContractSets()[te.RegistrySelector].CapabilitiesRegistry, p2pIDs) + + capabiltiesToSet := existing + for k, v := range newCapabilities { + capabiltiesToSet[k] = append(capabiltiesToSet[k], v...) + } + cfg := changeset.UpdateNodeCapabilitiesRequest{ + RegistryChainSel: te.RegistrySelector, + P2pToCapabilities: capabiltiesToSet, + MCMSConfig: &changeset.MCMSConfig{MinDuration: 0}, + } + + csOut, err := changeset.UpdateNodeCapabilities(te.Env, &cfg) + require.NoError(t, err) + require.Len(t, csOut.Proposals, 1) + require.Len(t, csOut.Proposals[0].Transactions, 1) + require.Len(t, csOut.Proposals[0].Transactions[0].Batch, 2) // add capabilities, update nodes + require.Nil(t, csOut.AddressBook) + + // now apply the changeset such that the proposal is signed and execed + contracts := te.ContractSets()[te.RegistrySelector] + timelockContracts := map[uint64]*proposalutils.TimelockExecutionContracts{ + te.RegistrySelector: { + Timelock: contracts.Timelock, + CallProxy: contracts.CallProxy, + }, + } + + _, err = commonchangeset.ApplyChangesets(t, te.Env, timelockContracts, []commonchangeset.ChangesetApplication{ + { + Changeset: commonchangeset.WrapChangeSet(changeset.UpdateNodeCapabilities), + Config: &cfg, + }, + }) + require.NoError(t, err) + validateCapabilityUpdates(t, te, capabiltiesToSet) + + }) + +} + +// validateUpdate checks reads nodes from the registry and checks they have the expected updates +func validateCapabilityUpdates(t *testing.T, te TestEnv, expected map[p2pkey.PeerID][]kcr.CapabilitiesRegistryCapability) { + registry := te.ContractSets()[te.RegistrySelector].CapabilitiesRegistry + wfP2PIDs := p2pIDs(t, maps.Keys(te.WFNodes)) + nodes, err := registry.GetNodesByP2PIds(nil, wfP2PIDs) + require.NoError(t, err) + require.Len(t, nodes, len(wfP2PIDs)) + for _, node := range nodes { + want := expected[node.P2pId] + require.NotNil(t, want) + assertEqualCapabilities(t, registry, want, node) + } +} + +func assertEqualCapabilities(t *testing.T, registry *kcr.CapabilitiesRegistry, want []kcr.CapabilitiesRegistryCapability, got kcr.INodeInfoProviderNodeInfo) { + wantHashes := make([][32]byte, len(want)) + for i, c := range want { + h, err := registry.GetHashedCapabilityId(nil, c.LabelledName, c.Version) + require.NoError(t, err) + wantHashes[i] = h + } + assert.Equal(t, len(want), len(got.HashedCapabilityIds)) + assert.ElementsMatch(t, wantHashes, got.HashedCapabilityIds) +} + +func getNodeCapabilities(registry *kcr.CapabilitiesRegistry, p2pIDs []p2pkey.PeerID) map[p2pkey.PeerID][]kcr.CapabilitiesRegistryCapability { + m := make(map[p2pkey.PeerID][]kcr.CapabilitiesRegistryCapability) + caps, err := registry.GetCapabilities(nil) + if err != nil { + panic(err) + } + var capMap = make(map[[32]byte]kcr.CapabilitiesRegistryCapability) + for _, c := range caps { + capMap[c.HashedId] = kcr.CapabilitiesRegistryCapability{ + LabelledName: c.LabelledName, + Version: c.Version, + CapabilityType: c.CapabilityType, + ResponseType: c.ResponseType, + ConfigurationContract: c.ConfigurationContract, + } + } + nodes, err := registry.GetNodesByP2PIds(nil, peerIDsToBytes(p2pIDs)) + if err != nil { + panic(err) + } + for _, n := range nodes { + caps := make([]kcr.CapabilitiesRegistryCapability, len(n.HashedCapabilityIds)) + for i, h := range n.HashedCapabilityIds { + c, ok := capMap[h] + if !ok { + panic("capability not found") + } + caps[i] = c + } + m[n.P2pId] = caps + } + return m +} + +func peerIDsToBytes(p2pIDs []p2pkey.PeerID) [][32]byte { + bs := make([][32]byte, len(p2pIDs)) + for i, p := range p2pIDs { + bs[i] = p + } + return bs +} diff --git a/deployment/keystone/changeset/update_nodes.go b/deployment/keystone/changeset/update_nodes.go index 76b095d0949..bb12f32cb94 100644 --- a/deployment/keystone/changeset/update_nodes.go +++ b/deployment/keystone/changeset/update_nodes.go @@ -2,22 +2,44 @@ package changeset import ( "fmt" + "time" + "github.com/ethereum/go-ethereum/common" + "github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers" + "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/timelock" "github.com/smartcontractkit/chainlink/deployment" + "github.com/smartcontractkit/chainlink/deployment/common/proposalutils" kslib "github.com/smartcontractkit/chainlink/deployment/keystone" "github.com/smartcontractkit/chainlink/deployment/keystone/changeset/internal" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/p2pkey" ) +type MCMSConfig struct { + MinDuration time.Duration +} + var _ deployment.ChangeSet[*UpdateNodesRequest] = UpdateNodes type UpdateNodesRequest struct { RegistryChainSel uint64 P2pToUpdates map[p2pkey.PeerID]NodeUpdate - UseMCMS bool + // MCMSConfig is optional. If non-nil, the changes will be proposed using MCMS. + MCMSConfig *MCMSConfig +} + +func (r *UpdateNodesRequest) Validate() error { + if r.P2pToUpdates == nil { + return fmt.Errorf("P2pToUpdates must be non-nil") + } + return nil +} + +func (r UpdateNodesRequest) UseMCMS() bool { + return r.MCMSConfig != nil } + type NodeUpdate = internal.NodeUpdate // UpdateNodes updates the a set of nodes. @@ -28,25 +50,52 @@ func UpdateNodes(env deployment.Environment, req *UpdateNodesRequest) (deploymen if !ok { return deployment.ChangesetOutput{}, fmt.Errorf("registry chain selector %d does not exist in environment", req.RegistryChainSel) } - contracts, err := kslib.GetContractSets(env.Logger, &kslib.GetContractSetsRequest{ + cresp, err := kslib.GetContractSets(env.Logger, &kslib.GetContractSetsRequest{ Chains: env.Chains, AddressBook: env.ExistingAddresses, }) if err != nil { return deployment.ChangesetOutput{}, fmt.Errorf("failed to get contract sets: %w", err) } + contracts, exists := cresp.ContractSets[req.RegistryChainSel] + if !exists { + return deployment.ChangesetOutput{}, fmt.Errorf("contract set not found for chain %d", req.RegistryChainSel) + } resp, err := internal.UpdateNodes(env.Logger, &internal.UpdateNodesRequest{ Chain: registryChain, - Registry: contracts.ContractSets[req.RegistryChainSel].CapabilitiesRegistry, - ContractSet: contracts.ContractSets[req.RegistryChainSel], + ContractSet: &contracts, P2pToUpdates: req.P2pToUpdates, - UseMCMS: req.UseMCMS, + UseMCMS: req.UseMCMS(), }) if err != nil { return deployment.ChangesetOutput{}, fmt.Errorf("failed to update don: %w", err) } - return deployment.ChangesetOutput{ - Proposals: resp.Proposals, - }, nil + + out := deployment.ChangesetOutput{} + if req.UseMCMS() { + if resp.Ops == nil { + return out, fmt.Errorf("expected MCMS operation to be non-nil") + } + timelocksPerChain := map[uint64]common.Address{ + req.RegistryChainSel: contracts.Timelock.Address(), + } + proposerMCMSes := map[uint64]*gethwrappers.ManyChainMultiSig{ + req.RegistryChainSel: contracts.ProposerMcm, + } + + proposal, err := proposalutils.BuildProposalFromBatches( + timelocksPerChain, + proposerMCMSes, + []timelock.BatchChainOperation{*resp.Ops}, + "proposal to set update nodes", + req.MCMSConfig.MinDuration, + ) + if err != nil { + return out, fmt.Errorf("failed to build proposal: %w", err) + } + out.Proposals = []timelock.MCMSWithTimelockProposal{*proposal} + } + + return out, nil } diff --git a/deployment/keystone/changeset/update_nodes_test.go b/deployment/keystone/changeset/update_nodes_test.go index 10c08333d22..31f71cd9603 100644 --- a/deployment/keystone/changeset/update_nodes_test.go +++ b/deployment/keystone/changeset/update_nodes_test.go @@ -8,8 +8,8 @@ import ( "github.com/stretchr/testify/require" "golang.org/x/exp/maps" - "github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers" commonchangeset "github.com/smartcontractkit/chainlink/deployment/common/changeset" + "github.com/smartcontractkit/chainlink/deployment/common/proposalutils" "github.com/smartcontractkit/chainlink/deployment/keystone/changeset" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/p2pkey" ) @@ -80,7 +80,7 @@ func TestUpdateNodes(t *testing.T) { cfg := changeset.UpdateNodesRequest{ RegistryChainSel: te.RegistrySelector, P2pToUpdates: updates, - UseMCMS: true, + MCMSConfig: &changeset.MCMSConfig{MinDuration: 0}, } csOut, err := changeset.UpdateNodes(te.Env, &cfg) @@ -90,16 +90,19 @@ func TestUpdateNodes(t *testing.T) { // now apply the changeset such that the proposal is signed and execed contracts := te.ContractSets()[te.RegistrySelector] - timelocks := map[uint64]*gethwrappers.RBACTimelock{ - te.RegistrySelector: contracts.Timelock, + timelockContracts := map[uint64]*proposalutils.TimelockExecutionContracts{ + te.RegistrySelector: { + Timelock: contracts.Timelock, + CallProxy: contracts.CallProxy, + }, } - _, err = commonchangeset.ApplyChangesets(t, te.Env, timelocks, []commonchangeset.ChangesetApplication{ + _, err = commonchangeset.ApplyChangesets(t, te.Env, timelockContracts, []commonchangeset.ChangesetApplication{ { Changeset: commonchangeset.WrapChangeSet(changeset.UpdateNodes), Config: &changeset.UpdateNodesRequest{ RegistryChainSel: te.RegistrySelector, P2pToUpdates: updates, - UseMCMS: true, + MCMSConfig: &changeset.MCMSConfig{MinDuration: 0}, }, }, }) diff --git a/deployment/keystone/deploy.go b/deployment/keystone/deploy.go index 4dd8b8c495a..cb7804d0051 100644 --- a/deployment/keystone/deploy.go +++ b/deployment/keystone/deploy.go @@ -14,15 +14,12 @@ import ( "time" "github.com/ethereum/go-ethereum/accounts/abi/bind" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/rpc" "golang.org/x/exp/maps" - "github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers" "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/mcms" "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/timelock" "github.com/smartcontractkit/chainlink/deployment" - "github.com/smartcontractkit/chainlink/deployment/common/proposalutils" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/durationpb" @@ -169,7 +166,7 @@ func DonInfos(dons []DonCapabilities, jd deployment.OffchainClient) ([]DonInfo, return donInfos, nil } -func GetRegistryContract(e *deployment.Environment, registryChainSel uint64, addrBook deployment.AddressBook) (*kcr.CapabilitiesRegistry, deployment.Chain, error) { +func GetRegistryContract(e *deployment.Environment, registryChainSel uint64) (*kcr.CapabilitiesRegistry, deployment.Chain, error) { registryChain, ok := e.Chains[registryChainSel] if !ok { return nil, deployment.Chain{}, fmt.Errorf("chain %d not found in environment", registryChainSel) @@ -177,7 +174,7 @@ func GetRegistryContract(e *deployment.Environment, registryChainSel uint64, add contractSetsResp, err := GetContractSets(e.Logger, &GetContractSetsRequest{ Chains: e.Chains, - AddressBook: addrBook, + AddressBook: e.ExistingAddresses, }) if err != nil { return nil, deployment.Chain{}, fmt.Errorf("failed to get contract sets: %w", err) @@ -348,7 +345,7 @@ func ConfigureOCR3Contract(env *deployment.Environment, chainSel uint64, dons [] type ConfigureOCR3Resp struct { OCR2OracleConfig - Proposal *timelock.MCMSWithTimelockProposal + Ops *timelock.BatchChainOperation } type ConfigureOCR3Config struct { @@ -405,7 +402,7 @@ func ConfigureOCR3ContractFromJD(env *deployment.Environment, cfg ConfigureOCR3C } return &ConfigureOCR3Resp{ OCR2OracleConfig: r.ocrConfig, - Proposal: r.proposal, + Ops: r.ops, }, nil } @@ -426,7 +423,7 @@ type RegisteredCapability struct { } func FromCapabilitiesRegistryCapability(cap *kcr.CapabilitiesRegistryCapability, e deployment.Environment, registryChainSelector uint64) (*RegisteredCapability, error) { - registry, _, err := GetRegistryContract(&e, registryChainSelector, e.ExistingAddresses) + registry, _, err := GetRegistryContract(&e, registryChainSelector) if err != nil { return nil, fmt.Errorf("failed to get registry: %w", err) } @@ -445,10 +442,14 @@ func RegisterCapabilities(lggr logger.Logger, req RegisterCapabilitiesRequest) ( if len(req.DonToCapabilities) == 0 { return nil, fmt.Errorf("no capabilities to register") } - registry, registryChain, err := GetRegistryContract(req.Env, req.RegistryChainSelector, req.Env.ExistingAddresses) - if err != nil { - return nil, fmt.Errorf("failed to get registry: %w", err) - } + cresp, err := GetContractSets(req.Env.Logger, &GetContractSetsRequest{ + Chains: req.Env.Chains, + AddressBook: req.Env.ExistingAddresses, + }) + contracts := cresp.ContractSets[req.RegistryChainSelector] + registry := contracts.CapabilitiesRegistry + registryChain := req.Env.Chains[req.RegistryChainSelector] + lggr.Infow("registering capabilities...", "len", len(req.DonToCapabilities)) resp := &RegisterCapabilitiesResponse{ DonToCapabilities: make(map[string][]RegisteredCapability), @@ -482,8 +483,12 @@ func RegisterCapabilities(lggr logger.Logger, req RegisterCapabilitiesRequest) ( for cap := range uniqueCaps { capabilities = append(capabilities, cap) } + if len(capabilities) == 0 { + lggr.Warn("no new capabilities to register") + return &RegisterCapabilitiesResponse{}, nil + } // not using mcms; ignore proposals - _, err = AddCapabilities(lggr, registry, registryChain, capabilities, false) + _, err = AddCapabilities(lggr, &contracts, registryChain, capabilities, false) if err != nil { return nil, fmt.Errorf("failed to add capabilities: %w", err) } @@ -501,7 +506,7 @@ type RegisterNOPSResponse struct { } func RegisterNOPS(ctx context.Context, lggr logger.Logger, req RegisterNOPSRequest) (*RegisterNOPSResponse, error) { - registry, registryChain, err := GetRegistryContract(req.Env, req.RegistryChainSelector, req.Env.ExistingAddresses) + registry, registryChain, err := GetRegistryContract(req.Env, req.RegistryChainSelector) if err != nil { return nil, fmt.Errorf("failed to get registry: %w", err) } @@ -640,7 +645,7 @@ type RegisterNodesResponse struct { // can sign the transactions update the contract state // TODO: 467 refactor to support MCMS. Specifically need to separate the call data generation from the actual contract call func RegisterNodes(lggr logger.Logger, req *RegisterNodesRequest) (*RegisterNodesResponse, error) { - registry, registryChain, err := GetRegistryContract(req.Env, req.RegistryChainSelector, req.Env.ExistingAddresses) + registry, registryChain, err := GetRegistryContract(req.Env, req.RegistryChainSelector) if err != nil { return nil, fmt.Errorf("failed to get registry: %w", err) } @@ -808,7 +813,7 @@ func sortedHash(p2pids [][32]byte) string { } func RegisterDons(lggr logger.Logger, req RegisterDonsRequest) (*RegisterDonsResponse, error) { - registry, registryChain, err := GetRegistryContract(req.Env, req.RegistryChainSelector, req.Env.ExistingAddresses) + registry, registryChain, err := GetRegistryContract(req.Env, req.RegistryChainSelector) if err != nil { return nil, fmt.Errorf("failed to get registry: %w", err) } @@ -937,13 +942,13 @@ func containsAllDONs(donInfos []kcr.CapabilitiesRegistryDONInfo, p2pIdsToDon map // configureForwarder sets the config for the forwarder contract on the chain for all Dons that accept workflows // dons that don't accept workflows are not registered with the forwarder -func configureForwarder(lggr logger.Logger, chain deployment.Chain, contractSet ContractSet, dons []RegisteredDon, useMCMS bool) ([]timelock.MCMSWithTimelockProposal, error) { +func configureForwarder(lggr logger.Logger, chain deployment.Chain, contractSet ContractSet, dons []RegisteredDon, useMCMS bool) (map[uint64]timelock.BatchChainOperation, error) { if contractSet.Forwarder == nil { return nil, errors.New("nil forwarder contract") } var ( - fwdr = contractSet.Forwarder - proposals []timelock.MCMSWithTimelockProposal + fwdr = contractSet.Forwarder + opMap = make(map[uint64]timelock.BatchChainOperation) ) for _, dn := range dons { if !dn.Info.AcceptsWorkflows { @@ -978,26 +983,9 @@ func configureForwarder(lggr logger.Logger, chain deployment.Chain, contractSet }, }, } - timelocksPerChain := map[uint64]common.Address{ - chain.Selector: contractSet.Timelock.Address(), - } - proposerMCMSes := map[uint64]*gethwrappers.ManyChainMultiSig{ - chain.Selector: contractSet.ProposerMcm, - } - - proposal, err := proposalutils.BuildProposalFromBatches( - timelocksPerChain, - proposerMCMSes, - []timelock.BatchChainOperation{ops}, - "proposal to set forward config", - 0, - ) - if err != nil { - return nil, fmt.Errorf("failed to build proposal: %w", err) - } - proposals = append(proposals, *proposal) + opMap[chain.Selector] = ops } lggr.Debugw("configured forwarder", "forwarder", fwdr.Address().String(), "donId", dn.Info.Id, "version", ver, "f", dn.Info.F, "signers", signers) } - return proposals, nil + return opMap, nil } diff --git a/deployment/keystone/forwarder_deployer.go b/deployment/keystone/forwarder_deployer.go index d7cfa7991f4..7c7b3a1ed93 100644 --- a/deployment/keystone/forwarder_deployer.go +++ b/deployment/keystone/forwarder_deployer.go @@ -64,7 +64,7 @@ type ConfigureForwarderContractsRequest struct { UseMCMS bool } type ConfigureForwarderContractsResponse struct { - Proposals []timelock.MCMSWithTimelockProposal + OpsPerChain map[uint64]timelock.BatchChainOperation } // Depreciated: use [changeset.ConfigureForwarders] instead @@ -79,7 +79,7 @@ func ConfigureForwardContracts(env *deployment.Environment, req ConfigureForward return nil, fmt.Errorf("failed to get contract sets: %w", err) } - var allProposals []timelock.MCMSWithTimelockProposal + opPerChain := make(map[uint64]timelock.BatchChainOperation) // configure forwarders on all chains for _, chain := range env.Chains { // get the forwarder contract for the chain @@ -87,13 +87,15 @@ func ConfigureForwardContracts(env *deployment.Environment, req ConfigureForward if !ok { return nil, fmt.Errorf("failed to get contract set for chain %d", chain.Selector) } - proposals, err := configureForwarder(env.Logger, chain, contracts, req.Dons, req.UseMCMS) + ops, err := configureForwarder(env.Logger, chain, contracts, req.Dons, req.UseMCMS) if err != nil { return nil, fmt.Errorf("failed to configure forwarder for chain selector %d: %w", chain.Selector, err) } - allProposals = append(allProposals, proposals...) + for k, op := range ops { + opPerChain[k] = op + } } return &ConfigureForwarderContractsResponse{ - Proposals: allProposals, + OpsPerChain: opPerChain, }, nil } diff --git a/deployment/keystone/ocr3config.go b/deployment/keystone/ocr3config.go index ccd738636ed..d80c4930df4 100644 --- a/deployment/keystone/ocr3config.go +++ b/deployment/keystone/ocr3config.go @@ -18,12 +18,10 @@ import ( "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3confighelper" "github.com/smartcontractkit/libocr/offchainreporting2plus/types" - "github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers" "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/mcms" "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/timelock" "github.com/smartcontractkit/chainlink/deployment" - "github.com/smartcontractkit/chainlink/deployment/common/proposalutils" kocr3 "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/ocr3_capability" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/chaintype" "github.com/smartcontractkit/chainlink/v2/core/services/ocrcommon" @@ -300,7 +298,7 @@ func (r configureOCR3Request) generateOCR3Config() (OCR2OracleConfig, error) { type configureOCR3Response struct { ocrConfig OCR2OracleConfig - proposal *timelock.MCMSWithTimelockProposal + ops *timelock.BatchChainOperation } func configureOCR3contract(req configureOCR3Request) (*configureOCR3Response, error) { @@ -330,10 +328,10 @@ func configureOCR3contract(req configureOCR3Request) (*configureOCR3Response, er ) if err != nil { err = DecodeErr(kocr3.OCR3CapabilityABI, err) - return nil, fmt.Errorf("failed to call SetConfig for OCR3 contract %s using mcms: %T: %w", req.contract.Address().String(), req.useMCMS, err) + return nil, fmt.Errorf("failed to call SetConfig for OCR3 contract %s using mcms: %t: %w", req.contract.Address().String(), req.useMCMS, err) } - var proposal *timelock.MCMSWithTimelockProposal + var ops *timelock.BatchChainOperation if !req.useMCMS { _, err = req.chain.Confirm(tx) if err != nil { @@ -341,7 +339,7 @@ func configureOCR3contract(req configureOCR3Request) (*configureOCR3Response, er return nil, fmt.Errorf("failed to confirm SetConfig for OCR3 contract %s: %w", req.contract.Address().String(), err) } } else { - ops := timelock.BatchChainOperation{ + ops = &timelock.BatchChainOperation{ ChainIdentifier: mcms.ChainIdentifier(req.chain.Selector), Batch: []mcms.Operation{ { @@ -351,24 +349,7 @@ func configureOCR3contract(req configureOCR3Request) (*configureOCR3Response, er }, }, } - timelocksPerChain := map[uint64]common.Address{ - req.chain.Selector: req.contractSet.Timelock.Address(), - } - proposerMCMSes := map[uint64]*gethwrappers.ManyChainMultiSig{ - req.chain.Selector: req.contractSet.ProposerMcm, - } - - proposal, err = proposalutils.BuildProposalFromBatches( - timelocksPerChain, - proposerMCMSes, - []timelock.BatchChainOperation{ops}, - "proposal to set ocr3 config", - 0, - ) - if err != nil { - return nil, fmt.Errorf("failed to build proposal: %w", err) - } } - return &configureOCR3Response{ocrConfig, proposal}, nil + return &configureOCR3Response{ocrConfig, ops}, nil } diff --git a/deployment/keystone/state.go b/deployment/keystone/state.go index cbf449c7f31..0ac7cdc89ed 100644 --- a/deployment/keystone/state.go +++ b/deployment/keystone/state.go @@ -78,7 +78,7 @@ func GetContractSets(lggr logger.Logger, req *GetContractSetsRequest) (*GetContr func loadContractSet(lggr logger.Logger, chain deployment.Chain, addresses map[string]deployment.TypeAndVersion) (*ContractSet, error) { var out ContractSet - mcmsWithTimelock, err := commonchangeset.MaybeLoadMCMSWithTimelockState(chain, addresses) + mcmsWithTimelock, err := commonchangeset.MaybeLoadMCMSWithTimelockChainState(chain, addresses) if err != nil { return nil, fmt.Errorf("failed to load mcms contract: %w", err) } diff --git a/deployment/solana_chain.go b/deployment/solana_chain.go new file mode 100644 index 00000000000..338642e3e32 --- /dev/null +++ b/deployment/solana_chain.go @@ -0,0 +1,5 @@ +package deployment + +// SolChain represents a Solana chain. +type SolChain struct { +} diff --git a/docs/CONFIG.md b/docs/CONFIG.md index 52276f027bc..946703695eb 100644 --- a/docs/CONFIG.md +++ b/docs/CONFIG.md @@ -5902,6 +5902,214 @@ GasLimitDefault = 400000

+
Ronin Mainnet (2020)

+ +```toml +AutoCreateKey = true +BlockBackfillDepth = 10 +BlockBackfillSkip = false +FinalityDepth = 50 +FinalityTagEnabled = true +LinkContractAddress = '0x3902228D6A3d2Dc44731fD9d45FeE6a61c722D0b' +LogBackfillBatchSize = 1000 +LogPollInterval = '3s' +LogKeepBlocksDepth = 100000 +LogPrunePageSize = 0 +BackupLogPollerBlockDelay = 100 +MinIncomingConfirmations = 3 +MinContractPayment = '0.00001 link' +NonceAutoSync = true +NoNewHeadsThreshold = '3m0s' +LogBroadcasterEnabled = true +RPCDefaultBatchSize = 250 +RPCBlockQueryDelay = 1 +FinalizedBlockOffset = 0 +NoNewFinalizedHeadsThreshold = '0s' + +[Transactions] +ForwardersEnabled = false +MaxInFlight = 16 +MaxQueued = 250 +ReaperInterval = '1h0m0s' +ReaperThreshold = '168h0m0s' +ResendAfterThreshold = '1m0s' + +[Transactions.AutoPurge] +Enabled = false + +[BalanceMonitor] +Enabled = true + +[GasEstimator] +Mode = 'FeeHistory' +PriceDefault = '20 gwei' +PriceMax = '1 micro' +PriceMin = '1 gwei' +LimitDefault = 500000 +LimitMax = 500000 +LimitMultiplier = '1' +LimitTransfer = 21000 +EstimateLimit = false +BumpMin = '5 gwei' +BumpPercent = 20 +BumpThreshold = 3 +EIP1559DynamicFees = false +FeeCapDefault = '100 gwei' +TipCapDefault = '1 wei' +TipCapMin = '1 wei' + +[GasEstimator.BlockHistory] +BatchSize = 25 +BlockHistorySize = 8 +CheckInclusionBlocks = 12 +CheckInclusionPercentile = 90 +TransactionPercentile = 60 + +[GasEstimator.FeeHistory] +CacheTimeout = '10s' + +[HeadTracker] +HistoryDepth = 100 +MaxBufferSize = 3 +SamplingInterval = '1s' +MaxAllowedFinalityDepth = 10000 +FinalityTagBypass = true +PersistenceEnabled = true + +[NodePool] +PollFailureThreshold = 5 +PollInterval = '10s' +SelectionMode = 'HighestHead' +SyncThreshold = 5 +LeaseDuration = '0s' +NodeIsSyncingEnabled = false +FinalizedBlockPollInterval = '5s' +EnforceRepeatableRead = true +DeathDeclarationDelay = '1m0s' +NewHeadsPollInterval = '0s' + +[OCR] +ContractConfirmations = 4 +ContractTransmitterTransmitTimeout = '10s' +DatabaseTimeout = '10s' +DeltaCOverride = '168h0m0s' +DeltaCJitterOverride = '1h0m0s' +ObservationGracePeriod = '1s' + +[OCR2] +[OCR2.Automation] +GasLimit = 5400000 + +[Workflow] +GasLimitDefault = 400000 +``` + +

+ +
Ronin Saigon (2021)

+ +```toml +AutoCreateKey = true +BlockBackfillDepth = 10 +BlockBackfillSkip = false +FinalityDepth = 50 +FinalityTagEnabled = true +LinkContractAddress = '0x5bB50A6888ee6a67E22afFDFD9513be7740F1c15' +LogBackfillBatchSize = 1000 +LogPollInterval = '3s' +LogKeepBlocksDepth = 100000 +LogPrunePageSize = 0 +BackupLogPollerBlockDelay = 100 +MinIncomingConfirmations = 3 +MinContractPayment = '0.00001 link' +NonceAutoSync = true +NoNewHeadsThreshold = '3m0s' +LogBroadcasterEnabled = true +RPCDefaultBatchSize = 250 +RPCBlockQueryDelay = 1 +FinalizedBlockOffset = 0 +NoNewFinalizedHeadsThreshold = '0s' + +[Transactions] +ForwardersEnabled = false +MaxInFlight = 16 +MaxQueued = 250 +ReaperInterval = '1h0m0s' +ReaperThreshold = '168h0m0s' +ResendAfterThreshold = '1m0s' + +[Transactions.AutoPurge] +Enabled = false + +[BalanceMonitor] +Enabled = true + +[GasEstimator] +Mode = 'FeeHistory' +PriceDefault = '20 gwei' +PriceMax = '1 micro' +PriceMin = '1 gwei' +LimitDefault = 500000 +LimitMax = 500000 +LimitMultiplier = '1' +LimitTransfer = 21000 +EstimateLimit = false +BumpMin = '5 gwei' +BumpPercent = 20 +BumpThreshold = 3 +EIP1559DynamicFees = false +FeeCapDefault = '100 gwei' +TipCapDefault = '1 wei' +TipCapMin = '1 wei' + +[GasEstimator.BlockHistory] +BatchSize = 25 +BlockHistorySize = 8 +CheckInclusionBlocks = 12 +CheckInclusionPercentile = 90 +TransactionPercentile = 60 + +[GasEstimator.FeeHistory] +CacheTimeout = '10s' + +[HeadTracker] +HistoryDepth = 100 +MaxBufferSize = 3 +SamplingInterval = '1s' +MaxAllowedFinalityDepth = 10000 +FinalityTagBypass = true +PersistenceEnabled = true + +[NodePool] +PollFailureThreshold = 5 +PollInterval = '10s' +SelectionMode = 'HighestHead' +SyncThreshold = 5 +LeaseDuration = '0s' +NodeIsSyncingEnabled = false +FinalizedBlockPollInterval = '5s' +EnforceRepeatableRead = true +DeathDeclarationDelay = '1m0s' +NewHeadsPollInterval = '0s' + +[OCR] +ContractConfirmations = 4 +ContractTransmitterTransmitTimeout = '10s' +DatabaseTimeout = '10s' +DeltaCOverride = '168h0m0s' +DeltaCJitterOverride = '1h0m0s' +ObservationGracePeriod = '1s' + +[OCR2] +[OCR2.Automation] +GasLimit = 5400000 + +[Workflow] +GasLimitDefault = 400000 +``` + +

+
Kroma Sepolia (2358)

```toml diff --git a/go.mod b/go.mod index 2dd7d3fcfe5..1767da64153 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/NethermindEth/juno v0.3.1 github.com/NethermindEth/starknet.go v0.7.1-0.20240401080518-34a506f3cfdb github.com/XSAM/otelsql v0.27.0 - github.com/andybalholm/brotli v1.1.0 + github.com/andybalholm/brotli v1.1.1 github.com/avast/retry-go/v4 v4.6.0 github.com/btcsuite/btcd/btcec/v2 v2.3.4 github.com/cometbft/cometbft v0.37.5 @@ -25,7 +25,7 @@ require ( github.com/fxamacker/cbor/v2 v2.7.0 github.com/gagliardetto/solana-go v1.8.4 github.com/getsentry/sentry-go v0.27.0 - github.com/gin-contrib/cors v1.5.0 + github.com/gin-contrib/cors v1.7.2 github.com/gin-contrib/expvar v0.0.1 github.com/gin-contrib/sessions v0.0.5 github.com/gin-contrib/size v0.0.0-20230212012657-e14a14094dc4 @@ -78,13 +78,13 @@ require ( github.com/shopspring/decimal v1.4.0 github.com/smartcontractkit/chain-selectors v1.0.34 github.com/smartcontractkit/chainlink-automation v0.8.1 - github.com/smartcontractkit/chainlink-ccip v0.0.0-20241204015713-8956bb614e9e - github.com/smartcontractkit/chainlink-common v0.3.1-0.20241209151352-70300ddcc776 + github.com/smartcontractkit/chainlink-ccip v0.0.0-20241213122413-5e8f65dd6b1b + github.com/smartcontractkit/chainlink-common v0.3.1-0.20241214155818-b403079b2805 github.com/smartcontractkit/chainlink-cosmos v0.5.2-0.20241202195413-82468150ac1e - github.com/smartcontractkit/chainlink-data-streams v0.1.1-0.20241202141438-a90db35252db + github.com/smartcontractkit/chainlink-data-streams v0.1.1-0.20241216163550-fa030d178ba3 github.com/smartcontractkit/chainlink-feeds v0.1.1 - github.com/smartcontractkit/chainlink-protos/orchestrator v0.3.2 - github.com/smartcontractkit/chainlink-solana v1.1.1-0.20241204153209-c3a71b0eef99 + github.com/smartcontractkit/chainlink-protos/orchestrator v0.4.0 + github.com/smartcontractkit/chainlink-solana v1.1.1-0.20241210172617-6fd1891d0fbc github.com/smartcontractkit/chainlink-starknet/relayer v0.1.1-0.20241202202529-2033490e77b8 github.com/smartcontractkit/libocr v0.0.0-20241007185508-adbe57025f12 github.com/smartcontractkit/tdh2/go/ocr2/decryptionplugin v0.0.0-20241009055228-33d0c0bf38de @@ -110,12 +110,12 @@ require ( go.opentelemetry.io/otel/trace v1.31.0 go.uber.org/multierr v1.11.0 go.uber.org/zap v1.27.0 - golang.org/x/crypto v0.28.0 + golang.org/x/crypto v0.31.0 golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c golang.org/x/mod v0.21.0 - golang.org/x/sync v0.8.0 - golang.org/x/term v0.25.0 - golang.org/x/text v0.19.0 + golang.org/x/sync v0.10.0 + golang.org/x/term v0.27.0 + golang.org/x/text v0.21.0 golang.org/x/time v0.7.0 golang.org/x/tools v0.26.0 gonum.org/v1/gonum v0.15.1 @@ -148,6 +148,7 @@ require ( github.com/Masterminds/goutils v1.1.1 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect github.com/VictoriaMetrics/fastcache v1.12.2 // indirect + github.com/apache/arrow-go/v18 v18.0.0 // indirect github.com/armon/go-metrics v0.4.1 // indirect github.com/atombender/go-jsonschema v0.16.1-0.20240916205339-a74cd4e2851c // indirect github.com/bahlo/generic-list-go v0.2.0 // indirect @@ -158,13 +159,14 @@ require ( github.com/blendle/zapdriver v1.3.1 // indirect github.com/buger/jsonparser v1.1.1 // indirect github.com/bytecodealliance/wasmtime-go/v23 v23.0.0 // indirect - github.com/bytedance/sonic v1.10.1 // indirect + github.com/bytedance/sonic v1.11.6 // indirect + github.com/bytedance/sonic/loader v0.1.1 // indirect github.com/cenkalti/backoff v2.2.1+incompatible // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cespare/xxhash v1.1.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect - github.com/chenzhuoyu/iasm v0.9.0 // indirect + github.com/cloudwego/base64x v0.1.4 // indirect + github.com/cloudwego/iasm v0.2.0 // indirect github.com/cockroachdb/errors v1.11.3 // indirect github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce // indirect github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect @@ -219,7 +221,7 @@ require ( github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.22.0 // indirect github.com/go-webauthn/x v0.1.5 // indirect - github.com/goccy/go-json v0.10.2 // indirect + github.com/goccy/go-json v0.10.3 // indirect github.com/goccy/go-yaml v1.12.0 // indirect github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect github.com/gofrs/flock v0.8.1 // indirect @@ -232,6 +234,7 @@ require ( github.com/golang/protobuf v1.5.4 // indirect github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb // indirect github.com/google/btree v1.1.2 // indirect + github.com/google/flatbuffers v24.3.25+incompatible // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/google/go-tpm v0.9.0 // indirect github.com/google/gofuzz v1.2.0 // indirect @@ -269,8 +272,8 @@ require ( github.com/jackpal/go-nat-pmp v1.0.2 // indirect github.com/jmhodges/levigo v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/compress v1.17.9 // indirect - github.com/klauspost/cpuid/v2 v2.2.5 // indirect + github.com/klauspost/compress v1.17.11 // indirect + github.com/klauspost/cpuid/v2 v2.2.8 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect @@ -279,6 +282,7 @@ require ( github.com/logrusorgru/aurora v2.0.3+incompatible // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect + github.com/marcboeker/go-duckdb v1.8.3 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.14 // indirect @@ -302,6 +306,7 @@ require ( github.com/opencontainers/runc v1.1.10 // indirect github.com/opentracing/opentracing-go v1.2.0 // indirect github.com/petermattis/goid v0.0.0-20230317030725-371a4b8eda08 // indirect + github.com/pierrec/lz4/v4 v4.1.21 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect github.com/prometheus/procfs v0.15.1 // indirect @@ -343,6 +348,7 @@ require ( github.com/x448/float16 v0.8.4 // indirect github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect + github.com/zeebo/xxh3 v1.0.2 // indirect github.com/zondax/hid v0.9.2 // indirect github.com/zondax/ledger-go v0.14.3 // indirect go.dedis.ch/protobuf v1.0.11 // indirect @@ -369,7 +375,7 @@ require ( go.uber.org/ratelimit v0.3.1 // indirect golang.org/x/arch v0.11.0 // indirect golang.org/x/net v0.30.0 // indirect - golang.org/x/sys v0.26.0 // indirect + golang.org/x/sys v0.28.0 // indirect golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect google.golang.org/api v0.202.0 // indirect google.golang.org/genproto v0.0.0-20241021214115-324edc3d5d38 // indirect diff --git a/go.sum b/go.sum index b8941bc7d01..8c7327b4997 100644 --- a/go.sum +++ b/go.sum @@ -144,9 +144,13 @@ github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax github.com/allegro/bigcache v1.2.1 h1:hg1sY1raCwic3Vnsvje6TT7/pnZba83LeFck5NrFKSc= github.com/allegro/bigcache v1.2.1/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129/go.mod h1:rFgpPQZYZ8vdbc+48xibu8ALc3yeyd64IhHS+PU6Yyg= -github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= -github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= +github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA= +github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/apache/arrow-go/v18 v18.0.0 h1:1dBDaSbH3LtulTyOVYaBCHO3yVRwjV+TZaqn3g6V7ZM= +github.com/apache/arrow-go/v18 v18.0.0/go.mod h1:t6+cWRSmKgdQ6HsxisQjok+jBpKGhRDiqcf3p0p/F+A= +github.com/apache/thrift v0.21.0 h1:tdPmh/ptjE1IJnhbhrcl2++TauVjy242rkV/UzJChnE= +github.com/apache/thrift v0.21.0/go.mod h1:W1H8aR/QRtYNvrPeFXBtobyRkd0/YVhTc6i07XIAgDw= github.com/appleboy/gofight/v2 v2.1.2 h1:VOy3jow4vIK8BRQJoC/I9muxyYlJ2yb9ht2hZoS3rf4= github.com/appleboy/gofight/v2 v2.1.2/go.mod h1:frW+U1QZEdDgixycTj4CygQ48yLTUhplt43+Wczp3rw= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= @@ -196,10 +200,10 @@ github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMU github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/bytecodealliance/wasmtime-go/v23 v23.0.0 h1:NJvU4S8KEk1GnF6+FvlnzMD/8wXTj/mYJSG6Q4yu3Pw= github.com/bytecodealliance/wasmtime-go/v23 v23.0.0/go.mod h1:5YIL+Ouiww2zpO7u+iZ1U1G5NvmwQYaXdmCZQGjQM0U= -github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= -github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM= -github.com/bytedance/sonic v1.10.1 h1:7a1wuFXL1cMy7a3f7/VFcEtriuXQnUBhtoVfOZiaysc= -github.com/bytedance/sonic v1.10.1/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4= +github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0= +github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4= +github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM= +github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= @@ -215,12 +219,6 @@ github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= -github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= -github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0= -github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA= -github.com/chenzhuoyu/iasm v0.9.0 h1:9fhXjVzq5hUy2gkhhgHl95zG2cEAhw9OSGs8toWWAwo= -github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/readline v1.5.1 h1:upd/6fQk4src78LMRzh5vItIt361/o4uq553V8B5sGI= @@ -229,6 +227,10 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= +github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= +github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= +github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= @@ -411,8 +413,8 @@ github.com/gedex/inflector v0.0.0-20170307190818-16278e9db813/go.mod h1:P+oSoE9y github.com/getsentry/sentry-go v0.27.0 h1:Pv98CIbtB3LkMWmXi4Joa5OOcwbmnX88sF5qbK3r3Ps= github.com/getsentry/sentry-go v0.27.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/gin-contrib/cors v1.5.0 h1:DgGKV7DDoOn36DFkNtbHrjoRiT5ExCe+PC9/xp7aKvk= -github.com/gin-contrib/cors v1.5.0/go.mod h1:TvU7MAZ3EwrPLI2ztzTt3tqgvBCq+wn8WpZmfADjupI= +github.com/gin-contrib/cors v1.7.2 h1:oLDHxdg8W/XDoN/8zamqk/Drgt4oVZDvaV0YmvVICQw= +github.com/gin-contrib/cors v1.7.2/go.mod h1:SUJVARKgQ40dmrzgXEVxj2m7Ig1v1qIboQkPDTQ9t2E= github.com/gin-contrib/expvar v0.0.1 h1:IuU5ArEgihz50vG8Onrwz22kJr7Mcvgv9xSSpfU5g+w= github.com/gin-contrib/expvar v0.0.1/go.mod h1:8o2CznfQi1JjktORdHr2/abg3wSV6OCnXh0yGypvvVw= github.com/gin-contrib/sessions v0.0.5 h1:CATtfHmLMQrMNpJRgzjWXD7worTh7g7ritsQfmF+0jE= @@ -484,8 +486,8 @@ github.com/go-webauthn/webauthn v0.9.4/go.mod h1:LqupCtzSef38FcxzaklmOn7AykGKhAh github.com/go-webauthn/x v0.1.5 h1:V2TCzDU2TGLd0kSZOXdrqDVV5JB9ILnKxA9S53CSBw0= github.com/go-webauthn/x v0.1.5/go.mod h1:qbzWwcFcv4rTwtCLOZd+icnr6B7oSsAGZJqlt8cukqY= github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= -github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= -github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= +github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/goccy/go-yaml v1.12.0 h1:/1WHjnMsI1dlIBQutrvSMGZRQufVO3asrHfTwfACoPM= github.com/goccy/go-yaml v1.12.0/go.mod h1:wKnAMd44+9JAAnGQpWVEgBzGt3YuTaQ4uXoHvE4m7WU= github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 h1:ZpnhV/YsD2/4cESfV5+Hoeu/iUR3ruzNvZ+yQfO03a0= @@ -550,6 +552,8 @@ github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Z github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= +github.com/google/flatbuffers v24.3.25+incompatible h1:CX395cjN9Kke9mmalRoL3d81AtFUxJM+yDthflgJGkI= +github.com/google/flatbuffers v24.3.25+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -814,14 +818,16 @@ github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfV github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/asmfmt v1.3.2 h1:4Ri7ox3EwapiOjCki+hw14RyKk201CN4rzyCJRFLpK4= +github.com/klauspost/asmfmt v1.3.2/go.mod h1:AG8TuvYojzulgDAMCnYn50l/5QV3Bs/tp6j0HLHbNSE= github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= -github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= -github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= +github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= -github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM= +github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -868,6 +874,8 @@ github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYt github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg= github.com/manyminds/api2go v0.0.0-20171030193247-e7b693844a6f h1:tVvGiZQFjOXP+9YyGqSA6jE55x1XVxmoPYudncxrZ8U= github.com/manyminds/api2go v0.0.0-20171030193247-e7b693844a6f/go.mod h1:Z60vy0EZVSu0bOugCHdcN5ZxFMKSpjRgsnh0XKPFqqk= +github.com/marcboeker/go-duckdb v1.8.3 h1:ZkYwiIZhbYsT6MmJsZ3UPTHrTZccDdM4ztoqSlEMXiQ= +github.com/marcboeker/go-duckdb v1.8.3/go.mod h1:C9bYRE1dPYb1hhfu/SSomm78B0FXmNgRvv6YBW/Hooc= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= @@ -900,6 +908,10 @@ github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3N github.com/mimoo/StrobeGo v0.0.0-20181016162300-f8f6d4d2b643/go.mod h1:43+3pMjjKimDBf5Kr4ZFNGbLql1zKkbImw+fZbw3geM= github.com/mimoo/StrobeGo v0.0.0-20210601165009-122bf33a46e0 h1:QRUSJEgZn2Snx0EmT/QLXibWjSUDjKWvXIT19NBVp94= github.com/mimoo/StrobeGo v0.0.0-20210601165009-122bf33a46e0/go.mod h1:43+3pMjjKimDBf5Kr4ZFNGbLql1zKkbImw+fZbw3geM= +github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8 h1:AMFGa4R4MiIpspGNG7Z948v4n35fFGB3RR3G/ry4FWs= +github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8/go.mod h1:mC1jAcsrzbxHt8iiaC+zU4b1ylILSosueou12R++wfY= +github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3 h1:+n/aFZefKZp7spd8DFdX7uMikMLXX4oubIzJF4kv/wI= +github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3/go.mod h1:RagcQ7I8IeTMnF8JTXieKnO4Z6JCsikNEzj0DwauVzE= github.com/minio/highwayhash v1.0.2 h1:Aak5U0nElisjDCfPSG79Tgzkn2gl66NxOMspRrKnA/g= github.com/minio/highwayhash v1.0.2/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= @@ -1002,6 +1014,8 @@ github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xl github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5/go.mod h1:jvVRKCrJTQWu0XVbaOlby/2lO20uSCHEMzzplHXte1o= github.com/petermattis/goid v0.0.0-20230317030725-371a4b8eda08 h1:hDSdbBuw3Lefr6R18ax0tZ2BJeNB3NehB3trOwYBsdU= github.com/petermattis/goid v0.0.0-20230317030725-371a4b8eda08/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= +github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= +github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= @@ -1123,20 +1137,20 @@ github.com/smartcontractkit/chain-selectors v1.0.34 h1:MJ17OGu8+jjl426pcKrJkCf3f github.com/smartcontractkit/chain-selectors v1.0.34/go.mod h1:xsKM0aN3YGcQKTPRPDDtPx2l4mlTN1Djmg0VVXV40b8= github.com/smartcontractkit/chainlink-automation v0.8.1 h1:sTc9LKpBvcKPc1JDYAmgBc2xpDKBco/Q4h4ydl6+UUU= github.com/smartcontractkit/chainlink-automation v0.8.1/go.mod h1:Iij36PvWZ6blrdC5A/nrQUBuf3MH3JvsBB9sSyc9W08= -github.com/smartcontractkit/chainlink-ccip v0.0.0-20241204015713-8956bb614e9e h1:GnM6ZWV6vlk2+n6c6o+v/R1LtXzBGVVx7r37nt/h6Uc= -github.com/smartcontractkit/chainlink-ccip v0.0.0-20241204015713-8956bb614e9e/go.mod h1:80vGBbOfertJig0xFKsRfm+i17FkjdKkk1dAaGE45Os= -github.com/smartcontractkit/chainlink-common v0.3.1-0.20241209151352-70300ddcc776 h1:NATQA1LfrEPXCdtEed9/G4SxaVuF8EZp5O2ucOK5C98= -github.com/smartcontractkit/chainlink-common v0.3.1-0.20241209151352-70300ddcc776/go.mod h1:bQktEJf7sJ0U3SmIcXvbGUox7SmXcnSEZ4kUbT8R5Nk= +github.com/smartcontractkit/chainlink-ccip v0.0.0-20241213122413-5e8f65dd6b1b h1:iSQJ6ng4FhEswf8SXunGkaJlVP3E3JlgLB8Oo2f3Ud4= +github.com/smartcontractkit/chainlink-ccip v0.0.0-20241213122413-5e8f65dd6b1b/go.mod h1:F8xQAIW0ymb2BZhqn89sWZLXreJhM5KDVF6Qb4y44N0= +github.com/smartcontractkit/chainlink-common v0.3.1-0.20241214155818-b403079b2805 h1:Pz8jB/6qe10xT10h2S3LFYJrnebNpG5rJ/w16HZGwPQ= +github.com/smartcontractkit/chainlink-common v0.3.1-0.20241214155818-b403079b2805/go.mod h1:yti7e1+G9hhkYhj+L5sVUULn9Bn3bBL5/AxaNqdJ5YQ= github.com/smartcontractkit/chainlink-cosmos v0.5.2-0.20241202195413-82468150ac1e h1:PRoeby6ZlTuTkv2f+7tVU4+zboTfRzI+beECynF4JQ0= github.com/smartcontractkit/chainlink-cosmos v0.5.2-0.20241202195413-82468150ac1e/go.mod h1:mUh5/woemsVaHgTorA080hrYmO3syBCmPdnWc/5dOqk= -github.com/smartcontractkit/chainlink-data-streams v0.1.1-0.20241202141438-a90db35252db h1:N1RH1hSr2ACzOFc9hkCcjE8pRBTdcU3p8nsTJByaLes= -github.com/smartcontractkit/chainlink-data-streams v0.1.1-0.20241202141438-a90db35252db/go.mod h1:yjb9d4q7+m8aGbjfTbkNoNuA4PeSxcUszsSZHDrvS0E= +github.com/smartcontractkit/chainlink-data-streams v0.1.1-0.20241216163550-fa030d178ba3 h1:aeiBdBHGY8QNftps+VqrIk6OnfeeOD5z4jrAabW4ZSc= +github.com/smartcontractkit/chainlink-data-streams v0.1.1-0.20241216163550-fa030d178ba3/go.mod h1:AS6zY2BkcRwfiGzNabGbHhfrLSrXrcI/GmjnT4jQ5/s= 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.2 h1:onBe3DqNrbtOAzKS4PrPIiJX65BGo1aYiYZxFVEW+jc= -github.com/smartcontractkit/chainlink-protos/orchestrator v0.3.2/go.mod h1:m/A3lqD7ms/RsQ9BT5P2uceYY0QX5mIt4KQxT2G6qEo= -github.com/smartcontractkit/chainlink-solana v1.1.1-0.20241204153209-c3a71b0eef99 h1:lvn9Yxah+QD1/PcgijLO0dNRa28HuQWZl8Kkxh46KJc= -github.com/smartcontractkit/chainlink-solana v1.1.1-0.20241204153209-c3a71b0eef99/go.mod h1:p8aUDfJeley6oer7y+Ucd3edOtRlMTnWg3mN6rhaLWo= +github.com/smartcontractkit/chainlink-protos/orchestrator v0.4.0 h1:ZBat8EBvE2LpSQR9U1gEbRV6PfAkiFdINmQ8nVnXIAQ= +github.com/smartcontractkit/chainlink-protos/orchestrator v0.4.0/go.mod h1:m/A3lqD7ms/RsQ9BT5P2uceYY0QX5mIt4KQxT2G6qEo= +github.com/smartcontractkit/chainlink-solana v1.1.1-0.20241210172617-6fd1891d0fbc h1:dssRwJhmzJkUN/OajaDj2GsxBn+Tupk3bI1BkPEoJg0= +github.com/smartcontractkit/chainlink-solana v1.1.1-0.20241210172617-6fd1891d0fbc/go.mod h1:p8aUDfJeley6oer7y+Ucd3edOtRlMTnWg3mN6rhaLWo= github.com/smartcontractkit/chainlink-starknet/relayer v0.1.1-0.20241202202529-2033490e77b8 h1:tNS7U9lrxkFvEuyxQv11HHOiV9LPDGC9wYEy+yM/Jv4= github.com/smartcontractkit/chainlink-starknet/relayer v0.1.1-0.20241202202529-2033490e77b8/go.mod h1:EBrEgcdIbwepqguClkv8Ohy7CbyWSJaE4EC9aBJlQK0= github.com/smartcontractkit/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7 h1:12ijqMM9tvYVEm+nR826WsrNi6zCKpwBhuApq127wHs= @@ -1280,6 +1294,8 @@ github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= +github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= +github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -1289,6 +1305,10 @@ github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1 github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ= +github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= +github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0= +github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= github.com/zondax/hid v0.9.2 h1:WCJFnEDMiqGF64nlZz28E9qLVZ0KSJ7xpc5DLEyma2U= github.com/zondax/hid v0.9.2/go.mod h1:l5wttcP0jwtdLjqjMMWFVEE7d1zO0jvSPA9OPZxWpEM= @@ -1425,8 +1445,8 @@ golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0 golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= -golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= -golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= +golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= +golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -1551,8 +1571,8 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1636,8 +1656,8 @@ golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= -golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -1647,8 +1667,8 @@ golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= -golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= -golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= +golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= +golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1663,8 +1683,8 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= -golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= diff --git a/integration-tests/ccip-tests/testconfig/README.md b/integration-tests/ccip-tests/testconfig/README.md index ff57ecaa220..d614ed62ea4 100644 --- a/integration-tests/ccip-tests/testconfig/README.md +++ b/integration-tests/ccip-tests/testconfig/README.md @@ -430,32 +430,6 @@ Example usage: TTL = "11h" ``` -### CCIP.Env.Logging - -Specifies the logging configuration for the test. Imported from [LoggingConfig](https://github.com/smartcontractkit/chainlink-testing-framework/blob/main/config/logging.go#L11) in chainlink-testing-framework. -Example usage: - -```toml -[CCIP.Env.Logging] -test_log_collect = false # if set to true will save logs even if test did not fail - -[CCIP.Env.Logging.LogStream] -# supported targets: file, loki, in-memory. if empty no logs will be persistet -log_targets = ["file"] -# context timeout for starting log producer and also time-frame for requesting logs -log_producer_timeout = "10s" -# number of retries before log producer gives up and stops listening to logs -log_producer_retry_limit = 10 - -[CCIP.Env.Logging.Loki] -tenant_id = "..." -endpoint = "https://loki...." - -[CCIP.Env.Logging.Grafana] -base_url = "https://grafana..../" -dashboard_url = "/d/6vjVx-1V8/ccip-long-running-tests" -``` - ### CCIP.Env.Lane.LeaderLaneEnabled Specifies whether to enable the leader lane feature. This setting is only applicable for new deployments. diff --git a/integration-tests/ccip-tests/testconfig/global.go b/integration-tests/ccip-tests/testconfig/global.go index 4caa8a9ac00..8866d31705a 100644 --- a/integration-tests/ccip-tests/testconfig/global.go +++ b/integration-tests/ccip-tests/testconfig/global.go @@ -175,120 +175,6 @@ type Common struct { func (p *Common) ReadFromEnvVar() error { logger := logging.GetTestLogger(nil) - testLogCollect := ctfconfig.MustReadEnvVar_Boolean(ctfconfig.E2E_TEST_LOG_COLLECT_ENV) - if testLogCollect != nil { - if p.Logging == nil { - p.Logging = &ctfconfig.LoggingConfig{} - } - logger.Debug().Msgf("Using %s env var to override Logging.TestLogCollect", ctfconfig.E2E_TEST_LOG_COLLECT_ENV) - p.Logging.TestLogCollect = testLogCollect - } - - loggingRunID := ctfconfig.MustReadEnvVar_String(ctfconfig.E2E_TEST_LOGGING_RUN_ID_ENV) - if loggingRunID != "" { - if p.Logging == nil { - p.Logging = &ctfconfig.LoggingConfig{} - } - logger.Debug().Msgf("Using %s env var to override Logging.RunID", ctfconfig.E2E_TEST_LOGGING_RUN_ID_ENV) - p.Logging.RunId = &loggingRunID - } - - logstreamLogTargets := ctfconfig.MustReadEnvVar_Strings(ctfconfig.E2E_TEST_LOG_STREAM_LOG_TARGETS_ENV, ",") - if len(logstreamLogTargets) > 0 { - if p.Logging == nil { - p.Logging = &ctfconfig.LoggingConfig{} - } - if p.Logging.LogStream == nil { - p.Logging.LogStream = &ctfconfig.LogStreamConfig{} - } - logger.Debug().Msgf("Using %s env var to override Logging.LogStream.LogTargets", ctfconfig.E2E_TEST_LOG_STREAM_LOG_TARGETS_ENV) - p.Logging.LogStream.LogTargets = logstreamLogTargets - } - - lokiTenantID := ctfconfig.MustReadEnvVar_String(ctfconfig.E2E_TEST_LOKI_TENANT_ID_ENV) - if lokiTenantID != "" { - if p.Logging == nil { - p.Logging = &ctfconfig.LoggingConfig{} - } - if p.Logging.Loki == nil { - p.Logging.Loki = &ctfconfig.LokiConfig{} - } - logger.Debug().Msgf("Using %s env var to override Logging.Loki.TenantId", ctfconfig.E2E_TEST_LOKI_TENANT_ID_ENV) - p.Logging.Loki.TenantId = &lokiTenantID - } - - lokiEndpoint := ctfconfig.MustReadEnvVar_String(ctfconfig.E2E_TEST_LOKI_ENDPOINT_ENV) - if lokiEndpoint != "" { - if p.Logging == nil { - p.Logging = &ctfconfig.LoggingConfig{} - } - if p.Logging.Loki == nil { - p.Logging.Loki = &ctfconfig.LokiConfig{} - } - logger.Debug().Msgf("Using %s env var to override Logging.Loki.Endpoint", ctfconfig.E2E_TEST_LOKI_ENDPOINT_ENV) - p.Logging.Loki.Endpoint = &lokiEndpoint - } - - lokiBasicAuth := ctfconfig.MustReadEnvVar_String(ctfconfig.E2E_TEST_LOKI_BASIC_AUTH_ENV) - if lokiBasicAuth != "" { - if p.Logging == nil { - p.Logging = &ctfconfig.LoggingConfig{} - } - if p.Logging.Loki == nil { - p.Logging.Loki = &ctfconfig.LokiConfig{} - } - logger.Debug().Msgf("Using %s env var to override Logging.Loki.BasicAuth", ctfconfig.E2E_TEST_LOKI_BASIC_AUTH_ENV) - p.Logging.Loki.BasicAuth = &lokiBasicAuth - } - - lokiBearerToken := ctfconfig.MustReadEnvVar_String(ctfconfig.E2E_TEST_LOKI_BEARER_TOKEN_ENV) - if lokiBearerToken != "" { - if p.Logging == nil { - p.Logging = &ctfconfig.LoggingConfig{} - } - if p.Logging.Loki == nil { - p.Logging.Loki = &ctfconfig.LokiConfig{} - } - logger.Debug().Msgf("Using %s env var to override Logging.Loki.BearerToken", ctfconfig.E2E_TEST_LOKI_BEARER_TOKEN_ENV) - p.Logging.Loki.BearerToken = &lokiBearerToken - } - - grafanaBaseUrl := ctfconfig.MustReadEnvVar_String(ctfconfig.E2E_TEST_GRAFANA_BASE_URL_ENV) - if grafanaBaseUrl != "" { - if p.Logging == nil { - p.Logging = &ctfconfig.LoggingConfig{} - } - if p.Logging.Grafana == nil { - p.Logging.Grafana = &ctfconfig.GrafanaConfig{} - } - logger.Debug().Msgf("Using %s env var to override Logging.Grafana.BaseUrl", ctfconfig.E2E_TEST_GRAFANA_BASE_URL_ENV) - p.Logging.Grafana.BaseUrl = &grafanaBaseUrl - } - - grafanaDashboardUrl := ctfconfig.MustReadEnvVar_String(ctfconfig.E2E_TEST_GRAFANA_DASHBOARD_URL_ENV) - if grafanaDashboardUrl != "" { - if p.Logging == nil { - p.Logging = &ctfconfig.LoggingConfig{} - } - if p.Logging.Grafana == nil { - p.Logging.Grafana = &ctfconfig.GrafanaConfig{} - } - logger.Debug().Msgf("Using %s env var to override Logging.Grafana.DashboardUrl", ctfconfig.E2E_TEST_GRAFANA_DASHBOARD_URL_ENV) - p.Logging.Grafana.DashboardUrl = &grafanaDashboardUrl - } - - grafanaBearerToken := ctfconfig.MustReadEnvVar_String(ctfconfig.E2E_TEST_GRAFANA_BEARER_TOKEN_ENV) - if grafanaBearerToken != "" { - if p.Logging == nil { - p.Logging = &ctfconfig.LoggingConfig{} - } - if p.Logging.Grafana == nil { - p.Logging.Grafana = &ctfconfig.GrafanaConfig{} - } - logger.Debug().Msgf("Using %s env var to override Logging.Grafana.BearerToken", ctfconfig.E2E_TEST_GRAFANA_BEARER_TOKEN_ENV) - p.Logging.Grafana.BearerToken = &grafanaBearerToken - } - selectedNetworks := ctfconfig.MustReadEnvVar_Strings(ctfconfig.E2E_TEST_SELECTED_NETWORK_ENV, ",") if len(selectedNetworks) > 0 { if p.Network == nil { @@ -421,9 +307,6 @@ func (p *Common) GetSethConfig() *seth.Config { } func (p *Common) Validate() error { - if err := p.Logging.Validate(); err != nil { - return fmt.Errorf("error validating logging config %w", err) - } if p.Network == nil { return errors.New("no networks specified") } diff --git a/integration-tests/ccip-tests/testconfig/tomls/ccip-default.toml b/integration-tests/ccip-tests/testconfig/tomls/ccip-default.toml index c82e2f930be..89858a94ddb 100644 --- a/integration-tests/ccip-tests/testconfig/tomls/ccip-default.toml +++ b/integration-tests/ccip-tests/testconfig/tomls/ccip-default.toml @@ -73,17 +73,6 @@ addresses_to_fund = [ [CCIP.Env.PrivateEthereumNetworks.SIMULATED_2.EthereumChainConfig.HardForkEpochs] Deneb = 500 -[CCIP.Env.Logging] -test_log_collect = false # if set to true will save logs even if test did not fail - -[CCIP.Env.Logging.LogStream] -# supported targets: file, loki, in-memory. if empty no logs will be persistet -log_targets = ["file"] -# context timeout for starting log producer and also time-frame for requesting logs -log_producer_timeout = "10s" -# number of retries before log producer gives up and stops listening to logs -log_producer_retry_limit = 10 - # these values will be used to set up chainlink DON # along with these values, the secrets needs to be specified as part of .env variables # diff --git a/integration-tests/ccip-tests/testsetups/test_env.go b/integration-tests/ccip-tests/testsetups/test_env.go index 263d291453d..3c3406a3e5a 100644 --- a/integration-tests/ccip-tests/testsetups/test_env.go +++ b/integration-tests/ccip-tests/testsetups/test_env.go @@ -352,7 +352,6 @@ func DeployLocalCluster( pointer.GetString(clNode.ChainlinkImage.Image), pointer.GetString(clNode.ChainlinkImage.Version), toml, - env.LogStream, test_env.WithPgDBOptions( ctftestenv.WithPostgresImageName(clNode.DBImage), ctftestenv.WithPostgresImageVersion(clNode.DBTag), @@ -381,7 +380,6 @@ func DeployLocalCluster( pointer.GetString(testInputs.EnvInput.NewCLCluster.Common.ChainlinkImage.Image), pointer.GetString(testInputs.EnvInput.NewCLCluster.Common.ChainlinkImage.Version), toml, - env.LogStream, test_env.WithPgDBOptions( ctftestenv.WithPostgresImageName(testInputs.EnvInput.NewCLCluster.Common.DBImage), ctftestenv.WithPostgresImageVersion(testInputs.EnvInput.NewCLCluster.Common.DBTag), diff --git a/integration-tests/contracts/ccipreader_test.go b/integration-tests/contracts/ccipreader_test.go index 3028f4707a4..3b0ac1d79a1 100644 --- a/integration-tests/contracts/ccipreader_test.go +++ b/integration-tests/contracts/ccipreader_test.go @@ -183,6 +183,123 @@ func emitCommitReports(ctx context.Context, t *testing.T, s *testSetupData, numR return firstReportTs } +func TestCCIPReader_GetOffRampConfigDigest(t *testing.T) { + t.Parallel() + ctx := tests.Context(t) + sb, auth := setupSimulatedBackendAndAuth(t) + + addr, _, _, err := offramp.DeployOffRamp(auth, sb.Client(), offramp.OffRampStaticConfig{ + ChainSelector: uint64(chainD), + GasForCallExactCheck: 5_000, + RmnRemote: utils.RandomAddress(), + TokenAdminRegistry: utils.RandomAddress(), + NonceManager: utils.RandomAddress(), + }, offramp.OffRampDynamicConfig{ + FeeQuoter: utils.RandomAddress(), + PermissionLessExecutionThresholdSeconds: 1, + IsRMNVerificationDisabled: true, + MessageInterceptor: utils.RandomAddress(), + }, []offramp.OffRampSourceChainConfigArgs{}) + require.NoError(t, err) + sb.Commit() + + offRamp, err := offramp.NewOffRamp(addr, sb.Client()) + require.NoError(t, err) + + commitConfigDigest := utils.RandomBytes32() + execConfigDigest := utils.RandomBytes32() + + _, err = offRamp.SetOCR3Configs(auth, []offramp.MultiOCR3BaseOCRConfigArgs{ + { + ConfigDigest: commitConfigDigest, + OcrPluginType: consts.PluginTypeCommit, + F: 1, + IsSignatureVerificationEnabled: true, + Signers: []common.Address{utils.RandomAddress(), utils.RandomAddress(), utils.RandomAddress(), utils.RandomAddress()}, + Transmitters: []common.Address{utils.RandomAddress(), utils.RandomAddress(), utils.RandomAddress(), utils.RandomAddress()}, + }, + { + ConfigDigest: execConfigDigest, + OcrPluginType: consts.PluginTypeExecute, + F: 1, + IsSignatureVerificationEnabled: false, + Signers: []common.Address{utils.RandomAddress(), utils.RandomAddress(), utils.RandomAddress(), utils.RandomAddress()}, + Transmitters: []common.Address{utils.RandomAddress(), utils.RandomAddress(), utils.RandomAddress(), utils.RandomAddress()}, + }, + }) + require.NoError(t, err) + sb.Commit() + + commitConfigDetails, err := offRamp.LatestConfigDetails(&bind.CallOpts{ + Context: ctx, + }, consts.PluginTypeCommit) + require.NoError(t, err) + require.Equal(t, commitConfigDigest, commitConfigDetails.ConfigInfo.ConfigDigest) + + execConfigDetails, err := offRamp.LatestConfigDetails(&bind.CallOpts{ + Context: ctx, + }, consts.PluginTypeExecute) + require.NoError(t, err) + require.Equal(t, execConfigDigest, execConfigDetails.ConfigInfo.ConfigDigest) + + db := pgtest.NewSqlxDB(t) + lggr := logger.TestLogger(t) + lggr.SetLogLevel(zapcore.ErrorLevel) + lpOpts := logpoller.Opts{ + PollPeriod: time.Millisecond, + FinalityDepth: 1, + BackfillBatchSize: 10, + RpcBatchSize: 10, + KeepFinalizedBlocksDepth: 100000, + } + cl := client.NewSimulatedBackendClient(t, sb, big.NewInt(1337)) + headTracker := headtracker.NewSimulatedHeadTracker(cl, lpOpts.UseFinalityTag, lpOpts.FinalityDepth) + orm := logpoller.NewORM(big.NewInt(1337), db, lggr) + lp := logpoller.NewLogPoller( + orm, + cl, + lggr, + headTracker, + lpOpts, + ) + require.NoError(t, lp.Start(ctx)) + t.Cleanup(func() { require.NoError(t, lp.Close()) }) + + cr, err := evm.NewChainReaderService(ctx, lggr, lp, headTracker, cl, evmconfig.DestReaderConfig) + require.NoError(t, err) + err = cr.Start(ctx) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, cr.Close()) }) + + extendedCr := contractreader.NewExtendedContractReader(cr) + err = extendedCr.Bind(ctx, []types.BoundContract{ + { + Address: addr.Hex(), + Name: consts.ContractNameOffRamp, + }, + }) + require.NoError(t, err) + + reader := ccipreaderpkg.NewCCIPReaderWithExtendedContractReaders( + ctx, + lggr, + map[cciptypes.ChainSelector]contractreader.Extended{ + chainD: extendedCr, + }, + nil, + chainD, + addr.Bytes(), + ) + + ccipReaderCommitDigest, err := reader.GetOffRampConfigDigest(ctx, consts.PluginTypeCommit) + require.NoError(t, err) + require.Equal(t, commitConfigDigest, ccipReaderCommitDigest) + + ccipReaderExecDigest, err := reader.GetOffRampConfigDigest(ctx, consts.PluginTypeExecute) + require.NoError(t, err) + require.Equal(t, execConfigDigest, ccipReaderExecDigest) +} + func TestCCIPReader_CommitReportsGTETimestamp(t *testing.T) { t.Parallel() ctx := tests.Context(t) @@ -473,11 +590,7 @@ func TestCCIPReader_GetExpectedNextSequenceNumber(t *testing.T) { t.Parallel() ctx := tests.Context(t) //env := NewMemoryEnvironmentContractsOnly(t, logger.TestLogger(t), 2, 4, nil) - env := changeset.NewMemoryEnvironmentWithJobsAndContracts(t, logger.TestLogger(t), memory.MemoryEnvironmentConfig{ - Chains: 2, - Nodes: 4, - Bootstraps: 1, - }, nil) + env := changeset.NewMemoryEnvironment(t) state, err := changeset.LoadOnchainState(env.Env) require.NoError(t, err) @@ -587,11 +700,7 @@ func TestCCIPReader_Nonces(t *testing.T) { func Test_GetChainFeePriceUpdates(t *testing.T) { t.Parallel() ctx := tests.Context(t) - env := changeset.NewMemoryEnvironmentWithJobsAndContracts(t, logger.TestLogger(t), memory.MemoryEnvironmentConfig{ - Chains: 2, - Nodes: 4, - Bootstraps: 1, - }, nil) + env := changeset.NewMemoryEnvironment(t) state, err := changeset.LoadOnchainState(env.Env) require.NoError(t, err) @@ -647,11 +756,7 @@ func Test_GetChainFeePriceUpdates(t *testing.T) { func Test_LinkPriceUSD(t *testing.T) { t.Parallel() ctx := tests.Context(t) - env := changeset.NewMemoryEnvironmentWithJobsAndContracts(t, logger.TestLogger(t), memory.MemoryEnvironmentConfig{ - Chains: 2, - Nodes: 4, - Bootstraps: 1, - }, nil) + env := changeset.NewMemoryEnvironment(t) state, err := changeset.LoadOnchainState(env.Env) require.NoError(t, err) @@ -686,11 +791,7 @@ func Test_LinkPriceUSD(t *testing.T) { func Test_GetMedianDataAvailabilityGasConfig(t *testing.T) { t.Parallel() ctx := tests.Context(t) - env := changeset.NewMemoryEnvironmentWithJobsAndContracts(t, logger.TestLogger(t), memory.MemoryEnvironmentConfig{ - Chains: 4, - Nodes: 4, - Bootstraps: 1, - }, nil) + env := changeset.NewMemoryEnvironment(t, changeset.WithChains(4)) state, err := changeset.LoadOnchainState(env.Env) require.NoError(t, err) @@ -749,11 +850,7 @@ func Test_GetMedianDataAvailabilityGasConfig(t *testing.T) { func Test_GetWrappedNativeTokenPriceUSD(t *testing.T) { t.Parallel() ctx := tests.Context(t) - env := changeset.NewMemoryEnvironmentWithJobsAndContracts(t, logger.TestLogger(t), memory.MemoryEnvironmentConfig{ - Chains: 2, - Nodes: 4, - Bootstraps: 1, - }, nil) + env := changeset.NewMemoryEnvironment(t) state, err := changeset.LoadOnchainState(env.Env) require.NoError(t, err) diff --git a/integration-tests/docker/test_env/cl_node.go b/integration-tests/docker/test_env/cl_node.go index b5c2505b252..8ebaf579d0a 100644 --- a/integration-tests/docker/test_env/cl_node.go +++ b/integration-tests/docker/test_env/cl_node.go @@ -24,7 +24,6 @@ import ( "github.com/smartcontractkit/chainlink-testing-framework/lib/docker" "github.com/smartcontractkit/chainlink-testing-framework/lib/docker/test_env" "github.com/smartcontractkit/chainlink-testing-framework/lib/logging" - "github.com/smartcontractkit/chainlink-testing-framework/lib/logstream" "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/testcontext" "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" @@ -126,11 +125,11 @@ func WithPgDBOptions(opts ...test_env.PostgresDbOption) ClNodeOption { } } -func NewClNode(networks []string, imageName, imageVersion string, nodeConfig *chainlink.Config, logStream *logstream.LogStream, opts ...ClNodeOption) (*ClNode, error) { +func NewClNode(networks []string, imageName, imageVersion string, nodeConfig *chainlink.Config, opts ...ClNodeOption) (*ClNode, error) { nodeDefaultCName := fmt.Sprintf("%s-%s", "cl-node", uuid.NewString()[0:8]) pgDefaultCName := fmt.Sprintf("pg-%s", nodeDefaultCName) - pgDb, err := test_env.NewPostgresDb(networks, test_env.WithPostgresDbContainerName(pgDefaultCName), test_env.WithPostgresDbLogStream(logStream)) + pgDb, err := test_env.NewPostgresDb(networks, test_env.WithPostgresDbContainerName(pgDefaultCName)) if err != nil { return nil, err } @@ -140,7 +139,6 @@ func NewClNode(networks []string, imageName, imageVersion string, nodeConfig *ch ContainerImage: imageName, ContainerVersion: imageVersion, Networks: networks, - LogStream: logStream, StartupTimeout: 3 * time.Minute, }, UserEmail: "local@local.com", @@ -490,28 +488,6 @@ func (n *ClNode) getContainerRequest(secrets string) ( FileMode: 0644, }, }, - LifecycleHooks: []tc.ContainerLifecycleHooks{ - { - PostStarts: []tc.ContainerHook{ - func(ctx context.Context, c tc.Container) error { - if n.LogStream != nil { - return n.LogStream.ConnectContainer(ctx, c, "") - } - return nil - }, - }, - PreStops: []tc.ContainerHook{ - func(ctx context.Context, c tc.Container) error { - if n.LogStream != nil { - return n.LogStream.DisconnectContainer(c) - } - return nil - }, - }, - PostStops: n.PostStopsHooks, - PreTerminates: n.PreTerminatesHooks, - }, - }, }, nil } diff --git a/integration-tests/docker/test_env/test_env.go b/integration-tests/docker/test_env/test_env.go index 1ca50760d17..a37b7f813a7 100644 --- a/integration-tests/docker/test_env/test_env.go +++ b/integration-tests/docker/test_env/test_env.go @@ -20,8 +20,6 @@ import ( "github.com/smartcontractkit/chainlink-testing-framework/lib/docker" "github.com/smartcontractkit/chainlink-testing-framework/lib/docker/test_env" "github.com/smartcontractkit/chainlink-testing-framework/lib/logging" - "github.com/smartcontractkit/chainlink-testing-framework/lib/logstream" - "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/runid" "github.com/smartcontractkit/chainlink/integration-tests/testconfig/ccip" "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" @@ -36,7 +34,6 @@ var ( type CLClusterTestEnv struct { Cfg *TestEnvConfig DockerNetwork *tc.DockerNetwork - LogStream *logstream.LogStream TestConfig ctf_config.GlobalTestConfig /* components */ @@ -69,7 +66,7 @@ func (te *CLClusterTestEnv) WithTestEnvConfig(cfg *TestEnvConfig) *CLClusterTest te.Cfg = cfg if cfg.MockAdapter.ContainerName != "" { n := []string{te.DockerNetwork.Name} - te.MockAdapter = test_env.NewKillgrave(n, te.Cfg.MockAdapter.ImpostersPath, test_env.WithContainerName(te.Cfg.MockAdapter.ContainerName), test_env.WithLogStream(te.LogStream)) + te.MockAdapter = test_env.NewKillgrave(n, te.Cfg.MockAdapter.ImpostersPath, test_env.WithContainerName(te.Cfg.MockAdapter.ContainerName)) } return te } @@ -99,7 +96,6 @@ func (te *CLClusterTestEnv) StartEthereumNetwork(cfg *ctf_config.EthereumNetwork builder := test_env.NewEthereumNetworkBuilder() c, err := builder.WithExistingConfig(*cfg). WithTest(te.t). - WithLogStream(te.LogStream). Build() if err != nil { return blockchain.EVMNetwork{}, test_env.RpcProvider{}, err @@ -132,7 +128,6 @@ func (te *CLClusterTestEnv) StartJobDistributor(cfg *ccip.JDConfig) error { job_distributor.WithVersion(cfg.GetJDVersion()), job_distributor.WithDBURL(jdDB.InternalURL.String()), ) - jd.LogStream = te.LogStream err = jd.StartContainer() if err != nil { return fmt.Errorf("failed to start job-distributor: %w", err) @@ -160,7 +155,7 @@ func (te *CLClusterTestEnv) StartClCluster(nodeConfig *chainlink.Config, count i opts = append(opts, WithSecrets(secretsConfig)) te.ClCluster = &ClCluster{} for i := 0; i < count; i++ { - ocrNode, err := NewClNode([]string{te.DockerNetwork.Name}, *testconfig.GetChainlinkImageConfig().Image, *testconfig.GetChainlinkImageConfig().Version, nodeConfig, te.LogStream, opts...) + ocrNode, err := NewClNode([]string{te.DockerNetwork.Name}, *testconfig.GetChainlinkImageConfig().Image, *testconfig.GetChainlinkImageConfig().Version, nodeConfig, opts...) if err != nil { return err } @@ -193,11 +188,6 @@ type CleanupOpts struct { func (te *CLClusterTestEnv) Cleanup(opts CleanupOpts) error { te.l.Info().Msg("Cleaning up test environment") - runIdErr := runid.RemoveLocalRunId(te.TestConfig.GetLoggingConfig().RunId) - if runIdErr != nil { - te.l.Warn().Msgf("Failed to remove .run.id file due to: %s (not a big deal, you can still remove it manually)", runIdErr.Error()) - } - if te.t == nil { return fmt.Errorf("cannot cleanup test environment without a testing.T") } diff --git a/integration-tests/docker/test_env/test_env_builder.go b/integration-tests/docker/test_env/test_env_builder.go index cdce826f2c2..e11a3c96095 100644 --- a/integration-tests/docker/test_env/test_env_builder.go +++ b/integration-tests/docker/test_env/test_env_builder.go @@ -2,28 +2,25 @@ package test_env import ( "fmt" - "math" "os" "path/filepath" - "slices" "strings" + "sync" "testing" "time" "github.com/rs/zerolog" "github.com/rs/zerolog/log" "go.uber.org/zap/zapcore" - - "github.com/smartcontractkit/chainlink-testing-framework/seth" + "golang.org/x/sync/errgroup" "github.com/smartcontractkit/chainlink-testing-framework/lib/blockchain" ctf_config "github.com/smartcontractkit/chainlink-testing-framework/lib/config" + ctf_docker "github.com/smartcontractkit/chainlink-testing-framework/lib/docker" "github.com/smartcontractkit/chainlink-testing-framework/lib/docker/test_env" "github.com/smartcontractkit/chainlink-testing-framework/lib/logging" - "github.com/smartcontractkit/chainlink-testing-framework/lib/logstream" "github.com/smartcontractkit/chainlink-testing-framework/lib/networks" "github.com/smartcontractkit/chainlink-testing-framework/lib/testreporters" - "github.com/smartcontractkit/chainlink-testing-framework/lib/testsummary" "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/osutil" "github.com/smartcontractkit/chainlink/integration-tests/testconfig/ccip" @@ -46,7 +43,6 @@ type ChainlinkNodeLogScannerSettings struct { } type CLTestEnvBuilder struct { - hasLogStream bool hasKillgrave bool jdConfig *ccip.JDConfig clNodeConfig *chainlink.Config @@ -90,7 +86,6 @@ func GetDefaultChainlinkNodeLogScannerSettingsWithExtraAllowedMessages(extraAllo func NewCLTestEnvBuilder() *CLTestEnvBuilder { return &CLTestEnvBuilder{ l: log.Logger, - hasLogStream: true, isEVM: true, chainlinkNodeLogScannerSettings: &DefaultChainlinkNodeLogScannerSettings, } @@ -134,12 +129,6 @@ func (b *CLTestEnvBuilder) WithTestInstance(t *testing.T) *CLTestEnvBuilder { return b } -// WithoutLogStream disables LogStream logging component -func (b *CLTestEnvBuilder) WithoutLogStream() *CLTestEnvBuilder { - b.hasLogStream = false - return b -} - func (b *CLTestEnvBuilder) WithoutChainlinkNodeLogScanner() *CLTestEnvBuilder { b.chainlinkNodeLogScannerSettings = &ChainlinkNodeLogScannerSettings{} return b @@ -250,102 +239,105 @@ func (b *CLTestEnvBuilder) Build() (*CLClusterTestEnv, error) { b.te.WithTestInstance(b.t) } - if b.hasLogStream { - loggingConfig := b.testConfig.GetLoggingConfig() - // we need to enable logging to file if we want to scan logs - if b.chainlinkNodeLogScannerSettings != nil && !slices.Contains(loggingConfig.LogStream.LogTargets, string(logstream.File)) { - b.l.Debug().Msg("Enabling logging to file in order to support Chainlink node log scanning") - loggingConfig.LogStream.LogTargets = append(loggingConfig.LogStream.LogTargets, string(logstream.File)) - } - b.te.LogStream, err = logstream.NewLogStream(b.te.t, b.testConfig.GetLoggingConfig()) - if err != nil { - return nil, err - } - - // this clean up has to be added as the FIRST one, because cleanup functions are executed in reverse order (LIFO) - if b.t != nil && b.cleanUpType != CleanUpTypeNone { - b.t.Cleanup(func() { - b.l.Info().Msg("Shutting down LogStream") - logPath, err := osutil.GetAbsoluteFolderPath("logs") - if err == nil { - b.l.Info().Str("Absolute path", logPath).Msg("LogStream logs folder location") - } - - // flush logs when test failed or when we are explicitly told to collect logs - flushLogStream := b.t.Failed() || *b.testConfig.GetLoggingConfig().TestLogCollect + // this clean up has to be added as the FIRST one, because cleanup functions are executed in reverse order (LIFO) + if b.t != nil && b.cleanUpType != CleanUpTypeNone { + b.t.Cleanup(func() { + logsDir := fmt.Sprintf("logs/%s-%s", b.t.Name(), time.Now().Format("2006-01-02T15-04-05")) + loggingErr := ctf_docker.WriteAllContainersLogs(b.l, logsDir) + if loggingErr != nil { + b.l.Error().Err(loggingErr).Msg("Error writing all Docker containers logs") + } - // run even if test has failed, as we might be able to catch additional problems without running the test again - if b.chainlinkNodeLogScannerSettings != nil { - logProcessor := logstream.NewLogProcessor[int](b.te.LogStream) + if b == nil || b.te == nil || b.te.ClCluster == nil || b.te.ClCluster.Nodes == nil { + log.Warn().Msg("Won't dump container and postgres logs, because test environment doesn't have any nodes") + return + } - processFn := func(log logstream.LogContent, count *int) error { - countSoFar := count - if *countSoFar < 0 { - return fmt.Errorf("negative count: %d", *countSoFar) - } - newCount, err := testreporters.ScanLogLine(b.l, string(log.Content), b.chainlinkNodeLogScannerSettings.FailingLogLevel, uint(*countSoFar), b.chainlinkNodeLogScannerSettings.Threshold, b.chainlinkNodeLogScannerSettings.AllowedMessages) - if err != nil { - return err - } - if newCount > math.MaxInt { - return fmt.Errorf("new count overflows int: %d", newCount) - } - *count = int(newCount) - return nil - } + if b.chainlinkNodeLogScannerSettings != nil { + var logFiles []*os.File - // we cannot do parallel processing here, because ProcessContainerLogs() locks a mutex that controls whether - // new logs can be added to the log stream, so parallel processing would get stuck on waiting for it to be unlocked - LogScanningLoop: - for i := 0; i < b.clNodesCount; i++ { - // if something went wrong during environment setup we might not have all nodes, and we don't want an NPE - if b == nil || b.te == nil || b.te.ClCluster == nil || b.te.ClCluster.Nodes == nil || len(b.te.ClCluster.Nodes)-1 < i || b.te.ClCluster.Nodes[i] == nil { + // when tests run in parallel, we need to make sure that we only process logs that belong to nodes created by the current test + // that is required, because some tests might have custom log messages that are allowed, but only for that test (e.g. because they restart the CL node) + var belongsToCurrentEnv = func(filePath string) bool { + for _, clNode := range b.te.ClCluster.Nodes { + if clNode == nil { continue } - // ignore count return, because we are only interested in the error - _, err := logProcessor.ProcessContainerLogs(b.te.ClCluster.Nodes[i].ContainerName, processFn) - if err != nil && !strings.Contains(err.Error(), testreporters.MultipleLogsAtLogLevelErr) && !strings.Contains(err.Error(), testreporters.OneLogAtLogLevelErr) { - b.l.Error().Err(err).Msg("Error processing CL node logs") - continue - } else if err != nil && (strings.Contains(err.Error(), testreporters.MultipleLogsAtLogLevelErr) || strings.Contains(err.Error(), testreporters.OneLogAtLogLevelErr)) { - flushLogStream = true - b.t.Errorf("Found a concerning log in Chainklink Node logs: %v", err) - break LogScanningLoop + if strings.EqualFold(filePath, clNode.ContainerName+".log") { + return true } } - b.l.Info().Msg("Finished scanning Chainlink Node logs for concerning errors") + return false } - if flushLogStream { - b.l.Info().Msg("Flushing LogStream logs") - // we can't do much if this fails, so we just log the error in LogStream - if err := b.te.LogStream.FlushAndShutdown(); err != nil { - b.l.Error().Err(err).Msg("Error flushing and shutting down LogStream") + fileWalkErr := filepath.Walk(logsDir, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err } - b.te.LogStream.PrintLogTargetsLocations() - b.te.LogStream.SaveLogLocationInTestSummary() - } - b.l.Info().Msg("Finished shutting down LogStream") + if !info.IsDir() && belongsToCurrentEnv(info.Name()) { + file, fileErr := os.Open(path) + if fileErr != nil { + return fmt.Errorf("failed to open file %s: %w", path, fileErr) + } + logFiles = append(logFiles, file) + } + return nil + }) - if b.t.Failed() || *b.testConfig.GetLoggingConfig().TestLogCollect { - b.l.Info().Msg("Dump state of all Postgres DBs used by Chainlink Nodes") + if len(logFiles) != len(b.te.ClCluster.Nodes) { + b.l.Warn().Int("Expected", len(b.te.ClCluster.Nodes)).Int("Got", len(logFiles)).Msg("Number of log files does not match number of nodes. Some logs might be missing.") + } - dbDumpFolder := "db_dumps" - dbDumpPath := fmt.Sprintf("%s/%s-%s", dbDumpFolder, b.t.Name(), time.Now().Format("2006-01-02T15-04-05")) - if err := os.MkdirAll(dbDumpPath, os.ModePerm); err != nil { - b.l.Error().Err(err).Msg("Error creating folder for Postgres DB dump") - return + if fileWalkErr != nil { + b.l.Error().Err(fileWalkErr).Msg("Error walking through log files. Skipping log verification.") + } else { + verifyLogsGroup := &errgroup.Group{} + for _, f := range logFiles { + file := f + verifyLogsGroup.Go(func() error { + verifyErr := testreporters.VerifyLogFile(file, b.chainlinkNodeLogScannerSettings.FailingLogLevel, b.chainlinkNodeLogScannerSettings.Threshold, b.chainlinkNodeLogScannerSettings.AllowedMessages...) + _ = file.Close() + // ignore processing errors + if verifyErr != nil && !strings.Contains(verifyErr.Error(), testreporters.MultipleLogsAtLogLevelErr) && !strings.Contains(verifyErr.Error(), testreporters.OneLogAtLogLevelErr) { + b.l.Error().Err(verifyErr).Msg("Error processing CL node logs") + + return nil + + // if it's not a processing error, we want to fail the test; we also can stop processing logs all together at this point + } else if verifyErr != nil && (strings.Contains(verifyErr.Error(), testreporters.MultipleLogsAtLogLevelErr) || strings.Contains(verifyErr.Error(), testreporters.OneLogAtLogLevelErr)) { + + return verifyErr + } + return nil + }) } - absDbDumpPath, err := osutil.GetAbsoluteFolderPath(dbDumpFolder) - if err == nil { - b.l.Info().Str("Absolute path", absDbDumpPath).Msg("PostgresDB dump folder location") + if logVerificationErr := verifyLogsGroup.Wait(); logVerificationErr != nil { + b.t.Errorf("Found a concerning log in Chainklink Node logs: %v", logVerificationErr) } + } + } - for i := 0; i < b.clNodesCount; i++ { + b.l.Info().Msg("Staring to dump state of all Postgres DBs used by Chainlink Nodes") + + dbDumpFolder := "db_dumps" + dbDumpPath := fmt.Sprintf("%s/%s-%s", dbDumpFolder, b.t.Name(), time.Now().Format("2006-01-02T15-04-05")) + if err := os.MkdirAll(dbDumpPath, os.ModePerm); err != nil { + b.l.Error().Err(err).Msg("Error creating folder for Postgres DB dump") + } else { + absDbDumpPath, err := osutil.GetAbsoluteFolderPath(dbDumpFolder) + if err == nil { + b.l.Info().Str("Absolute path", absDbDumpPath).Msg("PostgresDB dump folder location") + } + + dbDumpGroup := sync.WaitGroup{} + for i := 0; i < b.clNodesCount; i++ { + dbDumpGroup.Add(1) + go func() { + defer dbDumpGroup.Done() // if something went wrong during environment setup we might not have all nodes, and we don't want an NPE if b == nil || b.te == nil || b.te.ClCluster == nil || b.te.ClCluster.Nodes == nil || len(b.te.ClCluster.Nodes)-1 < i || b.te.ClCluster.Nodes[i] == nil || b.te.ClCluster.Nodes[i].PostgresDb == nil { - continue + return } filePath := filepath.Join(dbDumpPath, fmt.Sprintf("postgres_db_dump_%s.sql", b.te.ClCluster.Nodes[i].ContainerName)) @@ -353,24 +345,23 @@ func (b *CLTestEnvBuilder) Build() (*CLClusterTestEnv, error) { if err != nil { b.l.Error().Err(err).Msg("Error creating localDbDumpFile for Postgres DB dump") _ = localDbDumpFile.Close() - continue + return } if err := b.te.ClCluster.Nodes[i].PostgresDb.ExecPgDumpFromContainer(localDbDumpFile); err != nil { b.l.Error().Err(err).Msg("Error dumping Postgres DB") } _ = localDbDumpFile.Close() - } - b.l.Info().Msg("Finished dumping state of all Postgres DBs used by Chainlink Nodes") + }() } - if b.testConfig.GetSethConfig() != nil && ((b.t.Failed() && slices.Contains(b.testConfig.GetSethConfig().TraceOutputs, seth.TraceOutput_DOT) && b.testConfig.GetSethConfig().TracingLevel != seth.TracingLevel_None) || (!b.t.Failed() && slices.Contains(b.testConfig.GetSethConfig().TraceOutputs, seth.TraceOutput_DOT) && b.testConfig.GetSethConfig().TracingLevel == seth.TracingLevel_All)) { - _ = testsummary.AddEntry(b.t.Name(), "dot_graphs", "true") - } - }) - } else { - b.l.Warn().Msg("LogStream won't be cleaned up, because either test instance is not set or cleanup type is set to none") - } + dbDumpGroup.Wait() + + b.l.Info().Msg("Finished dumping state of all Postgres DBs used by Chainlink Nodes") + } + }) + } else { + b.l.Warn().Msg("Won't dump container and postgres logs, because either test instance is not set or cleanup type is set to none") } if b.hasKillgrave { @@ -378,7 +369,7 @@ func (b *CLTestEnvBuilder) Build() (*CLClusterTestEnv, error) { return nil, fmt.Errorf("test environment builder failed: %w", fmt.Errorf("cannot start mock adapter without a network")) } - b.te.MockAdapter = test_env.NewKillgrave([]string{b.te.DockerNetwork.Name}, "", test_env.WithLogStream(b.te.LogStream)) + b.te.MockAdapter = test_env.NewKillgrave([]string{b.te.DockerNetwork.Name}, "") err = b.te.StartMockAdapter() if err != nil { @@ -406,10 +397,6 @@ func (b *CLTestEnvBuilder) Build() (*CLClusterTestEnv, error) { return b.te, fmt.Errorf("test environment builder failed: %w", fmt.Errorf("explicit cleanup type must be set when building test environment")) } - if b.te.LogStream == nil && b.chainlinkNodeLogScannerSettings != nil { - log.Warn().Msg("Chainlink node log scanner settings provided, but LogStream is not enabled. Ignoring Chainlink node log scanner settings, as no logs will be available.") - } - if b.jdConfig != nil { err := b.te.StartJobDistributor(b.jdConfig) if err != nil { diff --git a/integration-tests/go.mod b/integration-tests/go.mod index 58b2a6fa1c4..b5da0a24d2d 100644 --- a/integration-tests/go.mod +++ b/integration-tests/go.mod @@ -9,6 +9,13 @@ replace github.com/smartcontractkit/chainlink/v2 => ../ replace github.com/smartcontractkit/chainlink/deployment => ../deployment +// Using a separate `require` here to avoid surrounding line changes +// creating potential merge conflicts. +require ( + github.com/smartcontractkit/chainlink/deployment v0.0.0-20241206210521-125d98cdaf66 + github.com/smartcontractkit/chainlink/v2 v2.0.0-20241206210521-125d98cdaf66 +) + require ( dario.cat/mergo v1.0.1 github.com/AlekSi/pointer v1.1.0 @@ -37,19 +44,16 @@ require ( github.com/segmentio/ksuid v1.0.4 github.com/shopspring/decimal v1.4.0 github.com/slack-go/slack v0.15.0 - github.com/smartcontractkit/ccip-owner-contracts v0.0.0-20240926212305-a6deabdfce86 github.com/smartcontractkit/chain-selectors v1.0.34 github.com/smartcontractkit/chainlink-automation v0.8.1 - github.com/smartcontractkit/chainlink-ccip v0.0.0-20241204015713-8956bb614e9e - github.com/smartcontractkit/chainlink-common v0.3.1-0.20241209151352-70300ddcc776 + github.com/smartcontractkit/chainlink-ccip v0.0.0-20241213122413-5e8f65dd6b1b + github.com/smartcontractkit/chainlink-common v0.3.1-0.20241214155818-b403079b2805 github.com/smartcontractkit/chainlink-protos/job-distributor v0.6.0 github.com/smartcontractkit/chainlink-testing-framework/havoc v1.50.2 - github.com/smartcontractkit/chainlink-testing-framework/lib v1.50.18 + github.com/smartcontractkit/chainlink-testing-framework/lib v1.50.19 github.com/smartcontractkit/chainlink-testing-framework/lib/grafana v1.50.0 github.com/smartcontractkit/chainlink-testing-framework/seth v1.50.9 github.com/smartcontractkit/chainlink-testing-framework/wasp v1.50.2 - github.com/smartcontractkit/chainlink/deployment v0.0.0-00010101000000-000000000000 - github.com/smartcontractkit/chainlink/v2 v2.14.0-mercury-20240807.0.20241120195829-bd7a1943ad07 github.com/smartcontractkit/libocr v0.0.0-20241007185508-adbe57025f12 github.com/spf13/cobra v1.8.1 github.com/stretchr/testify v1.9.0 @@ -60,10 +64,10 @@ require ( go.uber.org/atomic v1.11.0 go.uber.org/multierr v1.11.0 go.uber.org/zap v1.27.0 - golang.org/x/crypto v0.28.0 + golang.org/x/crypto v0.31.0 golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c - golang.org/x/sync v0.8.0 - golang.org/x/text v0.19.0 + golang.org/x/sync v0.10.0 + golang.org/x/text v0.21.0 google.golang.org/grpc v1.67.1 gopkg.in/guregu/null.v4 v4.0.0 k8s.io/apimachinery v0.31.2 @@ -101,7 +105,7 @@ require ( github.com/VictoriaMetrics/fastcache v1.12.2 // indirect github.com/XSAM/otelsql v0.27.0 // indirect github.com/alecthomas/units v0.0.0-20240626203959-61d1e3462e30 // indirect - github.com/andybalholm/brotli v1.1.0 // indirect + github.com/andybalholm/brotli v1.1.1 // indirect github.com/armon/go-metrics v0.4.1 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect github.com/atombender/go-jsonschema v0.16.1-0.20240916205339-a74cd4e2851c // indirect @@ -241,7 +245,7 @@ require ( github.com/go-viper/mapstructure/v2 v2.1.0 // indirect github.com/go-webauthn/webauthn v0.9.4 // indirect github.com/go-webauthn/x v0.1.5 // indirect - github.com/goccy/go-json v0.10.2 // indirect + github.com/goccy/go-json v0.10.3 // indirect github.com/goccy/go-yaml v1.12.0 // indirect github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect github.com/gofrs/flock v0.8.1 // indirect @@ -333,8 +337,8 @@ require ( github.com/json-iterator/go v1.1.12 // indirect github.com/julienschmidt/httprouter v1.3.0 // indirect github.com/kelseyhightower/envconfig v1.4.0 // indirect - github.com/klauspost/compress v1.17.9 // indirect - github.com/klauspost/cpuid/v2 v2.2.7 // indirect + github.com/klauspost/compress v1.17.11 // indirect + github.com/klauspost/cpuid/v2 v2.2.8 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect github.com/kylelemons/godebug v1.1.0 // indirect @@ -419,11 +423,12 @@ require ( github.com/shirou/gopsutil/v3 v3.24.3 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect github.com/sirupsen/logrus v1.9.3 // indirect + github.com/smartcontractkit/ccip-owner-contracts v0.0.0-salt-fix // indirect github.com/smartcontractkit/chainlink-cosmos v0.5.2-0.20241202195413-82468150ac1e // indirect - github.com/smartcontractkit/chainlink-data-streams v0.1.1-0.20241202141438-a90db35252db // indirect + github.com/smartcontractkit/chainlink-data-streams v0.1.1-0.20241216163550-fa030d178ba3 // indirect github.com/smartcontractkit/chainlink-feeds v0.1.1 // indirect - github.com/smartcontractkit/chainlink-protos/orchestrator v0.3.2 // indirect - github.com/smartcontractkit/chainlink-solana v1.1.1-0.20241204153209-c3a71b0eef99 // indirect + github.com/smartcontractkit/chainlink-protos/orchestrator v0.4.0 // indirect + github.com/smartcontractkit/chainlink-solana v1.1.1-0.20241210172617-6fd1891d0fbc // indirect github.com/smartcontractkit/chainlink-starknet/relayer v0.1.1-0.20241202202529-2033490e77b8 // indirect github.com/smartcontractkit/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7 // indirect github.com/smartcontractkit/tdh2/go/ocr2/decryptionplugin v0.0.0-20241009055228-33d0c0bf38de // indirect @@ -504,8 +509,8 @@ require ( golang.org/x/mod v0.21.0 // indirect golang.org/x/net v0.30.0 // indirect golang.org/x/oauth2 v0.23.0 // indirect - golang.org/x/sys v0.26.0 // indirect - golang.org/x/term v0.25.0 // indirect + golang.org/x/sys v0.28.0 // indirect + golang.org/x/term v0.27.0 // indirect golang.org/x/time v0.7.0 // indirect golang.org/x/tools v0.26.0 // indirect golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect diff --git a/integration-tests/go.sum b/integration-tests/go.sum index 4f31dd61871..71bf4dc9ccb 100644 --- a/integration-tests/go.sum +++ b/integration-tests/go.sum @@ -180,9 +180,11 @@ github.com/allegro/bigcache v1.2.1/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2uc github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129/go.mod h1:rFgpPQZYZ8vdbc+48xibu8ALc3yeyd64IhHS+PU6Yyg= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= -github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= -github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= +github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA= +github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/apache/arrow-go/v18 v18.0.0 h1:1dBDaSbH3LtulTyOVYaBCHO3yVRwjV+TZaqn3g6V7ZM= +github.com/apache/arrow-go/v18 v18.0.0/go.mod h1:t6+cWRSmKgdQ6HsxisQjok+jBpKGhRDiqcf3p0p/F+A= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= @@ -550,8 +552,8 @@ github.com/gedex/inflector v0.0.0-20170307190818-16278e9db813/go.mod h1:P+oSoE9y github.com/getsentry/sentry-go v0.27.0 h1:Pv98CIbtB3LkMWmXi4Joa5OOcwbmnX88sF5qbK3r3Ps= github.com/getsentry/sentry-go v0.27.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/gin-contrib/cors v1.5.0 h1:DgGKV7DDoOn36DFkNtbHrjoRiT5ExCe+PC9/xp7aKvk= -github.com/gin-contrib/cors v1.5.0/go.mod h1:TvU7MAZ3EwrPLI2ztzTt3tqgvBCq+wn8WpZmfADjupI= +github.com/gin-contrib/cors v1.7.2 h1:oLDHxdg8W/XDoN/8zamqk/Drgt4oVZDvaV0YmvVICQw= +github.com/gin-contrib/cors v1.7.2/go.mod h1:SUJVARKgQ40dmrzgXEVxj2m7Ig1v1qIboQkPDTQ9t2E= github.com/gin-contrib/expvar v0.0.1 h1:IuU5ArEgihz50vG8Onrwz22kJr7Mcvgv9xSSpfU5g+w= github.com/gin-contrib/expvar v0.0.1/go.mod h1:8o2CznfQi1JjktORdHr2/abg3wSV6OCnXh0yGypvvVw= github.com/gin-contrib/sessions v0.0.5 h1:CATtfHmLMQrMNpJRgzjWXD7worTh7g7ritsQfmF+0jE= @@ -642,8 +644,8 @@ github.com/go-webauthn/x v0.1.5 h1:V2TCzDU2TGLd0kSZOXdrqDVV5JB9ILnKxA9S53CSBw0= github.com/go-webauthn/x v0.1.5/go.mod h1:qbzWwcFcv4rTwtCLOZd+icnr6B7oSsAGZJqlt8cukqY= github.com/go-zookeeper/zk v1.0.3 h1:7M2kwOsc//9VeeFiPtf+uSJlVpU66x9Ba5+8XK7/TDg= github.com/go-zookeeper/zk v1.0.3/go.mod h1:nOB03cncLtlp4t+UAkGSV+9beXP/akpekBwL+UX1Qcw= -github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= -github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= +github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/goccy/go-yaml v1.12.0 h1:/1WHjnMsI1dlIBQutrvSMGZRQufVO3asrHfTwfACoPM= github.com/goccy/go-yaml v1.12.0/go.mod h1:wKnAMd44+9JAAnGQpWVEgBzGt3YuTaQ4uXoHvE4m7WU= github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 h1:ZpnhV/YsD2/4cESfV5+Hoeu/iUR3ruzNvZ+yQfO03a0= @@ -711,6 +713,8 @@ github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Z github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= +github.com/google/flatbuffers v24.3.25+incompatible h1:CX395cjN9Kke9mmalRoL3d81AtFUxJM+yDthflgJGkI= +github.com/google/flatbuffers v24.3.25+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 h1:0VpGH+cDhbDtdcweoyCVsF3fhN8kejK6rFe/2FFX2nU= github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49/go.mod h1:BkkQ4L1KS1xMt2aWSPStnn55ChGC0DPOn2FQYj+f25M= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -1039,11 +1043,11 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= -github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= -github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= +github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= -github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM= +github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b h1:udzkj9S/zlT5X367kqJis0QP7YMxobob6zhzq6Yre00= github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b/go.mod h1:pcaDhQK0/NJZEvtCO0qQPPropqV0sJOJ6YW7X+9kRwM= @@ -1100,6 +1104,8 @@ github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYt github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg= github.com/manyminds/api2go v0.0.0-20171030193247-e7b693844a6f h1:tVvGiZQFjOXP+9YyGqSA6jE55x1XVxmoPYudncxrZ8U= github.com/manyminds/api2go v0.0.0-20171030193247-e7b693844a6f/go.mod h1:Z60vy0EZVSu0bOugCHdcN5ZxFMKSpjRgsnh0XKPFqqk= +github.com/marcboeker/go-duckdb v1.8.3 h1:ZkYwiIZhbYsT6MmJsZ3UPTHrTZccDdM4ztoqSlEMXiQ= +github.com/marcboeker/go-duckdb v1.8.3/go.mod h1:C9bYRE1dPYb1hhfu/SSomm78B0FXmNgRvv6YBW/Hooc= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= @@ -1279,8 +1285,8 @@ github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5/go.mod h1:jvVRKCrJTQWu0XVbaOlby/2lO20uSCHEMzzplHXte1o= github.com/petermattis/goid v0.0.0-20230317030725-371a4b8eda08 h1:hDSdbBuw3Lefr6R18ax0tZ2BJeNB3NehB3trOwYBsdU= github.com/petermattis/goid v0.0.0-20230317030725-371a4b8eda08/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= -github.com/pierrec/lz4/v4 v4.1.18 h1:xaKrnTkyoqfh1YItXl56+6KJNVYWlEEPuAQW9xsplYQ= -github.com/pierrec/lz4/v4 v4.1.18/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= +github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= +github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= @@ -1424,34 +1430,34 @@ github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/slack-go/slack v0.15.0 h1:LE2lj2y9vqqiOf+qIIy0GvEoxgF1N5yLGZffmEZykt0= github.com/slack-go/slack v0.15.0/go.mod h1:hlGi5oXA+Gt+yWTPP0plCdRKmjsDxecdHxYQdlMQKOw= -github.com/smartcontractkit/ccip-owner-contracts v0.0.0-20240926212305-a6deabdfce86 h1:qQH6fZZe31nBAG6INHph3z5ysDTPptyu0TR9uoJ1+ok= -github.com/smartcontractkit/ccip-owner-contracts v0.0.0-20240926212305-a6deabdfce86/go.mod h1:WtWOoVQQEHxRHL2hNmuRrvDfYfQG/CioFNoa9Rr2mBE= +github.com/smartcontractkit/ccip-owner-contracts v0.0.0-salt-fix h1:DPJD++yKLSx0EfT+U14P8vLVxjXFmoIETiCO9lVwQo8= +github.com/smartcontractkit/ccip-owner-contracts v0.0.0-salt-fix/go.mod h1:NnT6w4Kj42OFFXhSx99LvJZWPpMjmo4+CpDEWfw61xY= github.com/smartcontractkit/chain-selectors v1.0.34 h1:MJ17OGu8+jjl426pcKrJkCf3fePb3eCreuAnUA3RBj4= github.com/smartcontractkit/chain-selectors v1.0.34/go.mod h1:xsKM0aN3YGcQKTPRPDDtPx2l4mlTN1Djmg0VVXV40b8= github.com/smartcontractkit/chainlink-automation v0.8.1 h1:sTc9LKpBvcKPc1JDYAmgBc2xpDKBco/Q4h4ydl6+UUU= github.com/smartcontractkit/chainlink-automation v0.8.1/go.mod h1:Iij36PvWZ6blrdC5A/nrQUBuf3MH3JvsBB9sSyc9W08= -github.com/smartcontractkit/chainlink-ccip v0.0.0-20241204015713-8956bb614e9e h1:GnM6ZWV6vlk2+n6c6o+v/R1LtXzBGVVx7r37nt/h6Uc= -github.com/smartcontractkit/chainlink-ccip v0.0.0-20241204015713-8956bb614e9e/go.mod h1:80vGBbOfertJig0xFKsRfm+i17FkjdKkk1dAaGE45Os= -github.com/smartcontractkit/chainlink-common v0.3.1-0.20241209151352-70300ddcc776 h1:NATQA1LfrEPXCdtEed9/G4SxaVuF8EZp5O2ucOK5C98= -github.com/smartcontractkit/chainlink-common v0.3.1-0.20241209151352-70300ddcc776/go.mod h1:bQktEJf7sJ0U3SmIcXvbGUox7SmXcnSEZ4kUbT8R5Nk= +github.com/smartcontractkit/chainlink-ccip v0.0.0-20241213122413-5e8f65dd6b1b h1:iSQJ6ng4FhEswf8SXunGkaJlVP3E3JlgLB8Oo2f3Ud4= +github.com/smartcontractkit/chainlink-ccip v0.0.0-20241213122413-5e8f65dd6b1b/go.mod h1:F8xQAIW0ymb2BZhqn89sWZLXreJhM5KDVF6Qb4y44N0= +github.com/smartcontractkit/chainlink-common v0.3.1-0.20241214155818-b403079b2805 h1:Pz8jB/6qe10xT10h2S3LFYJrnebNpG5rJ/w16HZGwPQ= +github.com/smartcontractkit/chainlink-common v0.3.1-0.20241214155818-b403079b2805/go.mod h1:yti7e1+G9hhkYhj+L5sVUULn9Bn3bBL5/AxaNqdJ5YQ= github.com/smartcontractkit/chainlink-cosmos v0.5.2-0.20241202195413-82468150ac1e h1:PRoeby6ZlTuTkv2f+7tVU4+zboTfRzI+beECynF4JQ0= github.com/smartcontractkit/chainlink-cosmos v0.5.2-0.20241202195413-82468150ac1e/go.mod h1:mUh5/woemsVaHgTorA080hrYmO3syBCmPdnWc/5dOqk= -github.com/smartcontractkit/chainlink-data-streams v0.1.1-0.20241202141438-a90db35252db h1:N1RH1hSr2ACzOFc9hkCcjE8pRBTdcU3p8nsTJByaLes= -github.com/smartcontractkit/chainlink-data-streams v0.1.1-0.20241202141438-a90db35252db/go.mod h1:yjb9d4q7+m8aGbjfTbkNoNuA4PeSxcUszsSZHDrvS0E= +github.com/smartcontractkit/chainlink-data-streams v0.1.1-0.20241216163550-fa030d178ba3 h1:aeiBdBHGY8QNftps+VqrIk6OnfeeOD5z4jrAabW4ZSc= +github.com/smartcontractkit/chainlink-data-streams v0.1.1-0.20241216163550-fa030d178ba3/go.mod h1:AS6zY2BkcRwfiGzNabGbHhfrLSrXrcI/GmjnT4jQ5/s= 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/job-distributor v0.6.0 h1:0ewLMbAz3rZrovdRUCgd028yOXX8KigB4FndAUdI2kM= github.com/smartcontractkit/chainlink-protos/job-distributor v0.6.0/go.mod h1:/dVVLXrsp+V0AbcYGJo3XMzKg3CkELsweA/TTopCsKE= -github.com/smartcontractkit/chainlink-protos/orchestrator v0.3.2 h1:onBe3DqNrbtOAzKS4PrPIiJX65BGo1aYiYZxFVEW+jc= -github.com/smartcontractkit/chainlink-protos/orchestrator v0.3.2/go.mod h1:m/A3lqD7ms/RsQ9BT5P2uceYY0QX5mIt4KQxT2G6qEo= -github.com/smartcontractkit/chainlink-solana v1.1.1-0.20241204153209-c3a71b0eef99 h1:lvn9Yxah+QD1/PcgijLO0dNRa28HuQWZl8Kkxh46KJc= -github.com/smartcontractkit/chainlink-solana v1.1.1-0.20241204153209-c3a71b0eef99/go.mod h1:p8aUDfJeley6oer7y+Ucd3edOtRlMTnWg3mN6rhaLWo= +github.com/smartcontractkit/chainlink-protos/orchestrator v0.4.0 h1:ZBat8EBvE2LpSQR9U1gEbRV6PfAkiFdINmQ8nVnXIAQ= +github.com/smartcontractkit/chainlink-protos/orchestrator v0.4.0/go.mod h1:m/A3lqD7ms/RsQ9BT5P2uceYY0QX5mIt4KQxT2G6qEo= +github.com/smartcontractkit/chainlink-solana v1.1.1-0.20241210172617-6fd1891d0fbc h1:dssRwJhmzJkUN/OajaDj2GsxBn+Tupk3bI1BkPEoJg0= +github.com/smartcontractkit/chainlink-solana v1.1.1-0.20241210172617-6fd1891d0fbc/go.mod h1:p8aUDfJeley6oer7y+Ucd3edOtRlMTnWg3mN6rhaLWo= github.com/smartcontractkit/chainlink-starknet/relayer v0.1.1-0.20241202202529-2033490e77b8 h1:tNS7U9lrxkFvEuyxQv11HHOiV9LPDGC9wYEy+yM/Jv4= github.com/smartcontractkit/chainlink-starknet/relayer v0.1.1-0.20241202202529-2033490e77b8/go.mod h1:EBrEgcdIbwepqguClkv8Ohy7CbyWSJaE4EC9aBJlQK0= github.com/smartcontractkit/chainlink-testing-framework/havoc v1.50.2 h1:GDGrC5OGiV0RyM1znYWehSQXyZQWTOzrEeJRYmysPCE= github.com/smartcontractkit/chainlink-testing-framework/havoc v1.50.2/go.mod h1:DsT43c1oTBmp3iQkMcoZOoKThwZvt8X3Pz6UmznJ4GY= -github.com/smartcontractkit/chainlink-testing-framework/lib v1.50.18 h1:a3xetGZh2nFO1iX5xd9OuqiCkgbWLvW6fTN6fgVubPo= -github.com/smartcontractkit/chainlink-testing-framework/lib v1.50.18/go.mod h1:NwmlNKqrb02v4Sci4b5KW644nfH2BW+FrKbWwTN5r6M= +github.com/smartcontractkit/chainlink-testing-framework/lib v1.50.19 h1:9PMwKNqFKc5FXf4VchyD3CGzZelnSgi13fgVdT2X7T4= +github.com/smartcontractkit/chainlink-testing-framework/lib v1.50.19/go.mod h1:ag7LEgejsVtPXaUNkcoFPpAoDkl1J8V2HSbqVUxfEtk= github.com/smartcontractkit/chainlink-testing-framework/lib/grafana v1.50.0 h1:VIxK8u0Jd0Q/VuhmsNm6Bls6Tb31H/sA3A/rbc5hnhg= github.com/smartcontractkit/chainlink-testing-framework/lib/grafana v1.50.0/go.mod h1:lyAu+oMXdNUzEDScj2DXB2IueY+SDXPPfyl/kb63tMM= github.com/smartcontractkit/chainlink-testing-framework/seth v1.50.9 h1:yB1x5UXvpZNka+5h57yo1/GrKfXKCqMzChCISpldZx4= @@ -1614,6 +1620,8 @@ github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= +github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= +github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -1625,6 +1633,8 @@ github.com/yuin/gopher-lua v1.1.0 h1:BojcDhfyDWgU2f2TOzYK/g5p2gxMrku8oupLDqlnSqE github.com/yuin/gopher-lua v1.1.0/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0= +github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= github.com/zondax/hid v0.9.2 h1:WCJFnEDMiqGF64nlZz28E9qLVZ0KSJ7xpc5DLEyma2U= github.com/zondax/hid v0.9.2/go.mod h1:l5wttcP0jwtdLjqjMMWFVEE7d1zO0jvSPA9OPZxWpEM= @@ -1772,8 +1782,8 @@ golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0 golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= -golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= -golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= +golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= +golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -1903,8 +1913,8 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -2000,8 +2010,8 @@ golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= -golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -2012,8 +2022,8 @@ golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= -golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= -golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= +golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= +golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -2028,8 +2038,8 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= -golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= diff --git a/integration-tests/load/go.mod b/integration-tests/load/go.mod index 47b128c7f60..96c3bb1dbce 100644 --- a/integration-tests/load/go.mod +++ b/integration-tests/load/go.mod @@ -11,6 +11,14 @@ replace github.com/smartcontractkit/chainlink/deployment => ../../deployment replace github.com/smartcontractkit/chainlink/integration-tests => ../ +// Using a separate `require` here to avoid surrounding line changes +// creating potential merge conflicts. +require ( + github.com/smartcontractkit/chainlink/deployment v0.0.0-20241206210521-125d98cdaf66 + github.com/smartcontractkit/chainlink/integration-tests v0.0.0-20241206210521-125d98cdaf66 + github.com/smartcontractkit/chainlink/v2 v2.0.0-20241206210521-125d98cdaf66 +) + require ( github.com/K-Phoen/grabana v0.22.2 github.com/ethereum/go-ethereum v1.14.11 @@ -19,13 +27,10 @@ require ( github.com/pkg/errors v0.9.1 github.com/rs/zerolog v1.33.0 github.com/slack-go/slack v0.15.0 - github.com/smartcontractkit/chainlink-common v0.3.1-0.20241209151352-70300ddcc776 - github.com/smartcontractkit/chainlink-testing-framework/lib v1.50.18 + github.com/smartcontractkit/chainlink-common v0.3.1-0.20241214155818-b403079b2805 + github.com/smartcontractkit/chainlink-testing-framework/lib v1.50.19 github.com/smartcontractkit/chainlink-testing-framework/seth v1.50.9 github.com/smartcontractkit/chainlink-testing-framework/wasp v1.50.2 - github.com/smartcontractkit/chainlink/deployment v0.0.0-20241120141814-47da13e86197 - github.com/smartcontractkit/chainlink/integration-tests v0.0.0-20241030133659-9ec788e78b4f - github.com/smartcontractkit/chainlink/v2 v2.14.0-mercury-20240807.0.20241120195829-bd7a1943ad07 github.com/smartcontractkit/tdh2/go/tdh2 v0.0.0-20241009055228-33d0c0bf38de github.com/stretchr/testify v1.9.0 github.com/wiremock/go-wiremock v1.9.0 @@ -67,7 +72,7 @@ require ( github.com/VictoriaMetrics/fastcache v1.12.2 // indirect github.com/XSAM/otelsql v0.27.0 // indirect github.com/alecthomas/units v0.0.0-20240626203959-61d1e3462e30 // indirect - github.com/andybalholm/brotli v1.1.0 // indirect + github.com/andybalholm/brotli v1.1.1 // indirect github.com/armon/go-metrics v0.4.1 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect github.com/atombender/go-jsonschema v0.16.1-0.20240916205339-a74cd4e2851c // indirect @@ -211,7 +216,7 @@ require ( github.com/go-viper/mapstructure/v2 v2.1.0 // indirect github.com/go-webauthn/webauthn v0.9.4 // indirect github.com/go-webauthn/x v0.1.5 // indirect - github.com/goccy/go-json v0.10.2 // indirect + github.com/goccy/go-json v0.10.3 // indirect github.com/goccy/go-yaml v1.12.0 // indirect github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect github.com/gofrs/flock v0.8.1 // indirect @@ -307,8 +312,8 @@ require ( github.com/json-iterator/go v1.1.12 // indirect github.com/julienschmidt/httprouter v1.3.0 // indirect github.com/kelseyhightower/envconfig v1.4.0 // indirect - github.com/klauspost/compress v1.17.9 // indirect - github.com/klauspost/cpuid/v2 v2.2.7 // indirect + github.com/klauspost/compress v1.17.11 // indirect + github.com/klauspost/cpuid/v2 v2.2.8 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect github.com/kylelemons/godebug v1.1.0 // indirect @@ -393,6 +398,7 @@ require ( github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 // indirect github.com/segmentio/ksuid v1.0.4 // indirect github.com/sercand/kuberesolver/v5 v5.1.1 // indirect + github.com/sethvargo/go-retry v0.2.4 // indirect github.com/shirou/gopsutil v3.21.11+incompatible // indirect github.com/shirou/gopsutil/v3 v3.24.3 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect @@ -401,12 +407,12 @@ require ( github.com/sirupsen/logrus v1.9.3 // indirect github.com/smartcontractkit/chain-selectors v1.0.34 // indirect github.com/smartcontractkit/chainlink-automation v0.8.1 // indirect - github.com/smartcontractkit/chainlink-ccip v0.0.0-20241204015713-8956bb614e9e // indirect + github.com/smartcontractkit/chainlink-ccip v0.0.0-20241213122413-5e8f65dd6b1b // indirect github.com/smartcontractkit/chainlink-cosmos v0.5.2-0.20241202195413-82468150ac1e // indirect - github.com/smartcontractkit/chainlink-data-streams v0.1.1-0.20241202141438-a90db35252db // indirect + github.com/smartcontractkit/chainlink-data-streams v0.1.1-0.20241216163550-fa030d178ba3 // indirect github.com/smartcontractkit/chainlink-feeds v0.1.1 // indirect - github.com/smartcontractkit/chainlink-protos/orchestrator v0.3.2 // indirect - github.com/smartcontractkit/chainlink-solana v1.1.1-0.20241204153209-c3a71b0eef99 // indirect + github.com/smartcontractkit/chainlink-protos/orchestrator v0.4.0 // indirect + github.com/smartcontractkit/chainlink-solana v1.1.1-0.20241210172617-6fd1891d0fbc // indirect github.com/smartcontractkit/chainlink-starknet/relayer v0.1.1-0.20241202202529-2033490e77b8 // indirect github.com/smartcontractkit/chainlink-testing-framework/havoc v1.50.2 // indirect github.com/smartcontractkit/chainlink-testing-framework/lib/grafana v1.50.0 // indirect @@ -493,14 +499,14 @@ require ( go.uber.org/zap v1.27.0 // indirect go4.org/netipx v0.0.0-20230125063823-8449b0a6169f // indirect golang.org/x/arch v0.11.0 // indirect - golang.org/x/crypto v0.28.0 // indirect + golang.org/x/crypto v0.31.0 // indirect golang.org/x/mod v0.21.0 // indirect golang.org/x/net v0.30.0 // indirect golang.org/x/oauth2 v0.23.0 // indirect - golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.26.0 // indirect - golang.org/x/term v0.25.0 // indirect - golang.org/x/text v0.19.0 // indirect + golang.org/x/sync v0.10.0 // indirect + golang.org/x/sys v0.28.0 // indirect + golang.org/x/term v0.27.0 // indirect + golang.org/x/text v0.21.0 // indirect golang.org/x/time v0.7.0 // indirect golang.org/x/tools v0.26.0 // indirect golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect diff --git a/integration-tests/load/go.sum b/integration-tests/load/go.sum index 59a4e9e64ad..79817cf4311 100644 --- a/integration-tests/load/go.sum +++ b/integration-tests/load/go.sum @@ -184,9 +184,11 @@ github.com/allegro/bigcache v1.2.1/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2uc github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129/go.mod h1:rFgpPQZYZ8vdbc+48xibu8ALc3yeyd64IhHS+PU6Yyg= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= -github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= -github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= +github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA= +github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/apache/arrow-go/v18 v18.0.0 h1:1dBDaSbH3LtulTyOVYaBCHO3yVRwjV+TZaqn3g6V7ZM= +github.com/apache/arrow-go/v18 v18.0.0/go.mod h1:t6+cWRSmKgdQ6HsxisQjok+jBpKGhRDiqcf3p0p/F+A= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= @@ -544,8 +546,8 @@ github.com/gedex/inflector v0.0.0-20170307190818-16278e9db813/go.mod h1:P+oSoE9y github.com/getsentry/sentry-go v0.27.0 h1:Pv98CIbtB3LkMWmXi4Joa5OOcwbmnX88sF5qbK3r3Ps= github.com/getsentry/sentry-go v0.27.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/gin-contrib/cors v1.5.0 h1:DgGKV7DDoOn36DFkNtbHrjoRiT5ExCe+PC9/xp7aKvk= -github.com/gin-contrib/cors v1.5.0/go.mod h1:TvU7MAZ3EwrPLI2ztzTt3tqgvBCq+wn8WpZmfADjupI= +github.com/gin-contrib/cors v1.7.2 h1:oLDHxdg8W/XDoN/8zamqk/Drgt4oVZDvaV0YmvVICQw= +github.com/gin-contrib/cors v1.7.2/go.mod h1:SUJVARKgQ40dmrzgXEVxj2m7Ig1v1qIboQkPDTQ9t2E= github.com/gin-contrib/expvar v0.0.1 h1:IuU5ArEgihz50vG8Onrwz22kJr7Mcvgv9xSSpfU5g+w= github.com/gin-contrib/expvar v0.0.1/go.mod h1:8o2CznfQi1JjktORdHr2/abg3wSV6OCnXh0yGypvvVw= github.com/gin-contrib/sessions v0.0.5 h1:CATtfHmLMQrMNpJRgzjWXD7worTh7g7ritsQfmF+0jE= @@ -636,8 +638,8 @@ github.com/go-webauthn/x v0.1.5 h1:V2TCzDU2TGLd0kSZOXdrqDVV5JB9ILnKxA9S53CSBw0= github.com/go-webauthn/x v0.1.5/go.mod h1:qbzWwcFcv4rTwtCLOZd+icnr6B7oSsAGZJqlt8cukqY= github.com/go-zookeeper/zk v1.0.3 h1:7M2kwOsc//9VeeFiPtf+uSJlVpU66x9Ba5+8XK7/TDg= github.com/go-zookeeper/zk v1.0.3/go.mod h1:nOB03cncLtlp4t+UAkGSV+9beXP/akpekBwL+UX1Qcw= -github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= -github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= +github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/goccy/go-yaml v1.12.0 h1:/1WHjnMsI1dlIBQutrvSMGZRQufVO3asrHfTwfACoPM= github.com/goccy/go-yaml v1.12.0/go.mod h1:wKnAMd44+9JAAnGQpWVEgBzGt3YuTaQ4uXoHvE4m7WU= github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 h1:ZpnhV/YsD2/4cESfV5+Hoeu/iUR3ruzNvZ+yQfO03a0= @@ -705,6 +707,8 @@ github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Z github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= +github.com/google/flatbuffers v24.3.25+incompatible h1:CX395cjN9Kke9mmalRoL3d81AtFUxJM+yDthflgJGkI= +github.com/google/flatbuffers v24.3.25+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 h1:0VpGH+cDhbDtdcweoyCVsF3fhN8kejK6rFe/2FFX2nU= github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49/go.mod h1:BkkQ4L1KS1xMt2aWSPStnn55ChGC0DPOn2FQYj+f25M= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -1035,11 +1039,11 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= -github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= -github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= +github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= -github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM= +github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b h1:udzkj9S/zlT5X367kqJis0QP7YMxobob6zhzq6Yre00= github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b/go.mod h1:pcaDhQK0/NJZEvtCO0qQPPropqV0sJOJ6YW7X+9kRwM= @@ -1094,6 +1098,8 @@ github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYt github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg= github.com/manyminds/api2go v0.0.0-20171030193247-e7b693844a6f h1:tVvGiZQFjOXP+9YyGqSA6jE55x1XVxmoPYudncxrZ8U= github.com/manyminds/api2go v0.0.0-20171030193247-e7b693844a6f/go.mod h1:Z60vy0EZVSu0bOugCHdcN5ZxFMKSpjRgsnh0XKPFqqk= +github.com/marcboeker/go-duckdb v1.8.3 h1:ZkYwiIZhbYsT6MmJsZ3UPTHrTZccDdM4ztoqSlEMXiQ= +github.com/marcboeker/go-duckdb v1.8.3/go.mod h1:C9bYRE1dPYb1hhfu/SSomm78B0FXmNgRvv6YBW/Hooc= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= @@ -1269,8 +1275,8 @@ github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5/go.mod h1:jvVRKCrJTQWu0XVbaOlby/2lO20uSCHEMzzplHXte1o= github.com/petermattis/goid v0.0.0-20230317030725-371a4b8eda08 h1:hDSdbBuw3Lefr6R18ax0tZ2BJeNB3NehB3trOwYBsdU= github.com/petermattis/goid v0.0.0-20230317030725-371a4b8eda08/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= -github.com/pierrec/lz4/v4 v4.1.18 h1:xaKrnTkyoqfh1YItXl56+6KJNVYWlEEPuAQW9xsplYQ= -github.com/pierrec/lz4/v4 v4.1.18/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= +github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= +github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= @@ -1415,34 +1421,34 @@ github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/slack-go/slack v0.15.0 h1:LE2lj2y9vqqiOf+qIIy0GvEoxgF1N5yLGZffmEZykt0= github.com/slack-go/slack v0.15.0/go.mod h1:hlGi5oXA+Gt+yWTPP0plCdRKmjsDxecdHxYQdlMQKOw= -github.com/smartcontractkit/ccip-owner-contracts v0.0.0-20240926212305-a6deabdfce86 h1:qQH6fZZe31nBAG6INHph3z5ysDTPptyu0TR9uoJ1+ok= -github.com/smartcontractkit/ccip-owner-contracts v0.0.0-20240926212305-a6deabdfce86/go.mod h1:WtWOoVQQEHxRHL2hNmuRrvDfYfQG/CioFNoa9Rr2mBE= +github.com/smartcontractkit/ccip-owner-contracts v0.0.0-salt-fix h1:DPJD++yKLSx0EfT+U14P8vLVxjXFmoIETiCO9lVwQo8= +github.com/smartcontractkit/ccip-owner-contracts v0.0.0-salt-fix/go.mod h1:NnT6w4Kj42OFFXhSx99LvJZWPpMjmo4+CpDEWfw61xY= github.com/smartcontractkit/chain-selectors v1.0.34 h1:MJ17OGu8+jjl426pcKrJkCf3fePb3eCreuAnUA3RBj4= github.com/smartcontractkit/chain-selectors v1.0.34/go.mod h1:xsKM0aN3YGcQKTPRPDDtPx2l4mlTN1Djmg0VVXV40b8= github.com/smartcontractkit/chainlink-automation v0.8.1 h1:sTc9LKpBvcKPc1JDYAmgBc2xpDKBco/Q4h4ydl6+UUU= github.com/smartcontractkit/chainlink-automation v0.8.1/go.mod h1:Iij36PvWZ6blrdC5A/nrQUBuf3MH3JvsBB9sSyc9W08= -github.com/smartcontractkit/chainlink-ccip v0.0.0-20241204015713-8956bb614e9e h1:GnM6ZWV6vlk2+n6c6o+v/R1LtXzBGVVx7r37nt/h6Uc= -github.com/smartcontractkit/chainlink-ccip v0.0.0-20241204015713-8956bb614e9e/go.mod h1:80vGBbOfertJig0xFKsRfm+i17FkjdKkk1dAaGE45Os= -github.com/smartcontractkit/chainlink-common v0.3.1-0.20241209151352-70300ddcc776 h1:NATQA1LfrEPXCdtEed9/G4SxaVuF8EZp5O2ucOK5C98= -github.com/smartcontractkit/chainlink-common v0.3.1-0.20241209151352-70300ddcc776/go.mod h1:bQktEJf7sJ0U3SmIcXvbGUox7SmXcnSEZ4kUbT8R5Nk= +github.com/smartcontractkit/chainlink-ccip v0.0.0-20241213122413-5e8f65dd6b1b h1:iSQJ6ng4FhEswf8SXunGkaJlVP3E3JlgLB8Oo2f3Ud4= +github.com/smartcontractkit/chainlink-ccip v0.0.0-20241213122413-5e8f65dd6b1b/go.mod h1:F8xQAIW0ymb2BZhqn89sWZLXreJhM5KDVF6Qb4y44N0= +github.com/smartcontractkit/chainlink-common v0.3.1-0.20241214155818-b403079b2805 h1:Pz8jB/6qe10xT10h2S3LFYJrnebNpG5rJ/w16HZGwPQ= +github.com/smartcontractkit/chainlink-common v0.3.1-0.20241214155818-b403079b2805/go.mod h1:yti7e1+G9hhkYhj+L5sVUULn9Bn3bBL5/AxaNqdJ5YQ= github.com/smartcontractkit/chainlink-cosmos v0.5.2-0.20241202195413-82468150ac1e h1:PRoeby6ZlTuTkv2f+7tVU4+zboTfRzI+beECynF4JQ0= github.com/smartcontractkit/chainlink-cosmos v0.5.2-0.20241202195413-82468150ac1e/go.mod h1:mUh5/woemsVaHgTorA080hrYmO3syBCmPdnWc/5dOqk= -github.com/smartcontractkit/chainlink-data-streams v0.1.1-0.20241202141438-a90db35252db h1:N1RH1hSr2ACzOFc9hkCcjE8pRBTdcU3p8nsTJByaLes= -github.com/smartcontractkit/chainlink-data-streams v0.1.1-0.20241202141438-a90db35252db/go.mod h1:yjb9d4q7+m8aGbjfTbkNoNuA4PeSxcUszsSZHDrvS0E= +github.com/smartcontractkit/chainlink-data-streams v0.1.1-0.20241216163550-fa030d178ba3 h1:aeiBdBHGY8QNftps+VqrIk6OnfeeOD5z4jrAabW4ZSc= +github.com/smartcontractkit/chainlink-data-streams v0.1.1-0.20241216163550-fa030d178ba3/go.mod h1:AS6zY2BkcRwfiGzNabGbHhfrLSrXrcI/GmjnT4jQ5/s= 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/job-distributor v0.6.0 h1:0ewLMbAz3rZrovdRUCgd028yOXX8KigB4FndAUdI2kM= github.com/smartcontractkit/chainlink-protos/job-distributor v0.6.0/go.mod h1:/dVVLXrsp+V0AbcYGJo3XMzKg3CkELsweA/TTopCsKE= -github.com/smartcontractkit/chainlink-protos/orchestrator v0.3.2 h1:onBe3DqNrbtOAzKS4PrPIiJX65BGo1aYiYZxFVEW+jc= -github.com/smartcontractkit/chainlink-protos/orchestrator v0.3.2/go.mod h1:m/A3lqD7ms/RsQ9BT5P2uceYY0QX5mIt4KQxT2G6qEo= -github.com/smartcontractkit/chainlink-solana v1.1.1-0.20241204153209-c3a71b0eef99 h1:lvn9Yxah+QD1/PcgijLO0dNRa28HuQWZl8Kkxh46KJc= -github.com/smartcontractkit/chainlink-solana v1.1.1-0.20241204153209-c3a71b0eef99/go.mod h1:p8aUDfJeley6oer7y+Ucd3edOtRlMTnWg3mN6rhaLWo= +github.com/smartcontractkit/chainlink-protos/orchestrator v0.4.0 h1:ZBat8EBvE2LpSQR9U1gEbRV6PfAkiFdINmQ8nVnXIAQ= +github.com/smartcontractkit/chainlink-protos/orchestrator v0.4.0/go.mod h1:m/A3lqD7ms/RsQ9BT5P2uceYY0QX5mIt4KQxT2G6qEo= +github.com/smartcontractkit/chainlink-solana v1.1.1-0.20241210172617-6fd1891d0fbc h1:dssRwJhmzJkUN/OajaDj2GsxBn+Tupk3bI1BkPEoJg0= +github.com/smartcontractkit/chainlink-solana v1.1.1-0.20241210172617-6fd1891d0fbc/go.mod h1:p8aUDfJeley6oer7y+Ucd3edOtRlMTnWg3mN6rhaLWo= github.com/smartcontractkit/chainlink-starknet/relayer v0.1.1-0.20241202202529-2033490e77b8 h1:tNS7U9lrxkFvEuyxQv11HHOiV9LPDGC9wYEy+yM/Jv4= github.com/smartcontractkit/chainlink-starknet/relayer v0.1.1-0.20241202202529-2033490e77b8/go.mod h1:EBrEgcdIbwepqguClkv8Ohy7CbyWSJaE4EC9aBJlQK0= github.com/smartcontractkit/chainlink-testing-framework/havoc v1.50.2 h1:GDGrC5OGiV0RyM1znYWehSQXyZQWTOzrEeJRYmysPCE= github.com/smartcontractkit/chainlink-testing-framework/havoc v1.50.2/go.mod h1:DsT43c1oTBmp3iQkMcoZOoKThwZvt8X3Pz6UmznJ4GY= -github.com/smartcontractkit/chainlink-testing-framework/lib v1.50.18 h1:a3xetGZh2nFO1iX5xd9OuqiCkgbWLvW6fTN6fgVubPo= -github.com/smartcontractkit/chainlink-testing-framework/lib v1.50.18/go.mod h1:NwmlNKqrb02v4Sci4b5KW644nfH2BW+FrKbWwTN5r6M= +github.com/smartcontractkit/chainlink-testing-framework/lib v1.50.19 h1:9PMwKNqFKc5FXf4VchyD3CGzZelnSgi13fgVdT2X7T4= +github.com/smartcontractkit/chainlink-testing-framework/lib v1.50.19/go.mod h1:ag7LEgejsVtPXaUNkcoFPpAoDkl1J8V2HSbqVUxfEtk= github.com/smartcontractkit/chainlink-testing-framework/lib/grafana v1.50.0 h1:VIxK8u0Jd0Q/VuhmsNm6Bls6Tb31H/sA3A/rbc5hnhg= github.com/smartcontractkit/chainlink-testing-framework/lib/grafana v1.50.0/go.mod h1:lyAu+oMXdNUzEDScj2DXB2IueY+SDXPPfyl/kb63tMM= github.com/smartcontractkit/chainlink-testing-framework/seth v1.50.9 h1:yB1x5UXvpZNka+5h57yo1/GrKfXKCqMzChCISpldZx4= @@ -1605,6 +1611,8 @@ github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= +github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= +github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -1616,6 +1624,8 @@ github.com/yuin/gopher-lua v1.1.0 h1:BojcDhfyDWgU2f2TOzYK/g5p2gxMrku8oupLDqlnSqE github.com/yuin/gopher-lua v1.1.0/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0= +github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= github.com/zondax/hid v0.9.2 h1:WCJFnEDMiqGF64nlZz28E9qLVZ0KSJ7xpc5DLEyma2U= github.com/zondax/hid v0.9.2/go.mod h1:l5wttcP0jwtdLjqjMMWFVEE7d1zO0jvSPA9OPZxWpEM= @@ -1763,8 +1773,8 @@ golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0 golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= -golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= -golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= +golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= +golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -1894,8 +1904,8 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1989,8 +1999,8 @@ golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= -golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -2001,8 +2011,8 @@ golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= -golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= -golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= +golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= +golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -2017,8 +2027,8 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= -golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= diff --git a/integration-tests/smoke/ccip/ccip_batching_test.go b/integration-tests/smoke/ccip/ccip_batching_test.go index 8c3615fbb20..58f4e922ac5 100644 --- a/integration-tests/smoke/ccip/ccip_batching_test.go +++ b/integration-tests/smoke/ccip/ccip_batching_test.go @@ -14,7 +14,8 @@ import ( "github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3" "github.com/smartcontractkit/chainlink-common/pkg/merklemulti" - "github.com/smartcontractkit/chainlink/deployment/environment/memory" + + testsetups "github.com/smartcontractkit/chainlink/integration-tests/testsetups/ccip" "github.com/smartcontractkit/chainlink/deployment" "github.com/smartcontractkit/chainlink/deployment/ccip/changeset" @@ -22,7 +23,6 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/onramp" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/router" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/shared/generated/multicall3" - "github.com/smartcontractkit/chainlink/v2/core/logger" ) const ( @@ -39,18 +39,11 @@ type batchTestSetup struct { func newBatchTestSetup(t *testing.T) batchTestSetup { // Setup 3 chains, with 2 lanes going to the dest. - e := changeset.NewMemoryEnvironmentWithJobsAndContracts( + e, _ := testsetups.NewIntegrationEnvironment( t, - logger.TestLogger(t), - memory.MemoryEnvironmentConfig{ - Chains: 3, - Nodes: 4, - Bootstraps: 1, - NumOfUsersPerChain: 2, - }, - &changeset.TestConfigs{ - IsMultiCall3: true, - }, + changeset.WithMultiCall3(), + changeset.WithChains(3), + changeset.WithUsersPerChain(2), ) state, err := changeset.LoadOnchainState(e.Env) diff --git a/integration-tests/smoke/ccip/ccip_fee_boosting_test.go b/integration-tests/smoke/ccip/ccip_fee_boosting_test.go index 1fe9d5817c9..48d9061ec63 100644 --- a/integration-tests/smoke/ccip/ccip_fee_boosting_test.go +++ b/integration-tests/smoke/ccip/ccip_fee_boosting_test.go @@ -9,8 +9,8 @@ import ( "github.com/pkg/errors" - "github.com/smartcontractkit/chainlink-common/pkg/config" commonchangeset "github.com/smartcontractkit/chainlink/deployment/common/changeset" + testsetups "github.com/smartcontractkit/chainlink/integration-tests/testsetups/ccip" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" @@ -20,7 +20,6 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/testcontext" - "github.com/smartcontractkit/chainlink/deployment/environment/memory" "github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/ccipevm" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/fee_quoter" @@ -30,7 +29,6 @@ import ( "github.com/smartcontractkit/chainlink/deployment/ccip/changeset" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/onramp" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/router" - "github.com/smartcontractkit/chainlink/v2/core/logger" ) var ( @@ -39,13 +37,10 @@ var ( ) func Test_CCIPFeeBoosting(t *testing.T) { - e := changeset.NewMemoryEnvironmentWithJobsAndContracts(t, logger.TestLogger(t), - memory.MemoryEnvironmentConfig{ - Chains: 2, - Nodes: 4, - Bootstraps: 1, - }, &changeset.TestConfigs{ - OCRConfigOverride: func(params changeset.CCIPOCRParams) changeset.CCIPOCRParams { + e, _ := testsetups.NewIntegrationEnvironment( + t, + // TODO check if test should use these overrides + /* changeset.WithOCRConfigOverride(func(params changeset.CCIPOCRParams) changeset.CCIPOCRParams { // Only 1 boost (=OCR round) is enough to cover the fee params.ExecuteOffChainConfig.RelativeBoostPerWaitHour = 10 // Disable token price updates @@ -55,8 +50,10 @@ func Test_CCIPFeeBoosting(t *testing.T) { // Disable token price updates params.CommitOffChainConfig.TokenInfo = nil return params - }, - }) + }), + + */ + ) state, err := changeset.LoadOnchainState(e.Env) require.NoError(t, err) diff --git a/integration-tests/smoke/ccip/ccip_fees_test.go b/integration-tests/smoke/ccip/ccip_fees_test.go index 791ba8f2619..55788a4aa5f 100644 --- a/integration-tests/smoke/ccip/ccip_fees_test.go +++ b/integration-tests/smoke/ccip/ccip_fees_test.go @@ -14,7 +14,7 @@ import ( "github.com/smartcontractkit/chainlink/deployment" "github.com/smartcontractkit/chainlink/deployment/ccip/changeset" - "github.com/smartcontractkit/chainlink/deployment/environment/memory" + testsetups "github.com/smartcontractkit/chainlink/integration-tests/testsetups/ccip" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/router" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/weth9_wrapper" @@ -101,11 +101,10 @@ func setupTokens( func Test_CCIPFees(t *testing.T) { t.Parallel() - tenv := changeset.NewMemoryEnvironmentWithJobsAndContracts(t, logger.TestLogger(t), memory.MemoryEnvironmentConfig{ - Chains: 2, - Nodes: 4, - Bootstraps: 1, - }, nil) + tenv, _ := testsetups.NewIntegrationEnvironment( + t, + changeset.WithMultiCall3(), + ) e := tenv.Env allChains := tenv.Env.AllChainSelectors() diff --git a/integration-tests/smoke/ccip/ccip_gas_price_updates_test.go b/integration-tests/smoke/ccip/ccip_gas_price_updates_test.go index 221d35bd992..d11e4304366 100644 --- a/integration-tests/smoke/ccip/ccip_gas_price_updates_test.go +++ b/integration-tests/smoke/ccip/ccip_gas_price_updates_test.go @@ -12,27 +12,26 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/config" "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" + "github.com/smartcontractkit/chainlink/deployment" "github.com/smartcontractkit/chainlink/deployment/ccip/changeset" testsetups "github.com/smartcontractkit/chainlink/integration-tests/testsetups/ccip" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/fee_quoter" - "github.com/smartcontractkit/chainlink/v2/core/logger" ) // Test_CCIPGasPriceUpdates tests that chain fee price updates are propagated correctly when // price reaches some deviation threshold or when the price has expired. func Test_CCIPGasPriceUpdates(t *testing.T) { - lggr := logger.TestLogger(t) ctx := changeset.Context(t) callOpts := &bind.CallOpts{Context: ctx} var gasPriceExpiry = 5 * time.Second - e, _, _ := testsetups.NewLocalDevEnvironmentWithDefaultPrice(t, lggr, &changeset.TestConfigs{ - OCRConfigOverride: func(params changeset.CCIPOCRParams) changeset.CCIPOCRParams { + e, _ := testsetups.NewIntegrationEnvironment(t, + changeset.WithOCRConfigOverride(func(params changeset.CCIPOCRParams) changeset.CCIPOCRParams { params.CommitOffChainConfig.RemoteGasPriceBatchWriteFrequency = *config.MustNewDuration(gasPriceExpiry) return params - }, - }) + }), + ) state, err := changeset.LoadOnchainState(e.Env) require.NoError(t, err) require.NoError(t, changeset.AddLanesForAll(e.Env, state)) diff --git a/integration-tests/smoke/ccip/ccip_message_limitations_test.go b/integration-tests/smoke/ccip/ccip_message_limitations_test.go index 902d07aec5c..9398fd9f932 100644 --- a/integration-tests/smoke/ccip/ccip_message_limitations_test.go +++ b/integration-tests/smoke/ccip/ccip_message_limitations_test.go @@ -17,15 +17,13 @@ import ( "github.com/smartcontractkit/chainlink/deployment/ccip/changeset" testsetups "github.com/smartcontractkit/chainlink/integration-tests/testsetups/ccip" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/router" - "github.com/smartcontractkit/chainlink/v2/core/logger" ) func Test_CCIPMessageLimitations(t *testing.T) { - lggr := logger.TestLogger(t) ctx := testcontext.Get(t) callOpts := &bind.CallOpts{Context: ctx} - testEnv, _, _ := testsetups.NewLocalDevEnvironmentWithDefaultPrice(t, lggr, &changeset.TestConfigs{}) + testEnv, _ := testsetups.NewIntegrationEnvironment(t) chains := maps.Keys(testEnv.Env.Chains) onChainState, err := changeset.LoadOnchainState(testEnv.Env) diff --git a/integration-tests/smoke/ccip/ccip_messaging_test.go b/integration-tests/smoke/ccip/ccip_messaging_test.go index 07e237451c8..13f14fcda16 100644 --- a/integration-tests/smoke/ccip/ccip_messaging_test.go +++ b/integration-tests/smoke/ccip/ccip_messaging_test.go @@ -18,11 +18,10 @@ import ( "github.com/smartcontractkit/chainlink/deployment" "github.com/smartcontractkit/chainlink/deployment/ccip/changeset" - "github.com/smartcontractkit/chainlink/deployment/environment/memory" + testsetups "github.com/smartcontractkit/chainlink/integration-tests/testsetups/ccip" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/offramp" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/onramp" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/router" - "github.com/smartcontractkit/chainlink/v2/core/logger" ) type testCaseSetup struct { @@ -48,11 +47,7 @@ type messagingTestCaseOutput struct { func Test_CCIPMessaging(t *testing.T) { // Setup 2 chains and a single lane. ctx := changeset.Context(t) - e := changeset.NewMemoryEnvironmentWithJobsAndContracts(t, logger.TestLogger(t), memory.MemoryEnvironmentConfig{ - Chains: 2, - Nodes: 4, - Bootstraps: 1, - }, nil) + e, _ := testsetups.NewIntegrationEnvironment(t) state, err := changeset.LoadOnchainState(e.Env) require.NoError(t, err) diff --git a/integration-tests/smoke/ccip/ccip_ooo_execution_test.go b/integration-tests/smoke/ccip/ccip_ooo_execution_test.go index 86ddd07ec85..19c36c6e021 100644 --- a/integration-tests/smoke/ccip/ccip_ooo_execution_test.go +++ b/integration-tests/smoke/ccip/ccip_ooo_execution_test.go @@ -12,9 +12,10 @@ import ( "github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3" "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" + "github.com/smartcontractkit/chainlink/deployment" "github.com/smartcontractkit/chainlink/deployment/ccip/changeset" - "github.com/smartcontractkit/chainlink/deployment/environment/memory" + testsetups "github.com/smartcontractkit/chainlink/integration-tests/testsetups/ccip" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/router" "github.com/smartcontractkit/chainlink/v2/core/logger" @@ -32,16 +33,12 @@ import ( func Test_OutOfOrderExecution(t *testing.T) { lggr := logger.TestLogger(t) ctx := tests.Context(t) - config := &changeset.TestConfigs{ - IsUSDC: true, - IsUSDCAttestationMissing: true, - } - tenv := changeset.NewMemoryEnvironmentWithJobsAndContracts(t, logger.TestLogger(t), memory.MemoryEnvironmentConfig{ - Chains: 2, - Nodes: 4, - Bootstraps: 1, - NumOfUsersPerChain: 2, - }, config) + tenv, _ := testsetups.NewIntegrationEnvironment( + t, + changeset.WithUSDC(), + changeset.WithUSDCAttestationMissing(), + changeset.WithUsersPerChain(2), + ) e := tenv.Env state, err := changeset.LoadOnchainState(e) diff --git a/integration-tests/smoke/ccip/ccip_rmn_test.go b/integration-tests/smoke/ccip/ccip_rmn_test.go index 6cd6bd9d63f..166f4422fe6 100644 --- a/integration-tests/smoke/ccip/ccip_rmn_test.go +++ b/integration-tests/smoke/ccip/ccip_rmn_test.go @@ -18,13 +18,14 @@ import ( "github.com/rs/zerolog" "github.com/stretchr/testify/require" - "github.com/smartcontractkit/chainlink-ccip/pkg/reader" "github.com/smartcontractkit/chainlink-protos/job-distributor/v1/node" "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/osutil" "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/testcontext" - "github.com/smartcontractkit/chainlink/deployment/environment/devenv" + + "github.com/smartcontractkit/chainlink-ccip/pkg/reader" "github.com/smartcontractkit/chainlink/deployment/ccip/changeset" + "github.com/smartcontractkit/chainlink/deployment/environment/devenv" "github.com/smartcontractkit/chainlink/deployment" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/rmn_home" @@ -32,7 +33,6 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/router" testsetups "github.com/smartcontractkit/chainlink/integration-tests/testsetups/ccip" - "github.com/smartcontractkit/chainlink/v2/core/logger" ) func TestRMN_TwoMessagesOnTwoLanesIncludingBatching(t *testing.T) { @@ -179,11 +179,14 @@ func TestRMN_DifferentRmnNodesForDifferentChains(t *testing.T) { func TestRMN_TwoMessagesOneSourceChainCursed(t *testing.T) { runRmnTestCase(t, rmnTestCase{ - name: "two messages, one source chain is cursed", + name: "two messages, one source chain is cursed the other chain was cursed but curse is revoked", passIfNoCommitAfter: 15 * time.Second, cursedSubjectsPerChain: map[int][]int{ chain1: {chain0}, }, + revokedCursedSubjectsPerChain: map[int]map[int]time.Duration{ + chain0: {globalCurse: 5 * time.Second}, // chain0 will be globally cursed and curse will be revoked later + }, homeChainConfig: homeChainConfig{ f: map[int]int{chain0: 1, chain1: 1}, }, @@ -244,7 +247,9 @@ func runRmnTestCase(t *testing.T, tc rmnTestCase) { ctx := testcontext.Get(t) t.Logf("Running RMN test case: %s", tc.name) - envWithRMN, rmnCluster := testsetups.NewLocalDevEnvironmentWithRMN(t, logger.TestLogger(t), len(tc.rmnNodes)) + envWithRMN, rmnCluster := testsetups.NewIntegrationEnvironment(t, + changeset.WithRMNEnabled(len(tc.rmnNodes)), + ) t.Logf("envWithRmn: %#v", envWithRMN) tc.populateFields(t, envWithRMN, rmnCluster) @@ -253,9 +258,6 @@ func runRmnTestCase(t *testing.T, tc rmnTestCase) { require.NoError(t, err) t.Logf("onChainState: %#v", onChainState) - homeChain, ok := envWithRMN.Env.Chains[envWithRMN.HomeChainSel] - require.True(t, ok) - homeChainState, ok := onChainState.Chains[envWithRMN.HomeChainSel] require.True(t, ok) @@ -269,23 +271,28 @@ func runRmnTestCase(t *testing.T, tc rmnTestCase) { dynamicConfig := rmn_home.RMNHomeDynamicConfig{SourceChains: tc.pf.rmnHomeSourceChains, OffchainConfig: []byte{}} t.Logf("Setting RMNHome candidate with staticConfig: %+v, dynamicConfig: %+v, current candidateDigest: %x", staticConfig, dynamicConfig, allDigests.CandidateConfigDigest[:]) - tx, err := homeChainState.RMNHome.SetCandidate(homeChain.DeployerKey, staticConfig, dynamicConfig, allDigests.CandidateConfigDigest) + + candidateDigest, err := homeChainState.RMNHome.GetCandidateDigest(&bind.CallOpts{Context: ctx}) require.NoError(t, err) - _, err = deployment.ConfirmIfNoError(homeChain, tx, err) + _, err = changeset.NewSetRMNHomeCandidateConfigChangeset(envWithRMN.Env, changeset.SetRMNHomeCandidateConfig{ + HomeChainSelector: envWithRMN.HomeChainSel, + RMNStaticConfig: staticConfig, + RMNDynamicConfig: dynamicConfig, + DigestToOverride: candidateDigest, + }) require.NoError(t, err) - candidateDigest, err := homeChainState.RMNHome.GetCandidateDigest(&bind.CallOpts{Context: ctx}) + candidateDigest, err = homeChainState.RMNHome.GetCandidateDigest(&bind.CallOpts{Context: ctx}) require.NoError(t, err) t.Logf("RMNHome candidateDigest after setting new candidate: %x", candidateDigest[:]) t.Logf("Promoting RMNHome candidate with candidateDigest: %x", candidateDigest[:]) - tx, err = homeChainState.RMNHome.PromoteCandidateAndRevokeActive( - homeChain.DeployerKey, candidateDigest, allDigests.ActiveConfigDigest) - require.NoError(t, err) - - _, err = deployment.ConfirmIfNoError(homeChain, tx, err) + _, err = changeset.NewPromoteCandidateConfigChangeset(envWithRMN.Env, changeset.PromoteRMNHomeCandidateConfig{ + HomeChainSelector: envWithRMN.HomeChainSel, + DigestToPromote: candidateDigest, + }) require.NoError(t, err) // check the active digest is the same as the candidate digest @@ -295,7 +302,23 @@ func runRmnTestCase(t *testing.T, tc rmnTestCase) { "active digest should be the same as the previously candidate digest after promotion, previous candidate: %x, active: %x", candidateDigest[:], activeDigest[:]) - tc.setRmnRemoteConfig(ctx, t, onChainState, activeDigest, envWithRMN) + rmnRemoteConfig := make(map[uint64]changeset.RMNRemoteConfig) + for _, remoteCfg := range tc.remoteChainsConfig { + selector := tc.pf.chainSelectors[remoteCfg.chainIdx] + if remoteCfg.f < 0 { + t.Fatalf("remoteCfg.f is negative: %d", remoteCfg.f) + } + rmnRemoteConfig[selector] = changeset.RMNRemoteConfig{ + F: uint64(remoteCfg.f), + Signers: tc.pf.rmnRemoteSigners, + } + } + + _, err = changeset.NewSetRMNRemoteConfigChangeset(envWithRMN.Env, changeset.SetRMNRemoteConfig{ + HomeChainSelector: envWithRMN.HomeChainSel, + RMNRemoteConfigs: rmnRemoteConfig, + }) + require.NoError(t, err) tc.killMarkedRmnNodes(t, rmnCluster) @@ -307,6 +330,7 @@ func runRmnTestCase(t *testing.T, tc rmnTestCase) { t.Logf("Sent all messages, seqNumCommit: %v seqNumExec: %v", seqNumCommit, seqNumExec) tc.callContractsToCurseChains(ctx, t, onChainState, envWithRMN) + tc.callContractsToCurseAndRevokeCurse(ctx, t, onChainState, envWithRMN) tc.enableOracles(ctx, t, envWithRMN, disabledNodes) @@ -419,22 +443,25 @@ type rmnTestCase struct { // If set to a positive value, the test will wait for that duration and will assert that commit report was not delivered. passIfNoCommitAfter time.Duration cursedSubjectsPerChain map[int][]int - waitForExec bool - homeChainConfig homeChainConfig - remoteChainsConfig []remoteChainConfig - rmnNodes []rmnNode - messagesToSend []messageToSend + // revokedCursedSubjectsPerChain is used to revoke this specific curses after a timer expires + revokedCursedSubjectsPerChain map[int]map[int]time.Duration // chainIdx -> subjectIdx -> timer to revoke + waitForExec bool + homeChainConfig homeChainConfig + remoteChainsConfig []remoteChainConfig + rmnNodes []rmnNode + messagesToSend []messageToSend // populated fields after environment setup pf testCasePopulatedFields } type testCasePopulatedFields struct { - chainSelectors []uint64 - rmnHomeNodes []rmn_home.RMNHomeNode - rmnRemoteSigners []rmn_remote.RMNRemoteSigner - rmnHomeSourceChains []rmn_home.RMNHomeSourceChain - cursedSubjectsPerChainSel map[uint64][]uint64 + chainSelectors []uint64 + rmnHomeNodes []rmn_home.RMNHomeNode + rmnRemoteSigners []rmn_remote.RMNRemoteSigner + rmnHomeSourceChains []rmn_home.RMNHomeSourceChain + cursedSubjectsPerChainSel map[uint64][]uint64 + revokedCursedSubjectsPerChainSel map[uint64]map[uint64]time.Duration } func (tc *rmnTestCase) populateFields(t *testing.T, envWithRMN changeset.DeployedEnv, rmnCluster devenv.RMNCluster) { @@ -489,6 +516,22 @@ func (tc *rmnTestCase) populateFields(t *testing.T, envWithRMN changeset.Deploye tc.pf.cursedSubjectsPerChainSel[chainSel] = append(tc.pf.cursedSubjectsPerChainSel[chainSel], subjSel) } } + + // populate revoked cursed subjects with actual chain selectors + tc.pf.revokedCursedSubjectsPerChainSel = make(map[uint64]map[uint64]time.Duration) + for chainIdx, subjects := range tc.revokedCursedSubjectsPerChain { + chainSel := tc.pf.chainSelectors[chainIdx] + for subject, revokeAfter := range subjects { + subjSel := uint64(globalCurse) + if subject != globalCurse { + subjSel = tc.pf.chainSelectors[subject] + } + if _, ok := tc.pf.revokedCursedSubjectsPerChainSel[chainSel]; !ok { + tc.pf.revokedCursedSubjectsPerChainSel[chainSel] = make(map[uint64]time.Duration) + } + tc.pf.revokedCursedSubjectsPerChainSel[chainSel][subjSel] = revokeAfter + } + } } func (tc rmnTestCase) validate() error { @@ -499,46 +542,6 @@ func (tc rmnTestCase) validate() error { return nil } -func (tc rmnTestCase) setRmnRemoteConfig( - ctx context.Context, - t *testing.T, - onChainState changeset.CCIPOnChainState, - activeDigest [32]byte, - envWithRMN changeset.DeployedEnv) { - for _, remoteCfg := range tc.remoteChainsConfig { - remoteSel := tc.pf.chainSelectors[remoteCfg.chainIdx] - chState, ok := onChainState.Chains[remoteSel] - require.True(t, ok) - if remoteCfg.f < 0 { - t.Fatalf("negative F: %d", remoteCfg.f) - } - rmnRemoteConfig := rmn_remote.RMNRemoteConfig{ - RmnHomeContractConfigDigest: activeDigest, - Signers: tc.pf.rmnRemoteSigners, - F: uint64(remoteCfg.f), - } - - chain := envWithRMN.Env.Chains[tc.pf.chainSelectors[remoteCfg.chainIdx]] - - t.Logf("Setting RMNRemote config with RMNHome active digest: %x, cfg: %+v", activeDigest[:], rmnRemoteConfig) - tx2, err2 := chState.RMNRemote.SetConfig(chain.DeployerKey, rmnRemoteConfig) - require.NoError(t, err2) - _, err2 = deployment.ConfirmIfNoError(chain, tx2, err2) - require.NoError(t, err2) - - // confirm the config is set correctly - config, err2 := chState.RMNRemote.GetVersionedConfig(&bind.CallOpts{Context: ctx}) - require.NoError(t, err2) - require.Equalf(t, - activeDigest, - config.Config.RmnHomeContractConfigDigest, - "RMNRemote config digest should be the same as the active digest of RMNHome after setting, RMNHome active: %x, RMNRemote config: %x", - activeDigest[:], config.Config.RmnHomeContractConfigDigest[:]) - - t.Logf("RMNRemote config digest after setting: %x", config.Config.RmnHomeContractConfigDigest[:]) - } -} - func (tc rmnTestCase) killMarkedRmnNodes(t *testing.T, rmnCluster devenv.RMNCluster) { for _, n := range tc.rmnNodes { if n.forceExit { @@ -636,6 +639,44 @@ func (tc rmnTestCase) callContractsToCurseChains(ctx context.Context, t *testing } } +func (tc rmnTestCase) callContractsToCurseAndRevokeCurse(ctx context.Context, t *testing.T, onChainState changeset.CCIPOnChainState, envWithRMN changeset.DeployedEnv) { + for _, remoteCfg := range tc.remoteChainsConfig { + remoteSel := tc.pf.chainSelectors[remoteCfg.chainIdx] + chState, ok := onChainState.Chains[remoteSel] + require.True(t, ok) + chain, ok := envWithRMN.Env.Chains[remoteSel] + require.True(t, ok) + + cursedSubjects, ok := tc.revokedCursedSubjectsPerChain[remoteCfg.chainIdx] + if !ok { + continue // nothing to curse on this chain + } + + for subjectDescription, revokeAfter := range cursedSubjects { + subj := reader.GlobalCurseSubject + if subjectDescription != globalCurse { + subj = chainSelectorToBytes16(tc.pf.chainSelectors[subjectDescription]) + } + t.Logf("cursing subject %d (%d)", subj, subjectDescription) + txCurse, errCurse := chState.RMNRemote.Curse(chain.DeployerKey, subj) + _, errConfirm := deployment.ConfirmIfNoError(chain, txCurse, errCurse) + require.NoError(t, errConfirm) + + go func() { + <-time.NewTimer(revokeAfter).C + t.Logf("revoking curse on subject %d (%d)", subj, subjectDescription) + txUncurse, errUncurse := chState.RMNRemote.Uncurse(chain.DeployerKey, subj) + _, errConfirm = deployment.ConfirmIfNoError(chain, txUncurse, errUncurse) + require.NoError(t, errConfirm) + }() + } + + cs, err := chState.RMNRemote.GetCursedSubjects(&bind.CallOpts{Context: ctx}) + require.NoError(t, err) + t.Logf("Cursed subjects: %v", cs) + } +} + func (tc rmnTestCase) enableOracles(ctx context.Context, t *testing.T, envWithRMN changeset.DeployedEnv, nodeIDs []string) { for _, n := range nodeIDs { _, err := envWithRMN.Env.Offchain.EnableNode(ctx, &node.EnableNodeRequest{Id: n}) diff --git a/integration-tests/smoke/ccip/ccip_token_price_updates_test.go b/integration-tests/smoke/ccip/ccip_token_price_updates_test.go index 6a193397d7e..e3496b6f407 100644 --- a/integration-tests/smoke/ccip/ccip_token_price_updates_test.go +++ b/integration-tests/smoke/ccip/ccip_token_price_updates_test.go @@ -16,25 +16,23 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/config" "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" "github.com/smartcontractkit/chainlink-protos/job-distributor/v1/node" + "github.com/smartcontractkit/chainlink/deployment" "github.com/smartcontractkit/chainlink/deployment/ccip/changeset" testsetups "github.com/smartcontractkit/chainlink/integration-tests/testsetups/ccip" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/fee_quoter" - "github.com/smartcontractkit/chainlink/v2/core/logger" ) func Test_CCIPTokenPriceUpdates(t *testing.T) { - lggr := logger.TestLogger(t) ctx := changeset.Context(t) callOpts := &bind.CallOpts{Context: ctx} var tokenPriceExpiry = 5 * time.Second - e, _, _ := testsetups.NewLocalDevEnvironmentWithDefaultPrice(t, lggr, &changeset.TestConfigs{ - OCRConfigOverride: func(params changeset.CCIPOCRParams) changeset.CCIPOCRParams { + e, _ := testsetups.NewIntegrationEnvironment(t, + changeset.WithOCRConfigOverride(func(params changeset.CCIPOCRParams) changeset.CCIPOCRParams { params.CommitOffChainConfig.TokenPriceBatchWriteFrequency = *config.MustNewDuration(tokenPriceExpiry) return params - }, - }) + })) state, err := changeset.LoadOnchainState(e.Env) require.NoError(t, err) require.NoError(t, changeset.AddLanesForAll(e.Env, state)) diff --git a/integration-tests/smoke/ccip/ccip_token_transfer_test.go b/integration-tests/smoke/ccip/ccip_token_transfer_test.go index 13abe33fe7c..2088960639e 100644 --- a/integration-tests/smoke/ccip/ccip_token_transfer_test.go +++ b/integration-tests/smoke/ccip/ccip_token_transfer_test.go @@ -10,8 +10,9 @@ import ( "github.com/stretchr/testify/require" "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" + "github.com/smartcontractkit/chainlink/deployment/ccip/changeset" - "github.com/smartcontractkit/chainlink/deployment/environment/memory" + testsetups "github.com/smartcontractkit/chainlink/integration-tests/testsetups/ccip" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/router" "github.com/smartcontractkit/chainlink/v2/core/logger" @@ -20,14 +21,9 @@ import ( func TestTokenTransfer(t *testing.T) { lggr := logger.TestLogger(t) ctx := tests.Context(t) - config := &changeset.TestConfigs{} - tenv := changeset.NewMemoryEnvironmentWithJobsAndContracts(t, logger.TestLogger(t), memory.MemoryEnvironmentConfig{ - Chains: 2, - Nodes: 4, - Bootstraps: 1, - NumOfUsersPerChain: 3, - }, config) + tenv, _ := testsetups.NewIntegrationEnvironment(t, + changeset.WithUsersPerChain(3)) e := tenv.Env state, err := changeset.LoadOnchainState(e) @@ -214,7 +210,7 @@ func TestTokenTransfer(t *testing.T) { t, e, state, - changeset.SeqNumberRageToSlice(expectedSeqNums), + changeset.SeqNumberRangeToSlice(expectedSeqNums), startBlocks, ) require.Equal(t, expectedExecutionStates, execStates) diff --git a/integration-tests/smoke/ccip/ccip_usdc_test.go b/integration-tests/smoke/ccip/ccip_usdc_test.go index 2dae3f3c48e..174ab941387 100644 --- a/integration-tests/smoke/ccip/ccip_usdc_test.go +++ b/integration-tests/smoke/ccip/ccip_usdc_test.go @@ -11,9 +11,10 @@ import ( "golang.org/x/sync/errgroup" "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" + "github.com/smartcontractkit/chainlink/deployment" "github.com/smartcontractkit/chainlink/deployment/ccip/changeset" - "github.com/smartcontractkit/chainlink/deployment/environment/memory" + testsetups "github.com/smartcontractkit/chainlink/integration-tests/testsetups/ccip" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/router" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/shared/generated/burn_mint_erc677" @@ -31,15 +32,11 @@ import ( func TestUSDCTokenTransfer(t *testing.T) { lggr := logger.TestLogger(t) ctx := tests.Context(t) - config := &changeset.TestConfigs{ - IsUSDC: true, - } - tenv := changeset.NewMemoryEnvironmentWithJobsAndContracts(t, lggr, memory.MemoryEnvironmentConfig{ - Chains: 3, - NumOfUsersPerChain: 3, - Nodes: 4, - Bootstraps: 1, - }, config) + tenv, _ := testsetups.NewIntegrationEnvironment(t, + changeset.WithUsersPerChain(3), + changeset.WithChains(3), + changeset.WithUSDC(), + ) e := tenv.Env state, err := changeset.LoadOnchainState(e) @@ -228,7 +225,7 @@ func TestUSDCTokenTransfer(t *testing.T) { t, e, state, - changeset.SeqNumberRageToSlice(expectedSeqNums), + changeset.SeqNumberRangeToSlice(expectedSeqNums), startBlocks, ) require.Equal(t, expectedExecutionStates, execStates) diff --git a/integration-tests/smoke/ocr2_test.go b/integration-tests/smoke/ocr2_test.go index a011dfdffc6..8416ec05c7e 100644 --- a/integration-tests/smoke/ocr2_test.go +++ b/integration-tests/smoke/ocr2_test.go @@ -1,14 +1,19 @@ package smoke import ( + "bufio" "fmt" "math/big" "net/http" + "os" + "path/filepath" + "regexp" "strings" + "sync" "testing" "time" - "github.com/smartcontractkit/chainlink/integration-tests/utils" + "github.com/onsi/gomega" "github.com/ethereum/go-ethereum/common" "github.com/rs/zerolog" @@ -16,8 +21,8 @@ import ( "github.com/smartcontractkit/chainlink-testing-framework/seth" + ctf_docker "github.com/smartcontractkit/chainlink-testing-framework/lib/docker" "github.com/smartcontractkit/chainlink-testing-framework/lib/logging" - "github.com/smartcontractkit/chainlink-testing-framework/lib/logstream" "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/testcontext" "github.com/smartcontractkit/chainlink/v2/core/config/env" @@ -26,6 +31,7 @@ import ( "github.com/smartcontractkit/chainlink/integration-tests/contracts" "github.com/smartcontractkit/chainlink/integration-tests/docker/test_env" tc "github.com/smartcontractkit/chainlink/integration-tests/testconfig" + "github.com/smartcontractkit/chainlink/integration-tests/utils" ) type ocr2test struct { @@ -224,33 +230,150 @@ func prepareORCv2SmokeTestEnv(t *testing.T, testData ocr2test, l zerolog.Logger, } func assertCorrectNodeConfiguration(t *testing.T, l zerolog.Logger, totalNodeCount int, testData ocr2test, testEnv *test_env.CLClusterTestEnv) { - expectedNodesWithConfiguration := totalNodeCount - 1 // minus bootstrap node - var expectedPatterns []string + l.Info().Msg("Checking if all nodes have correct plugin configuration applied") - if testData.env[string(env.MedianPlugin.Cmd)] != "" { - expectedPatterns = append(expectedPatterns, "Registered loopp.*OCR2.*Median.*") - } + // we have to use gomega here, because sometimes there's a delay in the logs being written (especially in the CI) + // and this check fails on the first execution, and we don't want to add any hardcoded sleeps - if testData.chainReaderAndCodec { - expectedPatterns = append(expectedPatterns, "relayConfig\\.chainReader") - } else { - expectedPatterns = append(expectedPatterns, "ChainReader missing from RelayConfig; falling back to internal MedianContract") - } + gom := gomega.NewGomegaWithT(t) + gom.Eventually(func(g gomega.Gomega) { + allNodesHaveCorrectConfig := false + + var expectedPatterns []string + expectedNodeCount := totalNodeCount - 1 + + if testData.env[string(env.MedianPlugin.Cmd)] != "" { + expectedPatterns = append(expectedPatterns, `Registered loopp.*OCR2.*Median.*`) + } + + if testData.chainReaderAndCodec { + expectedPatterns = append(expectedPatterns, `relayConfig.chainReader`) + } else { + expectedPatterns = append(expectedPatterns, "ChainReader missing from RelayConfig; falling back to internal MedianContract") + } + + logFilePaths := make(map[string]string) + tempLogsDir := os.TempDir() + + var nodesToInclude []string + for i := 1; i < totalNodeCount; i++ { + nodesToInclude = append(nodesToInclude, testEnv.ClCluster.Nodes[i].ContainerName+".log") + } + + // save all log files in temp dir + loggingErr := ctf_docker.WriteAllContainersLogs(l, tempLogsDir) + if loggingErr != nil { + l.Debug().Err(loggingErr).Msg("Error writing all containers logs. Trying again...") + + // try again + return + } + + var fileNameIncludeFilter = func(name string) bool { + for _, n := range nodesToInclude { + if strings.EqualFold(name, n) { + return true + } + } + return false + } + + // find log files for CL nodes + fileWalkErr := filepath.Walk(tempLogsDir, func(path string, info os.FileInfo, err error) error { + if err != nil { + if os.IsPermission(err) { + return nil + } + return err + } + if !info.IsDir() && fileNameIncludeFilter(info.Name()) { + absPath, err := filepath.Abs(path) + if err != nil { + return err + } + logFilePaths[strings.TrimSuffix(info.Name(), ".log")] = absPath + } + return nil + }) + + if fileWalkErr != nil { + l.Debug().Err(fileWalkErr).Msg("Error walking through log files. Trying again...") + + return + } + + if len(logFilePaths) != expectedNodeCount { + l.Debug().Msgf("Expected number of log files to match number of nodes (excluding bootstrap node). Expected: %d, Found: %d. Trying again...", expectedNodeCount, len(logFilePaths)) + + return + } + + // search for expected pattern in log file + var searchForLineInFile = func(filePath string, pattern string) bool { + file, fileErr := os.Open(filePath) + if fileErr != nil { + return false + } + + defer func(file *os.File) { + _ = file.Close() + }(file) + + scanner := bufio.NewScanner(file) + scanner.Split(bufio.ScanLines) + pc := regexp.MustCompile(pattern) + + for scanner.Scan() { + jsonLogLine := scanner.Text() + if pc.MatchString(jsonLogLine) { + return true + } + + } + return false + } + + wg := sync.WaitGroup{} + resultsCh := make(chan map[string][]string, len(logFilePaths)) + + // process all logs in parallel + for nodeName, logFilePath := range logFilePaths { + wg.Add(1) + filePath := logFilePath + go func() { + defer wg.Done() + var patternsFound []string + for _, pattern := range expectedPatterns { + found := searchForLineInFile(filePath, pattern) + if found { + patternsFound = append(patternsFound, pattern) + } + } + resultsCh <- map[string][]string{nodeName: patternsFound} + }() + } + + wg.Wait() + close(resultsCh) - // make sure that nodes are correctly configured by scanning the logs - for _, pattern := range expectedPatterns { - l.Info().Msgf("Checking for pattern: '%s' in CL node logs", pattern) var correctlyConfiguredNodes []string - for i := 1; i < len(testEnv.ClCluster.Nodes); i++ { - logProcessor, processFn, err := logstream.GetRegexMatchingProcessor(testEnv.LogStream, pattern) - require.NoError(t, err, "Error getting regex matching processor") - - count, err := logProcessor.ProcessContainerLogs(testEnv.ClCluster.Nodes[i].ContainerName, processFn) - require.NoError(t, err, "Error processing container logs") - if *count >= 1 { - correctlyConfiguredNodes = append(correctlyConfiguredNodes, testEnv.ClCluster.Nodes[i].ContainerName) + var incorrectlyConfiguredNodes []string + + // check results + for result := range resultsCh { + for nodeName, patternsFound := range result { + if len(patternsFound) == len(expectedPatterns) { + correctlyConfiguredNodes = append(correctlyConfiguredNodes, nodeName) + } else { + incorrectlyConfiguredNodes = append(incorrectlyConfiguredNodes, nodeName) + } } } - require.Equal(t, expectedNodesWithConfiguration, len(correctlyConfiguredNodes), "expected correct plugin config to be applied to %d cl-nodes, but only following ones had it: %s; regexp used: %s", expectedNodesWithConfiguration, strings.Join(correctlyConfiguredNodes, ", "), string(pattern)) - } + + allNodesHaveCorrectConfig = len(correctlyConfiguredNodes) == expectedNodeCount + + g.Expect(allNodesHaveCorrectConfig).To(gomega.BeTrue(), "%d nodes' logs were missing expected plugin configuration entries. Correctly configured nodes: %s. Nodes with missing configuration: %s. Expected log patterns: %s", expectedNodeCount-len(correctlyConfiguredNodes), strings.Join(correctlyConfiguredNodes, ", "), strings.Join(incorrectlyConfiguredNodes, ", "), strings.Join(expectedPatterns, ", ")) + }, "1m", "10s").Should(gomega.Succeed()) + + l.Info().Msg("All nodes have correct plugin configuration applied") } diff --git a/integration-tests/testconfig/automation/example.toml b/integration-tests/testconfig/automation/example.toml index 3bbe78d693d..c239e5a3966 100644 --- a/integration-tests/testconfig/automation/example.toml +++ b/integration-tests/testconfig/automation/example.toml @@ -7,14 +7,6 @@ version="2.7.0" # if set to true will save logs even if test did not fail test_log_collect=false -[Logging.LogStream] -# supported targets: file, loki, in-memory. if empty no logs will be persistet -log_targets=["file"] -# context timeout for starting log producer and also time-frame for requesting logs -log_producer_timeout="10s" -# number of retries before log producer gives up and stops listening to logs -log_producer_retry_limit=10 - # if you want to use polygon_mumbial [Network] selected_networks=["polygon_mumbai"] diff --git a/integration-tests/testconfig/ccip/config.go b/integration-tests/testconfig/ccip/config.go index 72c81f05f47..70c850fd591 100644 --- a/integration-tests/testconfig/ccip/config.go +++ b/integration-tests/testconfig/ccip/config.go @@ -147,6 +147,9 @@ func (o *JDConfig) GetJDDBVersion() string { func (o *Config) Validate() error { var chainIds []int64 for _, net := range o.PrivateEthereumNetworks { + if net.EthereumChainConfig.ChainID < 0 { + return fmt.Errorf("negative chain ID found for network %d", net.EthereumChainConfig.ChainID) + } chainIds = append(chainIds, int64(net.EthereumChainConfig.ChainID)) } homeChainSelector, err := strconv.ParseUint(pointer.GetString(o.HomeChainSelector), 10, 64) @@ -189,14 +192,21 @@ func IsSelectorValid(selector uint64, chainIds []int64) (bool, error) { if err != nil { return false, err } - if chainId >= math.MaxInt64 { - return false, fmt.Errorf("chain id overflows int64: %d", chainId) - } - expId := int64(chainId) - for _, id := range chainIds { - if id == expId { + + for _, cID := range chainIds { + if isEqualUint64AndInt64(chainId, cID) { return true, nil } } return false, nil } + +func isEqualUint64AndInt64(u uint64, i int64) bool { + if i < 0 { + return false // uint64 cannot be equal to a negative int64 + } + if u > math.MaxInt64 { + return false // uint64 cannot be equal to an int64 if it exceeds the maximum int64 value + } + return u == uint64(i) +} diff --git a/integration-tests/testconfig/ccip/overrides/sepolia_avax_binance.toml b/integration-tests/testconfig/ccip/overrides/sepolia_avax_binance.toml index 06af64d5d91..72c43b12da5 100644 --- a/integration-tests/testconfig/ccip/overrides/sepolia_avax_binance.toml +++ b/integration-tests/testconfig/ccip/overrides/sepolia_avax_binance.toml @@ -5,10 +5,6 @@ chainlink_node_funding = 2 [Logging] test_log_collect = true -[Logging.LogStream] -# supported targets: file, loki, in-memory. if empty no logs will be persisted -log_targets = ["loki"] - [Network] selected_networks = ['SEPOLIA', 'AVALANCHE_FUJI', 'BSC_TESTNET'] diff --git a/integration-tests/testconfig/default.toml b/integration-tests/testconfig/default.toml index b9987d4571d..8180b40ae21 100644 --- a/integration-tests/testconfig/default.toml +++ b/integration-tests/testconfig/default.toml @@ -2,19 +2,6 @@ # set to true to flush logs to selected target regardless of test result; otherwise logs are only flushed if test failed test_log_collect = false -[Logging.Grafana] -base_url = "https://grafana.ops.prod.cldev.sh" -base_url_github_ci = "http://localhost:8080/primary" -dashboard_url = "/d/ddf75041-1e39-42af-aa46-361fe4c36e9e/ci-e2e-tests-logs" - -[Logging.LogStream] -# supported targets: file, loki, in-memory. if empty no logs will be persisted -log_targets = ["file"] -# context timeout for starting log producer and also time-frame for requesting logs -log_producer_timeout = "10s" -# number of retries before log producer gives up and stops listening to logs -log_producer_retry_limit = 10 - [ChainlinkImage] # postgres version to use postgres_version = "12.0" diff --git a/integration-tests/testconfig/forwarder_ocr/example.toml b/integration-tests/testconfig/forwarder_ocr/example.toml index 517a341f803..6ca4b8bbcc3 100644 --- a/integration-tests/testconfig/forwarder_ocr/example.toml +++ b/integration-tests/testconfig/forwarder_ocr/example.toml @@ -7,33 +7,6 @@ version="2.7.0" # if set to true will save logs even if test did not fail test_log_collect=false -[Logging.LogStream] -# supported targets: file, loki, in-memory. if empty no logs will be persistet -log_targets=["file"] -# context timeout for starting log producer and also time-frame for requesting logs -log_producer_timeout="10s" -# number of retries before log producer gives up and stops listening to logs -log_producer_retry_limit=10 - -[Logging.Loki] -tenant_id="tenant_id" -# full URL of Loki ingest endpoint -endpoint="https://loki.url/api/v3/push" -# currently only needed when using public instance -basic_auth_secret="loki-basic-auth" -# only needed for cloud grafana -bearer_token_secret="bearer_token" - -# LogStream will try to shorten Grafana URLs by default (if all 3 variables are set) -[Logging.Grafana] -# grafana url (trailing "/" will be stripped) -base_url="http://grafana.url" -# url of your grafana dashboard (prefix and suffix "/" are stirpped), example: /d/ad61652-2712-1722/my-dashboard -dashboard_url="/d/your-dashboard" -# Grafana dashboard uid to annotate. Find it in Dashboard Settings -> JSON Model -dashboard_uid="dashboard-uid-to-annotate" -bearer_token_secret="my-awesome-token" - # if you want to use polygon_mumbial [Network] selected_networks=["polygon_mumbai"] diff --git a/integration-tests/testconfig/forwarder_ocr2/example.toml b/integration-tests/testconfig/forwarder_ocr2/example.toml index 3ec3e4c690a..e3fb66a0f3a 100644 --- a/integration-tests/testconfig/forwarder_ocr2/example.toml +++ b/integration-tests/testconfig/forwarder_ocr2/example.toml @@ -8,33 +8,6 @@ version="2.7.0" # if set to true will save logs even if test did not fail test_log_collect=false -[Logging.LogStream] -# supported targets: file, loki, in-memory. if empty no logs will be persistet -log_targets=["file"] -# context timeout for starting log producer and also time-frame for requesting logs -log_producer_timeout="10s" -# number of retries before log producer gives up and stops listening to logs -log_producer_retry_limit=10 - -[Logging.Loki] -tenant_id="tenant_id" -# full URL of Loki ingest endpoint -endpoint="https://loki.url/api/v3/push" -# currently only needed when using public instance -basic_auth_secret="loki-basic-auth" -# only needed for cloud grafana -bearer_token_secret="bearer_token" - -# LogStream will try to shorten Grafana URLs by default (if all 3 variables are set) -[Logging.Grafana] -# grafana url (trailing "/" will be stripped) -base_url="http://grafana.url" -# url of your grafana dashboard (prefix and suffix "/" are stirpped), example: /d/ad61652-2712-1722/my-dashboard -dashboard_url="/d/your-dashboard" -# Grafana dashboard uid to annotate. Find it in Dashboard Settings -> JSON Model -dashboard_uid="dashboard-uid-to-annotate" -bearer_token_secret="my-awesome-token" - # if you want to use polygon_mumbial [Network] selected_networks=["polygon_mumbai"] diff --git a/integration-tests/testconfig/functions/example.toml b/integration-tests/testconfig/functions/example.toml index 74d931632a8..ec7076fa9f9 100644 --- a/integration-tests/testconfig/functions/example.toml +++ b/integration-tests/testconfig/functions/example.toml @@ -7,14 +7,6 @@ version="2.7.0" # if set to true will save logs even if test did not fail test_log_collect=false -[Logging.LogStream] -# supported targets: file, loki, in-memory. if empty no logs will be persistet -log_targets=["file"] -# context timeout for starting log producer and also time-frame for requesting logs -log_producer_timeout="10s" -# number of retries before log producer gives up and stops listening to logs -log_producer_retry_limit=10 - # if you want to use simulated network [Network] selected_networks=["polygon_mumbai"] diff --git a/integration-tests/testconfig/keeper/example.toml b/integration-tests/testconfig/keeper/example.toml index 4efbf974827..7fe3bf26d0a 100644 --- a/integration-tests/testconfig/keeper/example.toml +++ b/integration-tests/testconfig/keeper/example.toml @@ -7,14 +7,6 @@ version="2.7.0" # if set to true will save logs even if test did not fail test_log_collect=false -[Logging.LogStream] -# supported targets: file, loki, in-memory. if empty no logs will be persistet -log_targets=["file"] -# context timeout for starting log producer and also time-frame for requesting logs -log_producer_timeout="10s" -# number of retries before log producer gives up and stops listening to logs -log_producer_retry_limit=10 - # if you want to use polygon_mumbial [Network] selected_networks=["polygon_mumbai"] diff --git a/integration-tests/testconfig/log_poller/example.toml b/integration-tests/testconfig/log_poller/example.toml index 78f3b5482d9..b94b6e0e202 100644 --- a/integration-tests/testconfig/log_poller/example.toml +++ b/integration-tests/testconfig/log_poller/example.toml @@ -7,14 +7,6 @@ version="2.7.0" # if set to true will save logs even if test did not fail test_log_collect=false -[Logging.LogStream] -# supported targets: file, loki, in-memory. if empty no logs will be persistet -log_targets=["file"] -# context timeout for starting log producer and also time-frame for requesting logs -log_producer_timeout="10s" -# number of retries before log producer gives up and stops listening to logs -log_producer_retry_limit=10 - # if you want to use polygon_mumbial [Network] selected_networks=["polygon_mumbai"] diff --git a/integration-tests/testconfig/node/example.toml b/integration-tests/testconfig/node/example.toml index bc5628e46b3..4635e40c037 100644 --- a/integration-tests/testconfig/node/example.toml +++ b/integration-tests/testconfig/node/example.toml @@ -7,14 +7,6 @@ version="2.7.0" # if set to true will save logs even if test did not fail test_log_collect=false -[Logging.LogStream] -# supported targets: file, loki, in-memory. if empty no logs will be persistet -log_targets=["file"] -# context timeout for starting log producer and also time-frame for requesting logs -log_producer_timeout="10s" -# number of retries before log producer gives up and stops listening to logs -log_producer_retry_limit=10 - # if you want to use polygon_mumbial [Network] selected_networks=["polygon_mumbai"] diff --git a/integration-tests/testconfig/ocr/example.toml b/integration-tests/testconfig/ocr/example.toml index 7c1c755567f..d1edd3a67fd 100644 --- a/integration-tests/testconfig/ocr/example.toml +++ b/integration-tests/testconfig/ocr/example.toml @@ -7,33 +7,6 @@ version="2.7.0" # if set to true will save logs even if test did not fail test_log_collect=false -[Logging.LogStream] -# supported targets: file, loki, in-memory. if empty no logs will be persistet -log_targets=["file"] -# context timeout for starting log producer and also time-frame for requesting logs -log_producer_timeout="10s" -# number of retries before log producer gives up and stops listening to logs -log_producer_retry_limit=10 - -[Logging.Loki] -tenant_id="tenant_id" -# full URL of Loki ingest endpoint -endpoint="https://loki.url/api/v3/push" -# currently only needed when using public instance -basic_auth_secret="loki-basic-auth" -# only needed for cloud grafana -bearer_token_secret="bearer_token" - -# LogStream will try to shorten Grafana URLs by default (if all 3 variables are set) -[Logging.Grafana] -# grafana url (trailing "/" will be stripped) -base_url="http://grafana.url" -# url of your grafana dashboard (prefix and suffix "/" are stirpped), example: /d/ad61652-2712-1722/my-dashboard -dashboard_url="/d/your-dashboard" -# Grafana dashboard uid to annotate. Find it in Dashboard Settings -> JSON Model -dashboard_uid="dashboard-uid-to-annotate" -bearer_token_secret="my-awesome-token" - # if you want to use polygon_mumbial [Network] selected_networks=["polygon_mumbai"] diff --git a/integration-tests/testconfig/ocr2/example.toml b/integration-tests/testconfig/ocr2/example.toml index 319f64d2580..679e4527a31 100644 --- a/integration-tests/testconfig/ocr2/example.toml +++ b/integration-tests/testconfig/ocr2/example.toml @@ -7,33 +7,6 @@ version="2.7.0" # if set to true will save logs even if test did not fail test_log_collect=false -[Logging.LogStream] -# supported targets: file, loki, in-memory. if empty no logs will be persistet -log_targets=["file"] -# context timeout for starting log producer and also time-frame for requesting logs -log_producer_timeout="10s" -# number of retries before log producer gives up and stops listening to logs -log_producer_retry_limit=10 - -[Logging.Loki] -tenant_id="tenant_id" -# full URL of Loki ingest endpoint -endpoint="https://loki.url/api/v3/push" -# currently only needed when using public instance -basic_auth_secret="loki-basic-auth" -# only needed for cloud grafana -bearer_token_secret="bearer_token" - -# LogStream will try to shorten Grafana URLs by default (if all 3 variables are set) -[Logging.Grafana] -# grafana url (trailing "/" will be stripped) -base_url="http://grafana.url" -# url of your grafana dashboard (prefix and suffix "/" are stirpped), example: /d/ad61652-2712-1722/my-dashboard -dashboard_url="/d/your-dashboard" -# Grafana dashboard uid to annotate. Find it in Dashboard Settings -> JSON Model -dashboard_uid="dashboard-uid-to-annotate" -bearer_token_secret="my-awesome-token" - # if you want to use polygon_mumbial [Network] selected_networks=["polygon_mumbai"] diff --git a/integration-tests/testconfig/testconfig.go b/integration-tests/testconfig/testconfig.go index 545818e3348..19e3f0b7ada 100644 --- a/integration-tests/testconfig/testconfig.go +++ b/integration-tests/testconfig/testconfig.go @@ -6,7 +6,6 @@ import ( "fmt" "math/big" "os" - "slices" "strings" "github.com/barkimedes/go-deepcopy" @@ -631,26 +630,6 @@ func (c *TestConfig) Validate() error { return fmt.Errorf("logging config must be set") } - if err := c.Logging.Validate(); err != nil { - return errors.Wrapf(err, "logging config validation failed") - } - - if c.Logging.Loki != nil { - if err := c.Logging.Loki.Validate(); err != nil { - return errors.Wrapf(err, "loki config validation failed") - } - } - - if c.Logging.LogStream != nil && slices.Contains(c.Logging.LogStream.LogTargets, "loki") { - if c.Logging.Loki == nil { - return fmt.Errorf("in order to use Loki as logging target you must set Loki config in logging config") - } - - if err := c.Logging.Loki.Validate(); err != nil { - return errors.Wrapf(err, "loki config validation failed") - } - } - if c.Pyroscope != nil { if err := c.Pyroscope.Validate(); err != nil { return errors.Wrapf(err, "pyroscope config validation failed") diff --git a/integration-tests/testconfig/vrfv2/example.toml b/integration-tests/testconfig/vrfv2/example.toml index 13af6dee620..3665c2f43cf 100644 --- a/integration-tests/testconfig/vrfv2/example.toml +++ b/integration-tests/testconfig/vrfv2/example.toml @@ -7,14 +7,6 @@ version="2.7.0" # if set to true will save logs even if test did not fail test_log_collect=false -[Logging.LogStream] -# supported targets: file, loki, in-memory. if empty no logs will be persistet -log_targets=["file"] -# context timeout for starting log producer and also time-frame for requesting logs -log_producer_timeout="10s" -# number of retries before log producer gives up and stops listening to logs -log_producer_retry_limit=10 - # if you want to use polygon_mumbial [Network] selected_networks=["polygon_mumbai"] diff --git a/integration-tests/testconfig/vrfv2plus/example.toml b/integration-tests/testconfig/vrfv2plus/example.toml index 160e9ba03a9..a45d53f67b8 100644 --- a/integration-tests/testconfig/vrfv2plus/example.toml +++ b/integration-tests/testconfig/vrfv2plus/example.toml @@ -7,14 +7,6 @@ version="2.7.0" # if set to true will save logs even if test did not fail test_log_collect=false -[Logging.LogStream] -# supported targets: file, loki, in-memory. if empty no logs will be persistet -log_targets=["file"] -# context timeout for starting log producer and also time-frame for requesting logs -log_producer_timeout="10s" -# number of retries before log producer gives up and stops listening to logs -log_producer_retry_limit=10 - # if you want to use polygon_mumbial [Network] selected_networks=["polygon_mumbai"] diff --git a/integration-tests/testsetups/ccip/test_helpers.go b/integration-tests/testsetups/ccip/test_helpers.go index 3112d738869..514a232bb80 100644 --- a/integration-tests/testsetups/ccip/test_helpers.go +++ b/integration-tests/testsetups/ccip/test_helpers.go @@ -8,20 +8,18 @@ import ( "os" "strconv" "testing" - "time" "github.com/ethereum/go-ethereum/accounts/abi/bind" - "github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers" chainsel "github.com/smartcontractkit/chain-selectors" + "go.uber.org/zap/zapcore" - cciptypes "github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3" - "github.com/smartcontractkit/chainlink-ccip/pluginconfig" commonconfig "github.com/smartcontractkit/chainlink-common/pkg/config" "github.com/smartcontractkit/chainlink-testing-framework/lib/blockchain" ctfconfig "github.com/smartcontractkit/chainlink-testing-framework/lib/config" ctftestenv "github.com/smartcontractkit/chainlink-testing-framework/lib/docker/test_env" "github.com/smartcontractkit/chainlink-testing-framework/lib/logging" "github.com/smartcontractkit/chainlink-testing-framework/lib/networks" + "github.com/smartcontractkit/chainlink-testing-framework/lib/testreporters" "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/conversions" "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/ptr" "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/testcontext" @@ -29,8 +27,6 @@ import ( "github.com/smartcontractkit/chainlink/deployment" "github.com/smartcontractkit/chainlink/deployment/ccip/changeset" - commonchangeset "github.com/smartcontractkit/chainlink/deployment/common/changeset" - commontypes "github.com/smartcontractkit/chainlink/deployment/common/types" integrationnodes "github.com/smartcontractkit/chainlink/integration-tests/types/config/node" evmcfg "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/toml" corechainlink "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" @@ -57,235 +53,145 @@ import ( // DeployedLocalDevEnvironment is a helper struct for setting up a local dev environment with docker type DeployedLocalDevEnvironment struct { changeset.DeployedEnv - testEnv *test_env.CLClusterTestEnv - DON *devenv.DON + testEnv *test_env.CLClusterTestEnv + DON *devenv.DON + devEnvTestCfg tc.TestConfig + devEnvCfg *devenv.EnvironmentConfig } -func (d DeployedLocalDevEnvironment) RestartChainlinkNodes(t *testing.T) error { - errGrp := errgroup.Group{} - for _, n := range d.testEnv.ClCluster.Nodes { - n := n - errGrp.Go(func() error { - if err := n.Container.Terminate(testcontext.Get(t)); err != nil { - return err - } - err := n.RestartContainer() - if err != nil { - return err - } - return nil - }) - - } - return errGrp.Wait() +func (l *DeployedLocalDevEnvironment) DeployedEnvironment() changeset.DeployedEnv { + return l.DeployedEnv } -func NewLocalDevEnvironmentWithDefaultPrice(t *testing.T, lggr logger.Logger, tCfg *changeset.TestConfigs) (changeset.DeployedEnv, *test_env.CLClusterTestEnv, tc.TestConfig) { - return NewLocalDevEnvironment(t, lggr, changeset.MockLinkPrice, changeset.MockWethPrice, tCfg) -} - -func NewLocalDevEnvironment( - t *testing.T, - lggr logger.Logger, - linkPrice, wethPrice *big.Int, - tCfg *changeset.TestConfigs, -) (changeset.DeployedEnv, *test_env.CLClusterTestEnv, tc.TestConfig) { - if tCfg == nil { - // set to the default constructed value - tCfg = &changeset.TestConfigs{} - } - +func (l *DeployedLocalDevEnvironment) StartChains(t *testing.T, _ *changeset.TestConfigs) { + lggr := logger.TestLogger(t) ctx := testcontext.Get(t) - // create a local docker environment with simulated chains and job-distributor - // we cannot create the chainlink nodes yet as we need to deploy the capability registry first envConfig, testEnv, cfg := CreateDockerEnv(t) - require.NotNil(t, envConfig) - require.NotEmpty(t, envConfig.Chains, "chainConfigs should not be empty") - require.NotEmpty(t, envConfig.JDConfig, "jdUrl should not be empty") + l.devEnvTestCfg = cfg + l.testEnv = testEnv + l.devEnvCfg = envConfig users := make(map[uint64][]*bind.TransactOpts) for _, chain := range envConfig.Chains { - sel, err := chainsel.SelectorFromChainId(chain.ChainID) - require.NoError(t, err) - users[sel] = chain.Users + details, found := chainsel.ChainByEvmChainID(chain.ChainID) + require.Truef(t, found, "chain not found") + users[details.Selector] = chain.Users } - chains, err := devenv.NewChains(lggr, envConfig.Chains) - require.NoError(t, err) - // locate the home chain - homeChainSel := cfg.CCIP.GetHomeChainSelector() + homeChainSel := l.devEnvTestCfg.CCIP.GetHomeChainSelector() require.NotEmpty(t, homeChainSel, "homeChainSel should not be empty") - feedSel := cfg.CCIP.GetFeedChainSelector() + feedSel := l.devEnvTestCfg.CCIP.GetFeedChainSelector() require.NotEmpty(t, feedSel, "feedSel should not be empty") + chains, err := devenv.NewChains(lggr, envConfig.Chains) + require.NoError(t, err) replayBlocks, err := changeset.LatestBlocksByChain(ctx, chains) require.NoError(t, err) + l.DeployedEnv.Users = users + l.DeployedEnv.Env.Chains = chains + l.DeployedEnv.FeedChainSel = feedSel + l.DeployedEnv.HomeChainSel = homeChainSel + l.DeployedEnv.ReplayBlocks = replayBlocks +} - ab := deployment.NewMemoryAddressBook() - crConfig := changeset.DeployTestContracts(t, lggr, ab, homeChainSel, feedSel, chains, linkPrice, wethPrice) - - // start the chainlink nodes with the CR address - err = StartChainlinkNodes(t, envConfig, +func (l *DeployedLocalDevEnvironment) StartNodes(t *testing.T, _ *changeset.TestConfigs, crConfig deployment.CapabilityRegistryConfig) { + require.NotNil(t, l.testEnv, "docker env is empty, start chains first") + require.NotEmpty(t, l.devEnvTestCfg, "integration test config is empty, start chains first") + require.NotNil(t, l.devEnvCfg, "dev environment config is empty, start chains first") + err := StartChainlinkNodes(t, l.devEnvCfg, crConfig, - testEnv, cfg) + l.testEnv, l.devEnvTestCfg) require.NoError(t, err) - - e, don, err := devenv.NewEnvironment(func() context.Context { return ctx }, lggr, *envConfig) + ctx := testcontext.Get(t) + lggr := logger.TestLogger(t) + e, don, err := devenv.NewEnvironment(func() context.Context { return ctx }, lggr, *l.devEnvCfg) require.NoError(t, err) require.NotNil(t, e) - e.ExistingAddresses = ab + l.DON = don + l.DeployedEnv.Env = *e // fund the nodes zeroLogLggr := logging.GetTestLogger(t) - FundNodes(t, zeroLogLggr, testEnv, cfg, don.PluginNodes()) + FundNodes(t, zeroLogLggr, l.testEnv, l.devEnvTestCfg, don.PluginNodes()) +} - env := *e - envNodes, err := deployment.NodeInfo(env.NodeIDs, env.Offchain) - require.NoError(t, err) - allChains := env.AllChainSelectors() - var usdcChains []uint64 - if tCfg.IsUSDC { - usdcChains = allChains - } - mcmsCfg := make(map[uint64]commontypes.MCMSWithTimelockConfig) - for _, c := range env.AllChainSelectors() { - mcmsCfg[c] = commontypes.MCMSWithTimelockConfig{ - Canceller: commonchangeset.SingleGroupMCMS(t), - Bypasser: commonchangeset.SingleGroupMCMS(t), - Proposer: commonchangeset.SingleGroupMCMS(t), - TimelockExecutors: env.AllDeployerKeys(), - TimelockMinDelay: big.NewInt(0), - } - } - // Need to deploy prerequisites first so that we can form the USDC config - // no proposals to be made, timelock can be passed as nil here - env, err = commonchangeset.ApplyChangesets(t, env, nil, []commonchangeset.ChangesetApplication{ - { - Changeset: commonchangeset.WrapChangeSet(changeset.DeployHomeChain), - Config: changeset.DeployHomeChainConfig{ - HomeChainSel: homeChainSel, - RMNStaticConfig: changeset.NewTestRMNStaticConfig(), - RMNDynamicConfig: changeset.NewTestRMNDynamicConfig(), - NodeOperators: changeset.NewTestNodeOperator(chains[homeChainSel].DeployerKey.From), - NodeP2PIDsPerNodeOpAdmin: map[string][][32]byte{ - "NodeOperator": envNodes.NonBootstraps().PeerIDs(), - }, - }, - }, - { - Changeset: commonchangeset.WrapChangeSet(commonchangeset.DeployLinkToken), - Config: allChains, - }, - { - Changeset: commonchangeset.WrapChangeSet(changeset.DeployPrerequisites), - Config: changeset.DeployPrerequisiteConfig{ - ChainSelectors: allChains, - Opts: []changeset.PrerequisiteOpt{ - changeset.WithUSDCChains(usdcChains), - changeset.WithMulticall3(tCfg.IsMultiCall3), - }, - }, - }, - { - Changeset: commonchangeset.WrapChangeSet(commonchangeset.DeployMCMSWithTimelock), - Config: mcmsCfg, - }, - { - Changeset: commonchangeset.WrapChangeSet(changeset.DeployChainContracts), - Config: changeset.DeployChainContractsConfig{ - ChainSelectors: allChains, - HomeChainSelector: homeChainSel, - }, - }, - }) - require.NoError(t, err) - state, err := changeset.LoadOnchainState(env) +func (l *DeployedLocalDevEnvironment) MockUSDCAttestationServer(t *testing.T, isUSDCAttestationMissing bool) string { + err := ccipactions.SetMockServerWithUSDCAttestation(l.testEnv.MockAdapter, nil, isUSDCAttestationMissing) require.NoError(t, err) + return l.testEnv.MockAdapter.InternalEndpoint +} - var tokenDataProviders []pluginconfig.TokenDataObserverConfig - if len(usdcChains) > 0 { - var endpoint string - err = ccipactions.SetMockServerWithUSDCAttestation(testEnv.MockAdapter, nil, tCfg.IsUSDCAttestationMissing) - require.NoError(t, err) - endpoint = testEnv.MockAdapter.InternalEndpoint - cctpContracts := make(map[cciptypes.ChainSelector]pluginconfig.USDCCCTPTokenConfig) - for _, usdcChain := range usdcChains { - cctpContracts[cciptypes.ChainSelector(usdcChain)] = pluginconfig.USDCCCTPTokenConfig{ - SourcePoolAddress: state.Chains[usdcChain].USDCTokenPool.Address().String(), - SourceMessageTransmitterAddr: state.Chains[usdcChain].MockUSDCTransmitter.Address().String(), +func (l *DeployedLocalDevEnvironment) RestartChainlinkNodes(t *testing.T) error { + errGrp := errgroup.Group{} + for _, n := range l.testEnv.ClCluster.Nodes { + n := n + errGrp.Go(func() error { + if err := n.Container.Terminate(testcontext.Get(t)); err != nil { + return err } - } - tokenDataProviders = append(tokenDataProviders, pluginconfig.TokenDataObserverConfig{ - Type: pluginconfig.USDCCCTPHandlerType, - Version: "1.0", - USDCCCTPObserverConfig: &pluginconfig.USDCCCTPObserverConfig{ - Tokens: cctpContracts, - AttestationAPI: endpoint, - AttestationAPITimeout: commonconfig.MustNewDuration(time.Second), - AttestationAPIInterval: commonconfig.MustNewDuration(500 * time.Millisecond), - }}) - } + err := n.RestartContainer() + if err != nil { + return err + } + return nil + }) - // Build the per chain config. - tokenConfig := changeset.NewTestTokenConfig(state.Chains[feedSel].USDFeeds) - chainConfigs := make(map[uint64]changeset.CCIPOCRParams) - timelocksPerChain := make(map[uint64]*gethwrappers.RBACTimelock) - for _, chain := range allChains { - timelocksPerChain[chain] = state.Chains[chain].Timelock - tokenInfo := tokenConfig.GetTokenInfo(e.Logger, state.Chains[chain].LinkToken, state.Chains[chain].Weth9) - ocrParams := changeset.DefaultOCRParams(feedSel, tokenInfo, tokenDataProviders) - if tCfg.OCRConfigOverride != nil { - ocrParams = tCfg.OCRConfigOverride(ocrParams) - } - chainConfigs[chain] = ocrParams } - - // Deploy second set of changesets to deploy and configure the CCIP contracts. - env, err = commonchangeset.ApplyChangesets(t, env, timelocksPerChain, []commonchangeset.ChangesetApplication{ - { - Changeset: commonchangeset.WrapChangeSet(changeset.ConfigureNewChains), - Config: changeset.NewChainsConfig{ - HomeChainSel: homeChainSel, - FeedChainSel: feedSel, - ChainConfigByChain: chainConfigs, - }, - }, - { - Changeset: commonchangeset.WrapChangeSet(changeset.CCIPCapabilityJobspec), - }, - }) - require.NoError(t, err) - - // Ensure capreg logs are up to date. - changeset.ReplayLogs(t, e.Offchain, replayBlocks) - - return changeset.DeployedEnv{ - Env: env, - HomeChainSel: homeChainSel, - FeedChainSel: feedSel, - ReplayBlocks: replayBlocks, - Users: users, - }, testEnv, cfg + return errGrp.Wait() } -func NewLocalDevEnvironmentWithRMN( - t *testing.T, - lggr logger.Logger, - numRmnNodes int, -) (changeset.DeployedEnv, devenv.RMNCluster) { - tenv, dockerenv, testCfg := NewLocalDevEnvironmentWithDefaultPrice(t, lggr, nil) - l := logging.GetTestLogger(t) - config := GenerateTestRMNConfig(t, numRmnNodes, tenv, MustNetworksToRPCMap(dockerenv.EVMNetworks)) - require.NotNil(t, testCfg.CCIP) - rmnCluster, err := devenv.NewRMNCluster( - t, l, - []string{dockerenv.DockerNetwork.ID}, - config, - testCfg.CCIP.RMNConfig.GetProxyImage(), - testCfg.CCIP.RMNConfig.GetProxyVersion(), - testCfg.CCIP.RMNConfig.GetAFN2ProxyImage(), - testCfg.CCIP.RMNConfig.GetAFN2ProxyVersion(), - dockerenv.LogStream, - ) - require.NoError(t, err) - return tenv, *rmnCluster +// NewIntegrationEnvironment creates a new integration test environment based on the provided test config +// It can create a memory environment or a docker environment based on env var CCIP_V16_TEST_ENV +// By default, it creates a memory environment if env var CCIP_V16_TEST_ENV is not set +// if CCIP_V16_TEST_ENV is set to 'docker', it creates a docker environment with test config provided under testconfig/ccip/ccip.toml +// It also creates a RMN cluster if the test config has RMN enabled +// It returns the deployed environment and RMN cluster ( in case of RMN enabled) +func NewIntegrationEnvironment(t *testing.T, opts ...changeset.TestOps) (changeset.DeployedEnv, devenv.RMNCluster) { + testCfg := changeset.DefaultTestConfigs() + for _, opt := range opts { + opt(testCfg) + } + // check for EnvType env var + testCfg.MustSetEnvTypeOrDefault(t) + require.NoError(t, testCfg.Validate(), "invalid test config") + switch testCfg.Type { + case changeset.Memory: + memEnv := changeset.NewMemoryEnvironment(t, opts...) + return memEnv, devenv.RMNCluster{} + case changeset.Docker: + dockerEnv := &DeployedLocalDevEnvironment{} + if testCfg.RMNEnabled { + deployedEnv := changeset.NewEnvironmentWithJobsAndContracts(t, testCfg, dockerEnv) + l := logging.GetTestLogger(t) + require.NotNil(t, dockerEnv.testEnv, "empty docker environment") + config := GenerateTestRMNConfig(t, testCfg.NumOfRMNNodes, deployedEnv, MustNetworksToRPCMap(dockerEnv.testEnv.EVMNetworks)) + require.NotNil(t, dockerEnv.devEnvTestCfg.CCIP) + rmnCluster, err := devenv.NewRMNCluster( + t, l, + []string{dockerEnv.testEnv.DockerNetwork.ID}, + config, + dockerEnv.devEnvTestCfg.CCIP.RMNConfig.GetProxyImage(), + dockerEnv.devEnvTestCfg.CCIP.RMNConfig.GetProxyVersion(), + dockerEnv.devEnvTestCfg.CCIP.RMNConfig.GetAFN2ProxyImage(), + dockerEnv.devEnvTestCfg.CCIP.RMNConfig.GetAFN2ProxyVersion(), + ) + require.NoError(t, err) + return deployedEnv, *rmnCluster + } + if testCfg.CreateJobAndContracts { + deployedEnv := changeset.NewEnvironmentWithJobsAndContracts(t, testCfg, dockerEnv) + require.NotNil(t, dockerEnv.testEnv, "empty docker environment") + return deployedEnv, devenv.RMNCluster{} + } + if testCfg.CreateJob { + deployedEnv := changeset.NewEnvironmentWithJobs(t, testCfg, dockerEnv) + require.NotNil(t, dockerEnv.testEnv, "empty docker environment") + return deployedEnv, devenv.RMNCluster{} + } + deployedEnv := changeset.NewEnvironment(t, testCfg, dockerEnv) + require.NotNil(t, dockerEnv.testEnv, "empty docker environment") + return deployedEnv, devenv.RMNCluster{} + default: + require.Failf(t, "Type %s not supported in integration tests choose between %s and %s", string(testCfg.Type), changeset.Memory, changeset.Docker) + } + return changeset.DeployedEnv{}, devenv.RMNCluster{} } func MustNetworksToRPCMap(evmNetworks []*blockchain.EVMNetwork) map[uint64]string { @@ -418,11 +324,30 @@ func CreateDockerEnv(t *testing.T) ( } } + // ignore critical CL node logs until they are fixed, as otherwise tests will fail + var logScannerSettings = test_env.GetDefaultChainlinkNodeLogScannerSettingsWithExtraAllowedMessages(testreporters.NewAllowedLogMessage( + "No live RPC nodes available", + "CL nodes are started before simulated chains, so this is expected", + zapcore.DPanicLevel, + testreporters.WarnAboutAllowedMsgs_No), + testreporters.NewAllowedLogMessage( + "Error stopping job service", + "Possible lifecycle bug in chainlink: failed to close RMN home reader: has already been stopped: already stopped", + zapcore.DPanicLevel, + testreporters.WarnAboutAllowedMsgs_No), + testreporters.NewAllowedLogMessage( + "Shutdown grace period of 5s exceeded, closing DB and exiting...", + "Possible lifecycle bug in chainlink.", + zapcore.DPanicLevel, + testreporters.WarnAboutAllowedMsgs_No), + ) + builder := test_env.NewCLTestEnvBuilder(). WithTestConfig(&cfg). WithTestInstance(t). WithMockAdapter(). WithJobDistributor(cfg.CCIP.JobDistributorConfig). + WithChainlinkNodeLogScanner(logScannerSettings). WithStandardCleanup() // if private ethereum networks are provided, we will use them to create the test environment @@ -528,7 +453,6 @@ func StartChainlinkNodes( pointer.GetString(cfg.GetChainlinkImageConfig().Image), pointer.GetString(cfg.GetChainlinkImageConfig().Version), toml, - env.LogStream, test_env.WithPgDBOptions( ctftestenv.WithPostgresImageVersion(pointer.GetString(cfg.GetChainlinkImageConfig().PostgresVersion)), ), diff --git a/integration-tests/utils/pgtest/pgtest.go b/integration-tests/utils/pgtest/pgtest.go index 8b11f9ef424..3baccc791b6 100644 --- a/integration-tests/utils/pgtest/pgtest.go +++ b/integration-tests/utils/pgtest/pgtest.go @@ -3,33 +3,18 @@ package pgtest import ( "testing" - "github.com/google/uuid" "github.com/jmoiron/sqlx" - "github.com/scylladb/go-reflectx" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "github.com/smartcontractkit/chainlink-common/pkg/sqlutil" - "github.com/smartcontractkit/chainlink-common/pkg/utils" - "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" - "github.com/smartcontractkit/chainlink/v2/core/store/dialects" + "github.com/smartcontractkit/chainlink-common/pkg/sqlutil/pg" + + "github.com/smartcontractkit/chainlink/v2/core/config/env" ) func NewSqlxDB(t testing.TB) *sqlx.DB { - db, err := sqlx.Open(string(dialects.TransactionWrappedPostgres), uuid.New().String()) - require.NoError(t, err) - t.Cleanup(func() { assert.NoError(t, db.Close()) }) - db.MapperFunc(reflectx.CamelToSnakeASCII) - - return db -} - -func MustExec(t *testing.T, ds sqlutil.DataSource, stmt string, args ...interface{}) { - ctx := tests.Context(t) - require.NoError(t, utils.JustError(ds.ExecContext(ctx, stmt, args...))) -} - -func MustCount(t *testing.T, db *sqlx.DB, stmt string, args ...interface{}) (cnt int) { - require.NoError(t, db.Get(&cnt, stmt, args...)) - return + dbURL := string(env.DatabaseURL.Get()) + if dbURL == "" { + t.Errorf("you must provide a CL_DATABASE_URL environment variable") + return nil + } + return pg.NewTestDB(t, dbURL) } diff --git a/integration-tests/utils/pgtest/txdb.go b/integration-tests/utils/pgtest/txdb.go deleted file mode 100644 index f28b6f95f2b..00000000000 --- a/integration-tests/utils/pgtest/txdb.go +++ /dev/null @@ -1,510 +0,0 @@ -package pgtest - -import ( - "context" - "database/sql" - "database/sql/driver" - "flag" - "fmt" - "io" - "net/url" - "strings" - "sync" - "testing" - - "github.com/jmoiron/sqlx" - "go.uber.org/multierr" - - "github.com/smartcontractkit/chainlink/v2/core/config/env" - "github.com/smartcontractkit/chainlink/v2/core/store/dialects" -) - -// txdb is a simplified version of https://github.com/DATA-DOG/go-txdb -// -// The original lib has various problems and is hard to understand because it -// tries to be more general. The version in this file is more tightly focused -// to our needs and should be easier to reason about and less likely to have -// subtle bugs/races. -// -// It doesn't currently support savepoints but could be made to if necessary. -// -// Transaction BEGIN/ROLLBACK effectively becomes a no-op, this should have no -// negative impact on normal test operation. -// -// If you MUST test BEGIN/ROLLBACK behaviour, you will have to configure your -// store to use the raw DialectPostgres dialect and setup a one-use database. -// See heavyweight.FullTestDB() as a convenience function to help you do this, -// but please use sparingly because as it's name implies, it is expensive. -func init() { - testing.Init() - if !flag.Parsed() { - flag.Parse() - } - if testing.Short() { - // -short tests don't need a DB - return - } - dbURL := string(env.DatabaseURL.Get()) - if dbURL == "" { - panic("you must provide a CL_DATABASE_URL environment variable") - } - - parsed, err := url.Parse(dbURL) - if err != nil { - panic(err) - } - if parsed.Path == "" { - msg := fmt.Sprintf("invalid %[1]s: `%[2]s`. You must set %[1]s env var to point to your test database. Note that the test database MUST end in `_test` to differentiate from a possible production DB. HINT: Try %[1]s=postgresql://postgres@localhost:5432/chainlink_test?sslmode=disable", env.DatabaseURL, parsed.String()) - panic(msg) - } - if !strings.HasSuffix(parsed.Path, "_test") { - msg := fmt.Sprintf("cannot run tests against database named `%s`. Note that the test database MUST end in `_test` to differentiate from a possible production DB. HINT: Try %s=postgresql://postgres@localhost:5432/chainlink_test?sslmode=disable", parsed.Path[1:], env.DatabaseURL) - panic(msg) - } - name := string(dialects.TransactionWrappedPostgres) - sql.Register(name, &txDriver{ - dbURL: dbURL, - conns: make(map[string]*conn), - }) - sqlx.BindDriver(name, sqlx.DOLLAR) -} - -var _ driver.Conn = &conn{} - -var _ driver.Validator = &conn{} -var _ driver.SessionResetter = &conn{} - -// txDriver is an sql driver which runs on a single transaction. -// When `Close` is called, transaction is rolled back. -type txDriver struct { - sync.Mutex - db *sql.DB - conns map[string]*conn - - dbURL string -} - -func (d *txDriver) Open(dsn string) (driver.Conn, error) { - d.Lock() - defer d.Unlock() - // Open real db connection if its the first call - if d.db == nil { - db, err := sql.Open(string(dialects.Postgres), d.dbURL) - if err != nil { - return nil, err - } - d.db = db - } - c, exists := d.conns[dsn] - if !exists || !c.tryOpen() { - tx, err := d.db.Begin() - if err != nil { - return nil, err - } - c = &conn{tx: tx, opened: 1, dsn: dsn} - c.removeSelf = func() error { - return d.deleteConn(c) - } - d.conns[dsn] = c - } - return c, nil -} - -// deleteConn is called by a connection when it is closed via the `close` method. -// It also auto-closes the DB when the last checked out connection is closed. -func (d *txDriver) deleteConn(c *conn) error { - // must lock here to avoid racing with Open - d.Lock() - defer d.Unlock() - - if d.conns[c.dsn] != c { - return nil // already been replaced - } - delete(d.conns, c.dsn) - if len(d.conns) == 0 && d.db != nil { - if err := d.db.Close(); err != nil { - return err - } - d.db = nil - } - return nil -} - -type conn struct { - sync.Mutex - dsn string - tx *sql.Tx // tx may be shared by many conns, definitive one lives in the map keyed by DSN on the txDriver. Do not modify from conn - closed bool - opened int - removeSelf func() error -} - -func (c *conn) Begin() (driver.Tx, error) { - c.Lock() - defer c.Unlock() - if c.closed { - panic("conn is closed") - } - // Begin is a noop because the transaction was already opened - return tx{c.tx}, nil -} - -// Implement the "ConnBeginTx" interface -func (c *conn) BeginTx(_ context.Context, opts driver.TxOptions) (driver.Tx, error) { - // Context is ignored, because single transaction is shared by all callers, thus caller should not be able to - // control it with local context - return c.Begin() -} - -// Prepare returns a prepared statement, bound to this connection. -func (c *conn) Prepare(query string) (driver.Stmt, error) { - return c.PrepareContext(context.Background(), query) -} - -// Implement the "ConnPrepareContext" interface -func (c *conn) PrepareContext(ctx context.Context, query string) (driver.Stmt, error) { - c.Lock() - defer c.Unlock() - if c.closed { - panic("conn is closed") - } - - // TODO: Fix context handling - // FIXME: It is not safe to give the passed in context to the tx directly - // because the tx is shared by many conns and cancelling the context will - // destroy the tx which can affect other conns - st, err := c.tx.PrepareContext(context.Background(), query) - if err != nil { - return nil, err - } - return &stmt{st, c}, nil -} - -// IsValid is called prior to placing the connection into the -// connection pool by database/sql. The connection will be discarded if false is returned. -func (c *conn) IsValid() bool { - c.Lock() - defer c.Unlock() - return !c.closed -} - -func (c *conn) ResetSession(ctx context.Context) error { - // Ensure bad connections are reported: From database/sql/driver: - // If a connection is never returned to the connection pool but immediately reused, then - // ResetSession is called prior to reuse but IsValid is not called. - c.Lock() - defer c.Unlock() - if c.closed { - return driver.ErrBadConn - } - - return nil -} - -// pgx returns nil -func (c *conn) CheckNamedValue(nv *driver.NamedValue) error { - return nil -} - -// Implement the "QueryerContext" interface -func (c *conn) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Rows, error) { - c.Lock() - defer c.Unlock() - if c.closed { - panic("conn is closed") - } - - // TODO: Fix context handling - rs, err := c.tx.QueryContext(context.Background(), query, mapNamedArgs(args)...) - if err != nil { - return nil, err - } - defer rs.Close() - - return buildRows(rs) -} - -// Implement the "ExecerContext" interface -func (c *conn) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Result, error) { - c.Lock() - defer c.Unlock() - if c.closed { - panic("conn is closed") - } - // TODO: Fix context handling - return c.tx.ExecContext(context.Background(), query, mapNamedArgs(args)...) -} - -// tryOpen attempts to increment the open count, but returns false if closed. -func (c *conn) tryOpen() bool { - c.Lock() - defer c.Unlock() - if c.closed { - return false - } - c.opened++ - return true -} - -// Close invalidates and potentially stops any current -// prepared statements and transactions, marking this -// connection as no longer in use. -// -// Because the sql package maintains a free pool of -// connections and only calls Close when there's a surplus of -// idle connections, it shouldn't be necessary for drivers to -// do their own connection caching. -// -// Drivers must ensure all network calls made by Close -// do not block indefinitely (e.g. apply a timeout). -func (c *conn) Close() (err error) { - if !c.close() { - return - } - // Wait to remove self to avoid nesting locks. - if err := c.removeSelf(); err != nil { - panic(err) - } - return -} - -//nolint:revive -func (c *conn) close() bool { - c.Lock() - defer c.Unlock() - if c.closed { - // Double close, should be a safe to make this a noop - // PGX allows double close - // See: https://github.com/jackc/pgx/blob/a457da8bffa4f90ad672fa093ee87f20cf06687b/conn.go#L249 - return false - } - - c.opened-- - if c.opened > 0 { - return false - } - if c.tx != nil { - if err := c.tx.Rollback(); err != nil { - panic(err) - } - c.tx = nil - } - c.closed = true - return true -} - -type tx struct { - tx *sql.Tx -} - -func (tx tx) Commit() error { - // Commit is a noop because the transaction will be rolled back at the end - return nil -} - -func (tx tx) Rollback() error { - // Rollback is a noop because the transaction will be rolled back at the end - return nil -} - -type stmt struct { - st *sql.Stmt - conn *conn -} - -func (s stmt) Exec(args []driver.Value) (driver.Result, error) { - s.conn.Lock() - defer s.conn.Unlock() - if s.conn.closed { - panic("conn is closed") - } - return s.st.Exec(mapArgs(args)...) -} - -// Implement the "StmtExecContext" interface -func (s *stmt) ExecContext(ctx context.Context, args []driver.NamedValue) (driver.Result, error) { - s.conn.Lock() - defer s.conn.Unlock() - if s.conn.closed { - panic("conn is closed") - } - // TODO: Fix context handling - return s.st.ExecContext(context.Background(), mapNamedArgs(args)...) -} - -func mapArgs(args []driver.Value) (res []interface{}) { - res = make([]interface{}, len(args)) - for i := range args { - res[i] = args[i] - } - return -} - -func (s stmt) NumInput() int { - return -1 -} - -func (s stmt) Query(args []driver.Value) (driver.Rows, error) { - s.conn.Lock() - defer s.conn.Unlock() - if s.conn.closed { - panic("conn is closed") - } - rows, err := s.st.Query(mapArgs(args)...) - defer func() { - err = multierr.Combine(err, rows.Close()) - }() - if err != nil { - return nil, err - } - return buildRows(rows) -} - -// Implement the "StmtQueryContext" interface -func (s *stmt) QueryContext(ctx context.Context, args []driver.NamedValue) (driver.Rows, error) { - s.conn.Lock() - defer s.conn.Unlock() - if s.conn.closed { - panic("conn is closed") - } - // TODO: Fix context handling - rows, err := s.st.QueryContext(context.Background(), mapNamedArgs(args)...) - if err != nil { - return nil, err - } - return buildRows(rows) -} - -func (s stmt) Close() error { - s.conn.Lock() - defer s.conn.Unlock() - return s.st.Close() -} - -func buildRows(r *sql.Rows) (driver.Rows, error) { - set := &rowSets{} - rs := &rows{} - if err := rs.read(r); err != nil { - return set, err - } - set.sets = append(set.sets, rs) - for r.NextResultSet() { - rss := &rows{} - if err := rss.read(r); err != nil { - return set, err - } - set.sets = append(set.sets, rss) - } - return set, nil -} - -// Implement the "RowsNextResultSet" interface -func (rs *rowSets) HasNextResultSet() bool { - return rs.pos+1 < len(rs.sets) -} - -// Implement the "RowsNextResultSet" interface -func (rs *rowSets) NextResultSet() error { - if !rs.HasNextResultSet() { - return io.EOF - } - - rs.pos++ - return nil -} - -type rows struct { - rows [][]driver.Value - pos int - cols []string - colTypes []*sql.ColumnType -} - -func (r *rows) Columns() []string { - return r.cols -} - -func (r *rows) ColumnTypeDatabaseTypeName(index int) string { - return r.colTypes[index].DatabaseTypeName() -} - -func (r *rows) Next(dest []driver.Value) error { - r.pos++ - if r.pos > len(r.rows) { - return io.EOF - } - - for i, val := range r.rows[r.pos-1] { - dest[i] = *(val.(*interface{})) - } - - return nil -} - -func (r *rows) Close() error { - return nil -} - -func (r *rows) read(rs *sql.Rows) error { - var err error - r.cols, err = rs.Columns() - if err != nil { - return err - } - - r.colTypes, err = rs.ColumnTypes() - if err != nil { - return err - } - - for rs.Next() { - values := make([]interface{}, len(r.cols)) - for i := range values { - values[i] = new(interface{}) - } - if err := rs.Scan(values...); err != nil { - return err - } - row := make([]driver.Value, len(r.cols)) - for i, v := range values { - row[i] = driver.Value(v) - } - r.rows = append(r.rows, row) - } - return rs.Err() -} - -type rowSets struct { - sets []*rows - pos int -} - -func (rs *rowSets) Columns() []string { - return rs.sets[rs.pos].cols -} - -func (rs *rowSets) ColumnTypeDatabaseTypeName(index int) string { - return rs.sets[rs.pos].ColumnTypeDatabaseTypeName(index) -} - -func (rs *rowSets) Close() error { - return nil -} - -// advances to next row -func (rs *rowSets) Next(dest []driver.Value) error { - return rs.sets[rs.pos].Next(dest) -} - -func mapNamedArgs(args []driver.NamedValue) (res []interface{}) { - res = make([]interface{}, len(args)) - for i := range args { - name := args[i].Name - if name != "" { - res[i] = sql.Named(name, args[i].Value) - } else { - res[i] = args[i].Value - } - } - return -} diff --git a/internal/testdb/testdb.go b/internal/testdb/testdb.go index 88251ae2c6f..1a52b1173e3 100644 --- a/internal/testdb/testdb.go +++ b/internal/testdb/testdb.go @@ -7,7 +7,7 @@ import ( "net/url" "strings" - "github.com/smartcontractkit/chainlink/v2/core/store/dialects" + pgcommon "github.com/smartcontractkit/chainlink-common/pkg/sqlutil/pg" ) const ( @@ -33,7 +33,7 @@ func CreateOrReplace(parsed url.URL, suffix string, withTemplate bool) (string, // Cannot drop test database if we are connected to it, so we must connect // to a different one. 'postgres' should be present on all postgres installations parsed.Path = "/postgres" - db, err := sql.Open(string(dialects.Postgres), parsed.String()) + db, err := sql.Open(string(pgcommon.Postgres), parsed.String()) if err != nil { return "", fmt.Errorf("in order to drop the test database, we need to connect to a separate database"+ " called 'postgres'. But we are unable to open 'postgres' database: %+v\n", err) @@ -66,7 +66,7 @@ func Drop(dbURL url.URL) error { // Cannot drop test database if we are connected to it, so we must connect // to a different one. 'postgres' should be present on all postgres installations dbURL.Path = "/postgres" - db, err := sql.Open(string(dialects.Postgres), dbURL.String()) + db, err := sql.Open(string(pgcommon.Postgres), dbURL.String()) if err != nil { return fmt.Errorf("in order to drop the test database, we need to connect to a separate database"+ " called 'postgres'. But we are unable to open 'postgres' database: %+v\n", err) diff --git a/plugins/registrar.go b/plugins/registrar.go index 2a82f2a6204..8523d3980cc 100644 --- a/plugins/registrar.go +++ b/plugins/registrar.go @@ -6,7 +6,7 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/loop" ) -// RegistrarConfig generates contains static configuration inher +// RegistrarConfig generates contains static configuration type RegistrarConfig interface { RegisterLOOP(config CmdConfig) (func() *exec.Cmd, loop.GRPCOpts, error) UnregisterLOOP(ID string) diff --git a/testdata/scripts/chains/cosmos/help.txtar b/testdata/scripts/chains/cosmos/help.txtar index edef6f7345c..4fe0a930ac0 100644 --- a/testdata/scripts/chains/cosmos/help.txtar +++ b/testdata/scripts/chains/cosmos/help.txtar @@ -3,13 +3,13 @@ cmp stdout out.txt -- out.txt -- NAME: - chainlink chains cosmos - Commands for handling Cosmos chains + chainlink chains cosmos - Commands for handling cosmos chains USAGE: chainlink chains cosmos command [command options] [arguments...] COMMANDS: - list List all existing Cosmos chains + list List all existing cosmos chains OPTIONS: --help, -h show help diff --git a/testdata/scripts/chains/cosmos/list/help.txtar b/testdata/scripts/chains/cosmos/list/help.txtar index 7e9be2efb00..d1f2d166374 100644 --- a/testdata/scripts/chains/cosmos/list/help.txtar +++ b/testdata/scripts/chains/cosmos/list/help.txtar @@ -3,7 +3,7 @@ cmp stdout out.txt -- out.txt -- NAME: - chainlink chains cosmos list - List all existing Cosmos chains + chainlink chains cosmos list - List all existing cosmos chains USAGE: chainlink chains cosmos list [arguments...] diff --git a/testdata/scripts/chains/evm/help.txtar b/testdata/scripts/chains/evm/help.txtar index e15dde5ecd2..b3e9e366810 100644 --- a/testdata/scripts/chains/evm/help.txtar +++ b/testdata/scripts/chains/evm/help.txtar @@ -3,13 +3,13 @@ cmp stdout out.txt -- out.txt -- NAME: - chainlink chains evm - Commands for handling EVM chains + chainlink chains evm - Commands for handling evm chains USAGE: chainlink chains evm command [command options] [arguments...] COMMANDS: - list List all existing EVM chains + list List all existing evm chains OPTIONS: --help, -h show help diff --git a/testdata/scripts/chains/evm/list/help.txtar b/testdata/scripts/chains/evm/list/help.txtar index 154ee110ad5..bb5eec199b7 100644 --- a/testdata/scripts/chains/evm/list/help.txtar +++ b/testdata/scripts/chains/evm/list/help.txtar @@ -3,7 +3,7 @@ cmp stdout out.txt -- out.txt -- NAME: - chainlink chains evm list - List all existing EVM chains + chainlink chains evm list - List all existing evm chains USAGE: chainlink chains evm list [arguments...] diff --git a/testdata/scripts/chains/help.txtar b/testdata/scripts/chains/help.txtar index 83a342925e1..ccfb54d2928 100644 --- a/testdata/scripts/chains/help.txtar +++ b/testdata/scripts/chains/help.txtar @@ -9,10 +9,11 @@ USAGE: chainlink chains command [command options] [arguments...] COMMANDS: - evm Commands for handling EVM chains - cosmos Commands for handling Cosmos chains - solana Commands for handling Solana chains - starknet Commands for handling StarkNet chains + aptos Commands for handling aptos chains + cosmos Commands for handling cosmos chains + evm Commands for handling evm chains + solana Commands for handling solana chains + starknet Commands for handling starknet chains OPTIONS: --help, -h show help diff --git a/testdata/scripts/chains/solana/help.txtar b/testdata/scripts/chains/solana/help.txtar index be3ab9b343b..ddc27fed18f 100644 --- a/testdata/scripts/chains/solana/help.txtar +++ b/testdata/scripts/chains/solana/help.txtar @@ -3,13 +3,13 @@ cmp stdout out.txt -- out.txt -- NAME: - chainlink chains solana - Commands for handling Solana chains + chainlink chains solana - Commands for handling solana chains USAGE: chainlink chains solana command [command options] [arguments...] COMMANDS: - list List all existing Solana chains + list List all existing solana chains OPTIONS: --help, -h show help diff --git a/testdata/scripts/chains/solana/list/help.txtar b/testdata/scripts/chains/solana/list/help.txtar index 794085d43d9..ed8a857529d 100644 --- a/testdata/scripts/chains/solana/list/help.txtar +++ b/testdata/scripts/chains/solana/list/help.txtar @@ -3,7 +3,7 @@ cmp stdout out.txt -- out.txt -- NAME: - chainlink chains solana list - List all existing Solana chains + chainlink chains solana list - List all existing solana chains USAGE: chainlink chains solana list [arguments...] diff --git a/testdata/scripts/chains/starknet/help.txtar b/testdata/scripts/chains/starknet/help.txtar index 992623d842c..5cb8fe93746 100644 --- a/testdata/scripts/chains/starknet/help.txtar +++ b/testdata/scripts/chains/starknet/help.txtar @@ -3,13 +3,13 @@ cmp stdout out.txt -- out.txt -- NAME: - chainlink chains starknet - Commands for handling StarkNet chains + chainlink chains starknet - Commands for handling starknet chains USAGE: chainlink chains starknet command [command options] [arguments...] COMMANDS: - list List all existing StarkNet chains + list List all existing starknet chains OPTIONS: --help, -h show help diff --git a/testdata/scripts/chains/starknet/list/help.txtar b/testdata/scripts/chains/starknet/list/help.txtar index 723318bf098..67315ce6c1e 100644 --- a/testdata/scripts/chains/starknet/list/help.txtar +++ b/testdata/scripts/chains/starknet/list/help.txtar @@ -3,7 +3,7 @@ cmp stdout out.txt -- out.txt -- NAME: - chainlink chains starknet list - List all existing StarkNet chains + chainlink chains starknet list - List all existing starknet chains USAGE: chainlink chains starknet list [arguments...] diff --git a/testdata/scripts/help-all/help-all.txtar b/testdata/scripts/help-all/help-all.txtar index 372b149bd19..078853ef6a5 100644 --- a/testdata/scripts/help-all/help-all.txtar +++ b/testdata/scripts/help-all/help-all.txtar @@ -24,14 +24,16 @@ bridges destroy # Destroys the Bridge for an External Adapter bridges list # List all Bridges to External Adapters bridges show # Show a Bridge's details chains # Commands for handling chain configuration -chains cosmos # Commands for handling Cosmos chains -chains cosmos list # List all existing Cosmos chains -chains evm # Commands for handling EVM chains -chains evm list # List all existing EVM chains -chains solana # Commands for handling Solana chains -chains solana list # List all existing Solana chains -chains starknet # Commands for handling StarkNet chains -chains starknet list # List all existing StarkNet chains +chains aptos # Commands for handling aptos chains +chains aptos list # List all existing aptos chains +chains cosmos # Commands for handling cosmos chains +chains cosmos list # List all existing cosmos chains +chains evm # Commands for handling evm chains +chains evm list # List all existing evm chains +chains solana # Commands for handling solana chains +chains solana list # List all existing solana chains +chains starknet # Commands for handling starknet chains +chains starknet list # List all existing starknet chains config # Commands for the node's configuration config loglevel # Set log level config logsql # Enable/disable SQL statement logging @@ -132,14 +134,16 @@ node start # Run the Chainlink node node status # Displays the health of various services running inside the node. node validate # Validate the TOML configuration and secrets that are passed as flags to the `node` command. Prints the full effective configuration, with defaults included nodes # Commands for handling node configuration -nodes cosmos # Commands for handling Cosmos node configuration -nodes cosmos list # List all existing Cosmos nodes -nodes evm # Commands for handling EVM node configuration -nodes evm list # List all existing EVM nodes -nodes solana # Commands for handling Solana node configuration -nodes solana list # List all existing Solana nodes -nodes starknet # Commands for handling StarkNet node configuration -nodes starknet list # List all existing StarkNet nodes +nodes aptos # Commands for handling aptos node configuration +nodes aptos list # List all existing aptos nodes +nodes cosmos # Commands for handling cosmos node configuration +nodes cosmos list # List all existing cosmos nodes +nodes evm # Commands for handling evm node configuration +nodes evm list # List all existing evm nodes +nodes solana # Commands for handling solana node configuration +nodes solana list # List all existing solana nodes +nodes starknet # Commands for handling starknet node configuration +nodes starknet list # List all existing starknet nodes txs # Commands for handling transactions txs cosmos # Commands for handling Cosmos transactions txs cosmos create # Send of from node Cosmos account to destination . diff --git a/testdata/scripts/nodes/cosmos/help.txtar b/testdata/scripts/nodes/cosmos/help.txtar index b605e676f8c..78939331b6b 100644 --- a/testdata/scripts/nodes/cosmos/help.txtar +++ b/testdata/scripts/nodes/cosmos/help.txtar @@ -3,13 +3,13 @@ cmp stdout out.txt -- out.txt -- NAME: - chainlink nodes cosmos - Commands for handling Cosmos node configuration + chainlink nodes cosmos - Commands for handling cosmos node configuration USAGE: chainlink nodes cosmos command [command options] [arguments...] COMMANDS: - list List all existing Cosmos nodes + list List all existing cosmos nodes OPTIONS: --help, -h show help diff --git a/testdata/scripts/nodes/cosmos/list/help.txtar b/testdata/scripts/nodes/cosmos/list/help.txtar index c984b4f3e18..372693c704c 100644 --- a/testdata/scripts/nodes/cosmos/list/help.txtar +++ b/testdata/scripts/nodes/cosmos/list/help.txtar @@ -3,7 +3,7 @@ cmp stdout out.txt -- out.txt -- NAME: - chainlink nodes cosmos list - List all existing Cosmos nodes + chainlink nodes cosmos list - List all existing cosmos nodes USAGE: chainlink nodes cosmos list [arguments...] diff --git a/testdata/scripts/nodes/evm/help.txtar b/testdata/scripts/nodes/evm/help.txtar index dfc13eba9aa..5e9d9d482ab 100644 --- a/testdata/scripts/nodes/evm/help.txtar +++ b/testdata/scripts/nodes/evm/help.txtar @@ -3,13 +3,13 @@ cmp stdout out.txt -- out.txt -- NAME: - chainlink nodes evm - Commands for handling EVM node configuration + chainlink nodes evm - Commands for handling evm node configuration USAGE: chainlink nodes evm command [command options] [arguments...] COMMANDS: - list List all existing EVM nodes + list List all existing evm nodes OPTIONS: --help, -h show help diff --git a/testdata/scripts/nodes/evm/list/help.txtar b/testdata/scripts/nodes/evm/list/help.txtar index 62d3814823d..3ecfe32de40 100644 --- a/testdata/scripts/nodes/evm/list/help.txtar +++ b/testdata/scripts/nodes/evm/list/help.txtar @@ -3,7 +3,7 @@ cmp stdout out.txt -- out.txt -- NAME: - chainlink nodes evm list - List all existing EVM nodes + chainlink nodes evm list - List all existing evm nodes USAGE: chainlink nodes evm list [arguments...] diff --git a/testdata/scripts/nodes/help.txtar b/testdata/scripts/nodes/help.txtar index 8a8f31f4166..f9132045d29 100644 --- a/testdata/scripts/nodes/help.txtar +++ b/testdata/scripts/nodes/help.txtar @@ -9,10 +9,11 @@ USAGE: chainlink nodes command [command options] [arguments...] COMMANDS: - evm Commands for handling EVM node configuration - cosmos Commands for handling Cosmos node configuration - solana Commands for handling Solana node configuration - starknet Commands for handling StarkNet node configuration + aptos Commands for handling aptos node configuration + cosmos Commands for handling cosmos node configuration + evm Commands for handling evm node configuration + solana Commands for handling solana node configuration + starknet Commands for handling starknet node configuration OPTIONS: --help, -h show help diff --git a/testdata/scripts/nodes/solana/help.txtar b/testdata/scripts/nodes/solana/help.txtar index 8a0d3751ed7..3b10772e3e4 100644 --- a/testdata/scripts/nodes/solana/help.txtar +++ b/testdata/scripts/nodes/solana/help.txtar @@ -3,13 +3,13 @@ cmp stdout out.txt -- out.txt -- NAME: - chainlink nodes solana - Commands for handling Solana node configuration + chainlink nodes solana - Commands for handling solana node configuration USAGE: chainlink nodes solana command [command options] [arguments...] COMMANDS: - list List all existing Solana nodes + list List all existing solana nodes OPTIONS: --help, -h show help diff --git a/testdata/scripts/nodes/solana/list/help.txtar b/testdata/scripts/nodes/solana/list/help.txtar index 9cb124a84d8..29787c54d94 100644 --- a/testdata/scripts/nodes/solana/list/help.txtar +++ b/testdata/scripts/nodes/solana/list/help.txtar @@ -3,7 +3,7 @@ cmp stdout out.txt -- out.txt -- NAME: - chainlink nodes solana list - List all existing Solana nodes + chainlink nodes solana list - List all existing solana nodes USAGE: chainlink nodes solana list [arguments...] diff --git a/testdata/scripts/nodes/starknet/help.txtar b/testdata/scripts/nodes/starknet/help.txtar index bd0b11c6ec5..0ec3bc19730 100644 --- a/testdata/scripts/nodes/starknet/help.txtar +++ b/testdata/scripts/nodes/starknet/help.txtar @@ -3,13 +3,13 @@ cmp stdout out.txt -- out.txt -- NAME: - chainlink nodes starknet - Commands for handling StarkNet node configuration + chainlink nodes starknet - Commands for handling starknet node configuration USAGE: chainlink nodes starknet command [command options] [arguments...] COMMANDS: - list List all existing StarkNet nodes + list List all existing starknet nodes OPTIONS: --help, -h show help diff --git a/testdata/scripts/nodes/starknet/list/help.txtar b/testdata/scripts/nodes/starknet/list/help.txtar index e38b2ff8867..b4bbdf5ae76 100644 --- a/testdata/scripts/nodes/starknet/list/help.txtar +++ b/testdata/scripts/nodes/starknet/list/help.txtar @@ -3,7 +3,7 @@ cmp stdout out.txt -- out.txt -- NAME: - chainlink nodes starknet list - List all existing StarkNet nodes + chainlink nodes starknet list - List all existing starknet nodes USAGE: chainlink nodes starknet list [arguments...] diff --git a/tools/bin/goreleaser_utils b/tools/bin/goreleaser_utils index 52e37cefd51..0bf745d5a58 100755 --- a/tools/bin/goreleaser_utils +++ b/tools/bin/goreleaser_utils @@ -27,8 +27,10 @@ before_hook() { # linux_arm64, rather than being suffixless on native platforms if [ "$GOARCH" = "arm64" ]; then if [ -d "$BIN_DIR/linux_arm64" ]; then + cp "$BIN_DIR/linux_arm64"/chainlink* "$PLUGIN_DIR" + elif [ -d "$BIN_DIR/linux_arm64_v8.0" ]; then cp "$BIN_DIR/linux_arm64_v8.0"/chainlink* "$PLUGIN_DIR" - else + else cp "$BIN_DIR"/chainlink* "$PLUGIN_DIR" fi # Call patchelf --set-interpreter on all plugins diff --git a/tools/gomod-local-update/README.md b/tools/gomod-local-update/README.md new file mode 100644 index 00000000000..a49a663ecaa --- /dev/null +++ b/tools/gomod-local-update/README.md @@ -0,0 +1,41 @@ +# gomod-local-update + +Updates any module that is `replace`'d with a local path to have its required module version in `go.mod` to match the latest git SHA from a remote branch. + +Is meant to run within each directory where a `go.mod` file is present. + +## Configuration + +Command Line Flags: + +```shell +Optional: + -org-name Organization name (default: smartcontractkit) + -repo-name Repository name (default: chainlink) + -repo-remote Git remote to use (default: origin) + -branch-trunk Branch to get SHA from (default: develop) + -dry-run Preview changes without applying them (default: false) +``` + +## Installation + +The installed binary will be placed in your `$GOPATH/bin` directory. Make sure this directory is in your system's PATH to run the command from anywhere. From the root of this repository, run: + +```shell +go install ./tools/gomod-local-update/cmd/gomod-local-update +``` + +## Usage Examples + +Run from the root of a go module directory. + +```shell +gomod-local-update +``` + +Was designed to be used with [gomods](https://github.com/jmank88/gomods) like: + +```shell +gomods -w gomod-local-update +gomods tidy +``` diff --git a/tools/gomod-local-update/cmd/gomod-local-update/main.go b/tools/gomod-local-update/cmd/gomod-local-update/main.go new file mode 100644 index 00000000000..c471e8db776 --- /dev/null +++ b/tools/gomod-local-update/cmd/gomod-local-update/main.go @@ -0,0 +1,48 @@ +package main + +import ( + "fmt" + "log" + "os" + + "github.com/smartcontractkit/chainlink/v2/tools/gomod-local-update/internal/updater" +) + +const ( + goBinaryName = "gomod-local-update" +) + +var version = "dev" +var usage = fmt.Sprintf(`%s version %%s + +Usage: + cd /path/to/go/module + %s [flags] +`, goBinaryName, goBinaryName) + +func main() { + cfg, err := updater.ParseFlags(os.Args[1:], version) + if err != nil { + fmt.Fprintf(os.Stderr, usage, version) + log.Fatal(err) + } + + if cfg.ShowVersion { + fmt.Printf("%s version %s\n", goBinaryName, version) + os.Exit(0) + } + + if err := cfg.Validate(); err != nil { + fmt.Fprintf(os.Stderr, usage, version) + log.Fatal(err) + } + + u := updater.New( + cfg, + updater.NewSystemOperator(), + ) + + if err := u.Run(); err != nil { + log.Fatal(err) + } +} diff --git a/tools/gomod-local-update/internal/updater/config.go b/tools/gomod-local-update/internal/updater/config.go new file mode 100644 index 00000000000..444193b8086 --- /dev/null +++ b/tools/gomod-local-update/internal/updater/config.go @@ -0,0 +1,69 @@ +package updater + +import ( + "flag" + "fmt" +) + +const ( + DefaultRepoRemote = "origin" + DefaultBranchTrunk = "develop" + DefaultOrgName = "smartcontractkit" + DefaultRepoName = "chainlink" +) + +type Config struct { + RepoRemote string + BranchTrunk string + DryRun bool + ShowVersion bool + OrgName string + RepoName string +} + +func ParseFlags(args []string, version string) (*Config, error) { + flags := flag.NewFlagSet("default", flag.ContinueOnError) + + cfg := &Config{} + + flags.StringVar(&cfg.RepoRemote, "repo-remote", DefaultRepoRemote, "Git remote to use") + flags.StringVar(&cfg.BranchTrunk, "branch-trunk", DefaultBranchTrunk, "Branch to get SHA from") + flags.BoolVar(&cfg.DryRun, "dry-run", false, "Preview changes without applying them") + flags.BoolVar(&cfg.ShowVersion, "version", false, "Show version information") + flags.StringVar(&cfg.OrgName, "org-name", DefaultOrgName, "GitHub organization name") + flags.StringVar(&cfg.RepoName, "repo-name", DefaultRepoName, "GitHub repository name") + + if err := flags.Parse(args); err != nil { + return nil, err + } + + return cfg, nil +} + +func (c *Config) Validate() error { + if c.ShowVersion { + return nil + } + + if c.OrgName == "" { + return fmt.Errorf("%w: org name must be provided", ErrInvalidConfig) + } + if c.RepoName == "" { + return fmt.Errorf("%w: repo name must be provided", ErrInvalidConfig) + } + if c.RepoRemote == "" { + return fmt.Errorf("%w: repo remote must be provided", ErrInvalidConfig) + } + if c.BranchTrunk == "" { + return fmt.Errorf("%w: trunk branch must be provided", ErrInvalidConfig) + } + + if !gitRemoteRE.MatchString(c.RepoRemote) { + return fmt.Errorf("%w: git remote '%s' contains invalid characters", ErrInvalidConfig, c.RepoRemote) + } + if !gitBranchRE.MatchString(c.BranchTrunk) { + return fmt.Errorf("%w: git branch '%s' contains invalid characters", ErrInvalidConfig, c.BranchTrunk) + } + + return nil +} diff --git a/tools/gomod-local-update/internal/updater/config_test.go b/tools/gomod-local-update/internal/updater/config_test.go new file mode 100644 index 00000000000..21b867aea8d --- /dev/null +++ b/tools/gomod-local-update/internal/updater/config_test.go @@ -0,0 +1,177 @@ +package updater + +import ( + "errors" + "testing" +) + +func TestConfig_Validate(t *testing.T) { + tests := []struct { + name string + config *Config + wantErr bool + }{ + { + name: "valid config", + config: &Config{ + RepoRemote: DefaultRepoRemote, + BranchTrunk: DefaultBranchTrunk, + OrgName: DefaultOrgName, + RepoName: DefaultRepoName, + }, + wantErr: false, + }, + { + name: "version flag bypasses validation", + config: &Config{ + ShowVersion: true, + }, + wantErr: false, + }, + { + name: "missing repo remote", + config: &Config{ + BranchTrunk: DefaultBranchTrunk, + OrgName: DefaultOrgName, + RepoName: DefaultRepoName, + }, + wantErr: true, + }, + { + name: "missing branch trunk", + config: &Config{ + RepoRemote: DefaultRepoRemote, + OrgName: DefaultOrgName, + RepoName: DefaultRepoName, + }, + wantErr: true, + }, + { + name: "missing org name", + config: &Config{ + RepoRemote: DefaultRepoRemote, + BranchTrunk: DefaultBranchTrunk, + RepoName: DefaultRepoName, + }, + wantErr: true, + }, + { + name: "missing repo name", + config: &Config{ + RepoRemote: DefaultRepoRemote, + BranchTrunk: DefaultBranchTrunk, + OrgName: DefaultOrgName, + }, + wantErr: true, + }, + { + name: "invalid remote characters", + config: &Config{ + OrgName: "test", + RepoName: "test", + RepoRemote: "origin!@#", + BranchTrunk: "main", + }, + wantErr: true, + }, + { + name: "invalid branch characters", + config: &Config{ + OrgName: "test", + RepoName: "test", + RepoRemote: "origin", + BranchTrunk: "main!@#", + }, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := tt.config.Validate() + if (err != nil) != tt.wantErr { + t.Errorf("Validate() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestConfig_ValidateErrorType(t *testing.T) { + cfg := &Config{ + RepoRemote: "invalid*remote", + BranchTrunk: "develop", + OrgName: "test", + RepoName: "test", + } + + err := cfg.Validate() + if err == nil { + t.Error("expected error due to invalid repo remote, got nil") + return + } + + if !errors.Is(err, ErrInvalidConfig) { + t.Errorf("expected error to be ErrInvalidConfig, got: %v", err) + } +} + +func TestParseFlags(t *testing.T) { + tests := []struct { + name string + args []string + wantCfg *Config + wantErr bool + }{ + { + name: "default flags", + args: []string{}, + wantCfg: &Config{ + RepoRemote: "origin", + BranchTrunk: "develop", + }, + wantErr: false, + }, + { + name: "show version", + args: []string{"-version"}, + wantCfg: &Config{ + ShowVersion: true, + RepoRemote: "origin", + BranchTrunk: "develop", + }, + wantErr: false, + }, + { + name: "custom remote and branch", + args: []string{"-repo-remote", "upstream", "-branch-trunk", "main"}, + wantCfg: &Config{ + RepoRemote: "upstream", + BranchTrunk: "main", + }, + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := ParseFlags(tt.args, "test-version") + if (err != nil) != tt.wantErr { + t.Errorf("ParseFlags() error = %v, wantErr %v", err, tt.wantErr) + return + } + if err != nil { + return + } + + if got.RepoRemote != tt.wantCfg.RepoRemote { + t.Errorf("ParseFlags() RepoRemote = %v, want %v", got.RepoRemote, tt.wantCfg.RepoRemote) + } + if got.BranchTrunk != tt.wantCfg.BranchTrunk { + t.Errorf("ParseFlags() BranchTrunk = %v, want %v", got.BranchTrunk, tt.wantCfg.BranchTrunk) + } + if got.ShowVersion != tt.wantCfg.ShowVersion { + t.Errorf("ParseFlags() ShowVersion = %v, want %v", got.ShowVersion, tt.wantCfg.ShowVersion) + } + }) + } +} diff --git a/tools/gomod-local-update/internal/updater/errors.go b/tools/gomod-local-update/internal/updater/errors.go new file mode 100644 index 00000000000..288eb49f66d --- /dev/null +++ b/tools/gomod-local-update/internal/updater/errors.go @@ -0,0 +1,11 @@ +package updater + +import "errors" + +var ( + // ErrModOperation indicates a failure in a module-related operation. + ErrModOperation = errors.New("module operation failed") + + // ErrInvalidConfig indicates invalid configuration parameters. + ErrInvalidConfig = errors.New("invalid configuration") +) diff --git a/tools/gomod-local-update/internal/updater/system_operator.go b/tools/gomod-local-update/internal/updater/system_operator.go new file mode 100644 index 00000000000..59ccfb15ed7 --- /dev/null +++ b/tools/gomod-local-update/internal/updater/system_operator.go @@ -0,0 +1,34 @@ +package updater + +import ( + "os" + "path/filepath" +) + +// SystemOperator provides an interface for file system operations. +type SystemOperator interface { + // ReadFile reads the entire contents of a file + ReadFile(path string) ([]byte, error) + // WriteFile writes data to a file with specific permissions + WriteFile(path string, data []byte, perm os.FileMode) error +} + +type systemOperator struct{} + +func NewSystemOperator() SystemOperator { + return &systemOperator{} +} + +func (s *systemOperator) ReadFile(path string) ([]byte, error) { + path = filepath.Clean(path) + return os.ReadFile(path) +} + +func (s *systemOperator) WriteFile(path string, data []byte, perm os.FileMode) error { + path = filepath.Clean(path) + dir := filepath.Dir(path) + if err := os.MkdirAll(dir, 0755); err != nil { + return err + } + return os.WriteFile(path, data, perm) +} diff --git a/tools/gomod-local-update/internal/updater/updater.go b/tools/gomod-local-update/internal/updater/updater.go new file mode 100644 index 00000000000..cfb873ed3ca --- /dev/null +++ b/tools/gomod-local-update/internal/updater/updater.go @@ -0,0 +1,253 @@ +package updater + +import ( + "context" + "errors" + "fmt" + "log" + "os" + "os/exec" + "regexp" + "strings" + "time" + + "golang.org/x/mod/modfile" + "golang.org/x/mod/module" +) + +// gitExecutor allows mocking git commands in tests +type gitExecutor interface { + Command(ctx context.Context, args ...string) ([]byte, error) +} + +// systemGitExecutor executes git commands on the host system +type systemGitExecutor struct{} + +func (g *systemGitExecutor) Command(ctx context.Context, args ...string) ([]byte, error) { + return exec.CommandContext(ctx, "git", args...).Output() +} + +const ( + // File and mode constants + goModFile = "go.mod" + goModFileMode = 0644 + gitSHALength = 12 + gitTimeout = 30 * time.Second + gitTimeFormat = time.RFC3339 + + // Regex pattern constants + gitRemotePattern = `^[a-zA-Z0-9][-a-zA-Z0-9_.]*$` + gitBranchPattern = `^[a-zA-Z0-9][-a-zA-Z0-9/_]*$` + majorVersionPattern = `/v\d+$` + shaPattern = `^[a-fA-F0-9]{40}$` // SHA-1 hashes are 40 hexadecimal characters +) + +var ( + // Pre-compiled regular expressions + gitRemoteRE = regexp.MustCompile(gitRemotePattern) + gitBranchRE = regexp.MustCompile(gitBranchPattern) + gitShaRE = regexp.MustCompile(shaPattern) + majorVersionRE = regexp.MustCompile(majorVersionPattern) +) + +type Updater struct { + config *Config + system SystemOperator + git gitExecutor +} + +// New creates a new Updater +func New(config *Config, system SystemOperator) *Updater { + return &Updater{ + config: config, + system: system, + git: &systemGitExecutor{}, + } +} + +// validateSHA checks if the SHA consists of exactly 40 hexadecimal digits +func (u *Updater) validateSHA(sha string) error { + if !gitShaRE.MatchString(sha) { + return fmt.Errorf("%w: invalid git SHA '%s'", ErrInvalidConfig, sha) + } + return nil +} + +// getGitInfo retrieves the latest commit SHA and timestamp from a Git repository +func (u *Updater) getGitInfo(remote, branch string) (string, time.Time, error) { + ctx, cancel := context.WithTimeout(context.Background(), gitTimeout) + defer cancel() + + // Use u.git.Command for ls-remote + out, err := u.git.Command(ctx, "ls-remote", remote, "refs/heads/"+branch) + if err != nil { + return "", time.Time{}, fmt.Errorf("%w: failed to fetch commit SHA from %s/%s: %w", + ErrModOperation, remote, branch, err) + } + if len(out) == 0 { + return "", time.Time{}, fmt.Errorf("%w: no output from git ls-remote", ErrModOperation) + } + sha := strings.Split(string(out), "\t")[0] + if len(sha) == 0 { + return "", time.Time{}, fmt.Errorf("%w: empty SHA from git ls-remote", ErrModOperation) + } + + // Validate the SHA + if err := u.validateSHA(sha); err != nil { + return "", time.Time{}, err + } + + // Use u.git.Command for show + showOut, showErr := u.git.Command(ctx, "show", "-s", "--format=%cI", sha) + if showErr != nil { + return "", time.Time{}, fmt.Errorf("failed to get commit time: %w", showErr) + } + + commitTime, parseErr := time.Parse(gitTimeFormat, strings.TrimSpace(string(showOut))) + if parseErr != nil { + return "", time.Time{}, fmt.Errorf("failed to parse commit time: %w", parseErr) + } + + return sha[:gitSHALength], commitTime, nil +} + +// Run starts the module update process +func (u *Updater) Run() error { + logger := log.New(os.Stdout, "", log.LstdFlags) + + logger.Printf("info: auto-detecting modules with local replace directives") + + f, err := u.readModFile() + if err != nil { + return fmt.Errorf("%w: failed to read and parse go.mod file", ErrModOperation) + } + + // Auto-detect modules to update + modulesToUpdate, err := u.findLocalReplaceModules() + if err != nil { + return fmt.Errorf("%w: failed to detect local replace modules", ErrModOperation) + } + if len(modulesToUpdate) == 0 { + logger.Printf("info: no modules found to update in %s", f.Module.Mod.Path) + return nil + } + logger.Printf("info: found %d modules with local replace directives: %v", + len(modulesToUpdate), modulesToUpdate) + + // Get commit info once for all modules + sha, commitTime, err := u.getGitInfo(u.config.RepoRemote, u.config.BranchTrunk) + if err != nil { + if errors.Is(err, ErrInvalidConfig) { + return err + } + return fmt.Errorf("%w: failed to get git commit info from remote", ErrModOperation) + } + + // Update the modules in the same file handle + if err := u.updateGoMod(f, modulesToUpdate, sha, commitTime); err != nil { + return fmt.Errorf("%w: failed to update module versions in go.mod", ErrModOperation) + } + + return u.writeModFile(f) +} + +// updateGoMod updates the go.mod file with new pseudo-versions +func (u *Updater) updateGoMod(modFile *modfile.File, modulesToUpdate []string, sha string, commitTime time.Time) error { + for _, modulePath := range modulesToUpdate { + majorVersion := getMajorVersion(modulePath) + pseudoVersion := module.PseudoVersion(majorVersion, "", commitTime, sha[:gitSHALength]) + + // Find and update version + for _, req := range modFile.Require { + if req.Mod.Path == modulePath { + if u.config.DryRun { + log.Printf("[DRY RUN] Would update %s: %s => %s", modulePath, req.Mod.Version, pseudoVersion) + continue + } + + if err := modFile.AddRequire(modulePath, pseudoVersion); err != nil { + return fmt.Errorf("%w: failed to update version for module %s", + ErrModOperation, modulePath) + } + break + } + } + } + + return nil +} + +// getMajorVersion extracts the major version number from a module path +// Returns "v2" for /v2, "v0" for no version suffix +func getMajorVersion(modulePath string) string { + if match := majorVersionRE.FindString(modulePath); match != "" { + return strings.TrimPrefix(match, "/") + } + return "v0" +} + +// findLocalReplaceModules finds modules with local replace directives +func (u *Updater) findLocalReplaceModules() ([]string, error) { + modFile, err := u.readModFile() + if err != nil { + return nil, err + } + + orgPrefix := fmt.Sprintf("github.com/%s/%s", u.config.OrgName, u.config.RepoName) + localModules := make(map[string]bool) + var modules []string + + // First find all local replaces for our org + for _, rep := range modFile.Replace { + if strings.HasPrefix(rep.Old.Path, orgPrefix) && + rep.New.Version == "" && + isLocalPath(rep.New.Path) { + localModules[rep.Old.Path] = true + } + } + + // Then check requires that match our replaces + for _, req := range modFile.Require { + if localModules[req.Mod.Path] { + modules = append(modules, req.Mod.Path) + } + } + + return modules, nil +} + +// isLocalPath checks if the path is a local path +func isLocalPath(path string) bool { + return path == "." || path == ".." || + strings.HasPrefix(path, "./") || + strings.HasPrefix(path, "../") +} + +// readModFile reads the go.mod file +func (u *Updater) readModFile() (*modfile.File, error) { + content, err := u.system.ReadFile(goModFile) + if err != nil { + return nil, fmt.Errorf("unable to read go.mod: %w", err) + } + + modFile, err := modfile.Parse(goModFile, content, nil) + if err != nil { + return nil, fmt.Errorf("%w: invalid go.mod format: %w", ErrModOperation, err) + } + + return modFile, nil +} + +// writeModFile writes the go.mod file +func (u *Updater) writeModFile(modFile *modfile.File) error { + content, err := modFile.Format() + if err != nil { + return fmt.Errorf("%w: failed to format go.mod content", ErrModOperation) + } + + if err := u.system.WriteFile(goModFile, content, goModFileMode); err != nil { + return fmt.Errorf("%w: failed to write updated go.mod file", ErrModOperation) + } + + return nil +} diff --git a/tools/gomod-local-update/internal/updater/updater_test.go b/tools/gomod-local-update/internal/updater/updater_test.go new file mode 100644 index 00000000000..ba17024dd37 --- /dev/null +++ b/tools/gomod-local-update/internal/updater/updater_test.go @@ -0,0 +1,320 @@ +package updater + +import ( + "context" + "fmt" + "os" + "strings" + "testing" + "time" +) + +// mockGitExecutor simulates git commands for testing as an alternative to systemGitExecutor +type mockGitExecutor struct { + sha string + time time.Time +} + +func (m *mockGitExecutor) Command(ctx context.Context, args ...string) ([]byte, error) { + switch args[0] { + case "ls-remote": + // Validate inputs + if len(args) != 3 { + return nil, fmt.Errorf("expected 3 args for ls-remote, got %d", len(args)) + } + remote := args[1] + if remote == "invalid*remote" { + return nil, fmt.Errorf("%w: git remote '%s' contains invalid characters", ErrInvalidConfig, remote) + } + // Return a full 40-character SHA + fullSHA := fmt.Sprintf("%s%s", m.sha, strings.Repeat("0", 40-len(m.sha))) + return []byte(fullSHA + "\trefs/heads/" + args[2] + "\n"), nil + case "show": + if len(args) != 4 { + return nil, fmt.Errorf("unexpected show args: %v", args) + } + // Use the full SHA for validation + fullSHA := fmt.Sprintf("%s%s", m.sha, strings.Repeat("0", 40-len(m.sha))) + if args[3] != fullSHA { + return nil, fmt.Errorf("unexpected SHA: got %s, want %s", args[3], fullSHA) + } + return []byte(m.time.Format(gitTimeFormat) + "\n"), nil + default: + return nil, fmt.Errorf("unexpected git command: %v", args) + } +} + +type mockSystemOperator struct { + files map[string][]byte + err error +} + +func newMockSystemOperator() *mockSystemOperator { + return &mockSystemOperator{ + files: make(map[string][]byte), + } +} + +func (m *mockSystemOperator) ReadFile(path string) ([]byte, error) { + if m.err != nil { + return nil, m.err + } + content, ok := m.files[path] + if !ok { + return nil, fmt.Errorf("file not found: %s", path) + } + return content, nil +} + +func (m *mockSystemOperator) WriteFile(path string, data []byte, perm os.FileMode) error { + if m.err != nil { + return m.err + } + m.files[path] = data + return nil +} + +func TestUpdater_Run(t *testing.T) { + testTime := time.Date(2024, 11, 22, 18, 21, 10, 0, time.UTC) + // Use a full 40-character SHA + testSHA := "ac7a7395feed" + strings.Repeat("0", 28) + + tests := []struct { + name string + config *Config + sysOp *mockSystemOperator + wantErr bool + wantFile string + }{ + { + name: "successful update", + config: &Config{ + RepoRemote: "origin", + BranchTrunk: "develop", + OrgName: "smartcontractkit", + RepoName: "chainlink", + }, + sysOp: func() *mockSystemOperator { + m := newMockSystemOperator() + m.files["go.mod"] = []byte(`module test +require github.com/smartcontractkit/chainlink/v2 v2.0.0 +replace github.com/smartcontractkit/chainlink/v2 => ../ +`) + return m + }(), + wantErr: false, + wantFile: fmt.Sprintf(`module test + +require github.com/smartcontractkit/chainlink/v2 v2.0.0-20241122182110-%s + +replace github.com/smartcontractkit/chainlink/v2 => ../ +`, testSHA[:12]), + }, + { + name: "handles module with local replace", + config: &Config{ + RepoRemote: "origin", + BranchTrunk: "develop", + OrgName: "smartcontractkit", + RepoName: "chainlink", + }, + sysOp: func() *mockSystemOperator { + m := newMockSystemOperator() + m.files["go.mod"] = []byte(`module test +require github.com/smartcontractkit/chainlink/v2 v2.0.0 +replace github.com/smartcontractkit/chainlink/v2 => ../ +`) + return m + }(), + wantErr: false, + }, + { + name: "v1 module update", + config: &Config{ + RepoRemote: "origin", + BranchTrunk: "develop", + OrgName: "example", + RepoName: "mod", + }, + sysOp: func() *mockSystemOperator { + m := newMockSystemOperator() + m.files["go.mod"] = []byte(`module test +require github.com/example/mod v1.0.0 +`) + return m + }(), + wantErr: false, + }, + { + name: "updates v2 module with timestamp", + config: &Config{ + RepoRemote: "origin", + BranchTrunk: "develop", + OrgName: "smartcontractkit", + RepoName: "chainlink", + }, + sysOp: func() *mockSystemOperator { + m := newMockSystemOperator() + m.files["go.mod"] = []byte(`module test +require github.com/smartcontractkit/chainlink/v2 v2.0.0-20241119120536-03115e80382d +`) + return m + }(), + wantErr: false, + wantFile: fmt.Sprintf(`module test + +require github.com/smartcontractkit/chainlink/v2 v2.0.0-20241122182110-%s + +replace github.com/smartcontractkit/chainlink/v2 => ../ +`, testSHA[:12]), + }, + { + name: "updates v0 module with timestamp", + config: &Config{ + RepoRemote: "origin", + BranchTrunk: "develop", + OrgName: "smartcontractkit", + RepoName: "chainlink", + }, + sysOp: func() *mockSystemOperator { + m := newMockSystemOperator() + m.files["go.mod"] = []byte(`module test +require github.com/smartcontractkit/chainlink/deployment v0.0.0-20241119120536-03115e80382d +`) + return m + }(), + wantErr: false, + wantFile: fmt.Sprintf(`module test + +require github.com/smartcontractkit/chainlink/deployment v0.0.0-20241122182110-%s + +replace github.com/smartcontractkit/chainlink/deployment => ../ +`, testSHA[:12]), + }, + { + name: "handles multiple modules with different versions", + config: &Config{ + RepoRemote: "origin", + BranchTrunk: "develop", + OrgName: "smartcontractkit", + RepoName: "chainlink", + }, + sysOp: func() *mockSystemOperator { + m := newMockSystemOperator() + m.files["go.mod"] = []byte(`module test +require ( + github.com/smartcontractkit/chainlink/v2 v2.0.0-20241119120536-03115e80382d + github.com/smartcontractkit/chainlink/deployment v0.0.0-20241119120536-03115e80382d +) +`) + return m + }(), + wantErr: false, + wantFile: fmt.Sprintf(`module test + +require ( + github.com/smartcontractkit/chainlink/v2 v2.0.0-20241122182110-%s + github.com/smartcontractkit/chainlink/deployment v0.0.0-20241122182110-%s +) + +replace github.com/smartcontractkit/chainlink/v2 => ../ + +replace github.com/smartcontractkit/chainlink/deployment => ../ +`, testSHA[:12], testSHA[:12]), + }, + { + name: "updates v3 module with timestamp", + config: &Config{ + RepoRemote: "origin", + BranchTrunk: "develop", + OrgName: "smartcontractkit", + RepoName: "chainlink", + }, + sysOp: func() *mockSystemOperator { + m := newMockSystemOperator() + m.files["go.mod"] = []byte(`module test +require github.com/smartcontractkit/chainlink/v3 v3.0.0-20241119120536-03115e80382d +`) + return m + }(), + wantErr: false, + wantFile: fmt.Sprintf(`module test + +require github.com/smartcontractkit/chainlink/v3 v3.0.0-20241122182110-%s + +replace github.com/smartcontractkit/chainlink/v3 => ../ +`, testSHA[:12]), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + u := New(tt.config, tt.sysOp) + + // Add local replace directive for modules that should be updated + if !strings.Contains(tt.name, "v1 module update") { + modContent := string(tt.sysOp.files["go.mod"]) + for _, module := range []string{"v2", "v3", "deployment"} { + modulePath := fmt.Sprintf("github.com/%s/%s/%s", + tt.config.OrgName, tt.config.RepoName, module) + if strings.Contains(modContent, modulePath) && + !strings.Contains(modContent, "replace "+modulePath) { + modContent += fmt.Sprintf("\nreplace %s => ../\n", modulePath) + } + } + tt.sysOp.files["go.mod"] = []byte(modContent) + } + + // Override the git executor with our mock + u.git = &mockGitExecutor{ + sha: testSHA, + time: testTime, + } + err := u.Run() + if (err != nil) != tt.wantErr { + t.Errorf("Updater.Run() error = %v, wantErr %v", err, tt.wantErr) + } + + if tt.wantFile != "" { + got := string(tt.sysOp.files["go.mod"]) + if got != tt.wantFile { + t.Errorf("File content mismatch\nGot:\n%s\nWant:\n%s", got, tt.wantFile) + } + } + }) + } +} + +func TestUpdater_FindLocalReplaceModules(t *testing.T) { + sysOp := newMockSystemOperator() + sysOp.files["go.mod"] = []byte(` +module test +require ( + github.com/smartcontractkit/chainlink/v2 v2.0.0 + github.com/other/repo v1.0.0 +) +replace ( + github.com/smartcontractkit/chainlink/v2 => ../ + github.com/other/repo => ../other +)`) + + cfg := &Config{ + OrgName: "smartcontractkit", + RepoName: "chainlink", + } + + u := New(cfg, sysOp) + modules, err := u.findLocalReplaceModules() + if err != nil { + t.Errorf("unexpected error: %v", err) + return + } + + if len(modules) != 1 { + t.Errorf("expected 1 module, got %d", len(modules)) + return + } + if modules[0] != "github.com/smartcontractkit/chainlink/v2" { + t.Errorf("expected chainlink module, got %s", modules[0]) + } +}