diff --git a/.changeset/curly-birds-guess.md b/.changeset/curly-birds-guess.md
new file mode 100644
index 0000000000..c66bd54178
--- /dev/null
+++ b/.changeset/curly-birds-guess.md
@@ -0,0 +1,5 @@
+---
+"chainlink": patch
+---
+
+Fixed deadlock in RPCClient causing CL Node to stop performing RPC requests for the affected chain #bugfix
diff --git a/.changeset/curly-onions-tell.md b/.changeset/curly-onions-tell.md
new file mode 100644
index 0000000000..249f616c01
--- /dev/null
+++ b/.changeset/curly-onions-tell.md
@@ -0,0 +1,5 @@
+---
+"chainlink": patch
+---
+
+#changed Make Mantle use default OP stack l1 gas oracle in core
diff --git a/.changeset/eight-bees-speak.md b/.changeset/eight-bees-speak.md
new file mode 100644
index 0000000000..9c8ebe428d
--- /dev/null
+++ b/.changeset/eight-bees-speak.md
@@ -0,0 +1,5 @@
+---
+"chainlink": patch
+---
+
+#bugfix head reporter non-zero reporting period
diff --git a/.changeset/four-kangaroos-appear.md b/.changeset/four-kangaroos-appear.md
new file mode 100644
index 0000000000..b8ef32ff69
--- /dev/null
+++ b/.changeset/four-kangaroos-appear.md
@@ -0,0 +1,5 @@
+---
+"chainlink": patch
+---
+
+Add config validation so it requires ws url when http polling disabled #bugfix
diff --git a/.changeset/great-timers-agree.md b/.changeset/great-timers-agree.md
new file mode 100644
index 0000000000..bfa27761fb
--- /dev/null
+++ b/.changeset/great-timers-agree.md
@@ -0,0 +1,5 @@
+---
+"chainlink": patch
+---
+
+Updated gas limit estimation feature to set From address #internal
diff --git a/.changeset/green-eagles-deliver.md b/.changeset/green-eagles-deliver.md
new file mode 100644
index 0000000000..179b93b108
--- /dev/null
+++ b/.changeset/green-eagles-deliver.md
@@ -0,0 +1,5 @@
+---
+"chainlink": patch
+---
+
+Handle zkEVM node level OOC error as TerminallyStuck #internal
diff --git a/.changeset/happy-feet-rhyme.md b/.changeset/happy-feet-rhyme.md
new file mode 100644
index 0000000000..6e1697d96a
--- /dev/null
+++ b/.changeset/happy-feet-rhyme.md
@@ -0,0 +1,11 @@
+---
+"chainlink": minor
+---
+
+This PR introduce few changes:
+- Add a new config option `EVM.NodePool.NewHeadsPollInterval` (0 by default indicate disabled), which is an interval for polling new block periodically using http client rather than subscribe to ws feed.
+- Updated new head handler for polling new head over http, and register the subscription in node lifecycle logic.
+- If the polling new heads is enabled, WS new heads subscription will be replaced with the new http based polling.
+
+Note: There will be another PR for making WS URL optional with some extra condition.
+#added
diff --git a/.changeset/hot-laws-deny.md b/.changeset/hot-laws-deny.md
new file mode 100644
index 0000000000..d71783d1b7
--- /dev/null
+++ b/.changeset/hot-laws-deny.md
@@ -0,0 +1,5 @@
+---
+"chainlink": patch
+---
+
+#internal log info on missed finalized head instead of returning an error
diff --git a/.changeset/khaki-tigers-agree.md b/.changeset/khaki-tigers-agree.md
new file mode 100644
index 0000000000..ba9e56cc1d
--- /dev/null
+++ b/.changeset/khaki-tigers-agree.md
@@ -0,0 +1,5 @@
+---
+"chainlink": patch
+---
+
+#added #nops Add Zircuit Configs
diff --git a/.changeset/kind-numbers-melt.md b/.changeset/kind-numbers-melt.md
new file mode 100644
index 0000000000..43e647399c
--- /dev/null
+++ b/.changeset/kind-numbers-melt.md
@@ -0,0 +1,13 @@
+---
+"chainlink": minor
+---
+
+Adding feature flag for `LogBroadcaster` called `LogBroadcasterEnabled`, which is `true` by default to support backwards compatibility.
+Adding `LogBroadcasterEnabled` allows certain chains to completely disable the `LogBroadcaster` feature, which is an old feature (getting replaced by logPoller) that only few products are using it:
+* OCR1 Median
+* *OCR2 Median when ChainReader is disabled
+* *pre-OCR2 Keeper
+* Flux Monitor
+* Direct RequestOCR1 Median
+
+#added
diff --git a/.changeset/loud-windows-call.md b/.changeset/loud-windows-call.md
new file mode 100644
index 0000000000..6dc8d6fac7
--- /dev/null
+++ b/.changeset/loud-windows-call.md
@@ -0,0 +1,5 @@
+---
+"chainlink": minor
+---
+
+Added gas limit estimation feature to EVM gas estimators. Introduced a new config `EVM.GasEstimator.EstimateLimit` to toggle this feature. #added
diff --git a/.changeset/moody-rules-agree.md b/.changeset/moody-rules-agree.md
new file mode 100644
index 0000000000..ef1f3bcaf6
--- /dev/null
+++ b/.changeset/moody-rules-agree.md
@@ -0,0 +1,8 @@
+---
+"chainlink": patch
+---
+
+- register polling subscription to avoid subscription leaking when rpc client gets closed.
+- add a temporary special treatment for SubscribeNewHead before we replace it with SubscribeToHeads. Add a goroutine that forwards new head from poller to caller channel.
+- fix a deadlock in poller, by using a new lock for subs slice in rpc client.
+#bugfix
diff --git a/.changeset/pretty-worms-smell.md b/.changeset/pretty-worms-smell.md
new file mode 100644
index 0000000000..9633de0950
--- /dev/null
+++ b/.changeset/pretty-worms-smell.md
@@ -0,0 +1,5 @@
+---
+"chainlink": patch
+---
+
+Added a custom client error message for Mantle to capture NonceTooLow error. #added
diff --git a/.changeset/proud-jokes-exercise.md b/.changeset/proud-jokes-exercise.md
new file mode 100644
index 0000000000..4e36d139de
--- /dev/null
+++ b/.changeset/proud-jokes-exercise.md
@@ -0,0 +1,5 @@
+---
+"chainlink": minor
+---
+
+#added Report new heads as a telemetry to OTI
diff --git a/.changeset/seven-kiwis-run.md b/.changeset/seven-kiwis-run.md
new file mode 100644
index 0000000000..3b56117c46
--- /dev/null
+++ b/.changeset/seven-kiwis-run.md
@@ -0,0 +1,5 @@
+---
+"chainlink": patch
+---
+
+Added custom client error messages for Mantle to capture InsufficientEth and Fatal errors. #added
diff --git a/.changeset/shiny-hornets-pretend.md b/.changeset/shiny-hornets-pretend.md
new file mode 100644
index 0000000000..ff9946f4e7
--- /dev/null
+++ b/.changeset/shiny-hornets-pretend.md
@@ -0,0 +1,5 @@
+---
+"chainlink": minor
+---
+
+Introduce new gas estimator #internal
diff --git a/.changeset/silly-lies-boil.md b/.changeset/silly-lies-boil.md
new file mode 100644
index 0000000000..b2a5084a36
--- /dev/null
+++ b/.changeset/silly-lies-boil.md
@@ -0,0 +1,8 @@
+---
+"chainlink": minor
+---
+
+Make websocket URL `WSURL` for `EVM.Nodes` optional, and apply logic so that:
+* If WS URL was not provided, SubscribeFilterLogs should fail with an explicit error
+* If WS URL was not provided LogBroadcaster should be disabled
+#nops
diff --git a/.changeset/swift-pumas-taste.md b/.changeset/swift-pumas-taste.md
new file mode 100644
index 0000000000..eb08662e20
--- /dev/null
+++ b/.changeset/swift-pumas-taste.md
@@ -0,0 +1,5 @@
+---
+"chainlink": patch
+---
+
+#internal add head report chain_id
diff --git a/.changeset/tender-lemons-obey.md b/.changeset/tender-lemons-obey.md
new file mode 100644
index 0000000000..2d6cb774b0
--- /dev/null
+++ b/.changeset/tender-lemons-obey.md
@@ -0,0 +1,5 @@
+---
+"chainlink": patch
+---
+
+Enable FeeHistory estimator for Polygon zkEVM #nops
diff --git a/.changeset/two-mugs-complain.md b/.changeset/two-mugs-complain.md
new file mode 100644
index 0000000000..77cdcbfe9e
--- /dev/null
+++ b/.changeset/two-mugs-complain.md
@@ -0,0 +1,5 @@
+---
+"chainlink": minor
+---
+
+Edited the Optimism Stack L1 Oracle to add support for Mantle #added
diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml
index a4d82ab67f..54cd2491b4 100644
--- a/.github/workflows/integration-tests.yml
+++ b/.github/workflows/integration-tests.yml
@@ -170,6 +170,7 @@ jobs:
only-new-issues: false # disabled for PRs due to unreliability
args: --out-format colored-line-number,checkstyle:golangci-lint-report.xml
working-directory: ${{ matrix.project.path }}
+ continue-on-error: true
build-chainlink:
environment: integration
@@ -584,7 +585,7 @@ jobs:
test_config_chainlink_version: ${{ inputs.evm-ref || github.sha }}
test_config_selected_networks: ${{ env.SELECTED_NETWORKS }}
test_config_logging_run_id: ${{ github.run_id }}
- test_config_logstream_log_targets: ${{ vars.LOGSTREAM_LOG_TARGETS }}
+ test_config_logstream_log_targets: "file"
test_config_test_log_collect: ${{ vars.TEST_LOG_COLLECT }}
cl_repo: ${{ env.CHAINLINK_IMAGE }}
cl_image_tag: ${{ inputs.evm-ref || github.sha }}${{ matrix.product.tag_suffix }}
@@ -606,7 +607,7 @@ jobs:
go_coverage_src_dir: /var/tmp/go-coverage
go_coverage_dest_dir: ${{ github.workspace }}/.covdata
DEFAULT_CHAINLINK_IMAGE: ${{ env.CHAINLINK_IMAGE }}
- DEFAULT_LOKI_TENANT_ID: ${{ secrets.GRAFANA_INTERNAL_TENANT_ID }}
+ DEFAULT_LOKI_TENANT_ID: "promtail"
DEFAULT_LOKI_ENDPOINT: https://${{ secrets.GRAFANA_INTERNAL_HOST }}/loki/api/v1/push
DEFAULT_LOKI_BASIC_AUTH: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }}
DEFAULT_GRAFANA_BASE_URL: "http://localhost:8080/primary"
diff --git a/.mockery.yaml b/.mockery.yaml
index 7fb335620c..07d4fbed65 100644
--- a/.mockery.yaml
+++ b/.mockery.yaml
@@ -65,6 +65,9 @@ packages:
feeEstimatorClient:
config:
mockname: FeeEstimatorClient
+ feeHistoryEstimatorClient:
+ config:
+ mockname: FeeHistoryEstimatorClient
EvmEstimator:
github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas/rollups:
interfaces:
@@ -258,11 +261,20 @@ packages:
ORM:
Runner:
PipelineParamUnmarshaler:
- github.com/smartcontractkit/chainlink/v2/core/services/promreporter:
+ github.com/smartcontractkit/chainlink/v2/core/services/headreporter:
config:
- dir: core/internal/mocks
+ dir: "{{ .InterfaceDir }}"
+ filename: "{{ .InterfaceName | snakecase }}_mock.go"
+ inpackage: true
+ mockname: "Mock{{ .InterfaceName | camelcase }}"
interfaces:
+ HeadReporter:
PrometheusBackend:
+ github.com/smartcontractkit/libocr/commontypes:
+ config:
+ dir: "common/types/mocks"
+ interfaces:
+ MonitoringEndpoint:
github.com/smartcontractkit/chainlink/v2/core/services/relay/evm:
interfaces:
BatchCaller:
@@ -297,6 +309,15 @@ packages:
interfaces:
Config:
FeeConfig:
+ github.com/smartcontractkit/chainlink/v2/core/services/telemetry:
+ config:
+ dir: "{{ .InterfaceDir }}"
+ filename: "{{ .InterfaceName | snakecase }}_mock.go"
+ inpackage: true
+ mockname: "Mock{{ .InterfaceName | camelcase }}"
+ interfaces:
+ MonitoringEndpointGenerator:
+ IngressAgent:
github.com/smartcontractkit/chainlink/v2/core/services/webhook:
interfaces:
ExternalInitiatorManager:
@@ -450,7 +471,7 @@ packages:
filename: optimism_portal2_interface.go
outpkg: mock_optimism_portal_2
interfaces:
- OptimismPortal2Interface:
+ OptimismPortal2Interface:
github.com/smartcontractkit/chainlink/v2/core/gethwrappers/liquiditymanager/generated/optimism_dispute_game_factory:
config:
dir: core/gethwrappers/liquiditymanager/mocks/mock_optimism_dispute_game_factory/
@@ -481,12 +502,20 @@ packages:
PriceRegistryReader:
config:
filename: price_registry_reader_mock.go
+ FeeEstimatorConfigReader:
+ config:
+ filename: fee_estimator_config_mock.go
TokenPoolReader:
config:
filename: token_pool_reader_mock.go
USDCReader:
config:
filename: usdc_reader_mock.go
+ github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/estimatorconfig:
+ interfaces:
+ GasPriceInterceptor:
+ config:
+ filename: gas_price_interceptor_mock.go
github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/ccipdata/batchreader:
config:
filename: token_pool_batched_reader_mock.go
diff --git a/common/client/node.go b/common/client/node.go
index d6543c772a..7885fe7676 100644
--- a/common/client/node.go
+++ b/common/client/node.go
@@ -45,6 +45,7 @@ type NodeConfig interface {
FinalizedBlockPollInterval() time.Duration
EnforceRepeatableRead() bool
DeathDeclarationDelay() time.Duration
+ NewHeadsPollInterval() time.Duration
}
type ChainConfig interface {
@@ -90,14 +91,14 @@ type node[
services.StateMachine
lfcLog logger.Logger
name string
- id int32
+ id int
chainID CHAIN_ID
nodePoolCfg NodeConfig
chainCfg ChainConfig
order int32
chainFamily string
- ws url.URL
+ ws *url.URL
http *url.URL
rpc RPC
@@ -120,10 +121,10 @@ func NewNode[
nodeCfg NodeConfig,
chainCfg ChainConfig,
lggr logger.Logger,
- wsuri url.URL,
+ wsuri *url.URL,
httpuri *url.URL,
name string,
- id int32,
+ id int,
chainID CHAIN_ID,
nodeOrder int32,
rpc RPC,
@@ -135,8 +136,10 @@ func NewNode[
n.chainID = chainID
n.nodePoolCfg = nodeCfg
n.chainCfg = chainCfg
- n.ws = wsuri
n.order = nodeOrder
+ if wsuri != nil {
+ n.ws = wsuri
+ }
if httpuri != nil {
n.http = httpuri
}
@@ -156,7 +159,10 @@ func NewNode[
}
func (n *node[CHAIN_ID, HEAD, RPC]) String() string {
- s := fmt.Sprintf("(%s)%s:%s", Primary.String(), n.name, n.ws.String())
+ s := fmt.Sprintf("(%s)%s", Primary.String(), n.name)
+ if n.ws != nil {
+ s = s + fmt.Sprintf(":%s", n.ws.String())
+ }
if n.http != nil {
s = s + fmt.Sprintf(":%s", n.http.String())
}
diff --git a/common/client/node_test.go b/common/client/node_test.go
index 3b971e8490..539964691c 100644
--- a/common/client/node_test.go
+++ b/common/client/node_test.go
@@ -20,6 +20,11 @@ type testNodeConfig struct {
enforceRepeatableRead bool
finalizedBlockPollInterval time.Duration
deathDeclarationDelay time.Duration
+ newHeadsPollInterval time.Duration
+}
+
+func (n testNodeConfig) NewHeadsPollInterval() time.Duration {
+ return n.newHeadsPollInterval
}
func (n testNodeConfig) PollFailureThreshold() uint32 {
@@ -62,10 +67,10 @@ type testNodeOpts struct {
config testNodeConfig
chainConfig clientMocks.ChainConfig
lggr logger.Logger
- wsuri url.URL
+ wsuri *url.URL
httpuri *url.URL
name string
- id int32
+ id int
chainID types.ID
nodeOrder int32
rpc *mockNodeClient[types.ID, Head]
diff --git a/common/fee/models.go b/common/fee/models.go
index 0568a2f143..0cc479d356 100644
--- a/common/fee/models.go
+++ b/common/fee/models.go
@@ -14,6 +14,7 @@ var (
ErrBumpFeeExceedsLimit = errors.New("fee bump exceeds limit")
ErrBump = errors.New("fee bump failed")
ErrConnectivity = errors.New("transaction propagation issue: transactions are not being mined")
+ ErrFeeLimitTooLow = errors.New("provided fee limit too low")
)
func IsBumpErr(err error) bool {
@@ -63,7 +64,7 @@ func CalculateBumpedFee(
// Returns highest bumped fee price of originalFeePrice bumped by fixed units or percentage.
func MaxBumpedFee(originalFeePrice *big.Int, feeBumpPercent uint16, feeBumpUnits *big.Int) *big.Int {
return bigmath.Max(
- addPercentage(originalFeePrice, feeBumpPercent),
+ AddPercentage(originalFeePrice, feeBumpPercent),
new(big.Int).Add(originalFeePrice, feeBumpUnits),
)
}
diff --git a/common/fee/utils.go b/common/fee/utils.go
index 26323e11e2..3d4b001e83 100644
--- a/common/fee/utils.go
+++ b/common/fee/utils.go
@@ -18,7 +18,7 @@ func ApplyMultiplier(feeLimit uint64, multiplier float32) (uint64, error) {
}
// Returns the input value increased by the given percentage.
-func addPercentage(value *big.Int, percentage uint16) *big.Int {
+func AddPercentage(value *big.Int, percentage uint16) *big.Int {
bumped := new(big.Int)
bumped.Mul(value, big.NewInt(int64(100+percentage)))
bumped.Div(bumped, big.NewInt(100))
diff --git a/common/txmgr/broadcaster.go b/common/txmgr/broadcaster.go
index be3d3ca2f6..1606f58ce0 100644
--- a/common/txmgr/broadcaster.go
+++ b/common/txmgr/broadcaster.go
@@ -20,6 +20,7 @@ import (
"github.com/smartcontractkit/chainlink-common/pkg/utils"
"github.com/smartcontractkit/chainlink/v2/common/client"
+ commonfee "github.com/smartcontractkit/chainlink/v2/common/fee"
feetypes "github.com/smartcontractkit/chainlink/v2/common/fee/types"
txmgrtypes "github.com/smartcontractkit/chainlink/v2/common/txmgr/types"
"github.com/smartcontractkit/chainlink/v2/common/types"
@@ -434,7 +435,11 @@ func (eb *Broadcaster[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) hand
}
attempt, _, _, retryable, err := eb.NewTxAttempt(ctx, *etx, eb.lggr)
- if err != nil {
+ // Mark transaction as fatal if provided gas limit is set too low
+ if errors.Is(err, commonfee.ErrFeeLimitTooLow) {
+ etx.Error = null.StringFrom(commonfee.ErrFeeLimitTooLow.Error())
+ return eb.saveFatallyErroredTransaction(eb.lggr, etx), false
+ } else if err != nil {
return fmt.Errorf("processUnstartedTxs failed on NewAttempt: %w", err), retryable
}
diff --git a/common/types/mocks/monitoring_endpoint.go b/common/types/mocks/monitoring_endpoint.go
new file mode 100644
index 0000000000..5afc04c909
--- /dev/null
+++ b/common/types/mocks/monitoring_endpoint.go
@@ -0,0 +1,65 @@
+// Code generated by mockery v2.43.2. DO NOT EDIT.
+
+package mocks
+
+import mock "github.com/stretchr/testify/mock"
+
+// MonitoringEndpoint is an autogenerated mock type for the MonitoringEndpoint type
+type MonitoringEndpoint struct {
+ mock.Mock
+}
+
+type MonitoringEndpoint_Expecter struct {
+ mock *mock.Mock
+}
+
+func (_m *MonitoringEndpoint) EXPECT() *MonitoringEndpoint_Expecter {
+ return &MonitoringEndpoint_Expecter{mock: &_m.Mock}
+}
+
+// SendLog provides a mock function with given fields: log
+func (_m *MonitoringEndpoint) SendLog(log []byte) {
+ _m.Called(log)
+}
+
+// MonitoringEndpoint_SendLog_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SendLog'
+type MonitoringEndpoint_SendLog_Call struct {
+ *mock.Call
+}
+
+// SendLog is a helper method to define mock.On call
+// - log []byte
+func (_e *MonitoringEndpoint_Expecter) SendLog(log interface{}) *MonitoringEndpoint_SendLog_Call {
+ return &MonitoringEndpoint_SendLog_Call{Call: _e.mock.On("SendLog", log)}
+}
+
+func (_c *MonitoringEndpoint_SendLog_Call) Run(run func(log []byte)) *MonitoringEndpoint_SendLog_Call {
+ _c.Call.Run(func(args mock.Arguments) {
+ run(args[0].([]byte))
+ })
+ return _c
+}
+
+func (_c *MonitoringEndpoint_SendLog_Call) Return() *MonitoringEndpoint_SendLog_Call {
+ _c.Call.Return()
+ return _c
+}
+
+func (_c *MonitoringEndpoint_SendLog_Call) RunAndReturn(run func([]byte)) *MonitoringEndpoint_SendLog_Call {
+ _c.Call.Return(run)
+ return _c
+}
+
+// NewMonitoringEndpoint creates a new instance of MonitoringEndpoint. 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 NewMonitoringEndpoint(t interface {
+ mock.TestingT
+ Cleanup(func())
+}) *MonitoringEndpoint {
+ mock := &MonitoringEndpoint{}
+ mock.Mock.Test(t)
+
+ t.Cleanup(func() { mock.AssertExpectations(t) })
+
+ return mock
+}
diff --git a/contracts/gas-snapshots/ccip.gas-snapshot b/contracts/gas-snapshots/ccip.gas-snapshot
index 7e79a922d3..8c84c95305 100644
--- a/contracts/gas-snapshots/ccip.gas-snapshot
+++ b/contracts/gas-snapshots/ccip.gas-snapshot
@@ -103,7 +103,7 @@ CommitStore_report:test_OnlyPriceUpdateStaleReport_Revert() (gas: 59049)
CommitStore_report:test_OnlyTokenPriceUpdates_Success() (gas: 53251)
CommitStore_report:test_Paused_Revert() (gas: 21259)
CommitStore_report:test_ReportAndPriceUpdate_Success() (gas: 84242)
-CommitStore_report:test_ReportOnlyRootSuccess_gas() (gas: 56313)
+CommitStore_report:test_ReportOnlyRootSuccess_gas() (gas: 56249)
CommitStore_report:test_RootAlreadyCommitted_Revert() (gas: 63969)
CommitStore_report:test_StaleReportWithRoot_Success() (gas: 119420)
CommitStore_report:test_Unhealthy_Revert() (gas: 44751)
@@ -123,13 +123,13 @@ CommitStore_verify:test_Paused_Revert() (gas: 18496)
CommitStore_verify:test_TooManyLeaves_Revert() (gas: 36785)
DefensiveExampleTest:test_HappyPath_Success() (gas: 200018)
DefensiveExampleTest:test_Recovery() (gas: 424256)
-E2E:test_E2E_3MessagesSuccess_gas() (gas: 1104303)
+E2E:test_E2E_3MessagesSuccess_gas() (gas: 1133785)
EVM2EVMOffRamp__releaseOrMintToken:test__releaseOrMintToken_NotACompatiblePool_Revert() (gas: 37797)
EVM2EVMOffRamp__releaseOrMintToken:test__releaseOrMintToken_Success() (gas: 103733)
-EVM2EVMOffRamp__releaseOrMintToken:test__releaseOrMintToken_TokenHandlingError_transfer_Revert() (gas: 85258)
+EVM2EVMOffRamp__releaseOrMintToken:test__releaseOrMintToken_TokenHandlingError_transfer_Revert() (gas: 82758)
EVM2EVMOffRamp__releaseOrMintToken:test_releaseOrMintToken_InvalidDataLength_Revert() (gas: 36786)
EVM2EVMOffRamp__releaseOrMintToken:test_releaseOrMintToken_ReleaseOrMintBalanceMismatch_Revert() (gas: 94302)
-EVM2EVMOffRamp__releaseOrMintToken:test_releaseOrMintToken_TokenHandlingError_BalanceOf_Revert() (gas: 39768)
+EVM2EVMOffRamp__releaseOrMintToken:test_releaseOrMintToken_TokenHandlingError_BalanceOf_Revert() (gas: 37268)
EVM2EVMOffRamp__releaseOrMintToken:test_releaseOrMintToken_skip_ReleaseOrMintBalanceMismatch_if_pool_Revert() (gas: 86559)
EVM2EVMOffRamp__releaseOrMintTokens:test_OverValueWithARLOff_Success() (gas: 385308)
EVM2EVMOffRamp__releaseOrMintTokens:test_PriceNotFoundForToken_Reverts() (gas: 141902)
@@ -162,13 +162,13 @@ EVM2EVMOffRamp_execute:test_RootNotCommitted_Revert() (gas: 42539)
EVM2EVMOffRamp_execute:test_SingleMessageNoTokensUnordered_Success() (gas: 157347)
EVM2EVMOffRamp_execute:test_SingleMessageNoTokens_Success() (gas: 172692)
EVM2EVMOffRamp_execute:test_SingleMessageToNonCCIPReceiver_Success() (gas: 247069)
-EVM2EVMOffRamp_execute:test_SingleMessagesNoTokensSuccess_gas() (gas: 114153)
+EVM2EVMOffRamp_execute:test_SingleMessagesNoTokensSuccess_gas() (gas: 113842)
EVM2EVMOffRamp_execute:test_SkippedIncorrectNonceStillExecutes_Success() (gas: 407507)
EVM2EVMOffRamp_execute:test_SkippedIncorrectNonce_Success() (gas: 54096)
EVM2EVMOffRamp_execute:test_StrictUntouchedToSuccess_Success() (gas: 131047)
EVM2EVMOffRamp_execute:test_TokenDataMismatch_Revert() (gas: 52090)
EVM2EVMOffRamp_execute:test_TwoMessagesWithTokensAndGE_Success() (gas: 563497)
-EVM2EVMOffRamp_execute:test_TwoMessagesWithTokensSuccess_gas() (gas: 495620)
+EVM2EVMOffRamp_execute:test_TwoMessagesWithTokensSuccess_gas() (gas: 494294)
EVM2EVMOffRamp_execute:test_UnexpectedTokenData_Revert() (gas: 35383)
EVM2EVMOffRamp_execute:test_Unhealthy_Revert() (gas: 544753)
EVM2EVMOffRamp_execute:test_UnsupportedNumberOfTokens_Revert() (gas: 64326)
@@ -178,7 +178,7 @@ EVM2EVMOffRamp_execute:test_execute_RouterYULCall_Success() (gas: 427337)
EVM2EVMOffRamp_executeSingleMessage:test_MessageSender_Revert() (gas: 18502)
EVM2EVMOffRamp_executeSingleMessage:test_NonContractWithTokens_Success() (gas: 278153)
EVM2EVMOffRamp_executeSingleMessage:test_NonContract_Success() (gas: 18659)
-EVM2EVMOffRamp_executeSingleMessage:test_TokenHandlingError_Revert() (gas: 221449)
+EVM2EVMOffRamp_executeSingleMessage:test_TokenHandlingError_Revert() (gas: 223949)
EVM2EVMOffRamp_executeSingleMessage:test_ZeroGasDONExecution_Revert() (gas: 47881)
EVM2EVMOffRamp_executeSingleMessage:test_executeSingleMessage_NoTokens_Success() (gas: 47352)
EVM2EVMOffRamp_executeSingleMessage:test_executeSingleMessage_WithTokens_Success() (gas: 313973)
@@ -230,21 +230,21 @@ EVM2EVMOnRamp_forwardFromRouter:test_MaxFeeBalanceReached_Revert() (gas: 36457)
EVM2EVMOnRamp_forwardFromRouter:test_MessageGasLimitTooHigh_Revert() (gas: 29015)
EVM2EVMOnRamp_forwardFromRouter:test_MessageTooLarge_Revert() (gas: 107571)
EVM2EVMOnRamp_forwardFromRouter:test_OriginalSender_Revert() (gas: 22679)
-EVM2EVMOnRamp_forwardFromRouter:test_OverValueWithARLOff_Success() (gas: 224625)
+EVM2EVMOnRamp_forwardFromRouter:test_OverValueWithARLOff_Success() (gas: 227125)
EVM2EVMOnRamp_forwardFromRouter:test_Paused_Revert() (gas: 53072)
EVM2EVMOnRamp_forwardFromRouter:test_Permissions_Revert() (gas: 25481)
EVM2EVMOnRamp_forwardFromRouter:test_PriceNotFoundForToken_Revert() (gas: 59347)
EVM2EVMOnRamp_forwardFromRouter:test_ShouldIncrementNonceOnlyOnOrdered_Success() (gas: 179148)
EVM2EVMOnRamp_forwardFromRouter:test_ShouldIncrementSeqNumAndNonce_Success() (gas: 177430)
EVM2EVMOnRamp_forwardFromRouter:test_ShouldStoreNonLinkFees() (gas: 137322)
-EVM2EVMOnRamp_forwardFromRouter:test_SourceTokenDataTooLarge_Revert() (gas: 3822827)
+EVM2EVMOnRamp_forwardFromRouter:test_SourceTokenDataTooLarge_Revert() (gas: 3825327)
EVM2EVMOnRamp_forwardFromRouter:test_TooManyTokens_Revert() (gas: 30187)
EVM2EVMOnRamp_forwardFromRouter:test_Unhealthy_Revert() (gas: 43300)
EVM2EVMOnRamp_forwardFromRouter:test_UnsupportedToken_Revert() (gas: 109283)
EVM2EVMOnRamp_forwardFromRouter:test_ZeroAddressReceiver_Revert() (gas: 312579)
EVM2EVMOnRamp_forwardFromRouter:test_forwardFromRouter_ShouldStoreLinkFees_Success() (gas: 112322)
EVM2EVMOnRamp_forwardFromRouter:test_forwardFromRouter_UnsupportedToken_Revert() (gas: 72206)
-EVM2EVMOnRamp_forwardFromRouter:test_forwardFromRouter_correctSourceTokenData_Success() (gas: 710531)
+EVM2EVMOnRamp_forwardFromRouter:test_forwardFromRouter_correctSourceTokenData_Success() (gas: 713031)
EVM2EVMOnRamp_forwardFromRouter_upgrade:test_V2NonceNewSenderStartsAtZero_Success() (gas: 147664)
EVM2EVMOnRamp_forwardFromRouter_upgrade:test_V2NonceStartsAtV1Nonce_Success() (gas: 190529)
EVM2EVMOnRamp_forwardFromRouter_upgrade:test_V2SenderNoncesReadsPreviousRamp_Success() (gas: 121320)
@@ -275,7 +275,7 @@ EVM2EVMOnRamp_getTokenTransferCost:test__getTokenTransferCost_selfServeUsesDefau
EVM2EVMOnRamp_linkAvailableForPayment:test_InsufficientLinkBalance_Success() (gas: 32615)
EVM2EVMOnRamp_linkAvailableForPayment:test_LinkAvailableForPayment_Success() (gas: 134833)
EVM2EVMOnRamp_payNops:test_AdminPayNops_Success() (gas: 143159)
-EVM2EVMOnRamp_payNops:test_InsufficientBalance_Revert() (gas: 26543)
+EVM2EVMOnRamp_payNops:test_InsufficientBalance_Revert() (gas: 29043)
EVM2EVMOnRamp_payNops:test_NoFeesToPay_Revert() (gas: 127367)
EVM2EVMOnRamp_payNops:test_NoNopsToPay_Revert() (gas: 133251)
EVM2EVMOnRamp_payNops:test_NopPayNops_Success() (gas: 146446)
@@ -307,7 +307,7 @@ EVM2EVMOnRamp_withdrawNonLinkFees:test_SettlingBalance_Success() (gas: 272035)
EVM2EVMOnRamp_withdrawNonLinkFees:test_WithdrawNonLinkFees_Success() (gas: 53446)
EVM2EVMOnRamp_withdrawNonLinkFees:test_WithdrawToZeroAddress_Revert() (gas: 12830)
EtherSenderReceiverTest_ccipReceive:test_ccipReceive_fallbackToWethTransfer() (gas: 96729)
-EtherSenderReceiverTest_ccipReceive:test_ccipReceive_happyPath() (gas: 47688)
+EtherSenderReceiverTest_ccipReceive:test_ccipReceive_happyPath() (gas: 49688)
EtherSenderReceiverTest_ccipReceive:test_ccipReceive_wrongToken() (gas: 17384)
EtherSenderReceiverTest_ccipReceive:test_ccipReceive_wrongTokenAmount() (gas: 15677)
EtherSenderReceiverTest_ccipSend:test_ccipSend_reverts_insufficientFee_feeToken() (gas: 99741)
@@ -360,9 +360,9 @@ MerkleMultiProofTest:test_MerkleRootSingleLeaf_Success() (gas: 3649)
MerkleMultiProofTest:test_SpecSync_gas() (gas: 34123)
MockRouterTest:test_ccipSendWithInsufficientNativeTokens_Revert() (gas: 33965)
MockRouterTest:test_ccipSendWithInvalidMsgValue_Revert() (gas: 60758)
-MockRouterTest:test_ccipSendWithLinkFeeTokenAndValidMsgValue_Success() (gas: 126294)
+MockRouterTest:test_ccipSendWithLinkFeeTokenAndValidMsgValue_Success() (gas: 126354)
MockRouterTest:test_ccipSendWithLinkFeeTokenbutInsufficientAllowance_Revert() (gas: 63302)
-MockRouterTest:test_ccipSendWithSufficientNativeFeeTokens_Success() (gas: 43853)
+MockRouterTest:test_ccipSendWithSufficientNativeFeeTokens_Success() (gas: 43913)
MultiAggregateRateLimiter_applyRateLimiterConfigUpdates:test_MultipleConfigsBothLanes_Success() (gas: 132031)
MultiAggregateRateLimiter_applyRateLimiterConfigUpdates:test_MultipleConfigs_Success() (gas: 312057)
MultiAggregateRateLimiter_applyRateLimiterConfigUpdates:test_OnlyCallableByOwner_Revert() (gas: 17717)
@@ -388,7 +388,7 @@ MultiAggregateRateLimiter_onInboundMessage:test_ValidateMessageWithRateLimitExce
MultiAggregateRateLimiter_onInboundMessage:test_ValidateMessageWithRateLimitReset_Success() (gas: 78780)
MultiAggregateRateLimiter_onInboundMessage:test_ValidateMessageWithTokensOnDifferentChains_Success() (gas: 312061)
MultiAggregateRateLimiter_onInboundMessage:test_ValidateMessageWithTokens_Success() (gas: 54784)
-MultiAggregateRateLimiter_onOutboundMessage:test_RateLimitValueDifferentLanes_Success() (gas: 9223372036854754743)
+MultiAggregateRateLimiter_onOutboundMessage:test_RateLimitValueDifferentLanes_Success() (gas: 53241)
MultiAggregateRateLimiter_onOutboundMessage:test_ValidateMessageWithNoTokens_Success() (gas: 19104)
MultiAggregateRateLimiter_onOutboundMessage:test_onOutboundMessage_ValidateMessageFromUnauthorizedCaller_Revert() (gas: 15778)
MultiAggregateRateLimiter_onOutboundMessage:test_onOutboundMessage_ValidateMessageWithDifferentTokensOnDifferentChains_Success() (gas: 213732)
@@ -430,16 +430,16 @@ MultiOCR3Base_transmit:test_InsufficientSignatures_Revert() (gas: 76930)
MultiOCR3Base_transmit:test_NonUniqueSignature_Revert() (gas: 66127)
MultiOCR3Base_transmit:test_SignatureOutOfRegistration_Revert() (gas: 33419)
MultiOCR3Base_transmit:test_TooManySignatures_Revert() (gas: 79521)
-MultiOCR3Base_transmit:test_TransmitSigners_gas_Success() (gas: 34131)
+MultiOCR3Base_transmit:test_TransmitSigners_gas_Success() (gas: 34020)
MultiOCR3Base_transmit:test_TransmitWithExtraCalldataArgs_Revert() (gas: 47114)
MultiOCR3Base_transmit:test_TransmitWithLessCalldataArgs_Revert() (gas: 25682)
-MultiOCR3Base_transmit:test_TransmitWithoutSignatureVerification_gas_Success() (gas: 18726)
+MultiOCR3Base_transmit:test_TransmitWithoutSignatureVerification_gas_Success() (gas: 18714)
MultiOCR3Base_transmit:test_UnAuthorizedTransmitter_Revert() (gas: 24191)
MultiOCR3Base_transmit:test_UnauthorizedSigner_Revert() (gas: 61409)
MultiOCR3Base_transmit:test_UnconfiguredPlugin_Revert() (gas: 39890)
MultiOCR3Base_transmit:test_ZeroSignatures_Revert() (gas: 32973)
MultiOnRampTokenPoolReentrancy:test_OnRampTokenPoolReentrancy_Success() (gas: 393335)
-MultiRampsE2E:test_E2E_3MessagesMMultiOffRampSuccess_gas() (gas: 1448084)
+MultiRampsE2E:test_E2E_3MessagesMMultiOffRampSuccess_gas() (gas: 1461081)
NonceManager_NonceIncrementation:test_getIncrementedOutboundNonce_Success() (gas: 37907)
NonceManager_NonceIncrementation:test_incrementInboundNonce_Skip() (gas: 23694)
NonceManager_NonceIncrementation:test_incrementInboundNonce_Success() (gas: 38763)
@@ -469,7 +469,7 @@ OCR2BaseNoChecks_setOCR2Config:test_TooManyTransmitter_Revert() (gas: 36938)
OCR2BaseNoChecks_setOCR2Config:test_TransmitterCannotBeZeroAddress_Revert() (gas: 24158)
OCR2BaseNoChecks_transmit:test_ConfigDigestMismatch_Revert() (gas: 17448)
OCR2BaseNoChecks_transmit:test_ForkedChain_Revert() (gas: 26726)
-OCR2BaseNoChecks_transmit:test_TransmitSuccess_gas() (gas: 27478)
+OCR2BaseNoChecks_transmit:test_TransmitSuccess_gas() (gas: 27466)
OCR2BaseNoChecks_transmit:test_UnAuthorizedTransmitter_Revert() (gas: 21296)
OCR2Base_setOCR2Config:test_FMustBePositive_Revert() (gas: 12189)
OCR2Base_setOCR2Config:test_FTooHigh_Revert() (gas: 12345)
@@ -483,16 +483,16 @@ OCR2Base_transmit:test_ConfigDigestMismatch_Revert() (gas: 19623)
OCR2Base_transmit:test_ForkedChain_Revert() (gas: 37683)
OCR2Base_transmit:test_NonUniqueSignature_Revert() (gas: 55309)
OCR2Base_transmit:test_SignatureOutOfRegistration_Revert() (gas: 20962)
-OCR2Base_transmit:test_Transmit2SignersSuccess_gas() (gas: 51686)
+OCR2Base_transmit:test_Transmit2SignersSuccess_gas() (gas: 51674)
OCR2Base_transmit:test_UnAuthorizedTransmitter_Revert() (gas: 23484)
OCR2Base_transmit:test_UnauthorizedSigner_Revert() (gas: 39665)
OCR2Base_transmit:test_WrongNumberOfSignatures_Revert() (gas: 20557)
OffRamp__releaseOrMintSingleToken:test__releaseOrMintSingleToken_NotACompatiblePool_Revert() (gas: 38408)
OffRamp__releaseOrMintSingleToken:test__releaseOrMintSingleToken_Success() (gas: 106250)
-OffRamp__releaseOrMintSingleToken:test__releaseOrMintSingleToken_TokenHandlingError_transfer_Revert() (gas: 87409)
+OffRamp__releaseOrMintSingleToken:test__releaseOrMintSingleToken_TokenHandlingError_transfer_Revert() (gas: 84909)
OffRamp__releaseOrMintSingleToken:test_releaseOrMintToken_InvalidDataLength_Revert() (gas: 38954)
OffRamp__releaseOrMintSingleToken:test_releaseOrMintToken_ReleaseOrMintBalanceMismatch_Revert() (gas: 96511)
-OffRamp__releaseOrMintSingleToken:test_releaseOrMintToken_TokenHandlingError_BalanceOf_Revert() (gas: 41956)
+OffRamp__releaseOrMintSingleToken:test_releaseOrMintToken_TokenHandlingError_BalanceOf_Revert() (gas: 39456)
OffRamp__releaseOrMintSingleToken:test_releaseOrMintToken_skip_ReleaseOrMintBalanceMismatch_if_pool_Revert() (gas: 88684)
OffRamp_applySourceChainConfigUpdates:test_AddMultipleChains_Success() (gas: 468115)
OffRamp_applySourceChainConfigUpdates:test_AddNewChain_Success() (gas: 99227)
@@ -550,7 +550,7 @@ OffRamp_execute:test_ZeroReports_Revert() (gas: 17159)
OffRamp_executeSingleMessage:test_MessageSender_Revert() (gas: 18190)
OffRamp_executeSingleMessage:test_NonContractWithTokens_Success() (gas: 246556)
OffRamp_executeSingleMessage:test_NonContract_Success() (gas: 20472)
-OffRamp_executeSingleMessage:test_TokenHandlingError_Revert() (gas: 205195)
+OffRamp_executeSingleMessage:test_TokenHandlingError_Revert() (gas: 207695)
OffRamp_executeSingleMessage:test_ZeroGasDONExecution_Revert() (gas: 48734)
OffRamp_executeSingleMessage:test_executeSingleMessage_NoTokens_Success() (gas: 48257)
OffRamp_executeSingleMessage:test_executeSingleMessage_WithFailingValidationNoRouterCall_Revert() (gas: 229631)
@@ -572,12 +572,12 @@ OffRamp_executeSingleReport:test_SingleMessageNoTokensOtherChain_Success() (gas:
OffRamp_executeSingleReport:test_SingleMessageNoTokensUnordered_Success() (gas: 187191)
OffRamp_executeSingleReport:test_SingleMessageNoTokens_Success() (gas: 206771)
OffRamp_executeSingleReport:test_SingleMessageToNonCCIPReceiver_Success() (gas: 263519)
-OffRamp_executeSingleReport:test_SingleMessagesNoTokensSuccess_gas() (gas: 138408)
+OffRamp_executeSingleReport:test_SingleMessagesNoTokensSuccess_gas() (gas: 138111)
OffRamp_executeSingleReport:test_SkippedIncorrectNonceStillExecutes_Success() (gas: 409328)
OffRamp_executeSingleReport:test_SkippedIncorrectNonce_Success() (gas: 65876)
OffRamp_executeSingleReport:test_TokenDataMismatch_Revert() (gas: 80909)
OffRamp_executeSingleReport:test_TwoMessagesWithTokensAndGE_Success() (gas: 566299)
-OffRamp_executeSingleReport:test_TwoMessagesWithTokensSuccess_gas() (gas: 517689)
+OffRamp_executeSingleReport:test_TwoMessagesWithTokensSuccess_gas() (gas: 516604)
OffRamp_executeSingleReport:test_UnexpectedTokenData_Revert() (gas: 35742)
OffRamp_executeSingleReport:test_UnhealthySingleChainCurse_Revert() (gas: 517721)
OffRamp_executeSingleReport:test_Unhealthy_Revert() (gas: 515089)
@@ -646,10 +646,10 @@ OnRamp_forwardFromRouter:test_ShouldIncrementNonceOnlyOnOrdered_Success() (gas:
OnRamp_forwardFromRouter:test_ShouldIncrementSeqNumAndNonce_Success() (gas: 205670)
OnRamp_forwardFromRouter:test_ShouldStoreLinkFees() (gas: 121815)
OnRamp_forwardFromRouter:test_ShouldStoreNonLinkFees() (gas: 143193)
-OnRamp_forwardFromRouter:test_SourceTokenDataTooLarge_Revert() (gas: 3872608)
+OnRamp_forwardFromRouter:test_SourceTokenDataTooLarge_Revert() (gas: 3875108)
OnRamp_forwardFromRouter:test_UnsupportedToken_Revert() (gas: 108546)
OnRamp_forwardFromRouter:test_forwardFromRouter_UnsupportedToken_Revert() (gas: 73975)
-OnRamp_forwardFromRouter:test_forwardFromRouter_WithValidation_Success() (gas: 262685)
+OnRamp_forwardFromRouter:test_forwardFromRouter_WithValidation_Success() (gas: 265185)
OnRamp_getFee:test_EmptyMessage_Success() (gas: 104467)
OnRamp_getFee:test_EnforceOutOfOrder_Revert() (gas: 74075)
OnRamp_getFee:test_SingleTokenMessage_Success() (gas: 119799)
@@ -662,7 +662,7 @@ OnRamp_setDynamicConfig:test_SetConfigInvalidConfig_Revert() (gas: 11112)
OnRamp_setDynamicConfig:test_SetConfigOnlyOwner_Revert() (gas: 15939)
OnRamp_setDynamicConfig:test_SetDynamicConfig_Success() (gas: 51996)
OnRamp_withdrawFeeTokens:test_WithdrawFeeTokens_Success() (gas: 97214)
-PingPong_ccipReceive:test_CcipReceive_Success() (gas: 150175)
+PingPong_ccipReceive:test_CcipReceive_Success() (gas: 152675)
PingPong_plumbing:test_OutOfOrderExecution_Success() (gas: 20277)
PingPong_plumbing:test_Pausing_Success() (gas: 17777)
PingPong_startPingPong:test_StartPingPong_With_OOO_Success() (gas: 163199)
@@ -775,7 +775,7 @@ RMN_ownerUnbless:test_Unbless_Success() (gas: 74699)
RMN_ownerUnvoteToCurse:test_CanBlessAndCurseAfterGlobalCurseIsLifted() (gas: 470965)
RMN_ownerUnvoteToCurse:test_IsIdempotent() (gas: 397532)
RMN_ownerUnvoteToCurse:test_NonOwner_Revert() (gas: 18591)
-RMN_ownerUnvoteToCurse:test_OwnerUnvoteToCurseSuccess_gas() (gas: 357403)
+RMN_ownerUnvoteToCurse:test_OwnerUnvoteToCurseSuccess_gas() (gas: 357400)
RMN_ownerUnvoteToCurse:test_UnknownVoter_Revert() (gas: 32980)
RMN_ownerUnvoteToCurse_Benchmark:test_OwnerUnvoteToCurse_1Voter_LiftsCurse_gas() (gas: 261985)
RMN_permaBlessing:test_PermaBlessing() (gas: 202686)
@@ -783,7 +783,7 @@ RMN_setConfig:test_BlessVoterIsZeroAddress_Revert() (gas: 15494)
RMN_setConfig:test_EitherThresholdIsZero_Revert() (gas: 21095)
RMN_setConfig:test_NonOwner_Revert() (gas: 14713)
RMN_setConfig:test_RepeatedAddress_Revert() (gas: 18213)
-RMN_setConfig:test_SetConfigSuccess_gas() (gas: 104204)
+RMN_setConfig:test_SetConfigSuccess_gas() (gas: 104022)
RMN_setConfig:test_TotalWeightsSmallerThanEachThreshold_Revert() (gas: 30173)
RMN_setConfig:test_VoteToBlessByEjectedVoter_Revert() (gas: 130303)
RMN_setConfig:test_VotersLengthIsZero_Revert() (gas: 12128)
@@ -868,18 +868,18 @@ Router_getFee:test_GetFeeSupportedChain_Success() (gas: 46464)
Router_getFee:test_UnsupportedDestinationChain_Revert() (gas: 17138)
Router_getSupportedTokens:test_GetSupportedTokens_Revert() (gas: 10460)
Router_recoverTokens:test_RecoverTokensInvalidRecipient_Revert() (gas: 11316)
-Router_recoverTokens:test_RecoverTokensNoFunds_Revert() (gas: 17761)
+Router_recoverTokens:test_RecoverTokensNoFunds_Revert() (gas: 20261)
Router_recoverTokens:test_RecoverTokensNonOwner_Revert() (gas: 11159)
Router_recoverTokens:test_RecoverTokensValueReceiver_Revert() (gas: 422138)
-Router_recoverTokens:test_RecoverTokens_Success() (gas: 50437)
+Router_recoverTokens:test_RecoverTokens_Success() (gas: 52437)
Router_routeMessage:test_AutoExec_Success() (gas: 42684)
Router_routeMessage:test_ExecutionEvent_Success() (gas: 157980)
Router_routeMessage:test_ManualExec_Success() (gas: 35381)
Router_routeMessage:test_OnlyOffRamp_Revert() (gas: 25116)
Router_routeMessage:test_WhenNotHealthy_Revert() (gas: 44724)
Router_setWrappedNative:test_OnlyOwner_Revert() (gas: 10985)
-SelfFundedPingPong_ccipReceive:test_FundingIfNotANop_Revert() (gas: 53531)
-SelfFundedPingPong_ccipReceive:test_Funding_Success() (gas: 417002)
+SelfFundedPingPong_ccipReceive:test_FundingIfNotANop_Revert() (gas: 55531)
+SelfFundedPingPong_ccipReceive:test_Funding_Success() (gas: 419502)
SelfFundedPingPong_setCountIncrBeforeFunding:test_setCountIncrBeforeFunding() (gas: 20151)
TokenAdminRegistry_acceptAdminRole:test_acceptAdminRole_OnlyPendingAdministrator_Revert() (gas: 51085)
TokenAdminRegistry_acceptAdminRole:test_acceptAdminRole_Success() (gas: 43947)
@@ -907,8 +907,8 @@ TokenPoolAndProxy:test_lockOrBurn_burnMint_Success() (gas: 6070353)
TokenPoolAndProxy:test_lockOrBurn_burnWithFromMint_Success() (gas: 6101826)
TokenPoolAndProxy:test_lockOrBurn_lockRelease_Success() (gas: 6319594)
TokenPoolAndProxy:test_setPreviousPool_Success() (gas: 3387124)
-TokenPoolAndProxyMigration:test_tokenPoolMigration_Success_1_2() (gas: 6913796)
-TokenPoolAndProxyMigration:test_tokenPoolMigration_Success_1_4() (gas: 7097821)
+TokenPoolAndProxyMigration:test_tokenPoolMigration_Success_1_2() (gas: 6916296)
+TokenPoolAndProxyMigration:test_tokenPoolMigration_Success_1_4() (gas: 7100321)
TokenPoolWithAllowList_applyAllowListUpdates:test_AllowListNotEnabled_Revert() (gas: 2209837)
TokenPoolWithAllowList_applyAllowListUpdates:test_OnlyOwner_Revert() (gas: 12089)
TokenPoolWithAllowList_applyAllowListUpdates:test_SetAllowListSkipsZero_Success() (gas: 23324)
diff --git a/contracts/src/v0.8/ccip/test/mocks/MockRouter.sol b/contracts/src/v0.8/ccip/test/mocks/MockRouter.sol
index 050cd6d457..9181fb37c2 100644
--- a/contracts/src/v0.8/ccip/test/mocks/MockRouter.sol
+++ b/contracts/src/v0.8/ccip/test/mocks/MockRouter.sol
@@ -45,8 +45,19 @@ contract MockCCIPRouter is IRouter, IRouterClient {
uint256 gasLimit,
address receiver
) internal returns (bool success, bytes memory retData, uint256 gasUsed) {
- // Only send through the router if the receiver is a contract and implements the IAny2EVMMessageReceiver interface.
- if (receiver.code.length == 0 || !receiver.supportsInterface(type(IAny2EVMMessageReceiver).interfaceId)) {
+ // There are three cases in which we skip calling the receiver:
+ // 1. If the message data is empty AND the gas limit is 0.
+ // This indicates a message that only transfers tokens. It is valid to only send tokens to a contract
+ // that supports the IAny2EVMMessageReceiver interface, but without this first check we would call the
+ // receiver without any gas, which would revert the transaction.
+ // 2. If the receiver is not a contract.
+ // 3. If the receiver is a contract but it does not support the IAny2EVMMessageReceiver interface.
+ //
+ // The ordering of these checks is important, as the first check is the cheapest to execute.
+ if (
+ (message.data.length == 0 && gasLimit == 0) || receiver.code.length == 0
+ || !receiver.supportsInterface(type(IAny2EVMMessageReceiver).interfaceId)
+ ) {
return (true, "", 0);
}
diff --git a/core/capabilities/ccip/ocrimpls/contract_transmitter_test.go b/core/capabilities/ccip/ocrimpls/contract_transmitter_test.go
index 4e0a7162aa..1726405e22 100644
--- a/core/capabilities/ccip/ocrimpls/contract_transmitter_test.go
+++ b/core/capabilities/ccip/ocrimpls/contract_transmitter_test.go
@@ -590,6 +590,10 @@ func (g *TestGasEstimatorConfig) BlockHistory() evmconfig.BlockHistory {
return &TestBlockHistoryConfig{}
}
+func (g *TestGasEstimatorConfig) FeeHistory() evmconfig.FeeHistory {
+ return &TestFeeHistoryConfig{}
+}
+
func (g *TestGasEstimatorConfig) EIP1559DynamicFees() bool { return false }
func (g *TestGasEstimatorConfig) LimitDefault() uint64 { return 1e6 }
func (g *TestGasEstimatorConfig) BumpPercent() uint16 { return 2 }
@@ -612,6 +616,7 @@ func (g *TestGasEstimatorConfig) LimitJobType() evmconfig.LimitJobType {
func (g *TestGasEstimatorConfig) PriceMaxKey(addr common.Address) *assets.Wei {
return assets.GWei(1)
}
+func (g *TestGasEstimatorConfig) EstimateLimit() bool { return false }
func (e *TestEvmConfig) GasEstimator() evmconfig.GasEstimator {
return &TestGasEstimatorConfig{bumpThreshold: e.BumpThreshold}
@@ -637,6 +642,10 @@ func (b *TestBlockHistoryConfig) BlockHistorySize() uint16 { return 42
func (b *TestBlockHistoryConfig) EIP1559FeeCapBufferBlocks() uint16 { return 42 }
func (b *TestBlockHistoryConfig) TransactionPercentile() uint16 { return 42 }
+type TestFeeHistoryConfig struct {
+ evmconfig.FeeHistory
+}
+
type transactionsConfig struct {
evmconfig.Transactions
e *TestEvmConfig
diff --git a/core/chains/evm/client/chain_client.go b/core/chains/evm/client/chain_client.go
index c27d294ebf..310528424d 100644
--- a/core/chains/evm/client/chain_client.go
+++ b/core/chains/evm/client/chain_client.go
@@ -84,6 +84,7 @@ type Client interface {
SuggestGasPrice(ctx context.Context) (*big.Int, error)
SuggestGasTipCap(ctx context.Context) (*big.Int, error)
LatestBlockHeight(ctx context.Context) (*big.Int, error)
+ FeeHistory(ctx context.Context, blockCount uint64, rewardPercentiles []float64) (feeHistory *ethereum.FeeHistory, err error)
HeaderByNumber(ctx context.Context, n *big.Int) (*types.Header, error)
HeaderByHash(ctx context.Context, h common.Hash) (*types.Header, error)
@@ -353,6 +354,14 @@ func (c *chainClient) LatestFinalizedBlock(ctx context.Context) (*evmtypes.Head,
return c.multiNode.LatestFinalizedBlock(ctx)
}
+func (c *chainClient) FeeHistory(ctx context.Context, blockCount uint64, rewardPercentiles []float64) (feeHistory *ethereum.FeeHistory, err error) {
+ rpc, err := c.multiNode.SelectNodeRPC()
+ if err != nil {
+ return feeHistory, err
+ }
+ return rpc.FeeHistory(ctx, blockCount, rewardPercentiles)
+}
+
func (c *chainClient) CheckTxValidity(ctx context.Context, from common.Address, to common.Address, data []byte) *SendError {
msg := ethereum.CallMsg{
From: from,
diff --git a/core/chains/evm/client/config_builder.go b/core/chains/evm/client/config_builder.go
index fa702bac11..a791e9aaa4 100644
--- a/core/chains/evm/client/config_builder.go
+++ b/core/chains/evm/client/config_builder.go
@@ -43,7 +43,7 @@ func NewClientConfigs(
deathDeclarationDelay time.Duration,
noNewFinalizedHeadsThreshold time.Duration,
finalizedBlockPollInterval time.Duration,
-
+ newHeadsPollInterval time.Duration,
) (commonclient.ChainConfig, evmconfig.NodePool, []*toml.Node, error) {
nodes, err := parseNodeConfigs(nodeCfgs)
if err != nil {
@@ -59,6 +59,7 @@ func NewClientConfigs(
EnforceRepeatableRead: enforceRepeatableRead,
DeathDeclarationDelay: commonconfig.MustNewDuration(deathDeclarationDelay),
FinalizedBlockPollInterval: commonconfig.MustNewDuration(finalizedBlockPollInterval),
+ NewHeadsPollInterval: commonconfig.MustNewDuration(newHeadsPollInterval),
}
nodePoolCfg := &evmconfig.NodePoolConfig{C: nodePool}
chainConfig := &evmconfig.EVMConfig{
@@ -79,15 +80,21 @@ func NewClientConfigs(
func parseNodeConfigs(nodeCfgs []NodeConfig) ([]*toml.Node, error) {
nodes := make([]*toml.Node, len(nodeCfgs))
for i, nodeCfg := range nodeCfgs {
- if nodeCfg.WSURL == nil || nodeCfg.HTTPURL == nil {
- return nil, fmt.Errorf("node config [%d]: missing WS or HTTP URL", i)
+ var wsURL, httpURL *commonconfig.URL
+ // wsUrl requirement will be checked in EVMConfig validation
+ if nodeCfg.WSURL != nil {
+ wsURL = commonconfig.MustParseURL(*nodeCfg.WSURL)
+ }
+
+ if nodeCfg.HTTPURL == nil {
+ return nil, fmt.Errorf("node config [%d]: missing HTTP URL", i)
}
- wsUrl := commonconfig.MustParseURL(*nodeCfg.WSURL)
- httpUrl := commonconfig.MustParseURL(*nodeCfg.HTTPURL)
+
+ httpURL = commonconfig.MustParseURL(*nodeCfg.HTTPURL)
node := &toml.Node{
Name: nodeCfg.Name,
- WSURL: wsUrl,
- HTTPURL: httpUrl,
+ WSURL: wsURL,
+ HTTPURL: httpURL,
SendOnly: nodeCfg.SendOnly,
Order: nodeCfg.Order,
}
diff --git a/core/chains/evm/client/config_builder_test.go b/core/chains/evm/client/config_builder_test.go
index 403c6c2d61..22956fb018 100644
--- a/core/chains/evm/client/config_builder_test.go
+++ b/core/chains/evm/client/config_builder_test.go
@@ -37,9 +37,11 @@ func TestClientConfigBuilder(t *testing.T) {
finalityDepth := ptr(uint32(10))
finalityTagEnabled := ptr(true)
noNewHeadsThreshold := time.Second
+ newHeadsPollInterval := 0 * time.Second
chainCfg, nodePool, nodes, err := client.NewClientConfigs(selectionMode, leaseDuration, chainTypeStr, nodeConfigs,
pollFailureThreshold, pollInterval, syncThreshold, nodeIsSyncingEnabled, noNewHeadsThreshold, finalityDepth,
- finalityTagEnabled, finalizedBlockOffset, enforceRepeatableRead, deathDeclarationDelay, noNewFinalizedBlocksThreshold, pollInterval)
+ finalityTagEnabled, finalizedBlockOffset, enforceRepeatableRead, deathDeclarationDelay, noNewFinalizedBlocksThreshold,
+ pollInterval, newHeadsPollInterval)
require.NoError(t, err)
// Validate node pool configs
@@ -52,6 +54,7 @@ func TestClientConfigBuilder(t *testing.T) {
require.Equal(t, *enforceRepeatableRead, nodePool.EnforceRepeatableRead())
require.Equal(t, deathDeclarationDelay, nodePool.DeathDeclarationDelay())
require.Equal(t, pollInterval, nodePool.FinalizedBlockPollInterval())
+ require.Equal(t, newHeadsPollInterval, nodePool.NewHeadsPollInterval())
// Validate node configs
require.Equal(t, *nodeConfigs[0].Name, *nodes[0].Name)
@@ -90,7 +93,7 @@ func TestNodeConfigs(t *testing.T) {
require.Len(t, tomlNodes, len(nodeConfigs))
})
- t.Run("parsing missing ws url fails", func(t *testing.T) {
+ t.Run("ws can be optional", func(t *testing.T) {
nodeConfigs := []client.NodeConfig{
{
Name: ptr("foo1"),
@@ -98,7 +101,7 @@ func TestNodeConfigs(t *testing.T) {
},
}
_, err := client.ParseTestNodeConfigs(nodeConfigs)
- require.Error(t, err)
+ require.Nil(t, err)
})
t.Run("parsing missing http url fails", func(t *testing.T) {
diff --git a/core/chains/evm/client/errors.go b/core/chains/evm/client/errors.go
index e7fff8d0db..812733b35f 100644
--- a/core/chains/evm/client/errors.go
+++ b/core/chains/evm/client/errors.go
@@ -247,7 +247,7 @@ var zkSync = ClientErrors{
}
var zkEvm = ClientErrors{
- TerminallyStuck: regexp.MustCompile(`(?:: |^)not enough .* counters to continue the execution$`),
+ TerminallyStuck: regexp.MustCompile(`(?:: |^)(?:not enough .* counters to continue the execution|out of counters at node level (?:.*))$`),
}
var aStar = ClientErrors{
@@ -257,6 +257,7 @@ var aStar = ClientErrors{
var mantle = ClientErrors{
InsufficientEth: regexp.MustCompile(`(: |^)'*insufficient funds for gas \* price \+ value`),
Fatal: regexp.MustCompile(`(: |^)'*invalid sender`),
+ NonceTooLow: regexp.MustCompile(`(: |^)'*nonce too low`),
}
var gnosis = ClientErrors{
diff --git a/core/chains/evm/client/errors_test.go b/core/chains/evm/client/errors_test.go
index bddc7dd3db..e4a9900d4a 100644
--- a/core/chains/evm/client/errors_test.go
+++ b/core/chains/evm/client/errors_test.go
@@ -44,6 +44,7 @@ func Test_Eth_Errors(t *testing.T) {
{"call failed: OldNonce, Current nonce: 22, nonce of rejected tx: 17", true, "Nethermind"},
{"nonce too low. allowed nonce range: 427 - 447, actual: 426", true, "zkSync"},
{"client error nonce too low", true, "tomlConfig"},
+ {"failed to forward tx to sequencer, please try again. Error message: 'nonce too low'", true, "Mantle"},
}
for _, test := range tests {
@@ -215,6 +216,7 @@ func Test_Eth_Errors(t *testing.T) {
{"insufficient funds for gas + value. balance: 42719769622667482000, fee: 48098250000000, value: 42719769622667482000", true, "celo"},
{"client error insufficient eth", true, "tomlConfig"},
{"transaction would cause overdraft", true, "Geth"},
+ {"failed to forward tx to sequencer, please try again. Error message: 'insufficient funds for gas * price + value'", true, "Mantle"},
}
for _, test := range tests {
err = evmclient.NewSendErrorS(test.message)
@@ -316,6 +318,8 @@ func Test_Eth_Errors(t *testing.T) {
{"failed to add tx to the pool: not enough step counters to continue the execution", true, "Xlayer"},
{"failed to add tx to the pool: not enough keccak counters to continue the execution", true, "zkEVM"},
{"failed to add tx to the pool: not enough keccak counters to continue the execution", true, "Xlayer"},
+ {"RPC error response: failed to add tx to the pool: out of counters at node level (Steps)", true, "zkEVM"},
+ {"RPC error response: failed to add tx to the pool: out of counters at node level (GasUsed, KeccakHashes, PoseidonHashes, PoseidonPaddings, MemAligns, Arithmetics, Binaries, Steps, Sha256Hashes)", true, "Xlayer"},
}
for _, test := range tests {
@@ -400,6 +404,8 @@ func Test_Eth_Errors_Fatal(t *testing.T) {
{"Failed to serialize transaction: max priority fee per gas higher than 2^64-1", true, "zkSync"},
{"Failed to serialize transaction: oversized data. max: 1000000; actual: 1000000", true, "zkSync"},
+ {"failed to forward tx to sequencer, please try again. Error message: 'invalid sender'", true, "Mantle"},
+
{"client error fatal", true, "tomlConfig"},
}
diff --git a/core/chains/evm/client/evm_client.go b/core/chains/evm/client/evm_client.go
index 1fd533d6aa..c596bbc3a9 100644
--- a/core/chains/evm/client/evm_client.go
+++ b/core/chains/evm/client/evm_client.go
@@ -15,22 +15,25 @@ import (
)
func NewEvmClient(cfg evmconfig.NodePool, chainCfg commonclient.ChainConfig, clientErrors evmconfig.ClientErrors, lggr logger.Logger, chainID *big.Int, nodes []*toml.Node, chainType chaintype.ChainType) Client {
- var empty url.URL
var primaries []commonclient.Node[*big.Int, *evmtypes.Head, RPCClient]
var sendonlys []commonclient.SendOnlyNode[*big.Int, RPCClient]
largePayloadRPCTimeout, defaultRPCTimeout := getRPCTimeouts(chainType)
for i, node := range nodes {
+ var ws *url.URL
+ if node.WSURL != nil {
+ ws = (*url.URL)(node.WSURL)
+ }
if node.SendOnly != nil && *node.SendOnly {
- rpc := NewRPCClient(lggr, empty, (*url.URL)(node.HTTPURL), *node.Name, int32(i), chainID,
- commonclient.Secondary, cfg.FinalizedBlockPollInterval(), largePayloadRPCTimeout, defaultRPCTimeout, chainType)
+ rpc := NewRPCClient(lggr, nil, (*url.URL)(node.HTTPURL), *node.Name, i, chainID,
+ commonclient.Secondary, cfg.FinalizedBlockPollInterval(), cfg.NewHeadsPollInterval(), largePayloadRPCTimeout, defaultRPCTimeout, chainType)
sendonly := commonclient.NewSendOnlyNode(lggr, (url.URL)(*node.HTTPURL),
*node.Name, chainID, rpc)
sendonlys = append(sendonlys, sendonly)
} else {
- rpc := NewRPCClient(lggr, (url.URL)(*node.WSURL), (*url.URL)(node.HTTPURL), *node.Name, int32(i),
- chainID, commonclient.Primary, cfg.FinalizedBlockPollInterval(), largePayloadRPCTimeout, defaultRPCTimeout, chainType)
+ rpc := NewRPCClient(lggr, ws, (*url.URL)(node.HTTPURL), *node.Name, i,
+ chainID, commonclient.Primary, cfg.FinalizedBlockPollInterval(), cfg.NewHeadsPollInterval(), largePayloadRPCTimeout, defaultRPCTimeout, chainType)
primaryNode := commonclient.NewNode(cfg, chainCfg,
- lggr, (url.URL)(*node.WSURL), (*url.URL)(node.HTTPURL), *node.Name, int32(i), chainID, *node.Order,
+ lggr, ws, (*url.URL)(node.HTTPURL), *node.Name, i, chainID, *node.Order,
rpc, "EVM")
primaries = append(primaries, primaryNode)
}
diff --git a/core/chains/evm/client/evm_client_test.go b/core/chains/evm/client/evm_client_test.go
index bdfcf42674..b762c14653 100644
--- a/core/chains/evm/client/evm_client_test.go
+++ b/core/chains/evm/client/evm_client_test.go
@@ -29,6 +29,7 @@ func TestNewEvmClient(t *testing.T) {
deathDeclarationDelay := time.Second * 3
noNewFinalizedBlocksThreshold := time.Second * 5
finalizedBlockPollInterval := time.Second * 4
+ newHeadsPollInterval := time.Second * 4
nodeConfigs := []client.NodeConfig{
{
Name: ptr("foo"),
@@ -40,7 +41,8 @@ func TestNewEvmClient(t *testing.T) {
finalityTagEnabled := ptr(true)
chainCfg, nodePool, nodes, err := client.NewClientConfigs(selectionMode, leaseDuration, chainTypeStr, nodeConfigs,
pollFailureThreshold, pollInterval, syncThreshold, nodeIsSyncingEnabled, noNewHeadsThreshold, finalityDepth,
- finalityTagEnabled, finalizedBlockOffset, enforceRepeatableRead, deathDeclarationDelay, noNewFinalizedBlocksThreshold, finalizedBlockPollInterval)
+ finalityTagEnabled, finalizedBlockOffset, enforceRepeatableRead, deathDeclarationDelay, noNewFinalizedBlocksThreshold,
+ finalizedBlockPollInterval, newHeadsPollInterval)
require.NoError(t, err)
client := client.NewEvmClient(nodePool, chainCfg, nil, logger.Test(t), testutils.FixtureChainID, nodes, chaintype.ChainType(chainTypeStr))
diff --git a/core/chains/evm/client/helpers_test.go b/core/chains/evm/client/helpers_test.go
index e996ccc5e4..328acdf5a3 100644
--- a/core/chains/evm/client/helpers_test.go
+++ b/core/chains/evm/client/helpers_test.go
@@ -89,6 +89,7 @@ type TestNodePoolConfig struct {
NodeErrors config.ClientErrors
EnforceRepeatableReadVal bool
NodeDeathDeclarationDelay time.Duration
+ NodeNewHeadsPollInterval time.Duration
}
func (tc TestNodePoolConfig) PollFailureThreshold() uint32 { return tc.NodePollFailureThreshold }
@@ -107,6 +108,10 @@ func (tc TestNodePoolConfig) FinalizedBlockPollInterval() time.Duration {
return tc.NodeFinalizedBlockPollInterval
}
+func (tc TestNodePoolConfig) NewHeadsPollInterval() time.Duration {
+ return tc.NodeNewHeadsPollInterval
+}
+
func (tc TestNodePoolConfig) Errors() config.ClientErrors {
return tc.NodeErrors
}
@@ -127,7 +132,7 @@ func NewChainClientWithTestNode(
rpcUrl string,
rpcHTTPURL *url.URL,
sendonlyRPCURLs []url.URL,
- id int32,
+ id int,
chainID *big.Int,
) (Client, error) {
parsed, err := url.ParseRequestURI(rpcUrl)
@@ -140,10 +145,10 @@ func NewChainClientWithTestNode(
}
lggr := logger.Test(t)
- rpc := NewRPCClient(lggr, *parsed, rpcHTTPURL, "eth-primary-rpc-0", id, chainID, commonclient.Primary, 0, commonclient.QueryTimeout, commonclient.QueryTimeout, "")
+ rpc := NewRPCClient(lggr, parsed, rpcHTTPURL, "eth-primary-rpc-0", id, chainID, commonclient.Primary, 0, 0, commonclient.QueryTimeout, commonclient.QueryTimeout, "")
n := commonclient.NewNode[*big.Int, *evmtypes.Head, RPCClient](
- nodeCfg, clientMocks.ChainConfig{NoNewHeadsThresholdVal: noNewHeadsThreshold}, lggr, *parsed, rpcHTTPURL, "eth-primary-node-0", id, chainID, 1, rpc, "EVM")
+ nodeCfg, clientMocks.ChainConfig{NoNewHeadsThresholdVal: noNewHeadsThreshold}, lggr, parsed, rpcHTTPURL, "eth-primary-node-0", id, chainID, 1, rpc, "EVM")
primaries := []commonclient.Node[*big.Int, *evmtypes.Head, RPCClient]{n}
var sendonlys []commonclient.SendOnlyNode[*big.Int, RPCClient]
@@ -152,7 +157,7 @@ func NewChainClientWithTestNode(
return nil, pkgerrors.Errorf("sendonly ethereum rpc url scheme must be http(s): %s", u.String())
}
var empty url.URL
- rpc := NewRPCClient(lggr, empty, &sendonlyRPCURLs[i], fmt.Sprintf("eth-sendonly-rpc-%d", i), id, chainID, commonclient.Secondary, 0, commonclient.QueryTimeout, commonclient.QueryTimeout, "")
+ rpc := NewRPCClient(lggr, &empty, &sendonlyRPCURLs[i], fmt.Sprintf("eth-sendonly-rpc-%d", i), id, chainID, commonclient.Secondary, 0, 0, commonclient.QueryTimeout, commonclient.QueryTimeout, "")
s := commonclient.NewSendOnlyNode[*big.Int, RPCClient](
lggr, u, fmt.Sprintf("eth-sendonly-%d", i), chainID, rpc)
sendonlys = append(sendonlys, s)
@@ -198,7 +203,7 @@ func NewChainClientWithMockedRpc(
parsed, _ := url.ParseRequestURI("ws://test")
n := commonclient.NewNode[*big.Int, *evmtypes.Head, RPCClient](
- cfg, clientMocks.ChainConfig{NoNewHeadsThresholdVal: noNewHeadsThreshold}, lggr, *parsed, nil, "eth-primary-node-0", 1, chainID, 1, rpc, "EVM")
+ cfg, clientMocks.ChainConfig{NoNewHeadsThresholdVal: noNewHeadsThreshold}, lggr, parsed, nil, "eth-primary-node-0", 1, chainID, 1, rpc, "EVM")
primaries := []commonclient.Node[*big.Int, *evmtypes.Head, RPCClient]{n}
clientErrors := NewTestClientErrors()
c := NewChainClient(lggr, selectionMode, leaseDuration, noNewHeadsThreshold, primaries, nil, chainID, chainType, &clientErrors, 0)
diff --git a/core/chains/evm/client/mocks/client.go b/core/chains/evm/client/mocks/client.go
index 7b5220033b..da034d9577 100644
--- a/core/chains/evm/client/mocks/client.go
+++ b/core/chains/evm/client/mocks/client.go
@@ -780,6 +780,66 @@ func (_c *Client_EstimateGas_Call) RunAndReturn(run func(context.Context, ethere
return _c
}
+// FeeHistory provides a mock function with given fields: ctx, blockCount, rewardPercentiles
+func (_m *Client) FeeHistory(ctx context.Context, blockCount uint64, rewardPercentiles []float64) (*ethereum.FeeHistory, error) {
+ ret := _m.Called(ctx, blockCount, rewardPercentiles)
+
+ if len(ret) == 0 {
+ panic("no return value specified for FeeHistory")
+ }
+
+ var r0 *ethereum.FeeHistory
+ var r1 error
+ if rf, ok := ret.Get(0).(func(context.Context, uint64, []float64) (*ethereum.FeeHistory, error)); ok {
+ return rf(ctx, blockCount, rewardPercentiles)
+ }
+ if rf, ok := ret.Get(0).(func(context.Context, uint64, []float64) *ethereum.FeeHistory); ok {
+ r0 = rf(ctx, blockCount, rewardPercentiles)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(*ethereum.FeeHistory)
+ }
+ }
+
+ if rf, ok := ret.Get(1).(func(context.Context, uint64, []float64) error); ok {
+ r1 = rf(ctx, blockCount, rewardPercentiles)
+ } else {
+ r1 = ret.Error(1)
+ }
+
+ return r0, r1
+}
+
+// Client_FeeHistory_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FeeHistory'
+type Client_FeeHistory_Call struct {
+ *mock.Call
+}
+
+// FeeHistory is a helper method to define mock.On call
+// - ctx context.Context
+// - blockCount uint64
+// - rewardPercentiles []float64
+func (_e *Client_Expecter) FeeHistory(ctx interface{}, blockCount interface{}, rewardPercentiles interface{}) *Client_FeeHistory_Call {
+ return &Client_FeeHistory_Call{Call: _e.mock.On("FeeHistory", ctx, blockCount, rewardPercentiles)}
+}
+
+func (_c *Client_FeeHistory_Call) Run(run func(ctx context.Context, blockCount uint64, rewardPercentiles []float64)) *Client_FeeHistory_Call {
+ _c.Call.Run(func(args mock.Arguments) {
+ run(args[0].(context.Context), args[1].(uint64), args[2].([]float64))
+ })
+ return _c
+}
+
+func (_c *Client_FeeHistory_Call) Return(feeHistory *ethereum.FeeHistory, err error) *Client_FeeHistory_Call {
+ _c.Call.Return(feeHistory, err)
+ return _c
+}
+
+func (_c *Client_FeeHistory_Call) RunAndReturn(run func(context.Context, uint64, []float64) (*ethereum.FeeHistory, error)) *Client_FeeHistory_Call {
+ _c.Call.Return(run)
+ return _c
+}
+
// FilterLogs provides a mock function with given fields: ctx, q
func (_m *Client) FilterLogs(ctx context.Context, q ethereum.FilterQuery) ([]types.Log, error) {
ret := _m.Called(ctx, q)
diff --git a/core/chains/evm/client/mocks/rpc_client.go b/core/chains/evm/client/mocks/rpc_client.go
index 06f79efd55..5567b3f897 100644
--- a/core/chains/evm/client/mocks/rpc_client.go
+++ b/core/chains/evm/client/mocks/rpc_client.go
@@ -889,6 +889,66 @@ func (_c *RPCClient_EstimateGas_Call) RunAndReturn(run func(context.Context, int
return _c
}
+// FeeHistory provides a mock function with given fields: ctx, blockCount, rewardPercentiles
+func (_m *RPCClient) FeeHistory(ctx context.Context, blockCount uint64, rewardPercentiles []float64) (*ethereum.FeeHistory, error) {
+ ret := _m.Called(ctx, blockCount, rewardPercentiles)
+
+ if len(ret) == 0 {
+ panic("no return value specified for FeeHistory")
+ }
+
+ var r0 *ethereum.FeeHistory
+ var r1 error
+ if rf, ok := ret.Get(0).(func(context.Context, uint64, []float64) (*ethereum.FeeHistory, error)); ok {
+ return rf(ctx, blockCount, rewardPercentiles)
+ }
+ if rf, ok := ret.Get(0).(func(context.Context, uint64, []float64) *ethereum.FeeHistory); ok {
+ r0 = rf(ctx, blockCount, rewardPercentiles)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(*ethereum.FeeHistory)
+ }
+ }
+
+ if rf, ok := ret.Get(1).(func(context.Context, uint64, []float64) error); ok {
+ r1 = rf(ctx, blockCount, rewardPercentiles)
+ } else {
+ r1 = ret.Error(1)
+ }
+
+ return r0, r1
+}
+
+// RPCClient_FeeHistory_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FeeHistory'
+type RPCClient_FeeHistory_Call struct {
+ *mock.Call
+}
+
+// FeeHistory is a helper method to define mock.On call
+// - ctx context.Context
+// - blockCount uint64
+// - rewardPercentiles []float64
+func (_e *RPCClient_Expecter) FeeHistory(ctx interface{}, blockCount interface{}, rewardPercentiles interface{}) *RPCClient_FeeHistory_Call {
+ return &RPCClient_FeeHistory_Call{Call: _e.mock.On("FeeHistory", ctx, blockCount, rewardPercentiles)}
+}
+
+func (_c *RPCClient_FeeHistory_Call) Run(run func(ctx context.Context, blockCount uint64, rewardPercentiles []float64)) *RPCClient_FeeHistory_Call {
+ _c.Call.Run(func(args mock.Arguments) {
+ run(args[0].(context.Context), args[1].(uint64), args[2].([]float64))
+ })
+ return _c
+}
+
+func (_c *RPCClient_FeeHistory_Call) Return(feeHistory *ethereum.FeeHistory, err error) *RPCClient_FeeHistory_Call {
+ _c.Call.Return(feeHistory, err)
+ return _c
+}
+
+func (_c *RPCClient_FeeHistory_Call) RunAndReturn(run func(context.Context, uint64, []float64) (*ethereum.FeeHistory, error)) *RPCClient_FeeHistory_Call {
+ _c.Call.Return(run)
+ return _c
+}
+
// FilterEvents provides a mock function with given fields: ctx, query
func (_m *RPCClient) FilterEvents(ctx context.Context, query ethereum.FilterQuery) ([]coretypes.Log, error) {
ret := _m.Called(ctx, query)
diff --git a/core/chains/evm/client/null_client.go b/core/chains/evm/client/null_client.go
index 3129bcff9b..5b1a4d7e1b 100644
--- a/core/chains/evm/client/null_client.go
+++ b/core/chains/evm/client/null_client.go
@@ -235,3 +235,7 @@ func (nc *NullClient) LatestFinalizedBlock(_ context.Context) (*evmtypes.Head, e
func (nc *NullClient) CheckTxValidity(_ context.Context, _ common.Address, _ common.Address, _ []byte) *SendError {
return nil
}
+
+func (nc *NullClient) FeeHistory(ctx context.Context, blockCount uint64, rewardPercentiles []float64) (feeHistory *ethereum.FeeHistory, err error) {
+ return nil, nil
+}
diff --git a/core/chains/evm/client/rpc_client.go b/core/chains/evm/client/rpc_client.go
index 07aa86fc45..0bc01d715f 100644
--- a/core/chains/evm/client/rpc_client.go
+++ b/core/chains/evm/client/rpc_client.go
@@ -103,6 +103,7 @@ type RPCClient interface {
SuggestGasTipCap(ctx context.Context) (t *big.Int, err error)
TransactionReceiptGeth(ctx context.Context, txHash common.Hash) (r *types.Receipt, err error)
GetInterceptedChainInfo() (latest, highestUserObservations commonclient.ChainInfo)
+ FeeHistory(ctx context.Context, blockCount uint64, rewardPercentiles []float64) (feeHistory *ethereum.FeeHistory, err error)
}
const rpcSubscriptionMethodNewHeads = "newHeads"
@@ -116,18 +117,20 @@ type rawclient struct {
type rpcClient struct {
rpcLog logger.SugaredLogger
name string
- id int32
+ id int
chainID *big.Int
tier commonclient.NodeTier
largePayloadRpcTimeout time.Duration
rpcTimeout time.Duration
finalizedBlockPollInterval time.Duration
+ newHeadsPollInterval time.Duration
chainType chaintype.ChainType
- ws rawclient
+ ws *rawclient
http *rawclient
- stateMu sync.RWMutex // protects state* fields
+ stateMu sync.RWMutex // protects state* fields
+ subsSliceMu sync.RWMutex // protects subscription slice
// Need to track subscriptions because closing the RPC does not (always?)
// close the underlying subscription
@@ -141,6 +144,7 @@ type rpcClient struct {
// stateMu since it can happen on state transitions as well as rpcClient Close.
chStopInFlight chan struct{}
+ chainInfoLock sync.RWMutex
// intercepted values seen by callers of the rpcClient excluding health check calls. Need to ensure MultiNode provides repeatable read guarantee
highestUserObservations commonclient.ChainInfo
// most recent chain info observed during current lifecycle (reseted on DisconnectAll)
@@ -150,13 +154,14 @@ type rpcClient struct {
// NewRPCCLient returns a new *rpcClient as commonclient.RPC
func NewRPCClient(
lggr logger.Logger,
- wsuri url.URL,
+ wsuri *url.URL,
httpuri *url.URL,
name string,
- id int32,
+ id int,
chainID *big.Int,
tier commonclient.NodeTier,
finalizedBlockPollInterval time.Duration,
+ newHeadsPollInterval time.Duration,
largePayloadRpcTimeout time.Duration,
rpcTimeout time.Duration,
chainType chaintype.ChainType,
@@ -170,8 +175,11 @@ func NewRPCClient(
r.id = id
r.chainID = chainID
r.tier = tier
- r.ws.uri = wsuri
r.finalizedBlockPollInterval = finalizedBlockPollInterval
+ r.newHeadsPollInterval = newHeadsPollInterval
+ if wsuri != nil {
+ r.ws = &rawclient{uri: *wsuri}
+ }
if httpuri != nil {
r.http = &rawclient{uri: *httpuri}
}
@@ -193,30 +201,33 @@ func (r *rpcClient) Dial(callerCtx context.Context) error {
ctx, cancel := r.makeQueryCtx(callerCtx, r.rpcTimeout)
defer cancel()
- promEVMPoolRPCNodeDials.WithLabelValues(r.chainID.String(), r.name).Inc()
- lggr := r.rpcLog.With("wsuri", r.ws.uri.Redacted())
- if r.http != nil {
- lggr = lggr.With("httpuri", r.http.uri.Redacted())
+ if r.ws == nil && r.http == nil {
+ return errors.New("cannot dial rpc client when both ws and http info are missing")
}
- lggr.Debugw("RPC dial: evmclient.Client#dial")
- wsrpc, err := rpc.DialWebsocket(ctx, r.ws.uri.String(), "")
- if err != nil {
- promEVMPoolRPCNodeDialsFailed.WithLabelValues(r.chainID.String(), r.name).Inc()
- return r.wrapRPCClientError(pkgerrors.Wrapf(err, "error while dialing websocket: %v", r.ws.uri.Redacted()))
- }
+ promEVMPoolRPCNodeDials.WithLabelValues(r.chainID.String(), r.name).Inc()
+ lggr := r.rpcLog
+ if r.ws != nil {
+ lggr = lggr.With("wsuri", r.ws.uri.Redacted())
+ wsrpc, err := rpc.DialWebsocket(ctx, r.ws.uri.String(), "")
+ if err != nil {
+ promEVMPoolRPCNodeDialsFailed.WithLabelValues(r.chainID.String(), r.name).Inc()
+ return r.wrapRPCClientError(pkgerrors.Wrapf(err, "error while dialing websocket: %v", r.ws.uri.Redacted()))
+ }
- r.ws.rpc = wsrpc
- r.ws.geth = ethclient.NewClient(wsrpc)
+ r.ws.rpc = wsrpc
+ r.ws.geth = ethclient.NewClient(wsrpc)
+ }
if r.http != nil {
+ lggr = lggr.With("httpuri", r.http.uri.Redacted())
if err := r.DialHTTP(); err != nil {
return err
}
}
+ lggr.Debugw("RPC dial: evmclient.Client#dial")
promEVMPoolRPCNodeDialsSuccess.WithLabelValues(r.chainID.String(), r.name).Inc()
-
return nil
}
@@ -225,7 +236,7 @@ func (r *rpcClient) Dial(callerCtx context.Context) error {
// It can only return error if the URL is malformed.
func (r *rpcClient) DialHTTP() error {
promEVMPoolRPCNodeDials.WithLabelValues(r.chainID.String(), r.name).Inc()
- lggr := r.rpcLog.With("httpuri", r.ws.uri.Redacted())
+ lggr := r.rpcLog.With("httpuri", r.http.uri.Redacted())
lggr.Debugw("RPC dial: evmclient.Client#dial")
var httprpc *rpc.Client
@@ -245,7 +256,7 @@ func (r *rpcClient) DialHTTP() error {
func (r *rpcClient) Close() {
defer func() {
- if r.ws.rpc != nil {
+ if r.ws != nil && r.ws.rpc != nil {
r.ws.rpc.Close()
}
}()
@@ -264,7 +275,10 @@ func (r *rpcClient) cancelInflightRequests() {
}
func (r *rpcClient) String() string {
- s := fmt.Sprintf("(%s)%s:%s", r.tier.String(), r.name, r.ws.uri.Redacted())
+ s := fmt.Sprintf("(%s)%s", r.tier.String(), r.name)
+ if r.ws != nil {
+ s = s + fmt.Sprintf(":%s", r.ws.uri.Redacted())
+ }
if r.http != nil {
s = s + fmt.Sprintf(":%s", r.http.uri.Redacted())
}
@@ -312,8 +326,8 @@ func (r *rpcClient) getRPCDomain() string {
// registerSub adds the sub to the rpcClient list
func (r *rpcClient) registerSub(sub ethereum.Subscription, stopInFLightCh chan struct{}) error {
- r.stateMu.Lock()
- defer r.stateMu.Unlock()
+ r.subsSliceMu.Lock()
+ defer r.subsSliceMu.Unlock()
// ensure that the `sub` belongs to current life cycle of the `rpcClient` and it should not be killed due to
// previous `DisconnectAll` call.
select {
@@ -330,13 +344,19 @@ func (r *rpcClient) registerSub(sub ethereum.Subscription, stopInFLightCh chan s
// DisconnectAll disconnects all clients connected to the rpcClient
func (r *rpcClient) DisconnectAll() {
r.stateMu.Lock()
- defer r.stateMu.Unlock()
- if r.ws.rpc != nil {
+ if r.ws != nil && r.ws.rpc != nil {
r.ws.rpc.Close()
}
r.cancelInflightRequests()
+ r.stateMu.Unlock()
+
+ r.subsSliceMu.Lock()
r.unsubscribeAll()
+ r.subsSliceMu.Unlock()
+
+ r.chainInfoLock.Lock()
r.latestChainInfo = commonclient.ChainInfo{}
+ r.chainInfoLock.Unlock()
}
// unsubscribeAll unsubscribes all subscriptions
@@ -485,6 +505,40 @@ func (r *rpcClient) SubscribeNewHead(ctx context.Context, channel chan<- *evmtyp
defer cancel()
args := []interface{}{"newHeads"}
lggr := r.newRqLggr().With("args", args)
+ if r.newHeadsPollInterval > 0 {
+ interval := r.newHeadsPollInterval
+ timeout := interval
+ poller, pollerCh := commonclient.NewPoller[*evmtypes.Head](interval, r.latestBlock, timeout, r.rpcLog)
+ if err = poller.Start(); err != nil {
+ return nil, err
+ }
+
+ // NOTE this is a temporary special treatment for SubscribeNewHead before we refactor head tracker to use SubscribeToHeads
+ // as we need to forward new head from the poller channel to the channel passed from caller.
+ go func() {
+ for head := range pollerCh {
+ select {
+ case channel <- head:
+ // forwarding new head to the channel passed from caller
+ case <-poller.Err():
+ // return as poller returns error
+ return
+ }
+ }
+ }()
+
+ err = r.registerSub(&poller, chStopInFlight)
+ if err != nil {
+ return nil, err
+ }
+
+ lggr.Debugf("Polling new heads over http")
+ return &poller, nil
+ }
+
+ if ws == nil {
+ return nil, errors.New("SubscribeNewHead is not allowed without ws url")
+ }
lggr.Debug("RPC call: evmclient.Client#EthSubscribe")
start := time.Now()
@@ -514,11 +568,32 @@ func (r *rpcClient) SubscribeNewHead(ctx context.Context, channel chan<- *evmtyp
func (r *rpcClient) SubscribeToHeads(ctx context.Context) (ch <-chan *evmtypes.Head, sub commontypes.Subscription, err error) {
ctx, cancel, chStopInFlight, ws, _ := r.acquireQueryCtx(ctx, r.rpcTimeout)
defer cancel()
-
args := []interface{}{rpcSubscriptionMethodNewHeads}
start := time.Now()
lggr := r.newRqLggr().With("args", args)
+ // if new head based on http polling is enabled, we will replace it for WS newHead subscription
+ if r.newHeadsPollInterval > 0 {
+ interval := r.newHeadsPollInterval
+ timeout := interval
+ poller, channel := commonclient.NewPoller[*evmtypes.Head](interval, r.latestBlock, timeout, r.rpcLog)
+ if err = poller.Start(); err != nil {
+ return nil, nil, err
+ }
+
+ err = r.registerSub(&poller, chStopInFlight)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ lggr.Debugf("Polling new heads over http")
+ return channel, &poller, nil
+ }
+
+ if ws == nil {
+ return nil, nil, errors.New("SubscribeNewHead is not allowed without ws url")
+ }
+
lggr.Debug("RPC call: evmclient.Client#EthSubscribe")
defer func() {
duration := time.Since(start)
@@ -546,7 +621,9 @@ func (r *rpcClient) SubscribeToHeads(ctx context.Context) (ch <-chan *evmtypes.H
return channel, forwarder, err
}
-func (r *rpcClient) SubscribeToFinalizedHeads(_ context.Context) (<-chan *evmtypes.Head, commontypes.Subscription, error) {
+func (r *rpcClient) SubscribeToFinalizedHeads(ctx context.Context) (<-chan *evmtypes.Head, commontypes.Subscription, error) {
+ _, cancel, chStopInFlight, _, _ := r.acquireQueryCtx(ctx, r.rpcTimeout)
+ defer cancel()
interval := r.finalizedBlockPollInterval
if interval == 0 {
return nil, nil, errors.New("FinalizedBlockPollInterval is 0")
@@ -556,6 +633,12 @@ func (r *rpcClient) SubscribeToFinalizedHeads(_ context.Context) (<-chan *evmtyp
if err := poller.Start(); err != nil {
return nil, nil, err
}
+
+ err := r.registerSub(&poller, chStopInFlight)
+ if err != nil {
+ return nil, nil, err
+ }
+
return channel, &poller, nil
}
@@ -596,6 +679,7 @@ func (r *rpcClient) TransactionReceiptGeth(ctx context.Context, txHash common.Ha
return
}
+
func (r *rpcClient) TransactionByHash(ctx context.Context, txHash common.Hash) (tx *types.Transaction, err error) {
ctx, cancel, ws, http := r.makeLiveQueryCtxAndSafeGetClients(ctx, r.rpcTimeout)
defer cancel()
@@ -690,6 +774,10 @@ func (r *rpcClient) LatestFinalizedBlock(ctx context.Context) (head *evmtypes.He
return
}
+func (r *rpcClient) latestBlock(ctx context.Context) (head *evmtypes.Head, err error) {
+ return r.BlockByNumber(ctx, nil)
+}
+
func (r *rpcClient) astarLatestFinalizedBlock(ctx context.Context, result interface{}) (err error) {
var hashResult string
err = r.CallContext(ctx, &hashResult, "chain_getFinalizedHead")
@@ -1116,6 +1204,29 @@ func (r *rpcClient) BalanceAt(ctx context.Context, account common.Address, block
return
}
+func (r *rpcClient) FeeHistory(ctx context.Context, blockCount uint64, rewardPercentiles []float64) (feeHistory *ethereum.FeeHistory, err error) {
+ ctx, cancel, ws, http := r.makeLiveQueryCtxAndSafeGetClients(ctx, r.rpcTimeout)
+ defer cancel()
+ lggr := r.newRqLggr().With("blockCount", blockCount, "rewardPercentiles", rewardPercentiles)
+
+ lggr.Debug("RPC call: evmclient.Client#FeeHistory")
+ start := time.Now()
+ if http != nil {
+ feeHistory, err = http.geth.FeeHistory(ctx, blockCount, nil, rewardPercentiles)
+ err = r.wrapHTTP(err)
+ } else {
+ feeHistory, err = ws.geth.FeeHistory(ctx, blockCount, nil, rewardPercentiles)
+ err = r.wrapWS(err)
+ }
+ duration := time.Since(start)
+
+ r.logResult(lggr, err, duration, r.getRPCDomain(), "FeeHistory",
+ "feeHistory", feeHistory,
+ )
+
+ return
+}
+
// CallArgs represents the data used to call the balance method of a contract.
// "To" is the address of the ERC contract. "Data" is the message sent
// to the contract. "From" is the sender address.
@@ -1189,6 +1300,9 @@ func (r *rpcClient) ClientVersion(ctx context.Context) (version string, err erro
func (r *rpcClient) SubscribeFilterLogs(ctx context.Context, q ethereum.FilterQuery, ch chan<- types.Log) (_ ethereum.Subscription, err error) {
ctx, cancel, chStopInFlight, ws, _ := r.acquireQueryCtx(ctx, r.rpcTimeout)
defer cancel()
+ if ws == nil {
+ return nil, errors.New("SubscribeFilterLogs is not allowed without ws url")
+ }
lggr := r.newRqLggr().With("q", q)
lggr.Debug("RPC call: evmclient.Client#SubscribeFilterLogs")
@@ -1293,18 +1407,21 @@ func (r *rpcClient) wrapHTTP(err error) error {
}
// makeLiveQueryCtxAndSafeGetClients wraps makeQueryCtx
-func (r *rpcClient) makeLiveQueryCtxAndSafeGetClients(parentCtx context.Context, timeout time.Duration) (ctx context.Context, cancel context.CancelFunc, ws rawclient, http *rawclient) {
+func (r *rpcClient) makeLiveQueryCtxAndSafeGetClients(parentCtx context.Context, timeout time.Duration) (ctx context.Context, cancel context.CancelFunc, ws *rawclient, http *rawclient) {
ctx, cancel, _, ws, http = r.acquireQueryCtx(parentCtx, timeout)
return
}
func (r *rpcClient) acquireQueryCtx(parentCtx context.Context, timeout time.Duration) (ctx context.Context, cancel context.CancelFunc,
- chStopInFlight chan struct{}, ws rawclient, http *rawclient) {
+ chStopInFlight chan struct{}, ws *rawclient, http *rawclient) {
// Need to wrap in mutex because state transition can cancel and replace the
// context
r.stateMu.RLock()
chStopInFlight = r.chStopInFlight
- ws = r.ws
+ if r.ws != nil {
+ cp := *r.ws
+ ws = &cp
+ }
if r.http != nil {
cp := *r.http
http = &cp
@@ -1379,8 +1496,8 @@ func (r *rpcClient) onNewHead(ctx context.Context, requestCh <-chan struct{}, he
return
}
- r.stateMu.Lock()
- defer r.stateMu.Unlock()
+ r.chainInfoLock.Lock()
+ defer r.chainInfoLock.Unlock()
if !commonclient.CtxIsHeathCheckRequest(ctx) {
r.highestUserObservations.BlockNumber = max(r.highestUserObservations.BlockNumber, head.Number)
r.highestUserObservations.TotalDifficulty = commonclient.MaxTotalDifficulty(r.highestUserObservations.TotalDifficulty, head.TotalDifficulty)
@@ -1398,8 +1515,8 @@ func (r *rpcClient) onNewFinalizedHead(ctx context.Context, requestCh <-chan str
if head == nil {
return
}
- r.stateMu.Lock()
- defer r.stateMu.Unlock()
+ r.chainInfoLock.Lock()
+ defer r.chainInfoLock.Unlock()
if !commonclient.CtxIsHeathCheckRequest(ctx) {
r.highestUserObservations.FinalizedBlockNumber = max(r.highestUserObservations.FinalizedBlockNumber, head.Number)
}
@@ -1412,8 +1529,8 @@ func (r *rpcClient) onNewFinalizedHead(ctx context.Context, requestCh <-chan str
}
func (r *rpcClient) GetInterceptedChainInfo() (latest, highestUserObservations commonclient.ChainInfo) {
- r.stateMu.RLock()
- defer r.stateMu.RUnlock()
+ r.chainInfoLock.RLock()
+ defer r.chainInfoLock.RUnlock()
return r.latestChainInfo, r.highestUserObservations
}
diff --git a/core/chains/evm/client/rpc_client_test.go b/core/chains/evm/client/rpc_client_test.go
index 1282188099..662c757ffb 100644
--- a/core/chains/evm/client/rpc_client_test.go
+++ b/core/chains/evm/client/rpc_client_test.go
@@ -7,6 +7,7 @@ import (
"fmt"
"math/big"
"net/url"
+ "sync"
"testing"
"time"
@@ -18,6 +19,8 @@ import (
"github.com/tidwall/gjson"
"go.uber.org/zap"
+ commontypes "github.com/smartcontractkit/chainlink/v2/common/types"
+
"github.com/smartcontractkit/chainlink-common/pkg/utils/tests"
"github.com/smartcontractkit/chainlink-common/pkg/logger"
@@ -56,11 +59,37 @@ func TestRPCClient_SubscribeNewHead(t *testing.T) {
}
return
}
+
+ checkClosedRPCClientShouldRemoveExistingSub := func(t tests.TestingT, ctx context.Context, sub commontypes.Subscription, rpcClient client.RPCClient) {
+ errCh := sub.Err()
+
+ // ensure sub exists
+ require.Equal(t, int32(1), rpcClient.SubscribersCount())
+ rpcClient.DisconnectAll()
+
+ // ensure sub is closed
+ select {
+ case <-errCh: // ok
+ default:
+ assert.Fail(t, "channel should be closed")
+ }
+
+ require.NoError(t, rpcClient.Dial(ctx))
+ require.Equal(t, int32(0), rpcClient.SubscribersCount())
+ }
+
+ t.Run("WS and HTTP URL cannot be both empty", func(t *testing.T) {
+ // ws is optional when LogBroadcaster is disabled, however SubscribeFilterLogs will return error if ws is missing
+ observedLggr, _ := logger.TestObserved(t, zap.DebugLevel)
+ rpcClient := client.NewRPCClient(observedLggr, nil, nil, "rpc", 1, chainId, commonclient.Primary, 0, 0, commonclient.QueryTimeout, commonclient.QueryTimeout, "")
+ require.Equal(t, errors.New("cannot dial rpc client when both ws and http info are missing"), rpcClient.Dial(ctx))
+ })
+
t.Run("Updates chain info on new blocks", func(t *testing.T) {
server := testutils.NewWSServer(t, chainId, serverCallBack)
wsURL := server.WSURL()
- rpc := client.NewRPCClient(lggr, *wsURL, nil, "rpc", 1, chainId, commonclient.Primary, 0, commonclient.QueryTimeout, commonclient.QueryTimeout, "")
+ rpc := client.NewRPCClient(lggr, wsURL, nil, "rpc", 1, chainId, commonclient.Primary, 0, 0, commonclient.QueryTimeout, commonclient.QueryTimeout, "")
defer rpc.Close()
require.NoError(t, rpc.Dial(ctx))
// set to default values
@@ -110,7 +139,7 @@ func TestRPCClient_SubscribeNewHead(t *testing.T) {
server := testutils.NewWSServer(t, chainId, serverCallBack)
wsURL := server.WSURL()
- rpc := client.NewRPCClient(lggr, *wsURL, nil, "rpc", 1, chainId, commonclient.Primary, 0, commonclient.QueryTimeout, commonclient.QueryTimeout, "")
+ rpc := client.NewRPCClient(lggr, wsURL, nil, "rpc", 1, chainId, commonclient.Primary, 0, 0, commonclient.QueryTimeout, commonclient.QueryTimeout, "")
defer rpc.Close()
require.NoError(t, rpc.Dial(ctx))
ch := make(chan *evmtypes.Head)
@@ -130,10 +159,80 @@ func TestRPCClient_SubscribeNewHead(t *testing.T) {
assert.Equal(t, int64(0), highestUserObservations.FinalizedBlockNumber)
assert.Equal(t, (*big.Int)(nil), highestUserObservations.TotalDifficulty)
})
+ t.Run("SubscribeToHeads with http polling enabled will update new heads", func(t *testing.T) {
+ type rpcServer struct {
+ Head *evmtypes.Head
+ URL *url.URL
+ }
+ createRPCServer := func() *rpcServer {
+ server := &rpcServer{}
+ server.Head = &evmtypes.Head{Number: 127}
+ server.URL = testutils.NewWSServer(t, chainId, func(method string, params gjson.Result) (resp testutils.JSONRPCResponse) {
+ assert.Equal(t, "eth_getBlockByNumber", method)
+ if assert.True(t, params.IsArray()) && assert.Equal(t, "latest", params.Array()[0].String()) {
+ head := server.Head
+ jsonHead, err := json.Marshal(head)
+ if err != nil {
+ panic(fmt.Errorf("failed to marshal head: %w", err))
+ }
+ resp.Result = string(jsonHead)
+ }
+
+ return
+ }).WSURL()
+ return server
+ }
+
+ server := createRPCServer()
+ rpc := client.NewRPCClient(lggr, server.URL, nil, "rpc", 1, chainId, commonclient.Primary, 0, tests.TestInterval, commonclient.QueryTimeout, commonclient.QueryTimeout, "")
+ defer rpc.Close()
+ require.NoError(t, rpc.Dial(ctx))
+ latest, highestUserObservations := rpc.GetInterceptedChainInfo()
+ // latest chain info hasn't been initialized
+ assert.Equal(t, int64(0), latest.BlockNumber)
+ assert.Equal(t, int64(0), highestUserObservations.BlockNumber)
+
+ headCh, sub, err := rpc.SubscribeToHeads(commonclient.CtxAddHealthCheckFlag(tests.Context(t)))
+ require.NoError(t, err)
+ defer sub.Unsubscribe()
+
+ head := <-headCh
+ assert.Equal(t, server.Head.Number, head.BlockNumber())
+ // the http polling subscription should update the head block
+ latest, highestUserObservations = rpc.GetInterceptedChainInfo()
+ assert.Equal(t, server.Head.Number, latest.BlockNumber)
+ assert.Equal(t, server.Head.Number, highestUserObservations.BlockNumber)
+ })
+ t.Run("Concurrent Unsubscribe and onNewHead calls do not lead to a deadlock", func(t *testing.T) {
+ const numberOfAttempts = 1000 // need a large number to increase the odds of reproducing the issue
+ server := testutils.NewWSServer(t, chainId, serverCallBack)
+ wsURL := server.WSURL()
+
+ rpc := client.NewRPCClient(lggr, wsURL, nil, "rpc", 1, chainId, commonclient.Primary, 0, 0, commonclient.QueryTimeout, commonclient.QueryTimeout, "")
+ defer rpc.Close()
+ require.NoError(t, rpc.Dial(ctx))
+ var wg sync.WaitGroup
+ for i := 0; i < numberOfAttempts; i++ {
+ ch := make(chan *evmtypes.Head)
+ sub, err := rpc.SubscribeNewHead(tests.Context(t), ch)
+ require.NoError(t, err)
+ wg.Add(2)
+ go func() {
+ server.MustWriteBinaryMessageSync(t, makeNewHeadWSMessage(&evmtypes.Head{Number: 256, TotalDifficulty: big.NewInt(1000)}))
+ wg.Done()
+ }()
+ go func() {
+ rpc.UnsubscribeAllExceptAliveLoop()
+ sub.Unsubscribe()
+ wg.Done()
+ }()
+ wg.Wait()
+ }
+ })
t.Run("Block's chain ID matched configured", func(t *testing.T) {
server := testutils.NewWSServer(t, chainId, serverCallBack)
wsURL := server.WSURL()
- rpc := client.NewRPCClient(lggr, *wsURL, nil, "rpc", 1, chainId, commonclient.Primary, 0, commonclient.QueryTimeout, commonclient.QueryTimeout, "")
+ rpc := client.NewRPCClient(lggr, wsURL, nil, "rpc", 1, chainId, commonclient.Primary, 0, 0, commonclient.QueryTimeout, commonclient.QueryTimeout, "")
defer rpc.Close()
require.NoError(t, rpc.Dial(ctx))
ch := make(chan *evmtypes.Head)
@@ -150,17 +249,79 @@ func TestRPCClient_SubscribeNewHead(t *testing.T) {
})
wsURL := server.WSURL()
observedLggr, observed := logger.TestObserved(t, zap.DebugLevel)
- rpc := client.NewRPCClient(observedLggr, *wsURL, nil, "rpc", 1, chainId, commonclient.Primary, 0, commonclient.QueryTimeout, commonclient.QueryTimeout, "")
+ rpc := client.NewRPCClient(observedLggr, wsURL, nil, "rpc", 1, chainId, commonclient.Primary, 0, 0, commonclient.QueryTimeout, commonclient.QueryTimeout, "")
require.NoError(t, rpc.Dial(ctx))
server.Close()
_, err := rpc.SubscribeNewHead(ctx, make(chan *evmtypes.Head))
require.ErrorContains(t, err, "RPCClient returned error (rpc)")
tests.AssertLogEventually(t, observed, "evmclient.Client#EthSubscribe RPC call failure")
})
+ t.Run("Closed rpc client should remove existing SubscribeNewHead subscription with WS", func(t *testing.T) {
+ server := testutils.NewWSServer(t, chainId, serverCallBack)
+ wsURL := server.WSURL()
+
+ rpc := client.NewRPCClient(lggr, wsURL, nil, "rpc", 1, chainId, commonclient.Primary, 0, 0, commonclient.QueryTimeout, commonclient.QueryTimeout, "")
+ defer rpc.Close()
+ require.NoError(t, rpc.Dial(ctx))
+
+ ch := make(chan *evmtypes.Head)
+ sub, err := rpc.SubscribeNewHead(tests.Context(t), ch)
+ require.NoError(t, err)
+ checkClosedRPCClientShouldRemoveExistingSub(t, ctx, sub, rpc)
+ })
+ t.Run("Closed rpc client should remove existing SubscribeNewHead subscription with HTTP polling", func(t *testing.T) {
+ server := testutils.NewWSServer(t, chainId, serverCallBack)
+ wsURL := server.WSURL()
+
+ rpc := client.NewRPCClient(lggr, wsURL, &url.URL{}, "rpc", 1, chainId, commonclient.Primary, 0, 1, commonclient.QueryTimeout, commonclient.QueryTimeout, "")
+ defer rpc.Close()
+ require.NoError(t, rpc.Dial(ctx))
+
+ ch := make(chan *evmtypes.Head)
+ sub, err := rpc.SubscribeNewHead(tests.Context(t), ch)
+ require.NoError(t, err)
+ checkClosedRPCClientShouldRemoveExistingSub(t, ctx, sub, rpc)
+ })
+ t.Run("Closed rpc client should remove existing SubscribeToHeads subscription with WS", func(t *testing.T) {
+ server := testutils.NewWSServer(t, chainId, serverCallBack)
+ wsURL := server.WSURL()
+
+ rpc := client.NewRPCClient(lggr, wsURL, nil, "rpc", 1, chainId, commonclient.Primary, 0, 0, commonclient.QueryTimeout, commonclient.QueryTimeout, "")
+ defer rpc.Close()
+ require.NoError(t, rpc.Dial(ctx))
+
+ _, sub, err := rpc.SubscribeToHeads(tests.Context(t))
+ require.NoError(t, err)
+ checkClosedRPCClientShouldRemoveExistingSub(t, ctx, sub, rpc)
+ })
+ t.Run("Closed rpc client should remove existing SubscribeToHeads subscription with HTTP polling", func(t *testing.T) {
+ server := testutils.NewWSServer(t, chainId, serverCallBack)
+ wsURL := server.WSURL()
+
+ rpc := client.NewRPCClient(lggr, wsURL, &url.URL{}, "rpc", 1, chainId, commonclient.Primary, 0, 1, commonclient.QueryTimeout, commonclient.QueryTimeout, "")
+ defer rpc.Close()
+ require.NoError(t, rpc.Dial(ctx))
+
+ _, sub, err := rpc.SubscribeToHeads(tests.Context(t))
+ require.NoError(t, err)
+ checkClosedRPCClientShouldRemoveExistingSub(t, ctx, sub, rpc)
+ })
+ t.Run("Closed rpc client should remove existing SubscribeToFinalizedHeads subscription", func(t *testing.T) {
+ server := testutils.NewWSServer(t, chainId, serverCallBack)
+ wsURL := server.WSURL()
+
+ rpc := client.NewRPCClient(lggr, wsURL, &url.URL{}, "rpc", 1, chainId, commonclient.Primary, 1, 0, commonclient.QueryTimeout, commonclient.QueryTimeout, "")
+ defer rpc.Close()
+ require.NoError(t, rpc.Dial(ctx))
+
+ _, sub, err := rpc.SubscribeToFinalizedHeads(tests.Context(t))
+ require.NoError(t, err)
+ checkClosedRPCClientShouldRemoveExistingSub(t, ctx, sub, rpc)
+ })
t.Run("Subscription error is properly wrapper", func(t *testing.T) {
server := testutils.NewWSServer(t, chainId, serverCallBack)
wsURL := server.WSURL()
- rpc := client.NewRPCClient(lggr, *wsURL, nil, "rpc", 1, chainId, commonclient.Primary, 0, commonclient.QueryTimeout, commonclient.QueryTimeout, "")
+ rpc := client.NewRPCClient(lggr, wsURL, nil, "rpc", 1, chainId, commonclient.Primary, 0, 0, commonclient.QueryTimeout, commonclient.QueryTimeout, "")
defer rpc.Close()
require.NoError(t, rpc.Dial(ctx))
sub, err := rpc.SubscribeNewHead(ctx, make(chan *evmtypes.Head))
@@ -182,13 +343,22 @@ func TestRPCClient_SubscribeFilterLogs(t *testing.T) {
lggr := logger.Test(t)
ctx, cancel := context.WithTimeout(tests.Context(t), tests.WaitTimeout(t))
defer cancel()
+ t.Run("Failed SubscribeFilterLogs when WSURL is empty", func(t *testing.T) {
+ // ws is optional when LogBroadcaster is disabled, however SubscribeFilterLogs will return error if ws is missing
+ observedLggr, _ := logger.TestObserved(t, zap.DebugLevel)
+ rpcClient := client.NewRPCClient(observedLggr, nil, &url.URL{}, "rpc", 1, chainId, commonclient.Primary, 0, 0, commonclient.QueryTimeout, commonclient.QueryTimeout, "")
+ require.Nil(t, rpcClient.Dial(ctx))
+
+ _, err := rpcClient.SubscribeFilterLogs(ctx, ethereum.FilterQuery{}, make(chan types.Log))
+ require.Equal(t, errors.New("SubscribeFilterLogs is not allowed without ws url"), err)
+ })
t.Run("Failed SubscribeFilterLogs logs and returns proper error", func(t *testing.T) {
server := testutils.NewWSServer(t, chainId, func(reqMethod string, reqParams gjson.Result) (resp testutils.JSONRPCResponse) {
return resp
})
wsURL := server.WSURL()
observedLggr, observed := logger.TestObserved(t, zap.DebugLevel)
- rpc := client.NewRPCClient(observedLggr, *wsURL, nil, "rpc", 1, chainId, commonclient.Primary, 0, commonclient.QueryTimeout, commonclient.QueryTimeout, "")
+ rpc := client.NewRPCClient(observedLggr, wsURL, nil, "rpc", 1, chainId, commonclient.Primary, 0, 0, commonclient.QueryTimeout, commonclient.QueryTimeout, "")
require.NoError(t, rpc.Dial(ctx))
server.Close()
_, err := rpc.SubscribeFilterLogs(ctx, ethereum.FilterQuery{}, make(chan types.Log))
@@ -205,7 +375,7 @@ func TestRPCClient_SubscribeFilterLogs(t *testing.T) {
return resp
})
wsURL := server.WSURL()
- rpc := client.NewRPCClient(lggr, *wsURL, nil, "rpc", 1, chainId, commonclient.Primary, 0, commonclient.QueryTimeout, commonclient.QueryTimeout, "")
+ rpc := client.NewRPCClient(lggr, wsURL, nil, "rpc", 1, chainId, commonclient.Primary, 0, 0, commonclient.QueryTimeout, commonclient.QueryTimeout, "")
defer rpc.Close()
require.NoError(t, rpc.Dial(ctx))
sub, err := rpc.SubscribeFilterLogs(ctx, ethereum.FilterQuery{}, make(chan types.Log))
@@ -254,7 +424,7 @@ func TestRPCClient_LatestFinalizedBlock(t *testing.T) {
}
server := createRPCServer()
- rpc := client.NewRPCClient(lggr, *server.URL, nil, "rpc", 1, chainId, commonclient.Primary, 0, commonclient.QueryTimeout, commonclient.QueryTimeout, "")
+ rpc := client.NewRPCClient(lggr, server.URL, nil, "rpc", 1, chainId, commonclient.Primary, 0, 0, commonclient.QueryTimeout, commonclient.QueryTimeout, "")
require.NoError(t, rpc.Dial(ctx))
defer rpc.Close()
server.Head = &evmtypes.Head{Number: 128}
@@ -364,7 +534,7 @@ func TestRpcClientLargePayloadTimeout(t *testing.T) {
// use something unreasonably large for RPC timeout to ensure that we use largePayloadRPCTimeout
const rpcTimeout = time.Hour
const largePayloadRPCTimeout = tests.TestInterval
- rpc := client.NewRPCClient(logger.Test(t), *rpcURL, nil, "rpc", 1, chainId, commonclient.Primary, 0, largePayloadRPCTimeout, rpcTimeout, "")
+ rpc := client.NewRPCClient(logger.Test(t), rpcURL, nil, "rpc", 1, chainId, commonclient.Primary, 0, 0, largePayloadRPCTimeout, rpcTimeout, "")
require.NoError(t, rpc.Dial(ctx))
defer rpc.Close()
err := testCase.Fn(ctx, rpc)
@@ -404,7 +574,7 @@ func TestAstarCustomFinality(t *testing.T) {
const expectedFinalizedBlockNumber = int64(4)
const expectedFinalizedBlockHash = "0x7441e97acf83f555e0deefef86db636bc8a37eb84747603412884e4df4d22804"
- rpcClient := client.NewRPCClient(logger.Test(t), *wsURL, nil, "rpc", 1, chainId, commonclient.Primary, 0, commonclient.QueryTimeout, commonclient.QueryTimeout, chaintype.ChainAstar)
+ rpcClient := client.NewRPCClient(logger.Test(t), wsURL, nil, "rpc", 1, chainId, commonclient.Primary, 0, 0, commonclient.QueryTimeout, commonclient.QueryTimeout, chaintype.ChainAstar)
defer rpcClient.Close()
err := rpcClient.Dial(tests.Context(t))
require.NoError(t, err)
diff --git a/core/chains/evm/client/simulated_backend_client.go b/core/chains/evm/client/simulated_backend_client.go
index 7dfd39f444..3ec1bff577 100644
--- a/core/chains/evm/client/simulated_backend_client.go
+++ b/core/chains/evm/client/simulated_backend_client.go
@@ -156,6 +156,10 @@ func (c *SimulatedBackendClient) LINKBalance(ctx context.Context, address common
panic("not implemented")
}
+func (c *SimulatedBackendClient) FeeHistory(ctx context.Context, blockCount uint64, rewardPercentiles []float64) (feeHistory *ethereum.FeeHistory, err error) {
+ panic("not implemented")
+}
+
// TransactionReceipt returns the transaction receipt for the given transaction hash.
func (c *SimulatedBackendClient) TransactionReceipt(ctx context.Context, receipt common.Hash) (*types.Receipt, error) {
return c.b.TransactionReceipt(ctx, receipt)
diff --git a/core/chains/evm/config/chain_scoped.go b/core/chains/evm/config/chain_scoped.go
index b9b19cdc2c..de89272b5e 100644
--- a/core/chains/evm/config/chain_scoped.go
+++ b/core/chains/evm/config/chain_scoped.go
@@ -176,6 +176,10 @@ func (e *EVMConfig) OperatorFactoryAddress() string {
return e.C.OperatorFactoryAddress.String()
}
+func (e *EVMConfig) LogBroadcasterEnabled() bool {
+ return e.C.LogBroadcasterEnabled == nil || *e.C.LogBroadcasterEnabled
+}
+
func (e *EVMConfig) LogPrunePageSize() uint32 {
return *e.C.LogPrunePageSize
}
diff --git a/core/chains/evm/config/chain_scoped_gas_estimator.go b/core/chains/evm/config/chain_scoped_gas_estimator.go
index 689d5e38b8..54c7c36063 100644
--- a/core/chains/evm/config/chain_scoped_gas_estimator.go
+++ b/core/chains/evm/config/chain_scoped_gas_estimator.go
@@ -1,6 +1,8 @@
package config
import (
+ "time"
+
gethcommon "github.com/ethereum/go-ethereum/common"
"github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets"
@@ -36,6 +38,10 @@ func (g *gasEstimatorConfig) BlockHistory() BlockHistory {
return &blockHistoryConfig{c: g.c.BlockHistory, blockDelay: g.blockDelay, bumpThreshold: g.c.BumpThreshold}
}
+func (g *gasEstimatorConfig) FeeHistory() FeeHistory {
+ return &feeHistoryConfig{c: g.c.FeeHistory}
+}
+
func (g *gasEstimatorConfig) EIP1559DynamicFees() bool {
return *g.c.EIP1559DynamicFees
}
@@ -108,6 +114,10 @@ func (g *gasEstimatorConfig) LimitJobType() LimitJobType {
return &limitJobTypeConfig{c: g.c.LimitJobType}
}
+func (g *gasEstimatorConfig) EstimateLimit() bool {
+ return *g.c.EstimateLimit
+}
+
type limitJobTypeConfig struct {
c toml.GasLimitJobType
}
@@ -172,3 +182,11 @@ func (b *blockHistoryConfig) TransactionPercentile() uint16 {
func (b *blockHistoryConfig) BlockDelay() uint16 {
return *b.blockDelay
}
+
+type feeHistoryConfig struct {
+ c toml.FeeHistoryEstimator
+}
+
+func (u *feeHistoryConfig) CacheTimeout() time.Duration {
+ return u.c.CacheTimeout.Duration()
+}
diff --git a/core/chains/evm/config/chain_scoped_node_pool.go b/core/chains/evm/config/chain_scoped_node_pool.go
index a497436648..4b1d02d148 100644
--- a/core/chains/evm/config/chain_scoped_node_pool.go
+++ b/core/chains/evm/config/chain_scoped_node_pool.go
@@ -38,6 +38,10 @@ func (n *NodePoolConfig) FinalizedBlockPollInterval() time.Duration {
return n.C.FinalizedBlockPollInterval.Duration()
}
+func (n *NodePoolConfig) NewHeadsPollInterval() time.Duration {
+ return n.C.NewHeadsPollInterval.Duration()
+}
+
func (n *NodePoolConfig) Errors() ClientErrors { return &clientErrorsConfig{c: n.C.Errors} }
func (n *NodePoolConfig) EnforceRepeatableRead() bool {
diff --git a/core/chains/evm/config/chaintype/chaintype.go b/core/chains/evm/config/chaintype/chaintype.go
index 07ea620624..35dd214b1f 100644
--- a/core/chains/evm/config/chaintype/chaintype.go
+++ b/core/chains/evm/config/chaintype/chaintype.go
@@ -14,6 +14,7 @@ const (
ChainGnosis ChainType = "gnosis"
ChainHedera ChainType = "hedera"
ChainKroma ChainType = "kroma"
+ ChainMantle ChainType = "mantle"
ChainMetis ChainType = "metis"
ChainOptimismBedrock ChainType = "optimismBedrock"
ChainScroll ChainType = "scroll"
@@ -37,7 +38,7 @@ func (c ChainType) IsL2() bool {
func (c ChainType) IsValid() bool {
switch c {
- case "", ChainArbitrum, ChainAstar, ChainCelo, ChainGnosis, ChainHedera, ChainKroma, ChainMetis, ChainOptimismBedrock, ChainScroll, ChainWeMix, ChainXLayer, ChainZkEvm, ChainZkSync:
+ case "", ChainArbitrum, ChainAstar, ChainCelo, ChainGnosis, ChainHedera, ChainKroma, ChainMantle, ChainMetis, ChainOptimismBedrock, ChainScroll, ChainWeMix, ChainXLayer, ChainZkEvm, ChainZkSync:
return true
}
return false
@@ -57,6 +58,8 @@ func ChainTypeFromSlug(slug string) ChainType {
return ChainHedera
case "kroma":
return ChainKroma
+ case "mantle":
+ return ChainMantle
case "metis":
return ChainMetis
case "optimismBedrock":
@@ -129,6 +132,7 @@ var ErrInvalidChainType = fmt.Errorf("must be one of %s or omitted", strings.Joi
string(ChainGnosis),
string(ChainHedera),
string(ChainKroma),
+ string(ChainMantle),
string(ChainMetis),
string(ChainOptimismBedrock),
string(ChainScroll),
diff --git a/core/chains/evm/config/config.go b/core/chains/evm/config/config.go
index 3ccdfeea8b..9ce28eb55a 100644
--- a/core/chains/evm/config/config.go
+++ b/core/chains/evm/config/config.go
@@ -43,6 +43,7 @@ type EVM interface {
MinIncomingConfirmations() uint32
NonceAutoSync() bool
OperatorFactoryAddress() string
+ LogBroadcasterEnabled() bool
RPCDefaultBatchSize() uint32
NodeNoNewHeadsThreshold() time.Duration
FinalizedBlockOffset() uint32
@@ -117,6 +118,7 @@ type AutoPurgeConfig interface {
type GasEstimator interface {
BlockHistory() BlockHistory
+ FeeHistory() FeeHistory
LimitJobType() LimitJobType
EIP1559DynamicFees() bool
@@ -136,6 +138,7 @@ type GasEstimator interface {
PriceMin() *assets.Wei
Mode() string
PriceMaxKey(gethcommon.Address) *assets.Wei
+ EstimateLimit() bool
}
type LimitJobType interface {
@@ -157,6 +160,10 @@ type BlockHistory interface {
TransactionPercentile() uint16
}
+type FeeHistory interface {
+ CacheTimeout() time.Duration
+}
+
type Workflow interface {
FromAddress() *types.EIP55Address
ForwarderAddress() *types.EIP55Address
@@ -173,6 +180,7 @@ type NodePool interface {
Errors() ClientErrors
EnforceRepeatableRead() bool
DeathDeclarationDelay() time.Duration
+ NewHeadsPollInterval() time.Duration
}
// TODO BCF-2509 does the chainscopedconfig really need the entire app config?
diff --git a/core/chains/evm/config/config_test.go b/core/chains/evm/config/config_test.go
index 617a1605d8..dd79f2b92f 100644
--- a/core/chains/evm/config/config_test.go
+++ b/core/chains/evm/config/config_test.go
@@ -205,6 +205,25 @@ func TestChainScopedConfig(t *testing.T) {
assert.Equal(t, val.String(), cfg3.EVM().OperatorFactoryAddress())
})
})
+
+ t.Run("LogBroadcasterEnabled", func(t *testing.T) {
+ t.Run("turn on LogBroadcasterEnabled by default", func(t *testing.T) {
+ assert.Equal(t, true, cfg.EVM().LogBroadcasterEnabled())
+ })
+
+ t.Run("verify LogBroadcasterEnabled is set correctly", func(t *testing.T) {
+ val := false
+ cfg3 := testutils.NewTestChainScopedConfig(t, func(c *toml.EVMConfig) {
+ c.LogBroadcasterEnabled = ptr(val)
+ })
+
+ assert.Equal(t, false, cfg3.EVM().LogBroadcasterEnabled())
+ })
+
+ t.Run("use Noop logBroadcaster when LogBroadcaster is disabled", func(t *testing.T) {
+
+ })
+ })
}
func TestChainScopedConfig_BlockHistory(t *testing.T) {
@@ -220,6 +239,13 @@ func TestChainScopedConfig_BlockHistory(t *testing.T) {
assert.Equal(t, uint16(1), bh.BlockDelay())
assert.Equal(t, uint16(4), bh.EIP1559FeeCapBufferBlocks())
}
+func TestChainScopedConfig_FeeHistory(t *testing.T) {
+ t.Parallel()
+ cfg := testutils.NewTestChainScopedConfig(t, nil)
+
+ u := cfg.EVM().GasEstimator().FeeHistory()
+ assert.Equal(t, 10*time.Second, u.CacheTimeout())
+}
func TestChainScopedConfig_GasEstimator(t *testing.T) {
t.Parallel()
@@ -243,6 +269,7 @@ func TestChainScopedConfig_GasEstimator(t *testing.T) {
assert.Equal(t, assets.GWei(100), ge.FeeCapDefault())
assert.Equal(t, assets.NewWeiI(1), ge.TipCapDefault())
assert.Equal(t, assets.NewWeiI(1), ge.TipCapMin())
+ assert.Equal(t, false, ge.EstimateLimit())
}
func TestChainScopedConfig_BSCDefaults(t *testing.T) {
diff --git a/core/chains/evm/config/mocks/gas_estimator.go b/core/chains/evm/config/mocks/gas_estimator.go
index b8e813e806..70b9c18d0b 100644
--- a/core/chains/evm/config/mocks/gas_estimator.go
+++ b/core/chains/evm/config/mocks/gas_estimator.go
@@ -298,6 +298,51 @@ func (_c *GasEstimator_EIP1559DynamicFees_Call) RunAndReturn(run func() bool) *G
return _c
}
+// EstimateLimit provides a mock function with given fields:
+func (_m *GasEstimator) EstimateLimit() bool {
+ ret := _m.Called()
+
+ if len(ret) == 0 {
+ panic("no return value specified for EstimateLimit")
+ }
+
+ var r0 bool
+ if rf, ok := ret.Get(0).(func() bool); ok {
+ r0 = rf()
+ } else {
+ r0 = ret.Get(0).(bool)
+ }
+
+ return r0
+}
+
+// GasEstimator_EstimateLimit_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'EstimateLimit'
+type GasEstimator_EstimateLimit_Call struct {
+ *mock.Call
+}
+
+// EstimateLimit is a helper method to define mock.On call
+func (_e *GasEstimator_Expecter) EstimateLimit() *GasEstimator_EstimateLimit_Call {
+ return &GasEstimator_EstimateLimit_Call{Call: _e.mock.On("EstimateLimit")}
+}
+
+func (_c *GasEstimator_EstimateLimit_Call) Run(run func()) *GasEstimator_EstimateLimit_Call {
+ _c.Call.Run(func(args mock.Arguments) {
+ run()
+ })
+ return _c
+}
+
+func (_c *GasEstimator_EstimateLimit_Call) Return(_a0 bool) *GasEstimator_EstimateLimit_Call {
+ _c.Call.Return(_a0)
+ return _c
+}
+
+func (_c *GasEstimator_EstimateLimit_Call) RunAndReturn(run func() bool) *GasEstimator_EstimateLimit_Call {
+ _c.Call.Return(run)
+ return _c
+}
+
// FeeCapDefault provides a mock function with given fields:
func (_m *GasEstimator) FeeCapDefault() *assets.Wei {
ret := _m.Called()
@@ -345,6 +390,53 @@ func (_c *GasEstimator_FeeCapDefault_Call) RunAndReturn(run func() *assets.Wei)
return _c
}
+// FeeHistory provides a mock function with given fields:
+func (_m *GasEstimator) FeeHistory() config.FeeHistory {
+ ret := _m.Called()
+
+ if len(ret) == 0 {
+ panic("no return value specified for FeeHistory")
+ }
+
+ var r0 config.FeeHistory
+ if rf, ok := ret.Get(0).(func() config.FeeHistory); ok {
+ r0 = rf()
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(config.FeeHistory)
+ }
+ }
+
+ return r0
+}
+
+// GasEstimator_FeeHistory_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FeeHistory'
+type GasEstimator_FeeHistory_Call struct {
+ *mock.Call
+}
+
+// FeeHistory is a helper method to define mock.On call
+func (_e *GasEstimator_Expecter) FeeHistory() *GasEstimator_FeeHistory_Call {
+ return &GasEstimator_FeeHistory_Call{Call: _e.mock.On("FeeHistory")}
+}
+
+func (_c *GasEstimator_FeeHistory_Call) Run(run func()) *GasEstimator_FeeHistory_Call {
+ _c.Call.Run(func(args mock.Arguments) {
+ run()
+ })
+ return _c
+}
+
+func (_c *GasEstimator_FeeHistory_Call) Return(_a0 config.FeeHistory) *GasEstimator_FeeHistory_Call {
+ _c.Call.Return(_a0)
+ return _c
+}
+
+func (_c *GasEstimator_FeeHistory_Call) RunAndReturn(run func() config.FeeHistory) *GasEstimator_FeeHistory_Call {
+ _c.Call.Return(run)
+ return _c
+}
+
// LimitDefault provides a mock function with given fields:
func (_m *GasEstimator) LimitDefault() uint64 {
ret := _m.Called()
diff --git a/core/chains/evm/config/toml/config.go b/core/chains/evm/config/toml/config.go
index ac7841ac49..1a3f911a8e 100644
--- a/core/chains/evm/config/toml/config.go
+++ b/core/chains/evm/config/toml/config.go
@@ -23,7 +23,9 @@ import (
"github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils/big"
)
-var ErrNotFound = errors.New("not found")
+var (
+ ErrNotFound = errors.New("not found")
+)
type HasEVMConfigs interface {
EVMConfigs() EVMConfigs
@@ -311,16 +313,38 @@ func (c *EVMConfig) ValidateConfig() (err error) {
err = multierr.Append(err, commonconfig.ErrMissing{Name: "Nodes", Msg: "must have at least one node"})
} else {
var hasPrimary bool
- for _, n := range c.Nodes {
+ var logBroadcasterEnabled bool
+ var newHeadsPollingInterval commonconfig.Duration
+ if c.LogBroadcasterEnabled != nil {
+ logBroadcasterEnabled = *c.LogBroadcasterEnabled
+ }
+
+ if c.NodePool.NewHeadsPollInterval != nil {
+ newHeadsPollingInterval = *c.NodePool.NewHeadsPollInterval
+ }
+
+ for i, n := range c.Nodes {
if n.SendOnly != nil && *n.SendOnly {
continue
}
+
hasPrimary = true
- break
+
+ // if the node is a primary node, then the WS URL is required when
+ // 1. LogBroadcaster is enabled
+ // 2. The http polling is disabled (newHeadsPollingInterval == 0)
+ if n.WSURL == nil || n.WSURL.IsZero() {
+ if logBroadcasterEnabled {
+ err = multierr.Append(err, commonconfig.ErrMissing{Name: "Nodes", Msg: fmt.Sprintf("%vth node (primary) must have a valid WSURL when LogBroadcaster is enabled", i)})
+ } else if newHeadsPollingInterval.Duration() == 0 {
+ err = multierr.Append(err, commonconfig.ErrMissing{Name: "Nodes", Msg: fmt.Sprintf("%vth node (primary) must have a valid WSURL when http polling is disabled", i)})
+ }
+ }
}
+
if !hasPrimary {
err = multierr.Append(err, commonconfig.ErrMissing{Name: "Nodes",
- Msg: "must have at least one primary node with WSURL"})
+ Msg: "must have at least one primary node"})
}
}
@@ -356,6 +380,7 @@ type Chain struct {
NonceAutoSync *bool
NoNewHeadsThreshold *commonconfig.Duration
OperatorFactoryAddress *types.EIP55Address
+ LogBroadcasterEnabled *bool
RPCDefaultBatchSize *uint32
RPCBlockQueryDelay *uint16
FinalizedBlockOffset *uint32
@@ -554,6 +579,7 @@ type GasEstimator struct {
LimitMultiplier *decimal.Decimal
LimitTransfer *uint64
LimitJobType GasLimitJobType `toml:",omitempty"`
+ EstimateLimit *bool
BumpMin *assets.Wei
BumpPercent *uint16
@@ -567,6 +593,7 @@ type GasEstimator struct {
TipCapMin *assets.Wei
BlockHistory BlockHistoryEstimator `toml:",omitempty"`
+ FeeHistory FeeHistoryEstimator `toml:",omitempty"`
}
func (e *GasEstimator) ValidateConfig() (err error) {
@@ -641,6 +668,9 @@ func (e *GasEstimator) setFrom(f *GasEstimator) {
if v := f.LimitTransfer; v != nil {
e.LimitTransfer = v
}
+ if v := f.EstimateLimit; v != nil {
+ e.EstimateLimit = v
+ }
if v := f.PriceDefault; v != nil {
e.PriceDefault = v
}
@@ -658,6 +688,7 @@ func (e *GasEstimator) setFrom(f *GasEstimator) {
}
e.LimitJobType.setFrom(&f.LimitJobType)
e.BlockHistory.setFrom(&f.BlockHistory)
+ e.FeeHistory.setFrom(&f.FeeHistory)
}
type GasLimitJobType struct {
@@ -720,6 +751,16 @@ func (e *BlockHistoryEstimator) setFrom(f *BlockHistoryEstimator) {
}
}
+type FeeHistoryEstimator struct {
+ CacheTimeout *commonconfig.Duration
+}
+
+func (u *FeeHistoryEstimator) setFrom(f *FeeHistoryEstimator) {
+ if v := f.CacheTimeout; v != nil {
+ u.CacheTimeout = v
+ }
+}
+
type KeySpecificConfig []KeySpecific
func (ks KeySpecificConfig) ValidateConfig() (err error) {
@@ -859,6 +900,7 @@ type NodePool struct {
Errors ClientErrors `toml:",omitempty"`
EnforceRepeatableRead *bool
DeathDeclarationDelay *commonconfig.Duration
+ NewHeadsPollInterval *commonconfig.Duration
}
func (p *NodePool) setFrom(f *NodePool) {
@@ -891,6 +933,11 @@ func (p *NodePool) setFrom(f *NodePool) {
if v := f.DeathDeclarationDelay; v != nil {
p.DeathDeclarationDelay = v
}
+
+ if v := f.NewHeadsPollInterval; v != nil {
+ p.NewHeadsPollInterval = v
+ }
+
p.Errors.setFrom(&f.Errors)
}
@@ -939,19 +986,8 @@ func (n *Node) ValidateConfig() (err error) {
err = multierr.Append(err, commonconfig.ErrEmpty{Name: "Name", Msg: "required for all nodes"})
}
- var sendOnly bool
- if n.SendOnly != nil {
- sendOnly = *n.SendOnly
- }
- if n.WSURL == nil {
- if !sendOnly {
- err = multierr.Append(err, commonconfig.ErrMissing{Name: "WSURL", Msg: "required for primary nodes"})
- }
- } else if n.WSURL.IsZero() {
- if !sendOnly {
- err = multierr.Append(err, commonconfig.ErrEmpty{Name: "WSURL", Msg: "required for primary nodes"})
- }
- } else {
+ // relax the check here as WSURL can potentially be empty if LogBroadcaster is disabled (checked in EVMConfig Validation)
+ if n.WSURL != nil && !n.WSURL.IsZero() {
switch n.WSURL.Scheme {
case "ws", "wss":
default:
diff --git a/core/chains/evm/config/toml/defaults.go b/core/chains/evm/config/toml/defaults.go
index c3f087da8c..d916754fde 100644
--- a/core/chains/evm/config/toml/defaults.go
+++ b/core/chains/evm/config/toml/defaults.go
@@ -155,6 +155,9 @@ func (c *Chain) SetFrom(f *Chain) {
if v := f.OperatorFactoryAddress; v != nil {
c.OperatorFactoryAddress = v
}
+ if v := f.LogBroadcasterEnabled; v != nil {
+ c.LogBroadcasterEnabled = v
+ }
if v := f.RPCDefaultBatchSize; v != nil {
c.RPCDefaultBatchSize = v
}
diff --git a/core/chains/evm/config/toml/defaults/Astar_Mainnet.toml b/core/chains/evm/config/toml/defaults/Astar_Mainnet.toml
index 2d356d9564..d1697d137a 100644
--- a/core/chains/evm/config/toml/defaults/Astar_Mainnet.toml
+++ b/core/chains/evm/config/toml/defaults/Astar_Mainnet.toml
@@ -1,6 +1,6 @@
ChainID = '592'
-# FinalityTag will be enabled once the custom tag is implemented
-# FinalityTagEnabled = true
+ChainType = 'astar'
+FinalityTagEnabled = true
FinalityDepth = 100
LogPollInterval = '6s'
diff --git a/core/chains/evm/config/toml/defaults/BOB_Mainnet.toml b/core/chains/evm/config/toml/defaults/BOB_Mainnet.toml
new file mode 100644
index 0000000000..806f7f8726
--- /dev/null
+++ b/core/chains/evm/config/toml/defaults/BOB_Mainnet.toml
@@ -0,0 +1,23 @@
+ChainID = '60808'
+# OP stack https://docs.gobob.xyz/learn/introduction/stack-overview#rollup-layer
+ChainType = 'optimismBedrock'
+# finality_depth was: ~850
+FinalityDepth = 900
+# block_time was: 2s
+LogPollInterval = '5s'
+
+# finality_depth * block_time / 60 secs = ~30 min (finality time)
+NoNewFinalizedHeadsThreshold = '35m'
+
+FinalityTagEnabled = true
+
+[GasEstimator]
+EIP1559DynamicFees = true
+Mode = 'FeeHistory'
+
+[GasEstimator.FeeHistory]
+# block_time was: 2s
+CacheTimeout = '2s'
+
+[GasEstimator.BlockHistory]
+BlockHistorySize = 100
diff --git a/core/chains/evm/config/toml/defaults/BOB_Testnet.toml b/core/chains/evm/config/toml/defaults/BOB_Testnet.toml
new file mode 100644
index 0000000000..2b5313b5e5
--- /dev/null
+++ b/core/chains/evm/config/toml/defaults/BOB_Testnet.toml
@@ -0,0 +1,23 @@
+ChainID = '808813'
+# OP stack https://docs.gobob.xyz/learn/introduction/stack-overview#rollup-layer
+ChainType = 'optimismBedrock'
+# finality_depth was: ~850
+FinalityDepth = 900
+# block_time was: 2s
+LogPollInterval = '5s'
+
+# finality_depth * block_time / 60 secs = ~30 min (finality time)
+NoNewFinalizedHeadsThreshold = '35m'
+
+FinalityTagEnabled = true
+
+[GasEstimator]
+EIP1559DynamicFees = true
+Mode = 'FeeHistory'
+
+[GasEstimator.FeeHistory]
+# block_time was: 2s
+CacheTimeout = '2s'
+
+[GasEstimator.BlockHistory]
+BlockHistorySize = 100
diff --git a/core/chains/evm/config/toml/defaults/Berachain_Testnet.toml b/core/chains/evm/config/toml/defaults/Berachain_Testnet.toml
new file mode 100644
index 0000000000..1014b7b8bf
--- /dev/null
+++ b/core/chains/evm/config/toml/defaults/Berachain_Testnet.toml
@@ -0,0 +1,19 @@
+ChainID = '80084'
+# finality_depth: instant
+FinalityDepth = 10
+# block_time: 5s
+LogPollInterval = '10s'
+
+# finality_depth * block_time / 60 secs = ~0.8 min (finality time)
+NoNewFinalizedHeadsThreshold = '5m'
+
+[GasEstimator]
+EIP1559DynamicFees = true
+Mode = 'FeeHistory'
+
+[GasEstimator.FeeHistory]
+# block_time was: 5s
+CacheTimeout = '5s'
+
+[GasEstimator.BlockHistory]
+BlockHistorySize = 100
diff --git a/core/chains/evm/config/toml/defaults/Bsquared_Mainnet.toml b/core/chains/evm/config/toml/defaults/Bsquared_Mainnet.toml
new file mode 100644
index 0000000000..98c17e9220
--- /dev/null
+++ b/core/chains/evm/config/toml/defaults/Bsquared_Mainnet.toml
@@ -0,0 +1,23 @@
+ChainID = '223'
+# OP stack from questionnaire https://docs.google.com/spreadsheets/d/1l8dx1GzxEnjgwH5x3vB60FUr5iFALzPcs6W_wOAiuDs/edit?gid=625078687#gid=625078687
+ChainType = 'optimismBedrock'
+# finality_depth was: ~1900
+FinalityDepth = 2000
+# block_time: ~2s
+LogPollInterval = '5s'
+
+# finality_depth * block_time / 60 secs = ~66 min (finality time)
+NoNewFinalizedHeadsThreshold = '70m'
+
+FinalityTagEnabled = true
+
+[GasEstimator]
+EIP1559DynamicFees = true
+Mode = 'FeeHistory'
+
+[GasEstimator.FeeHistory]
+# block_time was: 2s
+CacheTimeout = '2s'
+
+[GasEstimator.BlockHistory]
+BlockHistorySize = 100
diff --git a/core/chains/evm/config/toml/defaults/Bsquared_Testnet.toml b/core/chains/evm/config/toml/defaults/Bsquared_Testnet.toml
new file mode 100644
index 0000000000..1a5c7c26eb
--- /dev/null
+++ b/core/chains/evm/config/toml/defaults/Bsquared_Testnet.toml
@@ -0,0 +1,23 @@
+ChainID = '1123'
+# OP stack from questionnaire https://docs.google.com/spreadsheets/d/1l8dx1GzxEnjgwH5x3vB60FUr5iFALzPcs6W_wOAiuDs/edit?gid=625078687#gid=625078687
+ChainType = 'optimismBedrock'
+# finality_depth was: ~1900
+FinalityDepth = 2000
+# block_time: ~2s
+LogPollInterval = '5s'
+
+# finality_depth * block_time / 60 secs = ~66 min (finality time)
+NoNewFinalizedHeadsThreshold = '70m'
+
+FinalityTagEnabled = true
+
+[GasEstimator]
+EIP1559DynamicFees = true
+Mode = 'FeeHistory'
+
+[GasEstimator.FeeHistory]
+# block_time was: 2s
+CacheTimeout = '2s'
+
+[GasEstimator.BlockHistory]
+BlockHistorySize = 100
diff --git a/core/chains/evm/config/toml/defaults/Metis_Mainnet.toml b/core/chains/evm/config/toml/defaults/Metis_Mainnet.toml
index f057400d01..388b9f00b0 100644
--- a/core/chains/evm/config/toml/defaults/Metis_Mainnet.toml
+++ b/core/chains/evm/config/toml/defaults/Metis_Mainnet.toml
@@ -1,6 +1,6 @@
# Metis is an L2 chain based on Optimism.
ChainID = '1088'
-ChainType = 'metis'
+ChainType = 'optimismBedrock'
# Sequencer offers absolute finality
FinalityDepth = 10
FinalityTagEnabled = true
diff --git a/core/chains/evm/config/toml/defaults/Metis_Sepolia.toml b/core/chains/evm/config/toml/defaults/Metis_Sepolia.toml
index 286b888e1a..4ff4056c75 100644
--- a/core/chains/evm/config/toml/defaults/Metis_Sepolia.toml
+++ b/core/chains/evm/config/toml/defaults/Metis_Sepolia.toml
@@ -1,5 +1,5 @@
ChainID = '59902'
-ChainType = 'metis'
+ChainType = 'optimismBedrock'
FinalityDepth = 10
FinalityTagEnabled = true
MinIncomingConfirmations = 1
diff --git a/core/chains/evm/config/toml/defaults/Polygon_Zkevm_Cardona.toml b/core/chains/evm/config/toml/defaults/Polygon_Zkevm_Cardona.toml
index cd91465dae..46ce80e29f 100644
--- a/core/chains/evm/config/toml/defaults/Polygon_Zkevm_Cardona.toml
+++ b/core/chains/evm/config/toml/defaults/Polygon_Zkevm_Cardona.toml
@@ -13,12 +13,14 @@ ContractConfirmations = 1
ResendAfterThreshold = '3m'
[GasEstimator]
-PriceMin = '1 mwei'
+Mode = 'FeeHistory'
+# The FeeHistory estimator does not enforce PriceMin, setting it to 0 to not place any limits on the price
+PriceMin = '0'
BumpPercent = 40
-BumpMin = '20 mwei'
-[GasEstimator.BlockHistory]
-BlockHistorySize = 12
+[GasEstimator.FeeHistory]
+# Refresh the suggested price every 4 seconds, to stay slightly below their polling rate of 5s
+CacheTimeout = '4s'
[HeadTracker]
HistoryDepth = 2000
diff --git a/core/chains/evm/config/toml/defaults/Polygon_Zkevm_Goerli.toml b/core/chains/evm/config/toml/defaults/Polygon_Zkevm_Goerli.toml
deleted file mode 100644
index 6a9b47190f..0000000000
--- a/core/chains/evm/config/toml/defaults/Polygon_Zkevm_Goerli.toml
+++ /dev/null
@@ -1,24 +0,0 @@
-ChainID = '1442'
-ChainType = 'zkevm'
-FinalityDepth = 500
-NoNewHeadsThreshold = '12m'
-MinIncomingConfirmations = 1
-LogPollInterval = '30s'
-RPCDefaultBatchSize = 100
-
-[OCR]
-ContractConfirmations = 1
-
-[Transactions]
-ResendAfterThreshold = '3m'
-
-[GasEstimator]
-PriceMin = '50 mwei'
-BumpPercent = 40
-BumpMin = '20 mwei'
-
-[GasEstimator.BlockHistory]
-BlockHistorySize = 12
-
-[HeadTracker]
-HistoryDepth = 2000
diff --git a/core/chains/evm/config/toml/defaults/Polygon_Zkevm_Mainnet.toml b/core/chains/evm/config/toml/defaults/Polygon_Zkevm_Mainnet.toml
index 79e0cb0fce..2fef7874d1 100644
--- a/core/chains/evm/config/toml/defaults/Polygon_Zkevm_Mainnet.toml
+++ b/core/chains/evm/config/toml/defaults/Polygon_Zkevm_Mainnet.toml
@@ -14,12 +14,14 @@ ContractConfirmations = 1
ResendAfterThreshold = '3m'
[GasEstimator]
-PriceMin = '100 mwei'
+Mode = 'FeeHistory'
+# The FeeHistory estimator does not enforce PriceMin, setting it to 0 to not place any limits on the price
+PriceMin = '0'
BumpPercent = 40
-BumpMin = '100 mwei'
-[GasEstimator.BlockHistory]
-BlockHistorySize = 12
+[GasEstimator.FeeHistory]
+# Refresh the suggested price every 4 seconds, to stay slightly below their polling rate of 5s
+CacheTimeout = '4s'
[HeadTracker]
# Polygon suffers from a tremendous number of re-orgs, we need to set this to something very large to be conservative enough
diff --git a/core/chains/evm/config/toml/defaults/Unichain_Testnet.toml b/core/chains/evm/config/toml/defaults/Unichain_Testnet.toml
new file mode 100644
index 0000000000..68d18a1888
--- /dev/null
+++ b/core/chains/evm/config/toml/defaults/Unichain_Testnet.toml
@@ -0,0 +1,24 @@
+ChainID = '1301'
+# OP stack: https://docs.unichain.org/docs/getting-started/set-up-a-node#overview
+ChainType = 'optimismBedrock'
+# finality_depth was: ~1900
+FinalityDepth = 2000
+# block_time was: ~1s
+LogPollInterval = '5s'
+
+# batching_size_finalization_percentage = 30% according to the explorer batching view
+# ( batching_size_finalization_percentage * finality_depth) * block_time / 60 secs = ~33 min (finality time)
+NoNewFinalizedHeadsThreshold = '10m'
+
+FinalityTagEnabled = true
+
+[GasEstimator]
+EIP1559DynamicFees = true
+Mode = 'FeeHistory'
+
+[GasEstimator.FeeHistory]
+# block_time was: 1s
+CacheTimeout = '1s'
+
+[GasEstimator.BlockHistory]
+BlockHistorySize = 100
diff --git a/core/chains/evm/config/toml/defaults/Worldchain_Mainnet.toml b/core/chains/evm/config/toml/defaults/Worldchain_Mainnet.toml
new file mode 100644
index 0000000000..d33b75b22e
--- /dev/null
+++ b/core/chains/evm/config/toml/defaults/Worldchain_Mainnet.toml
@@ -0,0 +1,23 @@
+ChainID = '480'
+# OP stack: https://worldcoin.notion.site/World-Chain-Developer-Preview-Guide-23c94a67683f4e71986e5303ab88c9f3
+ChainType = 'optimismBedrock'
+# finality_depth was: ~2400
+FinalityDepth = 2500
+# block_time was: 2s
+LogPollInterval = '5s'
+
+# finality_depth * block_time / 60 secs = ~83 min (finality time)
+NoNewFinalizedHeadsThreshold = '90m'
+
+FinalityTagEnabled = true
+
+[GasEstimator]
+EIP1559DynamicFees = true
+Mode = 'FeeHistory'
+
+[GasEstimator.FeeHistory]
+# block_time was: 2s
+CacheTimeout = '2s'
+
+[GasEstimator.BlockHistory]
+BlockHistorySize = 100
diff --git a/core/chains/evm/config/toml/defaults/Worldchain_Testnet.toml b/core/chains/evm/config/toml/defaults/Worldchain_Testnet.toml
new file mode 100644
index 0000000000..34bc35d72b
--- /dev/null
+++ b/core/chains/evm/config/toml/defaults/Worldchain_Testnet.toml
@@ -0,0 +1,23 @@
+ChainID = '4801'
+# OP stack: https://worldcoin.notion.site/World-Chain-Developer-Preview-Guide-23c94a67683f4e71986e5303ab88c9f3
+ChainType = 'optimismBedrock'
+# finality_depth was: ~2400
+FinalityDepth = 2500
+# block_time was: 2s
+LogPollInterval = '5s'
+
+# finality_depth * block_time / 60 secs = ~83 min (finality time)
+NoNewFinalizedHeadsThreshold = '90m'
+
+FinalityTagEnabled = true
+
+[GasEstimator]
+EIP1559DynamicFees = true
+Mode = 'FeeHistory'
+
+[GasEstimator.FeeHistory]
+# block_time was: 2s
+CacheTimeout = '2s'
+
+[GasEstimator.BlockHistory]
+BlockHistorySize = 100
diff --git a/core/chains/evm/config/toml/defaults/Zircuit_Mainnet.toml b/core/chains/evm/config/toml/defaults/Zircuit_Mainnet.toml
new file mode 100644
index 0000000000..ec336d3efe
--- /dev/null
+++ b/core/chains/evm/config/toml/defaults/Zircuit_Mainnet.toml
@@ -0,0 +1,36 @@
+ChainID = '48900'
+ChainType = 'optimismBedrock'
+FinalityTagEnabled = true
+FinalityDepth = 1000
+LinkContractAddress = '0x5D6d033B4FbD2190D99D930719fAbAcB64d2439a'
+LogPollInterval = "2s"
+NoNewHeadsThreshold = "40s"
+NoNewFinalizedHeadsThreshold = "15m"
+
+[GasEstimator]
+EIP1559DynamicFees = true
+PriceMin = '1 wei'
+BumpMin = '100 wei'
+
+[GasEstimator.BlockHistory]
+BlockHistorySize = 24
+
+[Transactions]
+ResendAfterThreshold = '30s'
+
+[HeadTracker]
+HistoryDepth = 2000
+
+[NodePool]
+SyncThreshold = 10
+
+[OCR]
+ContractConfirmations = 1
+
+[OCR2.Automation]
+GasLimit = 6500000
+
+[Transactions.AutoPurge]
+Enabled = true
+Threshold = 90 # 90 blocks at 2s block time ~3 minutes
+MinAttempts = 3
\ No newline at end of file
diff --git a/core/chains/evm/config/toml/defaults/Zircuit_Sepolia.toml b/core/chains/evm/config/toml/defaults/Zircuit_Sepolia.toml
new file mode 100644
index 0000000000..d6934d533d
--- /dev/null
+++ b/core/chains/evm/config/toml/defaults/Zircuit_Sepolia.toml
@@ -0,0 +1,36 @@
+ChainID = '48899'
+ChainType = 'optimismBedrock'
+FinalityTagEnabled = true
+FinalityDepth = 1000
+LinkContractAddress = '0xDEE94506570cA186BC1e3516fCf4fd719C312cCD'
+LogPollInterval = "2s"
+NoNewHeadsThreshold = "40s"
+NoNewFinalizedHeadsThreshold = "15m"
+
+[GasEstimator]
+EIP1559DynamicFees = true
+PriceMin = '1 wei'
+BumpMin = '100 wei'
+
+[GasEstimator.BlockHistory]
+BlockHistorySize = 60
+
+[Transactions]
+ResendAfterThreshold = '30s'
+
+[HeadTracker]
+HistoryDepth = 2000
+
+[NodePool]
+SyncThreshold = 10
+
+[OCR]
+ContractConfirmations = 1
+
+[OCR2.Automation]
+GasLimit = 6500000
+
+[Transactions.AutoPurge]
+Enabled = true
+Threshold = 90 # 90 blocks at 2s block time ~3 minutes
+MinAttempts = 3
\ No newline at end of file
diff --git a/core/chains/evm/config/toml/defaults/fallback.toml b/core/chains/evm/config/toml/defaults/fallback.toml
index e3136323f6..ab28753383 100644
--- a/core/chains/evm/config/toml/defaults/fallback.toml
+++ b/core/chains/evm/config/toml/defaults/fallback.toml
@@ -17,6 +17,7 @@ RPCDefaultBatchSize = 250
RPCBlockQueryDelay = 1
FinalizedBlockOffset = 0
NoNewFinalizedHeadsThreshold = '0'
+LogBroadcasterEnabled = true
[Transactions]
ForwardersEnabled = false
@@ -48,6 +49,7 @@ EIP1559DynamicFees = false
FeeCapDefault = '100 gwei'
TipCapDefault = '1'
TipCapMin = '1'
+EstimateLimit = false
[GasEstimator.BlockHistory]
BatchSize = 25
@@ -56,6 +58,9 @@ CheckInclusionBlocks = 12
CheckInclusionPercentile = 90
TransactionPercentile = 60
+[GasEstimator.FeeHistory]
+CacheTimeout = '10s'
+
[HeadTracker]
HistoryDepth = 100
MaxBufferSize = 3
@@ -73,6 +78,7 @@ NodeIsSyncingEnabled = false
FinalizedBlockPollInterval = '5s'
EnforceRepeatableRead = false
DeathDeclarationDelay = '10s'
+NewHeadsPollInterval = '0s'
[OCR]
ContractConfirmations = 4
diff --git a/core/chains/evm/gas/fee_history_estimator.go b/core/chains/evm/gas/fee_history_estimator.go
new file mode 100644
index 0000000000..ba3192be10
--- /dev/null
+++ b/core/chains/evm/gas/fee_history_estimator.go
@@ -0,0 +1,440 @@
+package gas
+
+import (
+ "context"
+ "fmt"
+ "math/big"
+ "strconv"
+ "sync"
+ "time"
+
+ "github.com/ethereum/go-ethereum"
+ "github.com/prometheus/client_golang/prometheus"
+ "github.com/prometheus/client_golang/prometheus/promauto"
+
+ "github.com/smartcontractkit/chainlink-common/pkg/logger"
+ "github.com/smartcontractkit/chainlink-common/pkg/services"
+ bigmath "github.com/smartcontractkit/chainlink-common/pkg/utils/big_math"
+
+ commonfee "github.com/smartcontractkit/chainlink/v2/common/fee"
+ feetypes "github.com/smartcontractkit/chainlink/v2/common/fee/types"
+ "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets"
+ evmclient "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client"
+ "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas/rollups"
+ evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types"
+)
+
+// metrics are thread safe
+var (
+ promFeeHistoryEstimatorGasPrice = promauto.NewGaugeVec(prometheus.GaugeOpts{
+ Name: "gas_price_updater",
+ Help: "Sets latest gas price (in Wei)",
+ },
+ []string{"evmChainID"},
+ )
+ promFeeHistoryEstimatorBaseFee = promauto.NewGaugeVec(prometheus.GaugeOpts{
+ Name: "base_fee_updater",
+ Help: "Sets latest BaseFee (in Wei)",
+ },
+ []string{"evmChainID"},
+ )
+ promFeeHistoryEstimatorMaxPriorityFeePerGas = promauto.NewGaugeVec(prometheus.GaugeOpts{
+ Name: "max_priority_fee_per_gas_updater",
+ Help: "Sets latest MaxPriorityFeePerGas (in Wei)",
+ },
+ []string{"evmChainID"},
+ )
+ promFeeHistoryEstimatorMaxFeePerGas = promauto.NewGaugeVec(prometheus.GaugeOpts{
+ Name: "max_fee_per_gas_updater",
+ Help: "Sets latest MaxFeePerGas (in Wei)",
+ },
+ []string{"evmChainID"},
+ )
+)
+
+const (
+ MinimumBumpPercentage = 10 // based on geth's spec
+ ConnectivityPercentile = 85
+ BaseFeeBufferPercentage = 40
+)
+
+type FeeHistoryEstimatorConfig struct {
+ BumpPercent uint16
+ CacheTimeout time.Duration
+
+ EIP1559 bool
+ BlockHistorySize uint64
+ RewardPercentile float64
+}
+
+type feeHistoryEstimatorClient interface {
+ SuggestGasPrice(ctx context.Context) (*big.Int, error)
+ FeeHistory(ctx context.Context, blockCount uint64, rewardPercentiles []float64) (feeHistory *ethereum.FeeHistory, err error)
+}
+
+type FeeHistoryEstimator struct {
+ services.StateMachine
+
+ client feeHistoryEstimatorClient
+ logger logger.Logger
+ config FeeHistoryEstimatorConfig
+ chainID *big.Int
+
+ gasPriceMu sync.RWMutex
+ gasPrice *assets.Wei
+
+ dynamicPriceMu sync.RWMutex
+ dynamicPrice DynamicFee
+
+ priorityFeeThresholdMu sync.RWMutex
+ priorityFeeThreshold *assets.Wei
+
+ l1Oracle rollups.L1Oracle
+
+ wg *sync.WaitGroup
+ stopCh services.StopChan
+ refreshCh chan struct{}
+}
+
+func NewFeeHistoryEstimator(lggr logger.Logger, client feeHistoryEstimatorClient, cfg FeeHistoryEstimatorConfig, chainID *big.Int, l1Oracle rollups.L1Oracle) *FeeHistoryEstimator {
+ return &FeeHistoryEstimator{
+ client: client,
+ logger: logger.Named(lggr, "FeeHistoryEstimator"),
+ config: cfg,
+ chainID: chainID,
+ l1Oracle: l1Oracle,
+ wg: new(sync.WaitGroup),
+ stopCh: make(chan struct{}),
+ refreshCh: make(chan struct{}),
+ }
+}
+
+func (f *FeeHistoryEstimator) Start(context.Context) error {
+ return f.StartOnce("FeeHistoryEstimator", func() error {
+ if f.config.BumpPercent < MinimumBumpPercentage {
+ return fmt.Errorf("BumpPercent: %s is less than minimum allowed percentage: %s",
+ strconv.FormatUint(uint64(f.config.BumpPercent), 10), strconv.Itoa(MinimumBumpPercentage))
+ }
+ if f.config.EIP1559 && f.config.RewardPercentile > ConnectivityPercentile {
+ return fmt.Errorf("RewardPercentile: %s is greater than maximum allowed percentile: %s",
+ strconv.FormatUint(uint64(f.config.RewardPercentile), 10), strconv.Itoa(ConnectivityPercentile))
+ }
+ f.wg.Add(1)
+ go f.run()
+
+ return nil
+ })
+}
+
+func (f *FeeHistoryEstimator) Close() error {
+ return f.StopOnce("FeeHistoryEstimator", func() error {
+ close(f.stopCh)
+ f.wg.Wait()
+ return nil
+ })
+}
+
+func (f *FeeHistoryEstimator) run() {
+ defer f.wg.Done()
+
+ t := services.TickerConfig{
+ JitterPct: services.DefaultJitter,
+ }.NewTicker(f.config.CacheTimeout)
+
+ for {
+ select {
+ case <-f.stopCh:
+ return
+ case <-f.refreshCh:
+ t.Reset()
+ case <-t.C:
+ if f.config.EIP1559 {
+ if err := f.RefreshDynamicPrice(); err != nil {
+ f.logger.Error(err)
+ }
+ } else {
+ if _, err := f.RefreshGasPrice(); err != nil {
+ f.logger.Error(err)
+ }
+ }
+ }
+ }
+}
+
+// GetLegacyGas will fetch the cached gas price value.
+func (f *FeeHistoryEstimator) GetLegacyGas(ctx context.Context, _ []byte, gasLimit uint64, maxPrice *assets.Wei, opts ...feetypes.Opt) (gasPrice *assets.Wei, chainSpecificGasLimit uint64, err error) {
+ chainSpecificGasLimit = gasLimit
+ if gasPrice, err = f.getGasPrice(); err != nil {
+ return
+ }
+
+ if gasPrice.Cmp(maxPrice) > 0 {
+ f.logger.Warnf("estimated gas price: %s is greater than the maximum gas price configured: %s, returning the maximum price instead.", gasPrice, maxPrice)
+ return maxPrice, chainSpecificGasLimit, nil
+ }
+ return
+}
+
+// RefreshGasPrice will use eth_gasPrice to fetch and cache the latest gas price from the RPC.
+func (f *FeeHistoryEstimator) RefreshGasPrice() (*assets.Wei, error) {
+ ctx, cancel := f.stopCh.CtxCancel(evmclient.ContextWithDefaultTimeout())
+ defer cancel()
+
+ gasPrice, err := f.client.SuggestGasPrice(ctx)
+ if err != nil {
+ return nil, err
+ }
+
+ promFeeHistoryEstimatorGasPrice.WithLabelValues(f.chainID.String()).Set(float64(gasPrice.Int64()))
+
+ gasPriceWei := assets.NewWei(gasPrice)
+
+ f.logger.Debugf("fetched new gas price: %v", gasPriceWei)
+
+ f.gasPriceMu.Lock()
+ defer f.gasPriceMu.Unlock()
+ f.gasPrice = gasPriceWei
+ return f.gasPrice, nil
+}
+
+func (f *FeeHistoryEstimator) getGasPrice() (*assets.Wei, error) {
+ f.gasPriceMu.RLock()
+ defer f.gasPriceMu.RUnlock()
+ if f.gasPrice == nil {
+ return f.gasPrice, fmt.Errorf("gas price not set")
+ }
+ return f.gasPrice, nil
+}
+
+// GetDynamicFee will fetch the cached dynamic prices.
+func (f *FeeHistoryEstimator) GetDynamicFee(ctx context.Context, maxPrice *assets.Wei) (fee DynamicFee, err error) {
+ if fee, err = f.getDynamicPrice(); err != nil {
+ return
+ }
+
+ if fee.FeeCap.Cmp(maxPrice) > 0 {
+ f.logger.Warnf("estimated maxFeePerGas: %v is greater than the maximum price configured: %v, returning the maximum price instead.",
+ fee.FeeCap, maxPrice)
+ fee.FeeCap = maxPrice
+ if fee.TipCap.Cmp(maxPrice) > 0 {
+ f.logger.Warnf("estimated maxPriorityFeePerGas: %v is greater than the maximum price configured: %v, returning the maximum price instead.",
+ fee.TipCap, maxPrice)
+ fee.TipCap = maxPrice
+ }
+ }
+
+ return
+}
+
+// RefreshDynamicPrice uses eth_feeHistory to fetch the baseFee of the next block and the Nth maxPriorityFeePerGas percentiles
+// of the past X blocks. It also fetches the highest 85th maxPriorityFeePerGas percentile of the past X blocks, which represents
+// the highest percentile we're willing to pay. A buffer is added on top of the latest baseFee to catch fluctuations in the next
+// blocks. On Ethereum the increase is baseFee * 1.125 per block, however in some chains that may vary.
+func (f *FeeHistoryEstimator) RefreshDynamicPrice() error {
+ ctx, cancel := f.stopCh.CtxCancel(evmclient.ContextWithDefaultTimeout())
+ defer cancel()
+
+ // RewardPercentile will be used for maxPriorityFeePerGas estimations and connectivityPercentile to set the highest threshold for bumping.
+ feeHistory, err := f.client.FeeHistory(ctx, max(f.config.BlockHistorySize, 1), []float64{f.config.RewardPercentile, ConnectivityPercentile})
+ if err != nil {
+ return err
+ }
+
+ // eth_feeHistory doesn't return the latest baseFee of the range but rather the latest + 1, because it can be derived from the existing
+ // values. Source: https://github.com/ethereum/go-ethereum/blob/b0f66e34ca2a4ea7ae23475224451c8c9a569826/eth/gasprice/feehistory.go#L235
+ // nextBlock is the latest returned + 1 to be aligned with the base fee value.
+ nextBaseFee := assets.NewWei(feeHistory.BaseFee[len(feeHistory.BaseFee)-1])
+ nextBlock := big.NewInt(0).Add(feeHistory.OldestBlock, big.NewInt(int64(f.config.BlockHistorySize)))
+
+ // If BlockHistorySize is 0 it means priority fees will be ignored from the calculations, so we set them to 0.
+ // If it's not we exclude 0 priced priority fees from the RPC response, even though some networks allow them. For empty blocks, eth_feeHistory
+ // returns priority fees with 0 values so it's safer to discard them in order to pick values from a more representative sample.
+ maxPriorityFeePerGas := assets.NewWeiI(0)
+ priorityFeeThresholdWei := assets.NewWeiI(0)
+ if f.config.BlockHistorySize > 0 {
+ var nonZeroRewardsLen int64 = 0
+ priorityFee := big.NewInt(0)
+ priorityFeeThreshold := big.NewInt(0)
+ for _, reward := range feeHistory.Reward {
+ // reward needs to have values for two percentiles
+ if len(reward) < 2 {
+ return fmt.Errorf("reward size incorrect: %d", len(reward))
+ }
+ // We'll calculate the average of non-zero priority fees
+ if reward[0].Cmp(big.NewInt(0)) > 0 {
+ priorityFee = priorityFee.Add(priorityFee, reward[0])
+ nonZeroRewardsLen += 1
+ }
+ // We take the max value for the bumping threshold
+ if reward[1].Cmp(big.NewInt(0)) > 0 {
+ priorityFeeThreshold = bigmath.Max(priorityFeeThreshold, reward[1])
+ }
+ }
+
+ if nonZeroRewardsLen == 0 || priorityFeeThreshold.Cmp(big.NewInt(0)) == 0 {
+ return nil
+ }
+ priorityFeeThresholdWei = assets.NewWei(priorityFeeThreshold)
+ maxPriorityFeePerGas = assets.NewWei(priorityFee.Div(priorityFee, big.NewInt(nonZeroRewardsLen)))
+ }
+ // baseFeeBufferPercentage is added on top as a safety to catch fluctuations in the next blocks.
+ maxFeePerGas := nextBaseFee.AddPercentage(BaseFeeBufferPercentage).Add(maxPriorityFeePerGas)
+
+ promFeeHistoryEstimatorBaseFee.WithLabelValues(f.chainID.String()).Set(float64(nextBaseFee.Int64()))
+ promFeeHistoryEstimatorMaxPriorityFeePerGas.WithLabelValues(f.chainID.String()).Set(float64(maxPriorityFeePerGas.Int64()))
+ promFeeHistoryEstimatorMaxFeePerGas.WithLabelValues(f.chainID.String()).Set(float64(maxFeePerGas.Int64()))
+
+ f.logger.Debugf("Fetched new dynamic prices, nextBlock#: %v - oldestBlock#: %v - nextBaseFee: %v - maxFeePerGas: %v - maxPriorityFeePerGas: %v - maxPriorityFeeThreshold: %v",
+ nextBlock, feeHistory.OldestBlock, nextBaseFee, maxFeePerGas, maxPriorityFeePerGas, priorityFeeThresholdWei)
+
+ f.priorityFeeThresholdMu.Lock()
+ f.priorityFeeThreshold = priorityFeeThresholdWei
+ f.priorityFeeThresholdMu.Unlock()
+
+ f.dynamicPriceMu.Lock()
+ defer f.dynamicPriceMu.Unlock()
+ f.dynamicPrice.FeeCap = maxFeePerGas
+ f.dynamicPrice.TipCap = maxPriorityFeePerGas
+ return nil
+}
+
+func (f *FeeHistoryEstimator) getDynamicPrice() (fee DynamicFee, err error) {
+ f.dynamicPriceMu.RLock()
+ defer f.dynamicPriceMu.RUnlock()
+ if f.dynamicPrice.FeeCap == nil || f.dynamicPrice.TipCap == nil {
+ return fee, fmt.Errorf("dynamic price not set")
+ }
+ return f.dynamicPrice, nil
+}
+
+// BumpLegacyGas provides a bumped gas price value by bumping the previous one by BumpPercent.
+// If the original value is higher than the max price it returns an error as there is no room for bumping.
+// It aggregates the market, bumped, and max gas price to provide a correct value.
+func (f *FeeHistoryEstimator) BumpLegacyGas(ctx context.Context, originalGasPrice *assets.Wei, gasLimit uint64, maxPrice *assets.Wei, _ []EvmPriorAttempt) (*assets.Wei, uint64, error) {
+ // Sanitize original fee input
+ if originalGasPrice == nil || originalGasPrice.Cmp(maxPrice) >= 0 {
+ return nil, 0, fmt.Errorf("%w: error while retrieving original gas price: originalGasPrice: %s. Maximum price configured: %s",
+ commonfee.ErrBump, originalGasPrice, maxPrice)
+ }
+
+ currentGasPrice, err := f.RefreshGasPrice()
+ if err != nil {
+ return nil, 0, err
+ }
+ f.IfStarted(func() { f.refreshCh <- struct{}{} })
+
+ bumpedGasPrice := originalGasPrice.AddPercentage(f.config.BumpPercent)
+ bumpedGasPrice, err = LimitBumpedFee(originalGasPrice, currentGasPrice, bumpedGasPrice, maxPrice)
+ if err != nil {
+ return nil, 0, fmt.Errorf("failed to limit gas price: %w", err)
+ }
+
+ f.logger.Debugw("bumped gas price", "originalGasPrice", originalGasPrice, "marketGasPrice", currentGasPrice, "bumpedGasPrice", bumpedGasPrice)
+
+ return bumpedGasPrice, gasLimit, nil
+}
+
+// BumpDynamicFee provides a bumped dynamic fee by bumping the previous one by BumpPercent.
+// If the original values are higher than the max price it returns an error as there is no room for bumping. If maxPriorityFeePerGas is bumped
+// above the priority fee threshold then there is a good chance there is a connectivity issue and we shouldn't bump.
+// Both maxFeePerGas as well as maxPriorityFeePerGas need to be bumped otherwise the RPC won't accept the transaction and throw an error.
+// See: https://github.com/ethereum/go-ethereum/issues/24284
+// It aggregates the market, bumped, and max price to provide a correct value, for both maxFeePerGas as well as maxPriorityFerPergas.
+func (f *FeeHistoryEstimator) BumpDynamicFee(ctx context.Context, originalFee DynamicFee, maxPrice *assets.Wei, _ []EvmPriorAttempt) (bumped DynamicFee, err error) {
+ // For chains that don't have a mempool there is no concept of gas bumping so we force-call RefreshDynamicPrice to update the underlying base fee value
+ if f.config.BlockHistorySize == 0 {
+ if !f.IfStarted(func() {
+ if refreshErr := f.RefreshDynamicPrice(); refreshErr != nil {
+ err = refreshErr
+ return
+ }
+ f.refreshCh <- struct{}{}
+ bumped, err = f.GetDynamicFee(ctx, maxPrice)
+ }) {
+ return bumped, fmt.Errorf("estimator not started")
+ }
+ return bumped, err
+ }
+
+ // Sanitize original fee input
+ // According to geth's spec we need to bump both maxFeePerGas and maxPriorityFeePerGas for the new attempt to be accepted by the RPC
+ if originalFee.FeeCap == nil ||
+ originalFee.TipCap == nil ||
+ ((originalFee.TipCap.Cmp(originalFee.FeeCap)) > 0) ||
+ (originalFee.FeeCap.Cmp(maxPrice) >= 0) {
+ return bumped, fmt.Errorf("%w: error while retrieving original dynamic fees: (originalFeePerGas: %s - originalPriorityFeePerGas: %s). Maximum price configured: %s",
+ commonfee.ErrBump, originalFee.FeeCap, originalFee.TipCap, maxPrice)
+ }
+
+ currentDynamicPrice, err := f.getDynamicPrice()
+ if err != nil {
+ return
+ }
+
+ bumpedMaxPriorityFeePerGas := originalFee.TipCap.AddPercentage(f.config.BumpPercent)
+ bumpedMaxFeePerGas := originalFee.FeeCap.AddPercentage(f.config.BumpPercent)
+
+ bumpedMaxPriorityFeePerGas, err = LimitBumpedFee(originalFee.TipCap, currentDynamicPrice.TipCap, bumpedMaxPriorityFeePerGas, maxPrice)
+ if err != nil {
+ return bumped, fmt.Errorf("failed to limit maxPriorityFeePerGas: %w", err)
+ }
+
+ priorityFeeThreshold, e := f.getPriorityFeeThreshold()
+ if e != nil {
+ return bumped, e
+ }
+
+ if bumpedMaxPriorityFeePerGas.Cmp(priorityFeeThreshold) > 0 {
+ return bumped, fmt.Errorf("bumpedMaxPriorityFeePerGas: %s is above market's %sth percentile: %s, bumping is halted",
+ bumpedMaxPriorityFeePerGas, strconv.Itoa(ConnectivityPercentile), priorityFeeThreshold)
+ }
+
+ bumpedMaxFeePerGas, err = LimitBumpedFee(originalFee.FeeCap, currentDynamicPrice.FeeCap, bumpedMaxFeePerGas, maxPrice)
+ if err != nil {
+ return bumped, fmt.Errorf("failed to limit maxFeePerGas: %w", err)
+ }
+
+ bumpedFee := DynamicFee{FeeCap: bumpedMaxFeePerGas, TipCap: bumpedMaxPriorityFeePerGas}
+ f.logger.Debugw("bumped dynamic fee", "originalFee", originalFee, "marketFee", currentDynamicPrice, "bumpedFee", bumpedFee)
+
+ return bumpedFee, nil
+}
+
+// LimitBumpedFee selects the maximum value between the bumped attempt and the current fee, if there is one. If the result is higher than the max price it gets capped.
+// Geth's implementation has a hard 10% minimum limit for the bumped values, otherwise it rejects the transaction with an error.
+// See: https://github.com/ethereum/go-ethereum/blob/bff330335b94af3643ac2fb809793f77de3069d4/core/tx_list.go#L298
+//
+// Note: for chains that support EIP-1559 but we still choose to send Legacy transactions to them, the limit is still enforcable due to the fact that Legacy transactions
+// are treated the same way as Dynamic transactions under the hood. For chains that don't support EIP-1559 at all, the limit isn't enforcable but a 10% minimum bump percentage
+// makes sense anyway.
+func LimitBumpedFee(originalFee *assets.Wei, currentFee *assets.Wei, bumpedFee *assets.Wei, maxPrice *assets.Wei) (*assets.Wei, error) {
+ if currentFee != nil {
+ bumpedFee = assets.WeiMax(currentFee, bumpedFee)
+ }
+ bumpedFee = assets.WeiMin(bumpedFee, maxPrice)
+
+ // The first check is added for the following edge case:
+ // If originalFee is below 10 wei, then adding the minimum bump percentage won't have any effect on the final value because of rounding down.
+ // Similarly for bumpedFee, it can have the exact same value as the originalFee, even if we bumped, given an originalFee of less than 10 wei
+ // and a small enough BumpPercent.
+ if bumpedFee.Cmp(originalFee) == 0 ||
+ bumpedFee.Cmp(originalFee.AddPercentage(MinimumBumpPercentage)) < 0 {
+ return nil, fmt.Errorf("%w: %s is bumped less than minimum allowed percentage(%s) from originalFee: %s - maxPrice: %s",
+ commonfee.ErrBump, bumpedFee, strconv.Itoa(MinimumBumpPercentage), originalFee, maxPrice)
+ }
+ return bumpedFee, nil
+}
+
+func (f *FeeHistoryEstimator) getPriorityFeeThreshold() (*assets.Wei, error) {
+ f.priorityFeeThresholdMu.RLock()
+ defer f.priorityFeeThresholdMu.RUnlock()
+ if f.priorityFeeThreshold == nil {
+ return f.priorityFeeThreshold, fmt.Errorf("priorityFeeThreshold not set")
+ }
+ return f.priorityFeeThreshold, nil
+}
+
+func (f *FeeHistoryEstimator) Name() string { return f.logger.Name() }
+func (f *FeeHistoryEstimator) L1Oracle() rollups.L1Oracle { return f.l1Oracle }
+func (f *FeeHistoryEstimator) HealthReport() map[string]error { return map[string]error{f.Name(): nil} }
+func (f *FeeHistoryEstimator) OnNewLongestChain(context.Context, *evmtypes.Head) {}
diff --git a/core/chains/evm/gas/fee_history_estimator_test.go b/core/chains/evm/gas/fee_history_estimator_test.go
new file mode 100644
index 0000000000..6e42e0e209
--- /dev/null
+++ b/core/chains/evm/gas/fee_history_estimator_test.go
@@ -0,0 +1,513 @@
+package gas_test
+
+import (
+ "math/big"
+ "testing"
+ "time"
+
+ "github.com/ethereum/go-ethereum"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/mock"
+
+ "github.com/smartcontractkit/chainlink-common/pkg/logger"
+ "github.com/smartcontractkit/chainlink-common/pkg/services/servicetest"
+ "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/gas"
+ "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas/mocks"
+)
+
+func TestFeeHistoryEstimatorLifecycle(t *testing.T) {
+ t.Parallel()
+ var gasLimit uint64 = 21000
+ maxPrice := assets.NewWeiI(100)
+ chainID := big.NewInt(0)
+
+ t.Run("fails if you fetch gas price before the estimator starts", func(t *testing.T) {
+ cfg := gas.FeeHistoryEstimatorConfig{
+ BumpPercent: 20,
+ RewardPercentile: 60,
+ EIP1559: false,
+ }
+
+ u := gas.NewFeeHistoryEstimator(logger.Test(t), nil, cfg, chainID, nil)
+ _, _, err := u.GetLegacyGas(tests.Context(t), nil, gasLimit, maxPrice)
+ assert.ErrorContains(t, err, "gas price not set")
+ })
+
+ t.Run("fails to start if BumpPercent is lower than the minimum cap", func(t *testing.T) {
+ cfg := gas.FeeHistoryEstimatorConfig{BumpPercent: 9}
+
+ u := gas.NewFeeHistoryEstimator(logger.Test(t), nil, cfg, chainID, nil)
+ assert.ErrorContains(t, u.Start(tests.Context(t)), "BumpPercent")
+ })
+
+ t.Run("fails to start if RewardPercentile is higher than ConnectivityPercentile in EIP-1559", func(t *testing.T) {
+ cfg := gas.FeeHistoryEstimatorConfig{
+ BumpPercent: 20,
+ RewardPercentile: 99,
+ EIP1559: true,
+ }
+
+ u := gas.NewFeeHistoryEstimator(logger.Test(t), nil, cfg, chainID, nil)
+ assert.ErrorContains(t, u.Start(tests.Context(t)), "RewardPercentile")
+ })
+
+ t.Run("starts if configs are correct", func(t *testing.T) {
+ client := mocks.NewFeeHistoryEstimatorClient(t)
+ client.On("SuggestGasPrice", mock.Anything).Return(big.NewInt(10), nil).Maybe()
+
+ cfg := gas.FeeHistoryEstimatorConfig{
+ BumpPercent: 20,
+ RewardPercentile: 10,
+ CacheTimeout: 10 * time.Second,
+ }
+
+ u := gas.NewFeeHistoryEstimator(logger.Test(t), nil, cfg, chainID, nil)
+ err := u.Start(tests.Context(t))
+ assert.NoError(t, err)
+ err = u.Close()
+ assert.NoError(t, err)
+ })
+}
+
+func TestFeeHistoryEstimatorGetLegacyGas(t *testing.T) {
+ t.Parallel()
+
+ var gasLimit uint64 = 21000
+ maxPrice := assets.NewWeiI(100)
+ chainID := big.NewInt(0)
+
+ t.Run("fetches a new gas price when first called", func(t *testing.T) {
+ client := mocks.NewFeeHistoryEstimatorClient(t)
+ client.On("SuggestGasPrice", mock.Anything).Return(big.NewInt(10), nil).Once()
+
+ cfg := gas.FeeHistoryEstimatorConfig{}
+
+ u := gas.NewFeeHistoryEstimator(logger.Test(t), client, cfg, chainID, nil)
+ _, err := u.RefreshGasPrice()
+ assert.NoError(t, err)
+ gasPrice, _, err := u.GetLegacyGas(tests.Context(t), nil, gasLimit, maxPrice)
+ assert.NoError(t, err)
+ assert.Equal(t, assets.NewWeiI(10), gasPrice)
+ })
+
+ t.Run("will return max price if estimation exceeds it", func(t *testing.T) {
+ client := mocks.NewFeeHistoryEstimatorClient(t)
+ client.On("SuggestGasPrice", mock.Anything).Return(big.NewInt(10), nil).Once()
+
+ cfg := gas.FeeHistoryEstimatorConfig{}
+
+ maxPrice := assets.NewWeiI(1)
+ u := gas.NewFeeHistoryEstimator(logger.Test(t), client, cfg, chainID, nil)
+ _, err := u.RefreshGasPrice()
+ assert.NoError(t, err)
+ gas1, _, err := u.GetLegacyGas(tests.Context(t), nil, gasLimit, maxPrice)
+ assert.NoError(t, err)
+ assert.Equal(t, maxPrice, gas1)
+ })
+
+ t.Run("fails if gas price has not been set yet", func(t *testing.T) {
+ cfg := gas.FeeHistoryEstimatorConfig{}
+
+ maxPrice := assets.NewWeiI(1)
+ u := gas.NewFeeHistoryEstimator(logger.Test(t), nil, cfg, chainID, nil)
+ _, _, err := u.GetLegacyGas(tests.Context(t), nil, gasLimit, maxPrice)
+ assert.Error(t, err)
+ assert.ErrorContains(t, err, "gas price not set")
+ })
+}
+
+func TestFeeHistoryEstimatorBumpLegacyGas(t *testing.T) {
+ t.Parallel()
+
+ var gasLimit uint64 = 21000
+ maxPrice := assets.NewWeiI(100)
+ chainID := big.NewInt(0)
+
+ t.Run("bumps a previous attempt by BumpPercent", func(t *testing.T) {
+ client := mocks.NewFeeHistoryEstimatorClient(t)
+ originalGasPrice := assets.NewWeiI(10)
+ client.On("SuggestGasPrice", mock.Anything).Return(big.NewInt(10), nil)
+
+ cfg := gas.FeeHistoryEstimatorConfig{BumpPercent: 50, CacheTimeout: 5 * time.Second}
+
+ u := gas.NewFeeHistoryEstimator(logger.Test(t), client, cfg, chainID, nil)
+ servicetest.RunHealthy(t, u)
+ gasPrice, _, err := u.BumpLegacyGas(tests.Context(t), originalGasPrice, gasLimit, maxPrice, nil)
+ assert.NoError(t, err)
+ assert.Equal(t, assets.NewWeiI(15), gasPrice)
+ })
+
+ t.Run("fails if the original attempt is nil, or equal or higher than the max price", func(t *testing.T) {
+ client := mocks.NewFeeHistoryEstimatorClient(t)
+
+ cfg := gas.FeeHistoryEstimatorConfig{}
+ u := gas.NewFeeHistoryEstimator(logger.Test(t), client, cfg, chainID, nil)
+
+ var originalPrice *assets.Wei
+ _, _, err := u.BumpLegacyGas(tests.Context(t), originalPrice, gasLimit, maxPrice, nil)
+ assert.Error(t, err)
+
+ originalPrice = assets.NewWeiI(100)
+ _, _, err = u.BumpLegacyGas(tests.Context(t), originalPrice, gasLimit, maxPrice, nil)
+ assert.Error(t, err)
+ })
+
+ t.Run("returns market gas price if bumped original fee is lower", func(t *testing.T) {
+ client := mocks.NewFeeHistoryEstimatorClient(t)
+ client.On("SuggestGasPrice", mock.Anything).Return(big.NewInt(80), nil).Once()
+ originalGasPrice := assets.NewWeiI(10)
+
+ cfg := gas.FeeHistoryEstimatorConfig{}
+
+ u := gas.NewFeeHistoryEstimator(logger.Test(t), client, cfg, chainID, nil)
+ gas, _, err := u.BumpLegacyGas(tests.Context(t), originalGasPrice, gasLimit, maxPrice, nil)
+ assert.NoError(t, err)
+ assert.Equal(t, assets.NewWeiI(80), gas)
+ })
+
+ t.Run("returns max gas price if bumped original fee is higher", func(t *testing.T) {
+ client := mocks.NewFeeHistoryEstimatorClient(t)
+ client.On("SuggestGasPrice", mock.Anything).Return(big.NewInt(1), nil).Once()
+ originalGasPrice := assets.NewWeiI(10)
+
+ cfg := gas.FeeHistoryEstimatorConfig{BumpPercent: 50}
+
+ maxPrice := assets.NewWeiI(14)
+ u := gas.NewFeeHistoryEstimator(logger.Test(t), client, cfg, chainID, nil)
+ gas, _, err := u.BumpLegacyGas(tests.Context(t), originalGasPrice, gasLimit, maxPrice, nil)
+ assert.NoError(t, err)
+ assert.Equal(t, maxPrice, gas)
+ })
+
+ t.Run("returns max gas price if the aggregation of max and original bumped fee is higher", func(t *testing.T) {
+ client := mocks.NewFeeHistoryEstimatorClient(t)
+ client.On("SuggestGasPrice", mock.Anything).Return(big.NewInt(1), nil).Once()
+ originalGasPrice := assets.NewWeiI(10)
+
+ cfg := gas.FeeHistoryEstimatorConfig{BumpPercent: 50}
+
+ maxPrice := assets.NewWeiI(14)
+ u := gas.NewFeeHistoryEstimator(logger.Test(t), client, cfg, chainID, nil)
+ gas, _, err := u.BumpLegacyGas(tests.Context(t), originalGasPrice, gasLimit, maxPrice, nil)
+ assert.NoError(t, err)
+ assert.Equal(t, maxPrice, gas)
+ })
+
+ t.Run("fails if the bumped gas price is lower than the minimum bump percentage", func(t *testing.T) {
+ client := mocks.NewFeeHistoryEstimatorClient(t)
+ client.On("SuggestGasPrice", mock.Anything).Return(big.NewInt(100), nil).Once()
+ originalGasPrice := assets.NewWeiI(100)
+
+ cfg := gas.FeeHistoryEstimatorConfig{BumpPercent: 20}
+
+ // Price will be capped by the max price
+ maxPrice := assets.NewWeiI(101)
+ u := gas.NewFeeHistoryEstimator(logger.Test(t), client, cfg, chainID, nil)
+ _, _, err := u.BumpLegacyGas(tests.Context(t), originalGasPrice, gasLimit, maxPrice, nil)
+ assert.Error(t, err)
+ })
+}
+
+func TestFeeHistoryEstimatorGetDynamicFee(t *testing.T) {
+ t.Parallel()
+
+ maxPrice := assets.NewWeiI(100)
+ chainID := big.NewInt(0)
+
+ t.Run("fetches a new dynamic fee when first called", func(t *testing.T) {
+ client := mocks.NewFeeHistoryEstimatorClient(t)
+ baseFee := big.NewInt(5)
+ maxPriorityFeePerGas1 := big.NewInt(33)
+ maxPriorityFeePerGas2 := big.NewInt(20)
+
+ feeHistoryResult := ðereum.FeeHistory{
+ OldestBlock: big.NewInt(1),
+ Reward: [][]*big.Int{{maxPriorityFeePerGas1, big.NewInt(5)}, {maxPriorityFeePerGas2, big.NewInt(5)}}, // first one represents market price and second one connectivity price
+ BaseFee: []*big.Int{baseFee, baseFee},
+ GasUsedRatio: nil,
+ }
+ client.On("FeeHistory", mock.Anything, mock.Anything, mock.Anything).Return(feeHistoryResult, nil).Once()
+
+ blockHistoryLength := 2
+ cfg := gas.FeeHistoryEstimatorConfig{BlockHistorySize: uint64(blockHistoryLength)}
+ avrgPriorityFee := big.NewInt(0)
+ avrgPriorityFee.Add(maxPriorityFeePerGas1, maxPriorityFeePerGas2).Div(avrgPriorityFee, big.NewInt(int64(blockHistoryLength)))
+ maxFee := (*assets.Wei)(baseFee).AddPercentage(gas.BaseFeeBufferPercentage).Add((*assets.Wei)(avrgPriorityFee))
+
+ u := gas.NewFeeHistoryEstimator(logger.Test(t), client, cfg, chainID, nil)
+ err := u.RefreshDynamicPrice()
+ assert.NoError(t, err)
+ dynamicFee, err := u.GetDynamicFee(tests.Context(t), maxPrice)
+ assert.NoError(t, err)
+ assert.Equal(t, maxFee, dynamicFee.FeeCap)
+ assert.Equal(t, (*assets.Wei)(avrgPriorityFee), dynamicFee.TipCap)
+ })
+
+ t.Run("fails if dynamic prices have not been set yet", func(t *testing.T) {
+ cfg := gas.FeeHistoryEstimatorConfig{}
+
+ maxPrice := assets.NewWeiI(1)
+ u := gas.NewFeeHistoryEstimator(logger.Test(t), nil, cfg, chainID, nil)
+ _, err := u.GetDynamicFee(tests.Context(t), maxPrice)
+ assert.Error(t, err)
+ assert.ErrorContains(t, err, "dynamic price not set")
+ })
+
+ t.Run("will return max price if tip cap or fee cap exceed it", func(t *testing.T) {
+ client := mocks.NewFeeHistoryEstimatorClient(t)
+ baseFee := big.NewInt(1)
+ maxPriorityFeePerGas := big.NewInt(3)
+ maxPrice := assets.NewWeiI(2)
+
+ feeHistoryResult := ðereum.FeeHistory{
+ OldestBlock: big.NewInt(1),
+ Reward: [][]*big.Int{{maxPriorityFeePerGas, big.NewInt(5)}}, // first one represents market price and second one connectivity price
+ BaseFee: []*big.Int{baseFee},
+ GasUsedRatio: nil,
+ }
+ client.On("FeeHistory", mock.Anything, mock.Anything, mock.Anything).Return(feeHistoryResult, nil).Once()
+
+ cfg := gas.FeeHistoryEstimatorConfig{BlockHistorySize: 1}
+
+ u := gas.NewFeeHistoryEstimator(logger.Test(t), client, cfg, chainID, nil)
+ err := u.RefreshDynamicPrice()
+ assert.NoError(t, err)
+ dynamicFee, err := u.GetDynamicFee(tests.Context(t), maxPrice)
+ assert.NoError(t, err)
+ assert.Equal(t, maxPrice, dynamicFee.FeeCap)
+ assert.Equal(t, maxPrice, dynamicFee.TipCap)
+ })
+}
+
+func TestFeeHistoryEstimatorBumpDynamicFee(t *testing.T) {
+ t.Parallel()
+
+ globalMaxPrice := assets.NewWeiI(100)
+ chainID := big.NewInt(0)
+
+ t.Run("bumps a previous attempt by BumpPercent", func(t *testing.T) {
+ client := mocks.NewFeeHistoryEstimatorClient(t)
+ originalFee := gas.DynamicFee{
+ FeeCap: assets.NewWeiI(20),
+ TipCap: assets.NewWeiI(10),
+ }
+
+ // These values will be ignored because they are lower prices than the originalFee
+ feeHistoryResult := ðereum.FeeHistory{
+ OldestBlock: big.NewInt(1),
+ Reward: [][]*big.Int{{big.NewInt(5), big.NewInt(50)}}, // first one represents market price and second one connectivity price
+ BaseFee: []*big.Int{big.NewInt(5)},
+ GasUsedRatio: nil,
+ }
+ client.On("FeeHistory", mock.Anything, mock.Anything, mock.Anything).Return(feeHistoryResult, nil).Once()
+
+ cfg := gas.FeeHistoryEstimatorConfig{
+ BlockHistorySize: 2,
+ BumpPercent: 50,
+ }
+
+ expectedFeeCap := originalFee.FeeCap.AddPercentage(cfg.BumpPercent)
+ expectedTipCap := originalFee.TipCap.AddPercentage(cfg.BumpPercent)
+
+ u := gas.NewFeeHistoryEstimator(logger.Test(t), client, cfg, chainID, nil)
+ err := u.RefreshDynamicPrice()
+ assert.NoError(t, err)
+ dynamicFee, err := u.BumpDynamicFee(tests.Context(t), originalFee, globalMaxPrice, nil)
+ assert.NoError(t, err)
+ assert.Equal(t, expectedFeeCap, dynamicFee.FeeCap)
+ assert.Equal(t, expectedTipCap, dynamicFee.TipCap)
+ })
+
+ t.Run("fails if the original attempt is invalid", func(t *testing.T) {
+ client := mocks.NewFeeHistoryEstimatorClient(t)
+ maxPrice := assets.NewWeiI(20)
+ cfg := gas.FeeHistoryEstimatorConfig{BlockHistorySize: 1}
+
+ u := gas.NewFeeHistoryEstimator(logger.Test(t), client, cfg, chainID, nil)
+ // nil original fee
+ var originalFee gas.DynamicFee
+ _, err := u.BumpDynamicFee(tests.Context(t), originalFee, maxPrice, nil)
+ assert.Error(t, err)
+
+ // tip cap is higher than fee cap
+ originalFee = gas.DynamicFee{
+ FeeCap: assets.NewWeiI(10),
+ TipCap: assets.NewWeiI(11),
+ }
+ _, err = u.BumpDynamicFee(tests.Context(t), originalFee, maxPrice, nil)
+ assert.Error(t, err)
+
+ // fee cap is equal or higher to max price
+ originalFee = gas.DynamicFee{
+ FeeCap: assets.NewWeiI(20),
+ TipCap: assets.NewWeiI(10),
+ }
+ _, err = u.BumpDynamicFee(tests.Context(t), originalFee, maxPrice, nil)
+ assert.Error(t, err)
+ })
+
+ t.Run("returns market prices if bumped original fee is lower", func(t *testing.T) {
+ client := mocks.NewFeeHistoryEstimatorClient(t)
+ originalFee := gas.DynamicFee{
+ FeeCap: assets.NewWeiI(20),
+ TipCap: assets.NewWeiI(10),
+ }
+
+ // Market fees
+ baseFee := big.NewInt(5)
+ maxPriorityFeePerGas := big.NewInt(33)
+ feeHistoryResult := ðereum.FeeHistory{
+ OldestBlock: big.NewInt(1),
+ Reward: [][]*big.Int{{maxPriorityFeePerGas, big.NewInt(100)}}, // first one represents market price and second one connectivity price
+ BaseFee: []*big.Int{baseFee},
+ GasUsedRatio: nil,
+ }
+ client.On("FeeHistory", mock.Anything, mock.Anything, mock.Anything).Return(feeHistoryResult, nil).Once()
+
+ maxFee := (*assets.Wei)(baseFee).AddPercentage(gas.BaseFeeBufferPercentage).Add((*assets.Wei)(maxPriorityFeePerGas))
+
+ cfg := gas.FeeHistoryEstimatorConfig{
+ BlockHistorySize: 1,
+ BumpPercent: 50,
+ }
+
+ u := gas.NewFeeHistoryEstimator(logger.Test(t), client, cfg, chainID, nil)
+ err := u.RefreshDynamicPrice()
+ assert.NoError(t, err)
+ bumpedFee, err := u.BumpDynamicFee(tests.Context(t), originalFee, globalMaxPrice, nil)
+ assert.NoError(t, err)
+ assert.Equal(t, (*assets.Wei)(maxPriorityFeePerGas), bumpedFee.TipCap)
+ assert.Equal(t, maxFee, bumpedFee.FeeCap)
+ })
+
+ t.Run("fails if connectivity percentile value is reached", func(t *testing.T) {
+ client := mocks.NewFeeHistoryEstimatorClient(t)
+ originalFee := gas.DynamicFee{
+ FeeCap: assets.NewWeiI(20),
+ TipCap: assets.NewWeiI(10),
+ }
+
+ // Market fees
+ baseFee := big.NewInt(5)
+ maxPriorityFeePerGas := big.NewInt(33)
+ feeHistoryResult := ðereum.FeeHistory{
+ OldestBlock: big.NewInt(1),
+ Reward: [][]*big.Int{{maxPriorityFeePerGas, big.NewInt(30)}}, // first one represents market price and second one connectivity price
+ BaseFee: []*big.Int{baseFee},
+ GasUsedRatio: nil,
+ }
+ client.On("FeeHistory", mock.Anything, mock.Anything, mock.Anything).Return(feeHistoryResult, nil).Once()
+
+ cfg := gas.FeeHistoryEstimatorConfig{
+ BlockHistorySize: 1,
+ BumpPercent: 50,
+ }
+
+ u := gas.NewFeeHistoryEstimator(logger.Test(t), client, cfg, chainID, nil)
+ err := u.RefreshDynamicPrice()
+ assert.NoError(t, err)
+ _, err = u.BumpDynamicFee(tests.Context(t), originalFee, globalMaxPrice, nil)
+ assert.Error(t, err)
+ })
+
+ t.Run("returns max price if the aggregation of max and original bumped fee is higher", func(t *testing.T) {
+ client := mocks.NewFeeHistoryEstimatorClient(t)
+ originalFee := gas.DynamicFee{
+ FeeCap: assets.NewWeiI(20),
+ TipCap: assets.NewWeiI(18),
+ }
+
+ maxPrice := assets.NewWeiI(25)
+ // Market fees
+ baseFee := big.NewInt(1)
+ maxPriorityFeePerGas := big.NewInt(1)
+ feeHistoryResult := ðereum.FeeHistory{
+ OldestBlock: big.NewInt(1),
+ Reward: [][]*big.Int{{maxPriorityFeePerGas, big.NewInt(30)}}, // first one represents market price and second one connectivity price
+ BaseFee: []*big.Int{baseFee},
+ GasUsedRatio: nil,
+ }
+ client.On("FeeHistory", mock.Anything, mock.Anything, mock.Anything).Return(feeHistoryResult, nil).Once()
+
+ cfg := gas.FeeHistoryEstimatorConfig{
+ BlockHistorySize: 1,
+ BumpPercent: 50,
+ }
+
+ u := gas.NewFeeHistoryEstimator(logger.Test(t), client, cfg, chainID, nil)
+ err := u.RefreshDynamicPrice()
+ assert.NoError(t, err)
+ bumpedFee, err := u.BumpDynamicFee(tests.Context(t), originalFee, maxPrice, nil)
+ assert.NoError(t, err)
+ assert.Equal(t, maxPrice, bumpedFee.TipCap)
+ assert.Equal(t, maxPrice, bumpedFee.FeeCap)
+ })
+
+ t.Run("fails if the bumped gas price is lower than the minimum bump percentage", func(t *testing.T) {
+ client := mocks.NewFeeHistoryEstimatorClient(t)
+ originalFee := gas.DynamicFee{
+ FeeCap: assets.NewWeiI(20),
+ TipCap: assets.NewWeiI(18),
+ }
+
+ maxPrice := assets.NewWeiI(21)
+ // Market fees
+ baseFee := big.NewInt(1)
+ maxPriorityFeePerGas := big.NewInt(1)
+ feeHistoryResult := ðereum.FeeHistory{
+ OldestBlock: big.NewInt(1),
+ Reward: [][]*big.Int{{maxPriorityFeePerGas, big.NewInt(30)}}, // first one represents market price and second one connectivity price
+ BaseFee: []*big.Int{baseFee},
+ GasUsedRatio: nil,
+ }
+ client.On("FeeHistory", mock.Anything, mock.Anything, mock.Anything).Return(feeHistoryResult, nil).Once()
+
+ cfg := gas.FeeHistoryEstimatorConfig{
+ BlockHistorySize: 1,
+ BumpPercent: 50,
+ }
+
+ u := gas.NewFeeHistoryEstimator(logger.Test(t), client, cfg, chainID, nil)
+ err := u.RefreshDynamicPrice()
+ assert.NoError(t, err)
+ _, err = u.BumpDynamicFee(tests.Context(t), originalFee, maxPrice, nil)
+ assert.Error(t, err)
+ })
+
+ t.Run("ignores maxPriorityFeePerGas if there is no mempool and forces refetch", func(t *testing.T) {
+ client := mocks.NewFeeHistoryEstimatorClient(t)
+ originalFee := gas.DynamicFee{
+ FeeCap: assets.NewWeiI(40),
+ TipCap: assets.NewWeiI(0),
+ }
+
+ // Market fees
+ baseFee := big.NewInt(10)
+ maxPriorityFeePerGas := big.NewInt(0)
+ feeHistoryResult := ðereum.FeeHistory{
+ OldestBlock: big.NewInt(1),
+ Reward: [][]*big.Int{{maxPriorityFeePerGas, big.NewInt(0)}}, // first one represents market price and second one connectivity price
+ BaseFee: []*big.Int{baseFee},
+ GasUsedRatio: nil,
+ }
+ client.On("FeeHistory", mock.Anything, mock.Anything, mock.Anything).Return(feeHistoryResult, nil)
+
+ cfg := gas.FeeHistoryEstimatorConfig{
+ BlockHistorySize: 0,
+ BumpPercent: 20,
+ CacheTimeout: 10 * time.Second,
+ EIP1559: true,
+ }
+
+ maxFeePerGas := assets.NewWei(baseFee).AddPercentage(gas.BaseFeeBufferPercentage)
+ u := gas.NewFeeHistoryEstimator(logger.Test(t), client, cfg, chainID, nil)
+ servicetest.RunHealthy(t, u)
+ bumpedFee, err := u.BumpDynamicFee(tests.Context(t), originalFee, globalMaxPrice, nil)
+ assert.NoError(t, err)
+ assert.Equal(t, assets.NewWeiI(0), (*assets.Wei)(maxPriorityFeePerGas))
+ assert.Equal(t, maxFeePerGas, bumpedFee.FeeCap)
+ })
+}
diff --git a/core/chains/evm/gas/helpers_test.go b/core/chains/evm/gas/helpers_test.go
index 2c12ed426a..0c9a74a18d 100644
--- a/core/chains/evm/gas/helpers_test.go
+++ b/core/chains/evm/gas/helpers_test.go
@@ -157,6 +157,7 @@ type MockGasEstimatorConfig struct {
FeeCapDefaultF *assets.Wei
LimitMaxF uint64
ModeF string
+ EstimateLimitF bool
}
func NewMockGasConfig() *MockGasEstimatorConfig {
@@ -214,3 +215,7 @@ func (m *MockGasEstimatorConfig) LimitMax() uint64 {
func (m *MockGasEstimatorConfig) Mode() string {
return m.ModeF
}
+
+func (m *MockGasEstimatorConfig) EstimateLimit() bool {
+ return m.EstimateLimitF
+}
diff --git a/core/chains/evm/gas/mocks/evm_fee_estimator.go b/core/chains/evm/gas/mocks/evm_fee_estimator.go
index a9adc261ce..a7deca2c63 100644
--- a/core/chains/evm/gas/mocks/evm_fee_estimator.go
+++ b/core/chains/evm/gas/mocks/evm_fee_estimator.go
@@ -7,6 +7,8 @@ import (
assets "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets"
+ common "github.com/ethereum/go-ethereum/common"
+
context "context"
evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types"
@@ -145,14 +147,14 @@ func (_c *EvmFeeEstimator_Close_Call) RunAndReturn(run func() error) *EvmFeeEsti
return _c
}
-// GetFee provides a mock function with given fields: ctx, calldata, feeLimit, maxFeePrice, opts
-func (_m *EvmFeeEstimator) GetFee(ctx context.Context, calldata []byte, feeLimit uint64, maxFeePrice *assets.Wei, opts ...types.Opt) (gas.EvmFee, uint64, error) {
+// GetFee provides a mock function with given fields: ctx, calldata, feeLimit, maxFeePrice, fromAddress, toAddress, opts
+func (_m *EvmFeeEstimator) GetFee(ctx context.Context, calldata []byte, feeLimit uint64, maxFeePrice *assets.Wei, fromAddress *common.Address, toAddress *common.Address, opts ...types.Opt) (gas.EvmFee, uint64, error) {
_va := make([]interface{}, len(opts))
for _i := range opts {
_va[_i] = opts[_i]
}
var _ca []interface{}
- _ca = append(_ca, ctx, calldata, feeLimit, maxFeePrice)
+ _ca = append(_ca, ctx, calldata, feeLimit, maxFeePrice, fromAddress, toAddress)
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
@@ -163,23 +165,23 @@ func (_m *EvmFeeEstimator) GetFee(ctx context.Context, calldata []byte, feeLimit
var r0 gas.EvmFee
var r1 uint64
var r2 error
- if rf, ok := ret.Get(0).(func(context.Context, []byte, uint64, *assets.Wei, ...types.Opt) (gas.EvmFee, uint64, error)); ok {
- return rf(ctx, calldata, feeLimit, maxFeePrice, opts...)
+ if rf, ok := ret.Get(0).(func(context.Context, []byte, uint64, *assets.Wei, *common.Address, *common.Address, ...types.Opt) (gas.EvmFee, uint64, error)); ok {
+ return rf(ctx, calldata, feeLimit, maxFeePrice, fromAddress, toAddress, opts...)
}
- if rf, ok := ret.Get(0).(func(context.Context, []byte, uint64, *assets.Wei, ...types.Opt) gas.EvmFee); ok {
- r0 = rf(ctx, calldata, feeLimit, maxFeePrice, opts...)
+ if rf, ok := ret.Get(0).(func(context.Context, []byte, uint64, *assets.Wei, *common.Address, *common.Address, ...types.Opt) gas.EvmFee); ok {
+ r0 = rf(ctx, calldata, feeLimit, maxFeePrice, fromAddress, toAddress, opts...)
} else {
r0 = ret.Get(0).(gas.EvmFee)
}
- if rf, ok := ret.Get(1).(func(context.Context, []byte, uint64, *assets.Wei, ...types.Opt) uint64); ok {
- r1 = rf(ctx, calldata, feeLimit, maxFeePrice, opts...)
+ if rf, ok := ret.Get(1).(func(context.Context, []byte, uint64, *assets.Wei, *common.Address, *common.Address, ...types.Opt) uint64); ok {
+ r1 = rf(ctx, calldata, feeLimit, maxFeePrice, fromAddress, toAddress, opts...)
} else {
r1 = ret.Get(1).(uint64)
}
- if rf, ok := ret.Get(2).(func(context.Context, []byte, uint64, *assets.Wei, ...types.Opt) error); ok {
- r2 = rf(ctx, calldata, feeLimit, maxFeePrice, opts...)
+ if rf, ok := ret.Get(2).(func(context.Context, []byte, uint64, *assets.Wei, *common.Address, *common.Address, ...types.Opt) error); ok {
+ r2 = rf(ctx, calldata, feeLimit, maxFeePrice, fromAddress, toAddress, opts...)
} else {
r2 = ret.Error(2)
}
@@ -197,43 +199,45 @@ type EvmFeeEstimator_GetFee_Call struct {
// - calldata []byte
// - feeLimit uint64
// - maxFeePrice *assets.Wei
+// - fromAddress *common.Address
+// - toAddress *common.Address
// - opts ...types.Opt
-func (_e *EvmFeeEstimator_Expecter) GetFee(ctx interface{}, calldata interface{}, feeLimit interface{}, maxFeePrice interface{}, opts ...interface{}) *EvmFeeEstimator_GetFee_Call {
+func (_e *EvmFeeEstimator_Expecter) GetFee(ctx interface{}, calldata interface{}, feeLimit interface{}, maxFeePrice interface{}, fromAddress interface{}, toAddress interface{}, opts ...interface{}) *EvmFeeEstimator_GetFee_Call {
return &EvmFeeEstimator_GetFee_Call{Call: _e.mock.On("GetFee",
- append([]interface{}{ctx, calldata, feeLimit, maxFeePrice}, opts...)...)}
+ append([]interface{}{ctx, calldata, feeLimit, maxFeePrice, fromAddress, toAddress}, opts...)...)}
}
-func (_c *EvmFeeEstimator_GetFee_Call) Run(run func(ctx context.Context, calldata []byte, feeLimit uint64, maxFeePrice *assets.Wei, opts ...types.Opt)) *EvmFeeEstimator_GetFee_Call {
+func (_c *EvmFeeEstimator_GetFee_Call) Run(run func(ctx context.Context, calldata []byte, feeLimit uint64, maxFeePrice *assets.Wei, fromAddress *common.Address, toAddress *common.Address, opts ...types.Opt)) *EvmFeeEstimator_GetFee_Call {
_c.Call.Run(func(args mock.Arguments) {
- variadicArgs := make([]types.Opt, len(args)-4)
- for i, a := range args[4:] {
+ variadicArgs := make([]types.Opt, len(args)-6)
+ for i, a := range args[6:] {
if a != nil {
variadicArgs[i] = a.(types.Opt)
}
}
- run(args[0].(context.Context), args[1].([]byte), args[2].(uint64), args[3].(*assets.Wei), variadicArgs...)
+ run(args[0].(context.Context), args[1].([]byte), args[2].(uint64), args[3].(*assets.Wei), args[4].(*common.Address), args[5].(*common.Address), variadicArgs...)
})
return _c
}
-func (_c *EvmFeeEstimator_GetFee_Call) Return(fee gas.EvmFee, chainSpecificFeeLimit uint64, err error) *EvmFeeEstimator_GetFee_Call {
- _c.Call.Return(fee, chainSpecificFeeLimit, err)
+func (_c *EvmFeeEstimator_GetFee_Call) Return(fee gas.EvmFee, estimatedFeeLimit uint64, err error) *EvmFeeEstimator_GetFee_Call {
+ _c.Call.Return(fee, estimatedFeeLimit, err)
return _c
}
-func (_c *EvmFeeEstimator_GetFee_Call) RunAndReturn(run func(context.Context, []byte, uint64, *assets.Wei, ...types.Opt) (gas.EvmFee, uint64, error)) *EvmFeeEstimator_GetFee_Call {
+func (_c *EvmFeeEstimator_GetFee_Call) RunAndReturn(run func(context.Context, []byte, uint64, *assets.Wei, *common.Address, *common.Address, ...types.Opt) (gas.EvmFee, uint64, error)) *EvmFeeEstimator_GetFee_Call {
_c.Call.Return(run)
return _c
}
-// GetMaxCost provides a mock function with given fields: ctx, amount, calldata, feeLimit, maxFeePrice, opts
-func (_m *EvmFeeEstimator) GetMaxCost(ctx context.Context, amount assets.Eth, calldata []byte, feeLimit uint64, maxFeePrice *assets.Wei, opts ...types.Opt) (*big.Int, error) {
+// GetMaxCost provides a mock function with given fields: ctx, amount, calldata, feeLimit, maxFeePrice, fromAddress, toAddress, opts
+func (_m *EvmFeeEstimator) GetMaxCost(ctx context.Context, amount assets.Eth, calldata []byte, feeLimit uint64, maxFeePrice *assets.Wei, fromAddress *common.Address, toAddress *common.Address, opts ...types.Opt) (*big.Int, error) {
_va := make([]interface{}, len(opts))
for _i := range opts {
_va[_i] = opts[_i]
}
var _ca []interface{}
- _ca = append(_ca, ctx, amount, calldata, feeLimit, maxFeePrice)
+ _ca = append(_ca, ctx, amount, calldata, feeLimit, maxFeePrice, fromAddress, toAddress)
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
@@ -243,19 +247,19 @@ func (_m *EvmFeeEstimator) GetMaxCost(ctx context.Context, amount assets.Eth, ca
var r0 *big.Int
var r1 error
- if rf, ok := ret.Get(0).(func(context.Context, assets.Eth, []byte, uint64, *assets.Wei, ...types.Opt) (*big.Int, error)); ok {
- return rf(ctx, amount, calldata, feeLimit, maxFeePrice, opts...)
+ if rf, ok := ret.Get(0).(func(context.Context, assets.Eth, []byte, uint64, *assets.Wei, *common.Address, *common.Address, ...types.Opt) (*big.Int, error)); ok {
+ return rf(ctx, amount, calldata, feeLimit, maxFeePrice, fromAddress, toAddress, opts...)
}
- if rf, ok := ret.Get(0).(func(context.Context, assets.Eth, []byte, uint64, *assets.Wei, ...types.Opt) *big.Int); ok {
- r0 = rf(ctx, amount, calldata, feeLimit, maxFeePrice, opts...)
+ if rf, ok := ret.Get(0).(func(context.Context, assets.Eth, []byte, uint64, *assets.Wei, *common.Address, *common.Address, ...types.Opt) *big.Int); ok {
+ r0 = rf(ctx, amount, calldata, feeLimit, maxFeePrice, fromAddress, toAddress, opts...)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*big.Int)
}
}
- if rf, ok := ret.Get(1).(func(context.Context, assets.Eth, []byte, uint64, *assets.Wei, ...types.Opt) error); ok {
- r1 = rf(ctx, amount, calldata, feeLimit, maxFeePrice, opts...)
+ if rf, ok := ret.Get(1).(func(context.Context, assets.Eth, []byte, uint64, *assets.Wei, *common.Address, *common.Address, ...types.Opt) error); ok {
+ r1 = rf(ctx, amount, calldata, feeLimit, maxFeePrice, fromAddress, toAddress, opts...)
} else {
r1 = ret.Error(1)
}
@@ -274,21 +278,23 @@ type EvmFeeEstimator_GetMaxCost_Call struct {
// - calldata []byte
// - feeLimit uint64
// - maxFeePrice *assets.Wei
+// - fromAddress *common.Address
+// - toAddress *common.Address
// - opts ...types.Opt
-func (_e *EvmFeeEstimator_Expecter) GetMaxCost(ctx interface{}, amount interface{}, calldata interface{}, feeLimit interface{}, maxFeePrice interface{}, opts ...interface{}) *EvmFeeEstimator_GetMaxCost_Call {
+func (_e *EvmFeeEstimator_Expecter) GetMaxCost(ctx interface{}, amount interface{}, calldata interface{}, feeLimit interface{}, maxFeePrice interface{}, fromAddress interface{}, toAddress interface{}, opts ...interface{}) *EvmFeeEstimator_GetMaxCost_Call {
return &EvmFeeEstimator_GetMaxCost_Call{Call: _e.mock.On("GetMaxCost",
- append([]interface{}{ctx, amount, calldata, feeLimit, maxFeePrice}, opts...)...)}
+ append([]interface{}{ctx, amount, calldata, feeLimit, maxFeePrice, fromAddress, toAddress}, opts...)...)}
}
-func (_c *EvmFeeEstimator_GetMaxCost_Call) Run(run func(ctx context.Context, amount assets.Eth, calldata []byte, feeLimit uint64, maxFeePrice *assets.Wei, opts ...types.Opt)) *EvmFeeEstimator_GetMaxCost_Call {
+func (_c *EvmFeeEstimator_GetMaxCost_Call) Run(run func(ctx context.Context, amount assets.Eth, calldata []byte, feeLimit uint64, maxFeePrice *assets.Wei, fromAddress *common.Address, toAddress *common.Address, opts ...types.Opt)) *EvmFeeEstimator_GetMaxCost_Call {
_c.Call.Run(func(args mock.Arguments) {
- variadicArgs := make([]types.Opt, len(args)-5)
- for i, a := range args[5:] {
+ variadicArgs := make([]types.Opt, len(args)-7)
+ for i, a := range args[7:] {
if a != nil {
variadicArgs[i] = a.(types.Opt)
}
}
- run(args[0].(context.Context), args[1].(assets.Eth), args[2].([]byte), args[3].(uint64), args[4].(*assets.Wei), variadicArgs...)
+ run(args[0].(context.Context), args[1].(assets.Eth), args[2].([]byte), args[3].(uint64), args[4].(*assets.Wei), args[5].(*common.Address), args[6].(*common.Address), variadicArgs...)
})
return _c
}
@@ -298,7 +304,7 @@ func (_c *EvmFeeEstimator_GetMaxCost_Call) Return(_a0 *big.Int, _a1 error) *EvmF
return _c
}
-func (_c *EvmFeeEstimator_GetMaxCost_Call) RunAndReturn(run func(context.Context, assets.Eth, []byte, uint64, *assets.Wei, ...types.Opt) (*big.Int, error)) *EvmFeeEstimator_GetMaxCost_Call {
+func (_c *EvmFeeEstimator_GetMaxCost_Call) RunAndReturn(run func(context.Context, assets.Eth, []byte, uint64, *assets.Wei, *common.Address, *common.Address, ...types.Opt) (*big.Int, error)) *EvmFeeEstimator_GetMaxCost_Call {
_c.Call.Return(run)
return _c
}
diff --git a/core/chains/evm/gas/mocks/fee_estimator_client.go b/core/chains/evm/gas/mocks/fee_estimator_client.go
index 8e10107597..f5ca52ec92 100644
--- a/core/chains/evm/gas/mocks/fee_estimator_client.go
+++ b/core/chains/evm/gas/mocks/fee_estimator_client.go
@@ -241,6 +241,123 @@ func (_c *FeeEstimatorClient_ConfiguredChainID_Call) RunAndReturn(run func() *bi
return _c
}
+// EstimateGas provides a mock function with given fields: ctx, call
+func (_m *FeeEstimatorClient) EstimateGas(ctx context.Context, call ethereum.CallMsg) (uint64, error) {
+ ret := _m.Called(ctx, call)
+
+ if len(ret) == 0 {
+ panic("no return value specified for EstimateGas")
+ }
+
+ var r0 uint64
+ var r1 error
+ if rf, ok := ret.Get(0).(func(context.Context, ethereum.CallMsg) (uint64, error)); ok {
+ return rf(ctx, call)
+ }
+ if rf, ok := ret.Get(0).(func(context.Context, ethereum.CallMsg) uint64); ok {
+ r0 = rf(ctx, call)
+ } else {
+ r0 = ret.Get(0).(uint64)
+ }
+
+ if rf, ok := ret.Get(1).(func(context.Context, ethereum.CallMsg) error); ok {
+ r1 = rf(ctx, call)
+ } else {
+ r1 = ret.Error(1)
+ }
+
+ return r0, r1
+}
+
+// FeeEstimatorClient_EstimateGas_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'EstimateGas'
+type FeeEstimatorClient_EstimateGas_Call struct {
+ *mock.Call
+}
+
+// EstimateGas is a helper method to define mock.On call
+// - ctx context.Context
+// - call ethereum.CallMsg
+func (_e *FeeEstimatorClient_Expecter) EstimateGas(ctx interface{}, call interface{}) *FeeEstimatorClient_EstimateGas_Call {
+ return &FeeEstimatorClient_EstimateGas_Call{Call: _e.mock.On("EstimateGas", ctx, call)}
+}
+
+func (_c *FeeEstimatorClient_EstimateGas_Call) Run(run func(ctx context.Context, call ethereum.CallMsg)) *FeeEstimatorClient_EstimateGas_Call {
+ _c.Call.Run(func(args mock.Arguments) {
+ run(args[0].(context.Context), args[1].(ethereum.CallMsg))
+ })
+ return _c
+}
+
+func (_c *FeeEstimatorClient_EstimateGas_Call) Return(_a0 uint64, _a1 error) *FeeEstimatorClient_EstimateGas_Call {
+ _c.Call.Return(_a0, _a1)
+ return _c
+}
+
+func (_c *FeeEstimatorClient_EstimateGas_Call) RunAndReturn(run func(context.Context, ethereum.CallMsg) (uint64, error)) *FeeEstimatorClient_EstimateGas_Call {
+ _c.Call.Return(run)
+ return _c
+}
+
+// FeeHistory provides a mock function with given fields: ctx, blockCount, rewardPercentiles
+func (_m *FeeEstimatorClient) FeeHistory(ctx context.Context, blockCount uint64, rewardPercentiles []float64) (*ethereum.FeeHistory, error) {
+ ret := _m.Called(ctx, blockCount, rewardPercentiles)
+
+ if len(ret) == 0 {
+ panic("no return value specified for FeeHistory")
+ }
+
+ var r0 *ethereum.FeeHistory
+ var r1 error
+ if rf, ok := ret.Get(0).(func(context.Context, uint64, []float64) (*ethereum.FeeHistory, error)); ok {
+ return rf(ctx, blockCount, rewardPercentiles)
+ }
+ if rf, ok := ret.Get(0).(func(context.Context, uint64, []float64) *ethereum.FeeHistory); ok {
+ r0 = rf(ctx, blockCount, rewardPercentiles)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(*ethereum.FeeHistory)
+ }
+ }
+
+ if rf, ok := ret.Get(1).(func(context.Context, uint64, []float64) error); ok {
+ r1 = rf(ctx, blockCount, rewardPercentiles)
+ } else {
+ r1 = ret.Error(1)
+ }
+
+ return r0, r1
+}
+
+// FeeEstimatorClient_FeeHistory_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FeeHistory'
+type FeeEstimatorClient_FeeHistory_Call struct {
+ *mock.Call
+}
+
+// FeeHistory is a helper method to define mock.On call
+// - ctx context.Context
+// - blockCount uint64
+// - rewardPercentiles []float64
+func (_e *FeeEstimatorClient_Expecter) FeeHistory(ctx interface{}, blockCount interface{}, rewardPercentiles interface{}) *FeeEstimatorClient_FeeHistory_Call {
+ return &FeeEstimatorClient_FeeHistory_Call{Call: _e.mock.On("FeeHistory", ctx, blockCount, rewardPercentiles)}
+}
+
+func (_c *FeeEstimatorClient_FeeHistory_Call) Run(run func(ctx context.Context, blockCount uint64, rewardPercentiles []float64)) *FeeEstimatorClient_FeeHistory_Call {
+ _c.Call.Run(func(args mock.Arguments) {
+ run(args[0].(context.Context), args[1].(uint64), args[2].([]float64))
+ })
+ return _c
+}
+
+func (_c *FeeEstimatorClient_FeeHistory_Call) Return(feeHistory *ethereum.FeeHistory, err error) *FeeEstimatorClient_FeeHistory_Call {
+ _c.Call.Return(feeHistory, err)
+ return _c
+}
+
+func (_c *FeeEstimatorClient_FeeHistory_Call) RunAndReturn(run func(context.Context, uint64, []float64) (*ethereum.FeeHistory, error)) *FeeEstimatorClient_FeeHistory_Call {
+ _c.Call.Return(run)
+ return _c
+}
+
// HeadByNumber provides a mock function with given fields: ctx, n
func (_m *FeeEstimatorClient) HeadByNumber(ctx context.Context, n *big.Int) (*types.Head, error) {
ret := _m.Called(ctx, n)
@@ -300,6 +417,64 @@ func (_c *FeeEstimatorClient_HeadByNumber_Call) RunAndReturn(run func(context.Co
return _c
}
+// SuggestGasPrice provides a mock function with given fields: ctx
+func (_m *FeeEstimatorClient) SuggestGasPrice(ctx context.Context) (*big.Int, error) {
+ ret := _m.Called(ctx)
+
+ if len(ret) == 0 {
+ panic("no return value specified for SuggestGasPrice")
+ }
+
+ var r0 *big.Int
+ var r1 error
+ if rf, ok := ret.Get(0).(func(context.Context) (*big.Int, error)); ok {
+ return rf(ctx)
+ }
+ if rf, ok := ret.Get(0).(func(context.Context) *big.Int); ok {
+ r0 = rf(ctx)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(*big.Int)
+ }
+ }
+
+ if rf, ok := ret.Get(1).(func(context.Context) error); ok {
+ r1 = rf(ctx)
+ } else {
+ r1 = ret.Error(1)
+ }
+
+ return r0, r1
+}
+
+// FeeEstimatorClient_SuggestGasPrice_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SuggestGasPrice'
+type FeeEstimatorClient_SuggestGasPrice_Call struct {
+ *mock.Call
+}
+
+// SuggestGasPrice is a helper method to define mock.On call
+// - ctx context.Context
+func (_e *FeeEstimatorClient_Expecter) SuggestGasPrice(ctx interface{}) *FeeEstimatorClient_SuggestGasPrice_Call {
+ return &FeeEstimatorClient_SuggestGasPrice_Call{Call: _e.mock.On("SuggestGasPrice", ctx)}
+}
+
+func (_c *FeeEstimatorClient_SuggestGasPrice_Call) Run(run func(ctx context.Context)) *FeeEstimatorClient_SuggestGasPrice_Call {
+ _c.Call.Run(func(args mock.Arguments) {
+ run(args[0].(context.Context))
+ })
+ return _c
+}
+
+func (_c *FeeEstimatorClient_SuggestGasPrice_Call) Return(_a0 *big.Int, _a1 error) *FeeEstimatorClient_SuggestGasPrice_Call {
+ _c.Call.Return(_a0, _a1)
+ return _c
+}
+
+func (_c *FeeEstimatorClient_SuggestGasPrice_Call) RunAndReturn(run func(context.Context) (*big.Int, error)) *FeeEstimatorClient_SuggestGasPrice_Call {
+ _c.Call.Return(run)
+ return _c
+}
+
// NewFeeEstimatorClient creates a new instance of FeeEstimatorClient. 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 NewFeeEstimatorClient(t interface {
diff --git a/core/chains/evm/gas/mocks/fee_history_estimator_client.go b/core/chains/evm/gas/mocks/fee_history_estimator_client.go
new file mode 100644
index 0000000000..7486214501
--- /dev/null
+++ b/core/chains/evm/gas/mocks/fee_history_estimator_client.go
@@ -0,0 +1,157 @@
+// Code generated by mockery v2.43.2. DO NOT EDIT.
+
+package mocks
+
+import (
+ context "context"
+ big "math/big"
+
+ ethereum "github.com/ethereum/go-ethereum"
+
+ mock "github.com/stretchr/testify/mock"
+)
+
+// FeeHistoryEstimatorClient is an autogenerated mock type for the feeHistoryEstimatorClient type
+type FeeHistoryEstimatorClient struct {
+ mock.Mock
+}
+
+type FeeHistoryEstimatorClient_Expecter struct {
+ mock *mock.Mock
+}
+
+func (_m *FeeHistoryEstimatorClient) EXPECT() *FeeHistoryEstimatorClient_Expecter {
+ return &FeeHistoryEstimatorClient_Expecter{mock: &_m.Mock}
+}
+
+// FeeHistory provides a mock function with given fields: ctx, blockCount, rewardPercentiles
+func (_m *FeeHistoryEstimatorClient) FeeHistory(ctx context.Context, blockCount uint64, rewardPercentiles []float64) (*ethereum.FeeHistory, error) {
+ ret := _m.Called(ctx, blockCount, rewardPercentiles)
+
+ if len(ret) == 0 {
+ panic("no return value specified for FeeHistory")
+ }
+
+ var r0 *ethereum.FeeHistory
+ var r1 error
+ if rf, ok := ret.Get(0).(func(context.Context, uint64, []float64) (*ethereum.FeeHistory, error)); ok {
+ return rf(ctx, blockCount, rewardPercentiles)
+ }
+ if rf, ok := ret.Get(0).(func(context.Context, uint64, []float64) *ethereum.FeeHistory); ok {
+ r0 = rf(ctx, blockCount, rewardPercentiles)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(*ethereum.FeeHistory)
+ }
+ }
+
+ if rf, ok := ret.Get(1).(func(context.Context, uint64, []float64) error); ok {
+ r1 = rf(ctx, blockCount, rewardPercentiles)
+ } else {
+ r1 = ret.Error(1)
+ }
+
+ return r0, r1
+}
+
+// FeeHistoryEstimatorClient_FeeHistory_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FeeHistory'
+type FeeHistoryEstimatorClient_FeeHistory_Call struct {
+ *mock.Call
+}
+
+// FeeHistory is a helper method to define mock.On call
+// - ctx context.Context
+// - blockCount uint64
+// - rewardPercentiles []float64
+func (_e *FeeHistoryEstimatorClient_Expecter) FeeHistory(ctx interface{}, blockCount interface{}, rewardPercentiles interface{}) *FeeHistoryEstimatorClient_FeeHistory_Call {
+ return &FeeHistoryEstimatorClient_FeeHistory_Call{Call: _e.mock.On("FeeHistory", ctx, blockCount, rewardPercentiles)}
+}
+
+func (_c *FeeHistoryEstimatorClient_FeeHistory_Call) Run(run func(ctx context.Context, blockCount uint64, rewardPercentiles []float64)) *FeeHistoryEstimatorClient_FeeHistory_Call {
+ _c.Call.Run(func(args mock.Arguments) {
+ run(args[0].(context.Context), args[1].(uint64), args[2].([]float64))
+ })
+ return _c
+}
+
+func (_c *FeeHistoryEstimatorClient_FeeHistory_Call) Return(feeHistory *ethereum.FeeHistory, err error) *FeeHistoryEstimatorClient_FeeHistory_Call {
+ _c.Call.Return(feeHistory, err)
+ return _c
+}
+
+func (_c *FeeHistoryEstimatorClient_FeeHistory_Call) RunAndReturn(run func(context.Context, uint64, []float64) (*ethereum.FeeHistory, error)) *FeeHistoryEstimatorClient_FeeHistory_Call {
+ _c.Call.Return(run)
+ return _c
+}
+
+// SuggestGasPrice provides a mock function with given fields: ctx
+func (_m *FeeHistoryEstimatorClient) SuggestGasPrice(ctx context.Context) (*big.Int, error) {
+ ret := _m.Called(ctx)
+
+ if len(ret) == 0 {
+ panic("no return value specified for SuggestGasPrice")
+ }
+
+ var r0 *big.Int
+ var r1 error
+ if rf, ok := ret.Get(0).(func(context.Context) (*big.Int, error)); ok {
+ return rf(ctx)
+ }
+ if rf, ok := ret.Get(0).(func(context.Context) *big.Int); ok {
+ r0 = rf(ctx)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(*big.Int)
+ }
+ }
+
+ if rf, ok := ret.Get(1).(func(context.Context) error); ok {
+ r1 = rf(ctx)
+ } else {
+ r1 = ret.Error(1)
+ }
+
+ return r0, r1
+}
+
+// FeeHistoryEstimatorClient_SuggestGasPrice_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SuggestGasPrice'
+type FeeHistoryEstimatorClient_SuggestGasPrice_Call struct {
+ *mock.Call
+}
+
+// SuggestGasPrice is a helper method to define mock.On call
+// - ctx context.Context
+func (_e *FeeHistoryEstimatorClient_Expecter) SuggestGasPrice(ctx interface{}) *FeeHistoryEstimatorClient_SuggestGasPrice_Call {
+ return &FeeHistoryEstimatorClient_SuggestGasPrice_Call{Call: _e.mock.On("SuggestGasPrice", ctx)}
+}
+
+func (_c *FeeHistoryEstimatorClient_SuggestGasPrice_Call) Run(run func(ctx context.Context)) *FeeHistoryEstimatorClient_SuggestGasPrice_Call {
+ _c.Call.Run(func(args mock.Arguments) {
+ run(args[0].(context.Context))
+ })
+ return _c
+}
+
+func (_c *FeeHistoryEstimatorClient_SuggestGasPrice_Call) Return(_a0 *big.Int, _a1 error) *FeeHistoryEstimatorClient_SuggestGasPrice_Call {
+ _c.Call.Return(_a0, _a1)
+ return _c
+}
+
+func (_c *FeeHistoryEstimatorClient_SuggestGasPrice_Call) RunAndReturn(run func(context.Context) (*big.Int, error)) *FeeHistoryEstimatorClient_SuggestGasPrice_Call {
+ _c.Call.Return(run)
+ return _c
+}
+
+// NewFeeHistoryEstimatorClient creates a new instance of FeeHistoryEstimatorClient. 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 NewFeeHistoryEstimatorClient(t interface {
+ mock.TestingT
+ Cleanup(func())
+}) *FeeHistoryEstimatorClient {
+ mock := &FeeHistoryEstimatorClient{}
+ mock.Mock.Test(t)
+
+ t.Cleanup(func() { mock.AssertExpectations(t) })
+
+ return mock
+}
diff --git a/core/chains/evm/gas/models.go b/core/chains/evm/gas/models.go
index c9607091fb..92ae439492 100644
--- a/core/chains/evm/gas/models.go
+++ b/core/chains/evm/gas/models.go
@@ -26,6 +26,9 @@ import (
evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types"
)
+// EstimateGasBuffer is a multiplier applied to estimated gas when the EstimateLimit feature is enabled
+const EstimateGasBuffer = float32(1.15)
+
// EvmFeeEstimator provides a unified interface that wraps EvmEstimator and can determine if legacy or dynamic fee estimation should be used
type EvmFeeEstimator interface {
services.Service
@@ -33,11 +36,11 @@ type EvmFeeEstimator interface {
// L1Oracle returns the L1 gas price oracle only if the chain has one, e.g. OP stack L2s and Arbitrum.
L1Oracle() rollups.L1Oracle
- GetFee(ctx context.Context, calldata []byte, feeLimit uint64, maxFeePrice *assets.Wei, opts ...feetypes.Opt) (fee EvmFee, chainSpecificFeeLimit uint64, err error)
+ GetFee(ctx context.Context, calldata []byte, feeLimit uint64, maxFeePrice *assets.Wei, fromAddress, toAddress *common.Address, opts ...feetypes.Opt) (fee EvmFee, estimatedFeeLimit uint64, err error)
BumpFee(ctx context.Context, originalFee EvmFee, feeLimit uint64, maxFeePrice *assets.Wei, attempts []EvmPriorAttempt) (bumpedFee EvmFee, chainSpecificFeeLimit uint64, err error)
// GetMaxCost returns the total value = max price x fee units + transferred value
- GetMaxCost(ctx context.Context, amount assets.Eth, calldata []byte, feeLimit uint64, maxFeePrice *assets.Wei, opts ...feetypes.Opt) (*big.Int, error)
+ GetMaxCost(ctx context.Context, amount assets.Eth, calldata []byte, feeLimit uint64, maxFeePrice *assets.Wei, fromAddress, toAddress *common.Address, opts ...feetypes.Opt) (*big.Int, error)
}
type feeEstimatorClient interface {
@@ -46,6 +49,9 @@ type feeEstimatorClient interface {
CallContext(ctx context.Context, result interface{}, method string, args ...interface{}) error
ConfiguredChainID() *big.Int
HeadByNumber(ctx context.Context, n *big.Int) (*evmtypes.Head, error)
+ EstimateGas(ctx context.Context, call ethereum.CallMsg) (uint64, error)
+ SuggestGasPrice(ctx context.Context) (*big.Int, error)
+ FeeHistory(ctx context.Context, blockCount uint64, rewardPercentiles []float64) (feeHistory *ethereum.FeeHistory, err error)
}
// NewEstimator returns the estimator for a given config
@@ -70,6 +76,7 @@ func NewEstimator(lggr logger.Logger, ethClient feeEstimatorClient, cfg Config,
"tipCapMin", geCfg.TipCapMin(),
"priceMax", geCfg.PriceMax(),
"priceMin", geCfg.PriceMin(),
+ "estimateLimit", geCfg.EstimateLimit(),
)
df := geCfg.EIP1559DynamicFees()
@@ -105,13 +112,25 @@ func NewEstimator(lggr logger.Logger, ethClient feeEstimatorClient, cfg Config,
newEstimator = func(l logger.Logger) EvmEstimator {
return NewSuggestedPriceEstimator(lggr, ethClient, geCfg, l1Oracle)
}
+ case "FeeHistory":
+ newEstimator = func(l logger.Logger) EvmEstimator {
+ ccfg := FeeHistoryEstimatorConfig{
+ BumpPercent: geCfg.BumpPercent(),
+ CacheTimeout: geCfg.FeeHistory().CacheTimeout(),
+ EIP1559: geCfg.EIP1559DynamicFees(),
+ BlockHistorySize: uint64(geCfg.BlockHistory().BlockHistorySize()),
+ RewardPercentile: float64(geCfg.BlockHistory().TransactionPercentile()),
+ }
+ return NewFeeHistoryEstimator(lggr, ethClient, ccfg, ethClient.ConfiguredChainID(), l1Oracle)
+ }
+
default:
lggr.Warnf("GasEstimator: unrecognised mode '%s', falling back to FixedPriceEstimator", s)
newEstimator = func(l logger.Logger) EvmEstimator {
return NewFixedPriceEstimator(geCfg, ethClient, bh, lggr, l1Oracle)
}
}
- return NewEvmFeeEstimator(lggr, newEstimator, df, geCfg), nil
+ return NewEvmFeeEstimator(lggr, newEstimator, df, geCfg, ethClient), nil
}
// DynamicFee encompasses both FeeCap and TipCap for EIP1559 transactions
@@ -182,17 +201,19 @@ type evmFeeEstimator struct {
EvmEstimator
EIP1559Enabled bool
geCfg GasEstimatorConfig
+ ethClient feeEstimatorClient
}
var _ EvmFeeEstimator = (*evmFeeEstimator)(nil)
-func NewEvmFeeEstimator(lggr logger.Logger, newEstimator func(logger.Logger) EvmEstimator, eip1559Enabled bool, geCfg GasEstimatorConfig) EvmFeeEstimator {
+func NewEvmFeeEstimator(lggr logger.Logger, newEstimator func(logger.Logger) EvmEstimator, eip1559Enabled bool, geCfg GasEstimatorConfig, ethClient feeEstimatorClient) EvmFeeEstimator {
lggr = logger.Named(lggr, "WrappedEvmEstimator")
return &evmFeeEstimator{
lggr: lggr,
EvmEstimator: newEstimator(lggr),
EIP1559Enabled: eip1559Enabled,
geCfg: geCfg,
+ ethClient: ethClient,
}
}
@@ -262,7 +283,10 @@ func (e *evmFeeEstimator) L1Oracle() rollups.L1Oracle {
return e.EvmEstimator.L1Oracle()
}
-func (e *evmFeeEstimator) GetFee(ctx context.Context, calldata []byte, feeLimit uint64, maxFeePrice *assets.Wei, opts ...feetypes.Opt) (fee EvmFee, chainSpecificFeeLimit uint64, err error) {
+// GetFee returns an initial estimated gas price and gas limit for a transaction
+// The gas limit provided by the caller can be adjusted by gas estimation or for 2D fees
+func (e *evmFeeEstimator) GetFee(ctx context.Context, calldata []byte, feeLimit uint64, maxFeePrice *assets.Wei, fromAddress, toAddress *common.Address, opts ...feetypes.Opt) (fee EvmFee, estimatedFeeLimit uint64, err error) {
+ var chainSpecificFeeLimit uint64
// get dynamic fee
if e.EIP1559Enabled {
var dynamicFee DynamicFee
@@ -270,24 +294,23 @@ func (e *evmFeeEstimator) GetFee(ctx context.Context, calldata []byte, feeLimit
if err != nil {
return
}
- chainSpecificFeeLimit, err = commonfee.ApplyMultiplier(feeLimit, e.geCfg.LimitMultiplier())
fee.DynamicFeeCap = dynamicFee.FeeCap
fee.DynamicTipCap = dynamicFee.TipCap
- return
- }
-
- // get legacy fee
- fee.Legacy, chainSpecificFeeLimit, err = e.EvmEstimator.GetLegacyGas(ctx, calldata, feeLimit, maxFeePrice, opts...)
- if err != nil {
- return
+ chainSpecificFeeLimit = feeLimit
+ } else {
+ // get legacy fee
+ fee.Legacy, chainSpecificFeeLimit, err = e.EvmEstimator.GetLegacyGas(ctx, calldata, feeLimit, maxFeePrice, opts...)
+ if err != nil {
+ return
+ }
}
- chainSpecificFeeLimit, err = commonfee.ApplyMultiplier(chainSpecificFeeLimit, e.geCfg.LimitMultiplier())
+ estimatedFeeLimit, err = e.estimateFeeLimit(ctx, chainSpecificFeeLimit, calldata, fromAddress, toAddress)
return
}
-func (e *evmFeeEstimator) GetMaxCost(ctx context.Context, amount assets.Eth, calldata []byte, feeLimit uint64, maxFeePrice *assets.Wei, opts ...feetypes.Opt) (*big.Int, error) {
- fees, gasLimit, err := e.GetFee(ctx, calldata, feeLimit, maxFeePrice, opts...)
+func (e *evmFeeEstimator) GetMaxCost(ctx context.Context, amount assets.Eth, calldata []byte, feeLimit uint64, maxFeePrice *assets.Wei, fromAddress, toAddress *common.Address, opts ...feetypes.Opt) (*big.Int, error) {
+ fees, gasLimit, err := e.GetFee(ctx, calldata, feeLimit, maxFeePrice, fromAddress, toAddress, opts...)
if err != nil {
return nil, err
}
@@ -338,6 +361,56 @@ func (e *evmFeeEstimator) BumpFee(ctx context.Context, originalFee EvmFee, feeLi
return
}
+func (e *evmFeeEstimator) estimateFeeLimit(ctx context.Context, feeLimit uint64, calldata []byte, fromAddress, toAddress *common.Address) (estimatedFeeLimit uint64, err error) {
+ // Use the feeLimit * LimitMultiplier as the provided gas limit since this multiplier is applied on top of the caller specified gas limit
+ providedGasLimit, err := commonfee.ApplyMultiplier(feeLimit, e.geCfg.LimitMultiplier())
+ if err != nil {
+ return estimatedFeeLimit, err
+ }
+ // Use provided fee limit by default if EstimateLimit is disabled
+ if !e.geCfg.EstimateLimit() {
+ return providedGasLimit, nil
+ }
+ // Create call msg for gas limit estimation
+ // Skip setting Gas to avoid capping the results of the estimation
+ callMsg := ethereum.CallMsg{
+ To: toAddress,
+ Data: calldata,
+ }
+ if fromAddress != nil {
+ callMsg.From = *fromAddress
+ }
+ estimatedGas, estimateErr := e.ethClient.EstimateGas(ctx, callMsg)
+ if estimateErr != nil {
+ if providedGasLimit > 0 {
+ // Do not return error if estimate gas failed, we can still use the provided limit instead since it is an upper limit
+ e.lggr.Errorw("failed to estimate gas limit. falling back to the provided gas limit with multiplier", "callMsg", callMsg, "providedGasLimitWithMultiplier", providedGasLimit, "error", estimateErr)
+ return providedGasLimit, nil
+ }
+ return estimatedFeeLimit, fmt.Errorf("gas estimation failed and provided gas limit is 0: %w", estimateErr)
+ }
+ e.lggr.Debugw("estimated gas", "estimatedGas", estimatedGas, "providedGasLimitWithMultiplier", providedGasLimit)
+ // Return error if estimated gas without the buffer exceeds the provided gas limit, if provided
+ // Transaction would be destined to run out of gas and fail
+ if providedGasLimit > 0 && estimatedGas > providedGasLimit {
+ e.lggr.Errorw("estimated gas exceeds provided gas limit with multiplier", "estimatedGas", estimatedGas, "providedGasLimitWithMultiplier", providedGasLimit)
+ return estimatedFeeLimit, commonfee.ErrFeeLimitTooLow
+ }
+ // Apply EstimateGasBuffer to the estimated gas limit
+ estimatedFeeLimit, err = commonfee.ApplyMultiplier(estimatedGas, EstimateGasBuffer)
+ if err != nil {
+ return
+ }
+ // If provided gas limit is not 0, fallback to it if the buffer causes the estimated gas limit to exceed it
+ // The provided gas limit should be used as an upper bound to avoid unexpected behavior for products
+ if providedGasLimit > 0 && estimatedFeeLimit > providedGasLimit {
+ e.lggr.Debugw("estimated gas limit with buffer exceeds the provided gas limit with multiplier. falling back to the provided gas limit with multiplier", "estimatedGasLimit", estimatedFeeLimit, "providedGasLimitWithMultiplier", providedGasLimit)
+ estimatedFeeLimit = providedGasLimit
+ }
+
+ return
+}
+
// Config defines an interface for configuration in the gas package
type Config interface {
ChainType() chaintype.ChainType
@@ -359,6 +432,7 @@ type GasEstimatorConfig interface {
PriceMin() *assets.Wei
PriceMax() *assets.Wei
Mode() string
+ EstimateLimit() bool
}
type BlockHistoryConfig interface {
diff --git a/core/chains/evm/gas/models_test.go b/core/chains/evm/gas/models_test.go
index 92ea901596..f2afc26c85 100644
--- a/core/chains/evm/gas/models_test.go
+++ b/core/chains/evm/gas/models_test.go
@@ -1,6 +1,7 @@
package gas_test
import (
+ "errors"
"math/big"
"testing"
@@ -12,12 +13,14 @@ import (
"github.com/smartcontractkit/chainlink-common/pkg/logger"
"github.com/smartcontractkit/chainlink-common/pkg/utils/tests"
+ commonfee "github.com/smartcontractkit/chainlink/v2/common/fee"
"github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets"
"github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/chaintype"
"github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas"
"github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas/mocks"
"github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas/rollups"
rollupMocks "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas/rollups/mocks"
+ "github.com/smartcontractkit/chainlink/v2/core/chains/evm/testutils"
)
func TestWrappedEvmEstimator(t *testing.T) {
@@ -35,9 +38,9 @@ func TestWrappedEvmEstimator(t *testing.T) {
est := mocks.NewEvmEstimator(t)
est.On("GetDynamicFee", mock.Anything, mock.Anything).
- Return(dynamicFee, nil).Twice()
+ Return(dynamicFee, nil).Times(6)
est.On("GetLegacyGas", mock.Anything, mock.Anything, mock.Anything, mock.Anything).
- Return(legacyFee, gasLimit, nil).Twice()
+ Return(legacyFee, gasLimit, nil).Times(6)
est.On("BumpDynamicFee", mock.Anything, mock.Anything, mock.Anything, mock.Anything).
Return(dynamicFee, nil).Once()
est.On("BumpLegacyGas", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).
@@ -49,6 +52,9 @@ func TestWrappedEvmEstimator(t *testing.T) {
mockEstimatorName := "WrappedEvmEstimator"
mockEvmEstimatorName := "WrappedEvmEstimator.MockEstimator"
+ fromAddress := testutils.NewAddress()
+ toAddress := testutils.NewAddress()
+
// L1Oracle returns the correct L1Oracle interface
t.Run("L1Oracle", func(t *testing.T) {
lggr := logger.Test(t)
@@ -59,7 +65,7 @@ func TestWrappedEvmEstimator(t *testing.T) {
getEst := func(logger.Logger) gas.EvmEstimator { return evmEstimator }
// expect nil
- estimator := gas.NewEvmFeeEstimator(lggr, getEst, false, nil)
+ estimator := gas.NewEvmFeeEstimator(lggr, getEst, false, nil, nil)
l1Oracle := estimator.L1Oracle()
assert.Nil(t, l1Oracle)
@@ -68,7 +74,7 @@ func TestWrappedEvmEstimator(t *testing.T) {
oracle, err := rollups.NewL1GasOracle(lggr, nil, chaintype.ChainOptimismBedrock)
require.NoError(t, err)
// cast oracle to L1Oracle interface
- estimator = gas.NewEvmFeeEstimator(lggr, getEst, false, geCfg)
+ estimator = gas.NewEvmFeeEstimator(lggr, getEst, false, geCfg, nil)
evmEstimator.On("L1Oracle").Return(oracle).Once()
l1Oracle = estimator.L1Oracle()
@@ -80,8 +86,8 @@ func TestWrappedEvmEstimator(t *testing.T) {
lggr := logger.Test(t)
// expect legacy fee data
dynamicFees := false
- estimator := gas.NewEvmFeeEstimator(lggr, getRootEst, dynamicFees, geCfg)
- fee, max, err := estimator.GetFee(ctx, nil, 0, nil)
+ estimator := gas.NewEvmFeeEstimator(lggr, getRootEst, dynamicFees, geCfg, nil)
+ fee, max, err := estimator.GetFee(ctx, nil, 0, nil, nil, nil)
require.NoError(t, err)
assert.Equal(t, uint64(float32(gasLimit)*limitMultiplier), max)
assert.True(t, legacyFee.Equal(fee.Legacy))
@@ -90,8 +96,8 @@ func TestWrappedEvmEstimator(t *testing.T) {
// expect dynamic fee data
dynamicFees = true
- estimator = gas.NewEvmFeeEstimator(lggr, getRootEst, dynamicFees, geCfg)
- fee, max, err = estimator.GetFee(ctx, nil, gasLimit, nil)
+ estimator = gas.NewEvmFeeEstimator(lggr, getRootEst, dynamicFees, geCfg, nil)
+ fee, max, err = estimator.GetFee(ctx, nil, gasLimit, nil, nil, nil)
require.NoError(t, err)
assert.Equal(t, uint64(float32(gasLimit)*limitMultiplier), max)
assert.True(t, dynamicFee.FeeCap.Equal(fee.DynamicFeeCap))
@@ -103,7 +109,7 @@ func TestWrappedEvmEstimator(t *testing.T) {
t.Run("BumpFee", func(t *testing.T) {
lggr := logger.Test(t)
dynamicFees := false
- estimator := gas.NewEvmFeeEstimator(lggr, getRootEst, dynamicFees, geCfg)
+ estimator := gas.NewEvmFeeEstimator(lggr, getRootEst, dynamicFees, geCfg, nil)
// expect legacy fee data
fee, max, err := estimator.BumpFee(ctx, gas.EvmFee{Legacy: assets.NewWeiI(0)}, 0, nil, nil)
@@ -141,8 +147,8 @@ func TestWrappedEvmEstimator(t *testing.T) {
// expect legacy fee data
dynamicFees := false
- estimator := gas.NewEvmFeeEstimator(lggr, getRootEst, dynamicFees, geCfg)
- total, err := estimator.GetMaxCost(ctx, val, nil, gasLimit, nil)
+ estimator := gas.NewEvmFeeEstimator(lggr, getRootEst, dynamicFees, geCfg, nil)
+ total, err := estimator.GetMaxCost(ctx, val, nil, gasLimit, nil, nil, nil)
require.NoError(t, err)
fee := new(big.Int).Mul(legacyFee.ToInt(), big.NewInt(int64(gasLimit)))
fee, _ = new(big.Float).Mul(new(big.Float).SetInt(fee), big.NewFloat(float64(limitMultiplier))).Int(nil)
@@ -150,8 +156,8 @@ func TestWrappedEvmEstimator(t *testing.T) {
// expect dynamic fee data
dynamicFees = true
- estimator = gas.NewEvmFeeEstimator(lggr, getRootEst, dynamicFees, geCfg)
- total, err = estimator.GetMaxCost(ctx, val, nil, gasLimit, nil)
+ estimator = gas.NewEvmFeeEstimator(lggr, getRootEst, dynamicFees, geCfg, nil)
+ total, err = estimator.GetMaxCost(ctx, val, nil, gasLimit, nil, nil, nil)
require.NoError(t, err)
fee = new(big.Int).Mul(dynamicFee.FeeCap.ToInt(), big.NewInt(int64(gasLimit)))
fee, _ = new(big.Float).Mul(new(big.Float).SetInt(fee), big.NewFloat(float64(limitMultiplier))).Int(nil)
@@ -166,7 +172,7 @@ func TestWrappedEvmEstimator(t *testing.T) {
estimator := gas.NewEvmFeeEstimator(lggr, func(logger.Logger) gas.EvmEstimator {
return evmEstimator
- }, false, geCfg)
+ }, false, geCfg, nil)
require.Equal(t, mockEstimatorName, estimator.Name())
require.Equal(t, mockEvmEstimatorName, evmEstimator.Name())
@@ -185,7 +191,7 @@ func TestWrappedEvmEstimator(t *testing.T) {
evmEstimator.On("L1Oracle", mock.Anything).Return(nil).Twice()
- estimator := gas.NewEvmFeeEstimator(lggr, getEst, false, geCfg)
+ estimator := gas.NewEvmFeeEstimator(lggr, getEst, false, geCfg, nil)
err := estimator.Start(ctx)
require.NoError(t, err)
err = estimator.Close()
@@ -193,7 +199,7 @@ func TestWrappedEvmEstimator(t *testing.T) {
evmEstimator.On("L1Oracle", mock.Anything).Return(oracle).Twice()
- estimator = gas.NewEvmFeeEstimator(lggr, getEst, false, geCfg)
+ estimator = gas.NewEvmFeeEstimator(lggr, getEst, false, geCfg, nil)
err = estimator.Start(ctx)
require.NoError(t, err)
err = estimator.Close()
@@ -210,11 +216,11 @@ func TestWrappedEvmEstimator(t *testing.T) {
oracle.On("Ready").Return(nil).Twice()
getEst := func(logger.Logger) gas.EvmEstimator { return evmEstimator }
- estimator := gas.NewEvmFeeEstimator(lggr, getEst, false, geCfg)
+ estimator := gas.NewEvmFeeEstimator(lggr, getEst, false, geCfg, nil)
err := estimator.Ready()
require.NoError(t, err)
- estimator = gas.NewEvmFeeEstimator(lggr, getEst, false, geCfg)
+ estimator = gas.NewEvmFeeEstimator(lggr, getEst, false, geCfg, nil)
err = estimator.Ready()
require.NoError(t, err)
})
@@ -235,7 +241,7 @@ func TestWrappedEvmEstimator(t *testing.T) {
oracle.On("HealthReport").Return(map[string]error{oracleKey: oracleError}).Once()
getEst := func(logger.Logger) gas.EvmEstimator { return evmEstimator }
- estimator := gas.NewEvmFeeEstimator(lggr, getEst, false, geCfg)
+ estimator := gas.NewEvmFeeEstimator(lggr, getEst, false, geCfg, nil)
report := estimator.HealthReport()
require.True(t, pkgerrors.Is(report[evmEstimatorKey], evmEstimatorError))
require.Nil(t, report[oracleKey])
@@ -243,10 +249,160 @@ func TestWrappedEvmEstimator(t *testing.T) {
evmEstimator.On("L1Oracle").Return(oracle).Once()
- estimator = gas.NewEvmFeeEstimator(lggr, getEst, false, geCfg)
+ estimator = gas.NewEvmFeeEstimator(lggr, getEst, false, geCfg, nil)
report = estimator.HealthReport()
require.True(t, pkgerrors.Is(report[evmEstimatorKey], evmEstimatorError))
require.True(t, pkgerrors.Is(report[oracleKey], oracleError))
require.NotNil(t, report[mockEstimatorName])
})
+
+ t.Run("GetFee, estimate gas limit enabled, succeeds", func(t *testing.T) {
+ estimatedGasLimit := uint64(5)
+ lggr := logger.Test(t)
+ // expect legacy fee data
+ dynamicFees := false
+ geCfg.EstimateLimitF = true
+ ethClient := testutils.NewEthClientMockWithDefaultChain(t)
+ ethClient.On("EstimateGas", mock.Anything, mock.Anything).Return(estimatedGasLimit, nil).Twice()
+ estimator := gas.NewEvmFeeEstimator(lggr, getRootEst, dynamicFees, geCfg, ethClient)
+ fee, limit, err := estimator.GetFee(ctx, []byte{}, gasLimit, nil, &fromAddress, &toAddress)
+ require.NoError(t, err)
+ assert.Equal(t, uint64(float32(estimatedGasLimit)*gas.EstimateGasBuffer), limit)
+ assert.True(t, legacyFee.Equal(fee.Legacy))
+ assert.Nil(t, fee.DynamicTipCap)
+ assert.Nil(t, fee.DynamicFeeCap)
+
+ // expect dynamic fee data
+ dynamicFees = true
+ estimator = gas.NewEvmFeeEstimator(lggr, getRootEst, dynamicFees, geCfg, ethClient)
+ fee, limit, err = estimator.GetFee(ctx, []byte{}, gasLimit, nil, &fromAddress, &toAddress)
+ require.NoError(t, err)
+ assert.Equal(t, uint64(float32(estimatedGasLimit)*gas.EstimateGasBuffer), limit)
+ assert.True(t, dynamicFee.FeeCap.Equal(fee.DynamicFeeCap))
+ assert.True(t, dynamicFee.TipCap.Equal(fee.DynamicTipCap))
+ assert.Nil(t, fee.Legacy)
+ })
+
+ t.Run("GetFee, estimate gas limit enabled, estimate exceeds provided limit, returns error", func(t *testing.T) {
+ estimatedGasLimit := uint64(100)
+ lggr := logger.Test(t)
+ // expect legacy fee data
+ dynamicFees := false
+ geCfg.EstimateLimitF = true
+ ethClient := testutils.NewEthClientMockWithDefaultChain(t)
+ ethClient.On("EstimateGas", mock.Anything, mock.Anything).Return(estimatedGasLimit, nil).Twice()
+ estimator := gas.NewEvmFeeEstimator(lggr, getRootEst, dynamicFees, geCfg, ethClient)
+ _, _, err := estimator.GetFee(ctx, []byte{}, gasLimit, nil, &fromAddress, &toAddress)
+ require.ErrorIs(t, err, commonfee.ErrFeeLimitTooLow)
+
+ // expect dynamic fee data
+ dynamicFees = true
+ estimator = gas.NewEvmFeeEstimator(lggr, getRootEst, dynamicFees, geCfg, ethClient)
+ _, _, err = estimator.GetFee(ctx, []byte{}, gasLimit, nil, &fromAddress, &toAddress)
+ require.ErrorIs(t, err, commonfee.ErrFeeLimitTooLow)
+ })
+
+ t.Run("GetFee, estimate gas limit enabled, buffer exceeds provided limit, fallsback to provided limit", func(t *testing.T) {
+ estimatedGasLimit := uint64(15) // same as provided limit
+ lggr := logger.Test(t)
+ dynamicFees := false // expect legacy fee data
+ geCfg.EstimateLimitF = true
+ ethClient := testutils.NewEthClientMockWithDefaultChain(t)
+ ethClient.On("EstimateGas", mock.Anything, mock.Anything).Return(estimatedGasLimit, nil).Twice()
+ estimator := gas.NewEvmFeeEstimator(lggr, getRootEst, dynamicFees, geCfg, ethClient)
+ fee, limit, err := estimator.GetFee(ctx, []byte{}, gasLimit, nil, &fromAddress, &toAddress)
+ require.NoError(t, err)
+ assert.Equal(t, uint64(float32(gasLimit)*limitMultiplier), limit)
+ assert.True(t, legacyFee.Equal(fee.Legacy))
+ assert.Nil(t, fee.DynamicTipCap)
+ assert.Nil(t, fee.DynamicFeeCap)
+
+ dynamicFees = true // expect dynamic fee data
+ estimator = gas.NewEvmFeeEstimator(lggr, getRootEst, dynamicFees, geCfg, ethClient)
+ fee, limit, err = estimator.GetFee(ctx, []byte{}, gasLimit, nil, &fromAddress, &toAddress)
+ require.NoError(t, err)
+ assert.Equal(t, uint64(float32(gasLimit)*limitMultiplier), limit)
+ assert.True(t, dynamicFee.FeeCap.Equal(fee.DynamicFeeCap))
+ assert.True(t, dynamicFee.TipCap.Equal(fee.DynamicTipCap))
+ assert.Nil(t, fee.Legacy)
+ })
+
+ t.Run("GetFee, estimate gas limit enabled, RPC fails and fallsback to provided gas limit", func(t *testing.T) {
+ lggr := logger.Test(t)
+ // expect legacy fee data
+ dynamicFees := false
+ geCfg.EstimateLimitF = true
+ ethClient := testutils.NewEthClientMockWithDefaultChain(t)
+ ethClient.On("EstimateGas", mock.Anything, mock.Anything).Return(uint64(0), errors.New("something broke")).Twice()
+ estimator := gas.NewEvmFeeEstimator(lggr, getRootEst, dynamicFees, geCfg, ethClient)
+ fee, limit, err := estimator.GetFee(ctx, []byte{}, gasLimit, nil, &fromAddress, &toAddress)
+ require.NoError(t, err)
+ assert.Equal(t, uint64(float32(gasLimit)*limitMultiplier), limit)
+ assert.True(t, legacyFee.Equal(fee.Legacy))
+ assert.Nil(t, fee.DynamicTipCap)
+ assert.Nil(t, fee.DynamicFeeCap)
+
+ // expect dynamic fee data
+ dynamicFees = true
+ estimator = gas.NewEvmFeeEstimator(lggr, getRootEst, dynamicFees, geCfg, ethClient)
+ fee, limit, err = estimator.GetFee(ctx, []byte{}, gasLimit, nil, &fromAddress, &toAddress)
+ require.NoError(t, err)
+ assert.Equal(t, uint64(float32(gasLimit)*limitMultiplier), limit)
+ assert.True(t, dynamicFee.FeeCap.Equal(fee.DynamicFeeCap))
+ assert.True(t, dynamicFee.TipCap.Equal(fee.DynamicTipCap))
+ assert.Nil(t, fee.Legacy)
+ })
+
+ t.Run("GetFee, estimate gas limit enabled, provided fee limit 0, returns uncapped estimation", func(t *testing.T) {
+ est.On("GetDynamicFee", mock.Anything, mock.Anything).
+ Return(dynamicFee, nil).Once()
+ est.On("GetLegacyGas", mock.Anything, mock.Anything, uint64(0), mock.Anything).
+ Return(legacyFee, uint64(0), nil).Once()
+ estimatedGasLimit := uint64(100) // same as provided limit
+ lggr := logger.Test(t)
+ // expect legacy fee data
+ dynamicFees := false
+ geCfg.EstimateLimitF = true
+ ethClient := testutils.NewEthClientMockWithDefaultChain(t)
+ ethClient.On("EstimateGas", mock.Anything, mock.Anything).Return(estimatedGasLimit, nil).Twice()
+ estimator := gas.NewEvmFeeEstimator(lggr, getRootEst, dynamicFees, geCfg, ethClient)
+ fee, limit, err := estimator.GetFee(ctx, []byte{}, uint64(0), nil, &fromAddress, &toAddress)
+ require.NoError(t, err)
+ assert.Equal(t, uint64(float32(estimatedGasLimit)*gas.EstimateGasBuffer), limit)
+ assert.True(t, legacyFee.Equal(fee.Legacy))
+ assert.Nil(t, fee.DynamicTipCap)
+ assert.Nil(t, fee.DynamicFeeCap)
+
+ // expect dynamic fee data
+ dynamicFees = true
+ estimator = gas.NewEvmFeeEstimator(lggr, getRootEst, dynamicFees, geCfg, ethClient)
+ fee, limit, err = estimator.GetFee(ctx, []byte{}, 0, nil, &fromAddress, &toAddress)
+ require.NoError(t, err)
+ assert.Equal(t, uint64(float32(estimatedGasLimit)*gas.EstimateGasBuffer), limit)
+ assert.True(t, dynamicFee.FeeCap.Equal(fee.DynamicFeeCap))
+ assert.True(t, dynamicFee.TipCap.Equal(fee.DynamicTipCap))
+ assert.Nil(t, fee.Legacy)
+ })
+
+ t.Run("GetFee, estimate gas limit enabled, provided fee limit 0, returns error on failure", func(t *testing.T) {
+ est.On("GetDynamicFee", mock.Anything, mock.Anything).
+ Return(dynamicFee, nil).Once()
+ est.On("GetLegacyGas", mock.Anything, mock.Anything, uint64(0), mock.Anything).
+ Return(legacyFee, uint64(0), nil).Once()
+ lggr := logger.Test(t)
+ // expect legacy fee data
+ dynamicFees := false
+ geCfg.EstimateLimitF = true
+ ethClient := testutils.NewEthClientMockWithDefaultChain(t)
+ ethClient.On("EstimateGas", mock.Anything, mock.Anything).Return(uint64(0), errors.New("something broke")).Twice()
+ estimator := gas.NewEvmFeeEstimator(lggr, getRootEst, dynamicFees, geCfg, ethClient)
+ _, _, err := estimator.GetFee(ctx, []byte{}, 0, nil, &fromAddress, &toAddress)
+ require.Error(t, err)
+
+ // expect dynamic fee data
+ dynamicFees = true
+ estimator = gas.NewEvmFeeEstimator(lggr, getRootEst, dynamicFees, geCfg, ethClient)
+ _, _, err = estimator.GetFee(ctx, []byte{}, 0, nil, &fromAddress, &toAddress)
+ require.Error(t, err)
+ })
}
diff --git a/core/chains/evm/gas/rollups/arbitrum_l1_oracle.go b/core/chains/evm/gas/rollups/arbitrum_l1_oracle.go
index c01244db70..d758dc711e 100644
--- a/core/chains/evm/gas/rollups/arbitrum_l1_oracle.go
+++ b/core/chains/evm/gas/rollups/arbitrum_l1_oracle.go
@@ -112,6 +112,10 @@ func (o *arbitrumL1Oracle) Name() string {
return o.logger.Name()
}
+func (o *arbitrumL1Oracle) ChainType(_ context.Context) chaintype.ChainType {
+ return o.chainType
+}
+
func (o *arbitrumL1Oracle) Start(ctx context.Context) error {
return o.StartOnce(o.Name(), func() error {
go o.run()
diff --git a/core/chains/evm/gas/rollups/l1_oracle.go b/core/chains/evm/gas/rollups/l1_oracle.go
index f707fab684..4195175598 100644
--- a/core/chains/evm/gas/rollups/l1_oracle.go
+++ b/core/chains/evm/gas/rollups/l1_oracle.go
@@ -26,6 +26,7 @@ type L1Oracle interface {
GasPrice(ctx context.Context) (*assets.Wei, error)
GetGasCost(ctx context.Context, tx *types.Transaction, blockNum *big.Int) (*assets.Wei, error)
+ ChainType(ctx context.Context) chaintype.ChainType
}
type l1OracleClient interface {
@@ -43,7 +44,7 @@ const (
PollPeriod = 6 * time.Second
)
-var supportedChainTypes = []chaintype.ChainType{chaintype.ChainArbitrum, chaintype.ChainOptimismBedrock, chaintype.ChainKroma, chaintype.ChainScroll, chaintype.ChainZkSync}
+var supportedChainTypes = []chaintype.ChainType{chaintype.ChainArbitrum, chaintype.ChainOptimismBedrock, chaintype.ChainKroma, chaintype.ChainScroll, chaintype.ChainZkSync, chaintype.ChainMantle}
func IsRollupWithL1Support(chainType chaintype.ChainType) bool {
return slices.Contains(supportedChainTypes, chainType)
@@ -56,7 +57,7 @@ func NewL1GasOracle(lggr logger.Logger, ethClient l1OracleClient, chainType chai
var l1Oracle L1Oracle
var err error
switch chainType {
- case chaintype.ChainOptimismBedrock, chaintype.ChainKroma, chaintype.ChainScroll:
+ case chaintype.ChainOptimismBedrock, chaintype.ChainKroma, chaintype.ChainScroll, chaintype.ChainMantle:
l1Oracle, err = NewOpStackL1GasOracle(lggr, ethClient, chainType)
case chaintype.ChainArbitrum:
l1Oracle, err = NewArbitrumL1GasOracle(lggr, ethClient)
diff --git a/core/chains/evm/gas/rollups/mocks/l1_oracle.go b/core/chains/evm/gas/rollups/mocks/l1_oracle.go
index e82cb4ee90..25bb3a2db5 100644
--- a/core/chains/evm/gas/rollups/mocks/l1_oracle.go
+++ b/core/chains/evm/gas/rollups/mocks/l1_oracle.go
@@ -7,6 +7,8 @@ import (
assets "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets"
+ chaintype "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/chaintype"
+
context "context"
mock "github.com/stretchr/testify/mock"
@@ -27,6 +29,52 @@ func (_m *L1Oracle) EXPECT() *L1Oracle_Expecter {
return &L1Oracle_Expecter{mock: &_m.Mock}
}
+// ChainType provides a mock function with given fields: ctx
+func (_m *L1Oracle) ChainType(ctx context.Context) chaintype.ChainType {
+ ret := _m.Called(ctx)
+
+ if len(ret) == 0 {
+ panic("no return value specified for ChainType")
+ }
+
+ var r0 chaintype.ChainType
+ if rf, ok := ret.Get(0).(func(context.Context) chaintype.ChainType); ok {
+ r0 = rf(ctx)
+ } else {
+ r0 = ret.Get(0).(chaintype.ChainType)
+ }
+
+ return r0
+}
+
+// L1Oracle_ChainType_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ChainType'
+type L1Oracle_ChainType_Call struct {
+ *mock.Call
+}
+
+// ChainType is a helper method to define mock.On call
+// - ctx context.Context
+func (_e *L1Oracle_Expecter) ChainType(ctx interface{}) *L1Oracle_ChainType_Call {
+ return &L1Oracle_ChainType_Call{Call: _e.mock.On("ChainType", ctx)}
+}
+
+func (_c *L1Oracle_ChainType_Call) Run(run func(ctx context.Context)) *L1Oracle_ChainType_Call {
+ _c.Call.Run(func(args mock.Arguments) {
+ run(args[0].(context.Context))
+ })
+ return _c
+}
+
+func (_c *L1Oracle_ChainType_Call) Return(_a0 chaintype.ChainType) *L1Oracle_ChainType_Call {
+ _c.Call.Return(_a0)
+ return _c
+}
+
+func (_c *L1Oracle_ChainType_Call) RunAndReturn(run func(context.Context) chaintype.ChainType) *L1Oracle_ChainType_Call {
+ _c.Call.Return(run)
+ return _c
+}
+
// Close provides a mock function with given fields:
func (_m *L1Oracle) Close() error {
ret := _m.Called()
diff --git a/core/chains/evm/gas/rollups/op_l1_oracle.go b/core/chains/evm/gas/rollups/op_l1_oracle.go
index 6805cd7095..11babc5ca5 100644
--- a/core/chains/evm/gas/rollups/op_l1_oracle.go
+++ b/core/chains/evm/gas/rollups/op_l1_oracle.go
@@ -50,6 +50,7 @@ type optimismL1Oracle struct {
blobBaseFeeCalldata []byte
blobBaseFeeScalarCalldata []byte
decimalsCalldata []byte
+ tokenRatioCalldata []byte
isEcotoneCalldata []byte
isEcotoneMethodAbi abi.ABI
isFjordCalldata []byte
@@ -87,7 +88,7 @@ const (
// decimals is a hex encoded call to:
// `function decimals() public pure returns (uint256);`
decimalsMethod = "decimals"
- // OPGasOracleAddress is the address of the precompiled contract that exists on Optimism and Base.
+ // OPGasOracleAddress is the address of the precompiled contract that exists on Optimism, Base and Mantle.
OPGasOracleAddress = "0x420000000000000000000000000000000000000F"
// KromaGasOracleAddress is the address of the precompiled contract that exists on Kroma.
KromaGasOracleAddress = "0x4200000000000000000000000000000000000005"
@@ -98,7 +99,7 @@ const (
func NewOpStackL1GasOracle(lggr logger.Logger, ethClient l1OracleClient, chainType chaintype.ChainType) (*optimismL1Oracle, error) {
var precompileAddress string
switch chainType {
- case chaintype.ChainOptimismBedrock:
+ case chaintype.ChainOptimismBedrock, chaintype.ChainMantle:
precompileAddress = OPGasOracleAddress
case chaintype.ChainKroma:
precompileAddress = KromaGasOracleAddress
@@ -219,6 +220,10 @@ func (o *optimismL1Oracle) Name() string {
return o.logger.Name()
}
+func (o *optimismL1Oracle) ChainType(_ context.Context) chaintype.ChainType {
+ return o.chainType
+}
+
func (o *optimismL1Oracle) Start(ctx context.Context) error {
return o.StartOnce(o.Name(), func() error {
go o.run()
diff --git a/core/chains/evm/gas/rollups/zkSync_l1_oracle.go b/core/chains/evm/gas/rollups/zkSync_l1_oracle.go
index 31d93bc587..c294123354 100644
--- a/core/chains/evm/gas/rollups/zkSync_l1_oracle.go
+++ b/core/chains/evm/gas/rollups/zkSync_l1_oracle.go
@@ -83,6 +83,10 @@ func (o *zkSyncL1Oracle) Name() string {
return o.logger.Name()
}
+func (o *zkSyncL1Oracle) ChainType(_ context.Context) chaintype.ChainType {
+ return o.chainType
+}
+
func (o *zkSyncL1Oracle) Start(ctx context.Context) error {
return o.StartOnce(o.Name(), func() error {
go o.run()
diff --git a/core/chains/evm/txmgr/attempts.go b/core/chains/evm/txmgr/attempts.go
index 8566adcb5c..c284ee77bd 100644
--- a/core/chains/evm/txmgr/attempts.go
+++ b/core/chains/evm/txmgr/attempts.go
@@ -58,7 +58,7 @@ func (c *evmTxAttemptBuilder) NewTxAttempt(ctx context.Context, etx Tx, lggr log
// used for L2 re-estimation on broadcasting (note EIP1559 must be disabled otherwise this will fail with mismatched fees + tx type)
func (c *evmTxAttemptBuilder) NewTxAttemptWithType(ctx context.Context, etx Tx, lggr logger.Logger, txType int, opts ...feetypes.Opt) (attempt TxAttempt, fee gas.EvmFee, feeLimit uint64, retryable bool, err error) {
keySpecificMaxGasPriceWei := c.feeConfig.PriceMaxKey(etx.FromAddress)
- fee, feeLimit, err = c.EvmFeeEstimator.GetFee(ctx, etx.EncodedPayload, etx.FeeLimit, keySpecificMaxGasPriceWei, opts...)
+ fee, feeLimit, err = c.EvmFeeEstimator.GetFee(ctx, etx.EncodedPayload, etx.FeeLimit, keySpecificMaxGasPriceWei, &etx.FromAddress, &etx.ToAddress, opts...)
if err != nil {
return attempt, fee, feeLimit, true, pkgerrors.Wrap(err, "failed to get fee") // estimator errors are retryable
}
@@ -71,8 +71,8 @@ func (c *evmTxAttemptBuilder) NewTxAttemptWithType(ctx context.Context, etx Tx,
// used in the txm broadcaster + confirmer when tx ix rejected for too low fee or is not included in a timely manner
func (c *evmTxAttemptBuilder) NewBumpTxAttempt(ctx context.Context, etx Tx, previousAttempt TxAttempt, priorAttempts []TxAttempt, lggr logger.Logger) (attempt TxAttempt, bumpedFee gas.EvmFee, bumpedFeeLimit uint64, retryable bool, err error) {
keySpecificMaxGasPriceWei := c.feeConfig.PriceMaxKey(etx.FromAddress)
-
- bumpedFee, bumpedFeeLimit, err = c.EvmFeeEstimator.BumpFee(ctx, previousAttempt.TxFee, etx.FeeLimit, keySpecificMaxGasPriceWei, newEvmPriorAttempts(priorAttempts))
+ // Use the fee limit from the previous attempt to maintain limits adjusted for 2D fees or by estimation
+ bumpedFee, bumpedFeeLimit, err = c.EvmFeeEstimator.BumpFee(ctx, previousAttempt.TxFee, previousAttempt.ChainSpecificFeeLimit, keySpecificMaxGasPriceWei, newEvmPriorAttempts(priorAttempts))
if err != nil {
return attempt, bumpedFee, bumpedFeeLimit, true, pkgerrors.Wrap(err, "failed to bump fee") // estimator errors are retryable
}
diff --git a/core/chains/evm/txmgr/attempts_test.go b/core/chains/evm/txmgr/attempts_test.go
index 6be8cd7067..5c43368fcc 100644
--- a/core/chains/evm/txmgr/attempts_test.go
+++ b/core/chains/evm/txmgr/attempts_test.go
@@ -339,7 +339,7 @@ func TestTxm_NewCustomTxAttempt_NonRetryableErrors(t *testing.T) {
func TestTxm_EvmTxAttemptBuilder_RetryableEstimatorError(t *testing.T) {
est := gasmocks.NewEvmFeeEstimator(t)
- est.On("GetFee", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(gas.EvmFee{}, uint64(0), pkgerrors.New("fail"))
+ est.On("GetFee", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(gas.EvmFee{}, uint64(0), pkgerrors.New("fail"))
est.On("BumpFee", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(gas.EvmFee{}, uint64(0), pkgerrors.New("fail"))
kst := ksmocks.NewEth(t)
diff --git a/core/chains/evm/txmgr/broadcaster_test.go b/core/chains/evm/txmgr/broadcaster_test.go
index cbbe522ba1..514d533159 100644
--- a/core/chains/evm/txmgr/broadcaster_test.go
+++ b/core/chains/evm/txmgr/broadcaster_test.go
@@ -29,8 +29,10 @@ import (
"github.com/smartcontractkit/chainlink-common/pkg/utils/tests"
commonclient "github.com/smartcontractkit/chainlink/v2/common/client"
+ commmonfee "github.com/smartcontractkit/chainlink/v2/common/fee"
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"
"github.com/smartcontractkit/chainlink/v2/core/chains/evm/client"
evmconfig "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config"
@@ -69,7 +71,7 @@ func NewTestEthBroadcaster(
estimator := gas.NewEvmFeeEstimator(lggr, func(lggr logger.Logger) gas.EvmEstimator {
return gas.NewFixedPriceEstimator(config.EVM().GasEstimator(), nil, ge.BlockHistory(), lggr, nil)
- }, ge.EIP1559DynamicFees(), ge)
+ }, ge.EIP1559DynamicFees(), ge, ethClient)
txBuilder := txmgr.NewEvmTxAttemptBuilder(*ethClient.ConfiguredChainID(), ge, keyStore, estimator)
ethBroadcaster := txmgrcommon.NewBroadcaster(txStore, txmgr.NewEvmTxmClient(ethClient, nil), txmgr.NewEvmTxmConfig(config.EVM()), txmgr.NewEvmTxmFeeConfig(config.EVM().GasEstimator()), config.EVM().Transactions(), gconfig.Database().Listener(), keyStore, txBuilder, nonceTracker, lggr, checkerFactory, nonceAutoSync, "")
@@ -642,7 +644,7 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_OptimisticLockingOnEthTx(t *testi
chStartEstimate := make(chan struct{})
chBlock := make(chan struct{})
- estimator.On("GetFee", mock.Anything, mock.Anything, mock.Anything, ccfg.EVM().GasEstimator().PriceMaxKey(fromAddress)).Return(gas.EvmFee{Legacy: assets.GWei(32)}, uint64(500), nil).Run(func(_ mock.Arguments) {
+ estimator.On("GetFee", mock.Anything, mock.Anything, mock.Anything, ccfg.EVM().GasEstimator().PriceMaxKey(fromAddress), mock.Anything, mock.Anything).Return(gas.EvmFee{Legacy: assets.GWei(32)}, uint64(500), nil).Run(func(_ mock.Arguments) {
close(chStartEstimate)
<-chBlock
}).Once()
@@ -1177,7 +1179,7 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_Errors(t *testing.T) {
t.Run("callback set by ctor", func(t *testing.T) {
estimator := gas.NewEvmFeeEstimator(lggr, func(lggr logger.Logger) gas.EvmEstimator {
return gas.NewFixedPriceEstimator(evmcfg.EVM().GasEstimator(), nil, evmcfg.EVM().GasEstimator().BlockHistory(), lggr, nil)
- }, evmcfg.EVM().GasEstimator().EIP1559DynamicFees(), evmcfg.EVM().GasEstimator())
+ }, evmcfg.EVM().GasEstimator().EIP1559DynamicFees(), evmcfg.EVM().GasEstimator(), ethClient)
txBuilder := txmgr.NewEvmTxAttemptBuilder(*ethClient.ConfiguredChainID(), evmcfg.EVM().GasEstimator(), ethKeyStore, estimator)
localNextNonce = getLocalNextNonce(t, nonceTracker, fromAddress)
eb2 := txmgr.NewEvmBroadcaster(txStore, txmClient, txmgr.NewEvmTxmConfig(evmcfg.EVM()), txmgr.NewEvmTxmFeeConfig(evmcfg.EVM().GasEstimator()), evmcfg.EVM().Transactions(), cfg.Database().Listener(), ethKeyStore, txBuilder, lggr, &testCheckerFactory{}, false, "")
@@ -1666,6 +1668,73 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_Errors(t *testing.T) {
})
}
+func TestEthBroadcaster_ProcessUnstartedEthTxs_GasEstimationError(t *testing.T) {
+ toAddress := testutils.NewAddress()
+ value := big.Int(assets.NewEthValue(142))
+ gasLimit := uint64(242)
+ encodedPayload := []byte{0, 1}
+
+ db := pgtest.NewSqlxDB(t)
+ cfg := configtest.NewTestGeneralConfig(t)
+ cfg.EVMConfigs()[0].GasEstimator.EstimateLimit = ptr(true) // Enabled gas limit estimation
+ limitMultiplier := float32(1.25)
+ cfg.EVMConfigs()[0].GasEstimator.LimitMultiplier = ptr(decimal.NewFromFloat32(limitMultiplier)) // Set LimitMultiplier for the buffer
+ txStore := cltest.NewTestTxStore(t, db)
+
+ ethKeyStore := cltest.NewKeyStore(t, db).Eth()
+ _, fromAddress := cltest.MustInsertRandomKey(t, ethKeyStore)
+
+ config := evmtest.NewChainScopedConfig(t, cfg)
+ ethClient := testutils.NewEthClientMockWithDefaultChain(t)
+ ethClient.On("PendingNonceAt", mock.Anything, fromAddress).Return(uint64(0), nil).Once()
+ lggr := logger.Test(t)
+ txmClient := txmgr.NewEvmTxmClient(ethClient, nil)
+ nonceTracker := txmgr.NewNonceTracker(lggr, txStore, txmClient)
+ ge := config.EVM().GasEstimator()
+ estimator := gas.NewEvmFeeEstimator(lggr, func(lggr logger.Logger) gas.EvmEstimator {
+ return gas.NewFixedPriceEstimator(ge, nil, ge.BlockHistory(), lggr, nil)
+ }, ge.EIP1559DynamicFees(), ge, ethClient)
+ txBuilder := txmgr.NewEvmTxAttemptBuilder(*ethClient.ConfiguredChainID(), ge, ethKeyStore, estimator)
+ eb := txmgrcommon.NewBroadcaster(txStore, txmgr.NewEvmTxmClient(ethClient, nil), txmgr.NewEvmTxmConfig(config.EVM()), txmgr.NewEvmTxmFeeConfig(config.EVM().GasEstimator()), config.EVM().Transactions(), cfg.Database().Listener(), ethKeyStore, txBuilder, nonceTracker, lggr, &testCheckerFactory{}, false, "")
+
+ // Mark instance as test
+ eb.XXXTestDisableUnstartedTxAutoProcessing()
+ servicetest.Run(t, eb)
+ ctx := tests.Context(t)
+ t.Run("gas limit lowered after estimation", func(t *testing.T) {
+ estimatedGasLimit := uint64(100)
+ etx := mustCreateUnstartedTx(t, txStore, fromAddress, toAddress, encodedPayload, gasLimit, value, testutils.FixtureChainID)
+ ethClient.On("EstimateGas", mock.Anything, mock.Anything).Return(estimatedGasLimit, nil).Once()
+ ethClient.On("SendTransactionReturnCode", mock.Anything, mock.MatchedBy(func(tx *gethTypes.Transaction) bool {
+ return tx.Nonce() == uint64(0)
+ }), fromAddress).Return(commonclient.Successful, nil).Once()
+
+ // Do the thing
+ retryable, err := eb.ProcessUnstartedTxs(ctx, fromAddress)
+ assert.NoError(t, err)
+ assert.False(t, retryable)
+
+ dbEtx, err := txStore.FindTxWithAttempts(ctx, etx.ID)
+ require.NoError(t, err)
+ attempt := dbEtx.TxAttempts[0]
+ require.Equal(t, uint64(float32(estimatedGasLimit)*gas.EstimateGasBuffer), attempt.ChainSpecificFeeLimit)
+ })
+ t.Run("provided gas limit too low, transaction marked as fatal error", func(t *testing.T) {
+ etx := mustCreateUnstartedTx(t, txStore, fromAddress, toAddress, encodedPayload, gasLimit, value, testutils.FixtureChainID)
+ ethClient.On("EstimateGas", mock.Anything, mock.Anything).Return(uint64(float32(gasLimit)*limitMultiplier)+1, nil).Once()
+
+ // Do the thing
+ retryable, err := eb.ProcessUnstartedTxs(ctx, fromAddress)
+ assert.NoError(t, err)
+ assert.False(t, retryable)
+
+ dbEtx, err := txStore.FindTxWithAttempts(ctx, etx.ID)
+ require.NoError(t, err)
+ require.Equal(t, txmgrcommon.TxFatalError, dbEtx.State)
+ require.Equal(t, commmonfee.ErrFeeLimitTooLow.Error(), dbEtx.Error.String)
+ })
+}
+
func TestEthBroadcaster_ProcessUnstartedEthTxs_KeystoreErrors(t *testing.T) {
toAddress := gethCommon.HexToAddress("0x6C03DDA95a2AEd917EeCc6eddD4b9D16E6380411")
value := big.Int(assets.NewEthValue(142))
@@ -1760,15 +1829,15 @@ func TestEthBroadcaster_SyncNonce(t *testing.T) {
kst := cltest.NewKeyStore(t, db).Eth()
_, fromAddress := cltest.RandomKey{Disabled: false}.MustInsertWithState(t, kst)
+ ethClient := testutils.NewEthClientMockWithDefaultChain(t)
estimator := gas.NewEvmFeeEstimator(lggr, func(lggr logger.Logger) gas.EvmEstimator {
return gas.NewFixedPriceEstimator(evmcfg.EVM().GasEstimator(), nil, evmcfg.EVM().GasEstimator().BlockHistory(), lggr, nil)
- }, evmcfg.EVM().GasEstimator().EIP1559DynamicFees(), evmcfg.EVM().GasEstimator())
+ }, evmcfg.EVM().GasEstimator().EIP1559DynamicFees(), evmcfg.EVM().GasEstimator(), ethClient)
checkerFactory := &testCheckerFactory{}
ge := evmcfg.EVM().GasEstimator()
t.Run("does nothing if nonce sync is disabled", func(t *testing.T) {
- ethClient := testutils.NewEthClientMockWithDefaultChain(t)
txBuilder := txmgr.NewEvmTxAttemptBuilder(*ethClient.ConfiguredChainID(), ge, kst, estimator)
kst := ksmocks.NewEth(t)
@@ -1838,7 +1907,7 @@ func TestEthBroadcaster_HederaBroadcastValidation(t *testing.T) {
ge := evmcfg.EVM().GasEstimator()
estimator := gas.NewEvmFeeEstimator(lggr, func(lggr logger.Logger) gas.EvmEstimator {
return gas.NewFixedPriceEstimator(evmcfg.EVM().GasEstimator(), nil, ge.BlockHistory(), lggr, nil)
- }, ge.EIP1559DynamicFees(), ge)
+ }, ge.EIP1559DynamicFees(), ge, ethClient)
txBuilder := txmgr.NewEvmTxAttemptBuilder(*ethClient.ConfiguredChainID(), ge, ethKeyStore, estimator)
checkerFactory := &txmgr.CheckerFactory{Client: ethClient}
ctx := tests.Context(t)
diff --git a/core/chains/evm/txmgr/confirmer_test.go b/core/chains/evm/txmgr/confirmer_test.go
index 45d7ac9e12..82b668f168 100644
--- a/core/chains/evm/txmgr/confirmer_test.go
+++ b/core/chains/evm/txmgr/confirmer_test.go
@@ -128,7 +128,7 @@ func TestEthConfirmer_Lifecycle(t *testing.T) {
newEst := func(logger.Logger) gas.EvmEstimator { return estimator }
lggr := logger.Test(t)
ge := config.EVM().GasEstimator()
- feeEstimator := gas.NewEvmFeeEstimator(lggr, newEst, ge.EIP1559DynamicFees(), ge)
+ feeEstimator := gas.NewEvmFeeEstimator(lggr, newEst, ge.EIP1559DynamicFees(), ge, ethClient)
txBuilder := txmgr.NewEvmTxAttemptBuilder(*ethClient.ConfiguredChainID(), ge, ethKeyStore, feeEstimator)
stuckTxDetector := txmgr.NewStuckTxDetector(lggr, testutils.FixtureChainID, "", assets.NewWei(assets.NewEth(100).ToInt()), config.EVM().Transactions().AutoPurge(), feeEstimator, txStore, ethClient)
ec := txmgr.NewEvmConfirmer(txStore, txmgr.NewEvmTxmClient(ethClient, nil), txmgr.NewEvmTxmConfig(config.EVM()), txmgr.NewEvmTxmFeeConfig(ge), config.EVM().Transactions(), gconfig.Database(), ethKeyStore, txBuilder, lggr, stuckTxDetector)
@@ -1646,7 +1646,7 @@ func TestEthConfirmer_RebroadcastWhereNecessary_WithConnectivityCheck(t *testing
newEst := func(logger.Logger) gas.EvmEstimator { return estimator }
estimator.On("BumpLegacyGas", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, uint64(0), pkgerrors.Wrapf(commonfee.ErrConnectivity, "transaction..."))
ge := ccfg.EVM().GasEstimator()
- feeEstimator := gas.NewEvmFeeEstimator(lggr, newEst, ge.EIP1559DynamicFees(), ge)
+ feeEstimator := gas.NewEvmFeeEstimator(lggr, newEst, ge.EIP1559DynamicFees(), ge, ethClient)
txBuilder := txmgr.NewEvmTxAttemptBuilder(*ethClient.ConfiguredChainID(), ge, kst, feeEstimator)
addresses := []gethCommon.Address{fromAddress}
kst.On("EnabledAddressesForChain", mock.Anything, &cltest.FixtureChainID).Return(addresses, nil).Maybe()
@@ -1695,7 +1695,7 @@ func TestEthConfirmer_RebroadcastWhereNecessary_WithConnectivityCheck(t *testing
newEst := func(logger.Logger) gas.EvmEstimator { return estimator }
// Create confirmer with necessary state
ge := ccfg.EVM().GasEstimator()
- feeEstimator := gas.NewEvmFeeEstimator(lggr, newEst, ge.EIP1559DynamicFees(), ge)
+ feeEstimator := gas.NewEvmFeeEstimator(lggr, newEst, ge.EIP1559DynamicFees(), ge, ethClient)
txBuilder := txmgr.NewEvmTxAttemptBuilder(*ethClient.ConfiguredChainID(), ge, kst, feeEstimator)
addresses := []gethCommon.Address{fromAddress}
kst.On("EnabledAddressesForChain", mock.Anything, &cltest.FixtureChainID).Return(addresses, nil).Maybe()
@@ -3195,7 +3195,7 @@ func TestEthConfirmer_ProcessStuckTransactions(t *testing.T) {
fee := gas.EvmFee{Legacy: marketGasPrice}
bumpedLegacy := assets.GWei(30)
bumpedFee := gas.EvmFee{Legacy: bumpedLegacy}
- feeEstimator.On("GetFee", mock.Anything, []byte{}, uint64(0), mock.Anything).Return(fee, uint64(0), nil)
+ feeEstimator.On("GetFee", mock.Anything, []byte{}, uint64(0), mock.Anything, mock.Anything, mock.Anything).Return(fee, uint64(0), nil)
feeEstimator.On("BumpFee", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(bumpedFee, uint64(10_000), nil)
autoPurgeThreshold := uint32(5)
autoPurgeMinAttempts := uint32(3)
@@ -3286,7 +3286,7 @@ func newEthConfirmer(t testing.TB, txStore txmgr.EvmTxStore, ethClient client.Cl
ge := config.EVM().GasEstimator()
estimator := gas.NewEvmFeeEstimator(lggr, func(lggr logger.Logger) gas.EvmEstimator {
return gas.NewFixedPriceEstimator(ge, nil, ge.BlockHistory(), lggr, nil)
- }, ge.EIP1559DynamicFees(), ge)
+ }, ge.EIP1559DynamicFees(), ge, ethClient)
txBuilder := txmgr.NewEvmTxAttemptBuilder(*ethClient.ConfiguredChainID(), ge, ks, estimator)
stuckTxDetector := txmgr.NewStuckTxDetector(lggr, testutils.FixtureChainID, "", assets.NewWei(assets.NewEth(100).ToInt()), config.EVM().Transactions().AutoPurge(), estimator, txStore, ethClient)
ec := txmgr.NewEvmConfirmer(txStore, txmgr.NewEvmTxmClient(ethClient, nil), txmgr.NewEvmTxmConfig(config.EVM()), txmgr.NewEvmTxmFeeConfig(ge), config.EVM().Transactions(), gconfig.Database(), ks, txBuilder, lggr, stuckTxDetector)
diff --git a/core/chains/evm/txmgr/stuck_tx_detector.go b/core/chains/evm/txmgr/stuck_tx_detector.go
index 5d621dc0b2..d756e95058 100644
--- a/core/chains/evm/txmgr/stuck_tx_detector.go
+++ b/core/chains/evm/txmgr/stuck_tx_detector.go
@@ -25,7 +25,7 @@ import (
)
type stuckTxDetectorGasEstimator interface {
- GetFee(ctx context.Context, calldata []byte, feeLimit uint64, maxFeePrice *assets.Wei, opts ...feetypes.Opt) (fee gas.EvmFee, chainSpecificFeeLimit uint64, err error)
+ GetFee(ctx context.Context, calldata []byte, feeLimit uint64, maxFeePrice *assets.Wei, fromAddress, toAddress *common.Address, opts ...feetypes.Opt) (fee gas.EvmFee, chainSpecificFeeLimit uint64, err error)
}
type stuckTxDetectorClient interface {
@@ -199,7 +199,7 @@ func (d *stuckTxDetector) detectStuckTransactionsHeuristic(ctx context.Context,
defer d.purgeBlockNumLock.RUnlock()
// Get gas price from internal gas estimator
// Send with max gas price time 2 to prevent the results from being capped. Need the market gas price here.
- marketGasPrice, _, err := d.gasEstimator.GetFee(ctx, []byte{}, 0, d.maxPrice.Mul(big.NewInt(2)))
+ marketGasPrice, _, err := d.gasEstimator.GetFee(ctx, []byte{}, 0, d.maxPrice.Mul(big.NewInt(2)), nil, nil)
if err != nil {
return txs, fmt.Errorf("failed to get market gas price for overflow detection: %w", err)
}
@@ -215,6 +215,20 @@ func (d *stuckTxDetector) detectStuckTransactionsHeuristic(ctx context.Context,
}
// Tx attempts are loaded from newest to oldest
oldestBroadcastAttempt, newestBroadcastAttempt, broadcastedAttemptsCount := findBroadcastedAttempts(tx)
+ d.lggr.Debugf("found %d broadcasted attempts for tx id %d in stuck transaction heuristic", broadcastedAttemptsCount, tx.ID)
+
+ // attempt shouldn't be nil as we validated in FindUnconfirmedTxWithLowestNonce, but added anyway for a "belts and braces" approach
+ if oldestBroadcastAttempt == nil || newestBroadcastAttempt == nil {
+ d.lggr.Debugw("failed to find broadcast attempt for tx in stuck transaction heuristic", "tx", tx)
+ continue
+ }
+
+ // sanity check
+ if oldestBroadcastAttempt.BroadcastBeforeBlockNum == nil {
+ d.lggr.Debugw("BroadcastBeforeBlockNum was not set for broadcast attempt in stuck transaction heuristic", "attempt", oldestBroadcastAttempt)
+ continue
+ }
+
// 2. Check if Threshold amount of blocks have passed since the oldest attempt's broadcast block num
if *oldestBroadcastAttempt.BroadcastBeforeBlockNum > blockNum-int64(*d.cfg.Threshold()) {
continue
@@ -244,17 +258,18 @@ func compareGasFees(attemptGas gas.EvmFee, marketGas gas.EvmFee) int {
}
// Assumes tx attempts are loaded newest to oldest
-func findBroadcastedAttempts(tx Tx) (oldestAttempt TxAttempt, newestAttempt TxAttempt, broadcastedCount uint32) {
+func findBroadcastedAttempts(tx Tx) (oldestAttempt *TxAttempt, newestAttempt *TxAttempt, broadcastedCount uint32) {
foundNewest := false
- for _, attempt := range tx.TxAttempts {
+ for i := range tx.TxAttempts {
+ attempt := tx.TxAttempts[i]
if attempt.State != types.TxAttemptBroadcast {
continue
}
if !foundNewest {
- newestAttempt = attempt
+ newestAttempt = &attempt
foundNewest = true
}
- oldestAttempt = attempt
+ oldestAttempt = &attempt
broadcastedCount++
}
return
diff --git a/core/chains/evm/txmgr/stuck_tx_detector_test.go b/core/chains/evm/txmgr/stuck_tx_detector_test.go
index def49f8e11..c653d297e8 100644
--- a/core/chains/evm/txmgr/stuck_tx_detector_test.go
+++ b/core/chains/evm/txmgr/stuck_tx_detector_test.go
@@ -73,7 +73,7 @@ func TestStuckTxDetector_LoadPurgeBlockNumMap(t *testing.T) {
feeEstimator := gasmocks.NewEvmFeeEstimator(t)
marketGasPrice := assets.GWei(15)
fee := gas.EvmFee{Legacy: marketGasPrice}
- feeEstimator.On("GetFee", mock.Anything, []byte{}, uint64(0), mock.Anything).Return(fee, uint64(0), nil)
+ feeEstimator.On("GetFee", mock.Anything, []byte{}, uint64(0), mock.Anything, mock.Anything, mock.Anything).Return(fee, uint64(0), nil)
autoPurgeThreshold := uint32(5)
autoPurgeMinAttempts := uint32(3)
autoPurgeCfg := testAutoPurgeConfig{
@@ -194,7 +194,7 @@ func TestStuckTxDetector_DetectStuckTransactionsHeuristic(t *testing.T) {
// Return 10 gwei as market gas price
marketGasPrice := tenGwei
fee := gas.EvmFee{Legacy: marketGasPrice}
- feeEstimator.On("GetFee", mock.Anything, []byte{}, uint64(0), mock.Anything).Return(fee, uint64(0), nil)
+ feeEstimator.On("GetFee", mock.Anything, []byte{}, uint64(0), mock.Anything, mock.Anything, mock.Anything).Return(fee, uint64(0), nil)
ethClient := testutils.NewEthClientMockWithDefaultChain(t)
autoPurgeThreshold := uint32(5)
autoPurgeMinAttempts := uint32(3)
@@ -278,6 +278,15 @@ func TestStuckTxDetector_DetectStuckTransactionsHeuristic(t *testing.T) {
require.NoError(t, err)
require.Len(t, txs, 1)
})
+
+ t.Run("detects stuck transaction with empty BroadcastBeforeBlockNum in attempts will be skipped without panic", func(t *testing.T) {
+ _, fromAddress := cltest.MustInsertRandomKey(t, ethKeyStore)
+ enabledAddresses := []common.Address{fromAddress}
+ mustInsertUnconfirmedTxWithBroadcastAttemptsContainsEmptyBroadcastBeforeBlockNum(t, txStore, 0, fromAddress, autoPurgeMinAttempts, marketGasPrice.Add(oneGwei))
+ txs, err := stuckTxDetector.DetectStuckTransactions(ctx, enabledAddresses, blockNum)
+ require.NoError(t, err)
+ require.Len(t, txs, 0)
+ })
}
func TestStuckTxDetector_DetectStuckTransactionsZkEVM(t *testing.T) {
@@ -435,6 +444,23 @@ func mustInsertUnconfirmedTxWithBroadcastAttempts(t *testing.T, txStore txmgr.Te
return etx
}
+// helper function for edge case where broadcast attempt contains empty pointer
+func mustInsertUnconfirmedTxWithBroadcastAttemptsContainsEmptyBroadcastBeforeBlockNum(t *testing.T, txStore txmgr.TestEvmTxStore, nonce int64, fromAddress common.Address, numAttempts uint32, latestGasPrice *assets.Wei) txmgr.Tx {
+ ctx := tests.Context(t)
+ etx := cltest.MustInsertUnconfirmedEthTx(t, txStore, nonce, fromAddress)
+ // Insert attempts from oldest to newest
+ for i := int64(numAttempts - 1); i >= 0; i-- {
+ attempt := cltest.NewLegacyEthTxAttempt(t, etx.ID)
+ attempt.State = txmgrtypes.TxAttemptBroadcast
+ attempt.BroadcastBeforeBlockNum = nil
+ attempt.TxFee = gas.EvmFee{Legacy: latestGasPrice.Sub(assets.NewWeiI(i))}
+ require.NoError(t, txStore.InsertTxAttempt(ctx, &attempt))
+ }
+ etx, err := txStore.FindTxWithAttempts(ctx, etx.ID)
+ require.NoError(t, err)
+ return etx
+}
+
func mustInsertFatalErrorTxWithError(t *testing.T, txStore txmgr.TestEvmTxStore, nonce int64, fromAddress common.Address, blockNum int64) txmgr.Tx {
etx := cltest.NewEthTx(fromAddress)
etx.State = txmgrcommon.TxFatalError
diff --git a/core/chains/evm/txmgr/test_helpers.go b/core/chains/evm/txmgr/test_helpers.go
index 3b3584a988..b1317cb421 100644
--- a/core/chains/evm/txmgr/test_helpers.go
+++ b/core/chains/evm/txmgr/test_helpers.go
@@ -73,6 +73,10 @@ func (g *TestGasEstimatorConfig) BlockHistory() evmconfig.BlockHistory {
return &TestBlockHistoryConfig{}
}
+func (g *TestGasEstimatorConfig) FeeHistory() evmconfig.FeeHistory {
+ return &TestFeeHistoryConfig{}
+}
+
func (g *TestGasEstimatorConfig) EIP1559DynamicFees() bool { return false }
func (g *TestGasEstimatorConfig) LimitDefault() uint64 { return 42 }
func (g *TestGasEstimatorConfig) BumpPercent() uint16 { return 42 }
@@ -89,6 +93,7 @@ func (g *TestGasEstimatorConfig) LimitTransfer() uint64 { return 42 }
func (g *TestGasEstimatorConfig) PriceMax() *assets.Wei { return assets.NewWeiI(42) }
func (g *TestGasEstimatorConfig) PriceMin() *assets.Wei { return assets.NewWeiI(42) }
func (g *TestGasEstimatorConfig) Mode() string { return "FixedPrice" }
+func (g *TestGasEstimatorConfig) EstimateLimit() bool { return false }
func (g *TestGasEstimatorConfig) LimitJobType() evmconfig.LimitJobType {
return &TestLimitJobTypeConfig{}
}
@@ -120,6 +125,12 @@ func (b *TestBlockHistoryConfig) BlockHistorySize() uint16 { return 42
func (b *TestBlockHistoryConfig) EIP1559FeeCapBufferBlocks() uint16 { return 42 }
func (b *TestBlockHistoryConfig) TransactionPercentile() uint16 { return 42 }
+type TestFeeHistoryConfig struct {
+ evmconfig.FeeHistory
+}
+
+func (b *TestFeeHistoryConfig) CacheTimeout() time.Duration { return 0 * time.Second }
+
type transactionsConfig struct {
evmconfig.Transactions
e *TestEvmConfig
diff --git a/core/chains/legacyevm/chain.go b/core/chains/legacyevm/chain.go
index 129c031882..27f2fee43c 100644
--- a/core/chains/legacyevm/chain.go
+++ b/core/chains/legacyevm/chain.go
@@ -269,6 +269,8 @@ func newChain(ctx context.Context, cfg *evmconfig.ChainScoped, nodes []*toml.Nod
var logBroadcaster log.Broadcaster
if !opts.AppConfig.EVMRPCEnabled() {
logBroadcaster = &log.NullBroadcaster{ErrMsg: fmt.Sprintf("Ethereum is disabled for chain %d", chainID)}
+ } else if !cfg.EVM().LogBroadcasterEnabled() {
+ logBroadcaster = &log.NullBroadcaster{ErrMsg: fmt.Sprintf("LogBroadcaster disabled for chain %d", chainID)}
} else if opts.GenLogBroadcaster == nil {
logORM := log.NewORM(opts.DS, *chainID)
logBroadcaster = log.NewBroadcaster(logORM, client, cfg.EVM(), l, highestSeenHead, opts.MailMon)
diff --git a/core/config/docs/chains-evm.toml b/core/config/docs/chains-evm.toml
index 73ea0ebb35..2f03ed4db2 100644
--- a/core/config/docs/chains-evm.toml
+++ b/core/config/docs/chains-evm.toml
@@ -98,6 +98,8 @@ RPCBlockQueryDelay = 1 # Default
# Block 64 will be treated as finalized by CL Node only when chain's latest finalized block is 65. As chain finalizes blocks in batches of 32,
# CL Node has to wait for a whole new batch to be finalized to treat block 64 as finalized.
FinalizedBlockOffset = 0 # Default
+# LogBroadcasterEnabled is a feature flag for LogBroadcaster, by default it's true.
+LogBroadcasterEnabled = true # Default
# NoNewFinalizedHeadsThreshold controls how long to wait for new finalized block before `NodePool` marks rpc endpoints as
# out-of-sync. Only applicable if `FinalityTagEnabled=true`
#
@@ -194,6 +196,8 @@ LimitMax = 8_000_000 # Default
LimitMultiplier = '1.0' # Default
# LimitTransfer is the gas limit used for an ordinary ETH transfer.
LimitTransfer = 21_000 # Default
+# EstimateLimit enables estimating gas limits for transactions. This feature respects the gas limit provided during transaction creation as an upper bound.
+EstimateLimit = false # Default
# BumpMin is the minimum fixed amount of wei by which gas is bumped on each transaction attempt.
BumpMin = '5 gwei' # Default
# BumpPercent is the percentage by which to bump gas on a transaction that has exceeded `BumpThreshold`. The larger of `BumpPercent` and `BumpMin` is taken for gas bumps.
@@ -310,6 +314,15 @@ EIP1559FeeCapBufferBlocks = 13 # Example
# Setting it lower will tend to set lower gas prices.
TransactionPercentile = 60 # Default
+[EVM.GasEstimator.FeeHistory]
+# CacheTimeout is the time to wait in order to refresh the cached values stored in the FeeHistory estimator. A small jitter is applied so the timeout won't be exactly the same each time.
+#
+# You want this value to be close to the block time. For slower chains, like Ethereum, you can set it to 12s, the same as the block time. For faster chains you can skip a block or two
+# and set it to two times the block time i.e. on Optimism you can set it to 4s. Ideally, you don't want to go lower than 1s since the RTT times of the RPC requests will be comparable to
+# the timeout. The estimator is already adding a buffer to account for a potential increase in prices within one or two blocks. On the other hand, slower frequency will fail to refresh
+# the prices and end up in stale values.
+CacheTimeout = '10s' # Default
+
# The head tracker continually listens for new heads from the chain.
#
# In addition to these settings, it log warnings if `EVM.NoNewHeadsThreshold` is exceeded without any new blocks being emitted.
@@ -397,6 +410,10 @@ EnforceRepeatableRead = false # Default
# trigger declaration of `FinalizedBlockOutOfSync` due to insignificant network delays in broadcasting of the finalized state among RPCs.
# RPC will not be picked to handle a request even if this option is set to a nonzero value.
DeathDeclarationDelay = '10s' # Default
+# NewHeadsPollInterval define an interval for polling new block periodically using http client rather than subscribe to ws feed
+#
+# Set to 0 to disable.
+NewHeadsPollInterval = '0s' # Default
# **ADVANCED**
# Errors enable the node to provide custom regex patterns to match against error messages from RPCs.
[EVM.NodePool.Errors]
@@ -450,7 +467,7 @@ ObservationGracePeriod = '1s' # Default
[[EVM.Nodes]]
# Name is a unique (per-chain) identifier for this node.
Name = 'foo' # Example
-# WSURL is the WS(S) endpoint for this node. Required for primary nodes.
+# WSURL is the WS(S) endpoint for this node. Required for primary nodes when `LogBroadcasterEnabled` is `true`
WSURL = 'wss://web.socket/test' # Example
# HTTPURL is the HTTP(S) endpoint for this node. Required for all nodes.
HTTPURL = 'https://foo.web' # Example
diff --git a/core/internal/features/features_test.go b/core/internal/features/features_test.go
index 1259597379..7be0307798 100644
--- a/core/internal/features/features_test.go
+++ b/core/internal/features/features_test.go
@@ -1339,7 +1339,7 @@ func TestIntegration_BlockHistoryEstimator(t *testing.T) {
chain := evmtest.MustGetDefaultChain(t, legacyChains)
estimator := chain.GasEstimator()
- gasPrice, gasLimit, err := estimator.GetFee(testutils.Context(t), nil, 500_000, maxGasPrice)
+ gasPrice, gasLimit, err := estimator.GetFee(testutils.Context(t), nil, 500_000, maxGasPrice, nil, nil)
require.NoError(t, err)
assert.Equal(t, uint64(500000), gasLimit)
assert.Equal(t, "41.5 gwei", gasPrice.Legacy.String())
@@ -1360,7 +1360,7 @@ func TestIntegration_BlockHistoryEstimator(t *testing.T) {
newHeads.TrySend(h43)
gomega.NewWithT(t).Eventually(func() string {
- gasPrice, _, err := estimator.GetFee(testutils.Context(t), nil, 500000, maxGasPrice)
+ gasPrice, _, err := estimator.GetFee(testutils.Context(t), nil, 500000, maxGasPrice, nil, nil)
require.NoError(t, err)
return gasPrice.Legacy.String()
}, testutils.WaitTimeout(t), cltest.DBPollingInterval).Should(gomega.Equal("45 gwei"))
diff --git a/core/internal/mocks/prometheus_backend.go b/core/internal/mocks/prometheus_backend.go
deleted file mode 100644
index d02f7062cb..0000000000
--- a/core/internal/mocks/prometheus_backend.go
+++ /dev/null
@@ -1,204 +0,0 @@
-// Code generated by mockery v2.43.2. DO NOT EDIT.
-
-package mocks
-
-import (
- big "math/big"
-
- mock "github.com/stretchr/testify/mock"
-)
-
-// PrometheusBackend is an autogenerated mock type for the PrometheusBackend type
-type PrometheusBackend struct {
- mock.Mock
-}
-
-type PrometheusBackend_Expecter struct {
- mock *mock.Mock
-}
-
-func (_m *PrometheusBackend) EXPECT() *PrometheusBackend_Expecter {
- return &PrometheusBackend_Expecter{mock: &_m.Mock}
-}
-
-// SetMaxUnconfirmedAge provides a mock function with given fields: _a0, _a1
-func (_m *PrometheusBackend) SetMaxUnconfirmedAge(_a0 *big.Int, _a1 float64) {
- _m.Called(_a0, _a1)
-}
-
-// PrometheusBackend_SetMaxUnconfirmedAge_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SetMaxUnconfirmedAge'
-type PrometheusBackend_SetMaxUnconfirmedAge_Call struct {
- *mock.Call
-}
-
-// SetMaxUnconfirmedAge is a helper method to define mock.On call
-// - _a0 *big.Int
-// - _a1 float64
-func (_e *PrometheusBackend_Expecter) SetMaxUnconfirmedAge(_a0 interface{}, _a1 interface{}) *PrometheusBackend_SetMaxUnconfirmedAge_Call {
- return &PrometheusBackend_SetMaxUnconfirmedAge_Call{Call: _e.mock.On("SetMaxUnconfirmedAge", _a0, _a1)}
-}
-
-func (_c *PrometheusBackend_SetMaxUnconfirmedAge_Call) Run(run func(_a0 *big.Int, _a1 float64)) *PrometheusBackend_SetMaxUnconfirmedAge_Call {
- _c.Call.Run(func(args mock.Arguments) {
- run(args[0].(*big.Int), args[1].(float64))
- })
- return _c
-}
-
-func (_c *PrometheusBackend_SetMaxUnconfirmedAge_Call) Return() *PrometheusBackend_SetMaxUnconfirmedAge_Call {
- _c.Call.Return()
- return _c
-}
-
-func (_c *PrometheusBackend_SetMaxUnconfirmedAge_Call) RunAndReturn(run func(*big.Int, float64)) *PrometheusBackend_SetMaxUnconfirmedAge_Call {
- _c.Call.Return(run)
- return _c
-}
-
-// SetMaxUnconfirmedBlocks provides a mock function with given fields: _a0, _a1
-func (_m *PrometheusBackend) SetMaxUnconfirmedBlocks(_a0 *big.Int, _a1 int64) {
- _m.Called(_a0, _a1)
-}
-
-// PrometheusBackend_SetMaxUnconfirmedBlocks_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SetMaxUnconfirmedBlocks'
-type PrometheusBackend_SetMaxUnconfirmedBlocks_Call struct {
- *mock.Call
-}
-
-// SetMaxUnconfirmedBlocks is a helper method to define mock.On call
-// - _a0 *big.Int
-// - _a1 int64
-func (_e *PrometheusBackend_Expecter) SetMaxUnconfirmedBlocks(_a0 interface{}, _a1 interface{}) *PrometheusBackend_SetMaxUnconfirmedBlocks_Call {
- return &PrometheusBackend_SetMaxUnconfirmedBlocks_Call{Call: _e.mock.On("SetMaxUnconfirmedBlocks", _a0, _a1)}
-}
-
-func (_c *PrometheusBackend_SetMaxUnconfirmedBlocks_Call) Run(run func(_a0 *big.Int, _a1 int64)) *PrometheusBackend_SetMaxUnconfirmedBlocks_Call {
- _c.Call.Run(func(args mock.Arguments) {
- run(args[0].(*big.Int), args[1].(int64))
- })
- return _c
-}
-
-func (_c *PrometheusBackend_SetMaxUnconfirmedBlocks_Call) Return() *PrometheusBackend_SetMaxUnconfirmedBlocks_Call {
- _c.Call.Return()
- return _c
-}
-
-func (_c *PrometheusBackend_SetMaxUnconfirmedBlocks_Call) RunAndReturn(run func(*big.Int, int64)) *PrometheusBackend_SetMaxUnconfirmedBlocks_Call {
- _c.Call.Return(run)
- return _c
-}
-
-// SetPipelineRunsQueued provides a mock function with given fields: n
-func (_m *PrometheusBackend) SetPipelineRunsQueued(n int) {
- _m.Called(n)
-}
-
-// PrometheusBackend_SetPipelineRunsQueued_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SetPipelineRunsQueued'
-type PrometheusBackend_SetPipelineRunsQueued_Call struct {
- *mock.Call
-}
-
-// SetPipelineRunsQueued is a helper method to define mock.On call
-// - n int
-func (_e *PrometheusBackend_Expecter) SetPipelineRunsQueued(n interface{}) *PrometheusBackend_SetPipelineRunsQueued_Call {
- return &PrometheusBackend_SetPipelineRunsQueued_Call{Call: _e.mock.On("SetPipelineRunsQueued", n)}
-}
-
-func (_c *PrometheusBackend_SetPipelineRunsQueued_Call) Run(run func(n int)) *PrometheusBackend_SetPipelineRunsQueued_Call {
- _c.Call.Run(func(args mock.Arguments) {
- run(args[0].(int))
- })
- return _c
-}
-
-func (_c *PrometheusBackend_SetPipelineRunsQueued_Call) Return() *PrometheusBackend_SetPipelineRunsQueued_Call {
- _c.Call.Return()
- return _c
-}
-
-func (_c *PrometheusBackend_SetPipelineRunsQueued_Call) RunAndReturn(run func(int)) *PrometheusBackend_SetPipelineRunsQueued_Call {
- _c.Call.Return(run)
- return _c
-}
-
-// SetPipelineTaskRunsQueued provides a mock function with given fields: n
-func (_m *PrometheusBackend) SetPipelineTaskRunsQueued(n int) {
- _m.Called(n)
-}
-
-// PrometheusBackend_SetPipelineTaskRunsQueued_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SetPipelineTaskRunsQueued'
-type PrometheusBackend_SetPipelineTaskRunsQueued_Call struct {
- *mock.Call
-}
-
-// SetPipelineTaskRunsQueued is a helper method to define mock.On call
-// - n int
-func (_e *PrometheusBackend_Expecter) SetPipelineTaskRunsQueued(n interface{}) *PrometheusBackend_SetPipelineTaskRunsQueued_Call {
- return &PrometheusBackend_SetPipelineTaskRunsQueued_Call{Call: _e.mock.On("SetPipelineTaskRunsQueued", n)}
-}
-
-func (_c *PrometheusBackend_SetPipelineTaskRunsQueued_Call) Run(run func(n int)) *PrometheusBackend_SetPipelineTaskRunsQueued_Call {
- _c.Call.Run(func(args mock.Arguments) {
- run(args[0].(int))
- })
- return _c
-}
-
-func (_c *PrometheusBackend_SetPipelineTaskRunsQueued_Call) Return() *PrometheusBackend_SetPipelineTaskRunsQueued_Call {
- _c.Call.Return()
- return _c
-}
-
-func (_c *PrometheusBackend_SetPipelineTaskRunsQueued_Call) RunAndReturn(run func(int)) *PrometheusBackend_SetPipelineTaskRunsQueued_Call {
- _c.Call.Return(run)
- return _c
-}
-
-// SetUnconfirmedTransactions provides a mock function with given fields: _a0, _a1
-func (_m *PrometheusBackend) SetUnconfirmedTransactions(_a0 *big.Int, _a1 int64) {
- _m.Called(_a0, _a1)
-}
-
-// PrometheusBackend_SetUnconfirmedTransactions_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SetUnconfirmedTransactions'
-type PrometheusBackend_SetUnconfirmedTransactions_Call struct {
- *mock.Call
-}
-
-// SetUnconfirmedTransactions is a helper method to define mock.On call
-// - _a0 *big.Int
-// - _a1 int64
-func (_e *PrometheusBackend_Expecter) SetUnconfirmedTransactions(_a0 interface{}, _a1 interface{}) *PrometheusBackend_SetUnconfirmedTransactions_Call {
- return &PrometheusBackend_SetUnconfirmedTransactions_Call{Call: _e.mock.On("SetUnconfirmedTransactions", _a0, _a1)}
-}
-
-func (_c *PrometheusBackend_SetUnconfirmedTransactions_Call) Run(run func(_a0 *big.Int, _a1 int64)) *PrometheusBackend_SetUnconfirmedTransactions_Call {
- _c.Call.Run(func(args mock.Arguments) {
- run(args[0].(*big.Int), args[1].(int64))
- })
- return _c
-}
-
-func (_c *PrometheusBackend_SetUnconfirmedTransactions_Call) Return() *PrometheusBackend_SetUnconfirmedTransactions_Call {
- _c.Call.Return()
- return _c
-}
-
-func (_c *PrometheusBackend_SetUnconfirmedTransactions_Call) RunAndReturn(run func(*big.Int, int64)) *PrometheusBackend_SetUnconfirmedTransactions_Call {
- _c.Call.Return(run)
- return _c
-}
-
-// NewPrometheusBackend creates a new instance of PrometheusBackend. 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 NewPrometheusBackend(t interface {
- mock.TestingT
- Cleanup(func())
-}) *PrometheusBackend {
- mock := &PrometheusBackend{}
- mock.Mock.Test(t)
-
- t.Cleanup(func() { mock.AssertExpectations(t) })
-
- return mock
-}
diff --git a/core/scripts/ccip/manual-execution/go.mod b/core/scripts/ccip/manual-execution/go.mod
index 8d646c03f8..356cf7d8dc 100644
--- a/core/scripts/ccip/manual-execution/go.mod
+++ b/core/scripts/ccip/manual-execution/go.mod
@@ -5,7 +5,7 @@ go 1.20
require (
github.com/ethereum/go-ethereum v1.11.3
github.com/pkg/errors v0.9.1
- github.com/smartcontractkit/chain-selectors v1.0.21
+ github.com/smartcontractkit/chain-selectors v1.0.27
go.uber.org/multierr v1.1.0
golang.org/x/crypto v0.1.0
)
diff --git a/core/scripts/ccip/manual-execution/go.sum b/core/scripts/ccip/manual-execution/go.sum
index d79872f13f..1e38a8a5e0 100644
--- a/core/scripts/ccip/manual-execution/go.sum
+++ b/core/scripts/ccip/manual-execution/go.sum
@@ -70,8 +70,8 @@ github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible h1:Bn1aCHHRnjv4Bl16T8rcaFjYSrGrIZvpiGO6P3Q4GpU=
github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
-github.com/smartcontractkit/chain-selectors v1.0.21 h1:KCR9SA7PhOexaBzFieHoLv1WonwhVOPtOStpqTmLC4E=
-github.com/smartcontractkit/chain-selectors v1.0.21/go.mod h1:d4Hi+E1zqjy9HqMkjBE5q1vcG9VGgxf5VxiRHfzi2kE=
+github.com/smartcontractkit/chain-selectors v1.0.27 h1:VE/ftX9Aae4gnw67yR1raKi+30iWKL/sWq8uyiLHM8k=
+github.com/smartcontractkit/chain-selectors v1.0.27/go.mod h1:d4Hi+E1zqjy9HqMkjBE5q1vcG9VGgxf5VxiRHfzi2kE=
github.com/status-im/keycard-go v0.2.0 h1:QDLFswOQu1r5jsycloeQh3bVU8n/NatHHaZobtDnDzA=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY=
diff --git a/core/scripts/go.mod b/core/scripts/go.mod
index 13ed1c4aa9..2a8aaff9dd 100644
--- a/core/scripts/go.mod
+++ b/core/scripts/go.mod
@@ -22,7 +22,7 @@ require (
github.com/pkg/errors v0.9.1
github.com/prometheus/client_golang v1.17.0
github.com/shopspring/decimal v1.4.0
- github.com/smartcontractkit/chain-selectors v1.0.21
+ github.com/smartcontractkit/chain-selectors v1.0.27
github.com/smartcontractkit/chainlink-automation v1.0.4
github.com/smartcontractkit/chainlink-common v0.2.1-0.20240717132349-ee5af9b79834
github.com/smartcontractkit/chainlink/v2 v2.0.0-00010101000000-000000000000
diff --git a/core/scripts/go.sum b/core/scripts/go.sum
index 468c13a670..38c63819f8 100644
--- a/core/scripts/go.sum
+++ b/core/scripts/go.sum
@@ -1066,8 +1066,8 @@ github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMB
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
-github.com/smartcontractkit/chain-selectors v1.0.21 h1:KCR9SA7PhOexaBzFieHoLv1WonwhVOPtOStpqTmLC4E=
-github.com/smartcontractkit/chain-selectors v1.0.21/go.mod h1:d4Hi+E1zqjy9HqMkjBE5q1vcG9VGgxf5VxiRHfzi2kE=
+github.com/smartcontractkit/chain-selectors v1.0.27 h1:VE/ftX9Aae4gnw67yR1raKi+30iWKL/sWq8uyiLHM8k=
+github.com/smartcontractkit/chain-selectors v1.0.27/go.mod h1:d4Hi+E1zqjy9HqMkjBE5q1vcG9VGgxf5VxiRHfzi2kE=
github.com/smartcontractkit/chainlink-automation v1.0.4 h1:iyW181JjKHLNMnDleI8umfIfVVlwC7+n5izbLSFgjw8=
github.com/smartcontractkit/chainlink-automation v1.0.4/go.mod h1:u4NbPZKJ5XiayfKHD/v3z3iflQWqvtdhj13jVZXj/cM=
github.com/smartcontractkit/chainlink-ccip v0.0.0-20240816163757-48726fd8165f h1:lQZBOjeYFpCdk0mGQUhbrJipd00tu49xK4zSijC/9Co=
diff --git a/core/services/chainlink/application.go b/core/services/chainlink/application.go
index de3e4c6280..a759f0ee11 100644
--- a/core/services/chainlink/application.go
+++ b/core/services/chainlink/application.go
@@ -49,6 +49,7 @@ import (
"github.com/smartcontractkit/chainlink/v2/core/services/feeds"
"github.com/smartcontractkit/chainlink/v2/core/services/fluxmonitorv2"
"github.com/smartcontractkit/chainlink/v2/core/services/gateway"
+ "github.com/smartcontractkit/chainlink/v2/core/services/headreporter"
"github.com/smartcontractkit/chainlink/v2/core/services/job"
"github.com/smartcontractkit/chainlink/v2/core/services/keeper"
"github.com/smartcontractkit/chainlink/v2/core/services/keystore"
@@ -60,7 +61,6 @@ import (
externalp2p "github.com/smartcontractkit/chainlink/v2/core/services/p2p/wrapper"
"github.com/smartcontractkit/chainlink/v2/core/services/periodicbackup"
"github.com/smartcontractkit/chainlink/v2/core/services/pipeline"
- "github.com/smartcontractkit/chainlink/v2/core/services/promreporter"
"github.com/smartcontractkit/chainlink/v2/core/services/registrysyncer"
"github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury"
"github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/wsrpc"
@@ -324,8 +324,6 @@ func NewApplication(opts ApplicationOpts) (Application, error) {
srvcs = append(srvcs, mailMon)
srvcs = append(srvcs, relayerChainInterops.Services()...)
- promReporter := promreporter.NewPromReporter(opts.DS, legacyEVMChains, globalLogger)
- srvcs = append(srvcs, promReporter)
// Initialize Local Users ORM and Authentication Provider specified in config
// BasicAdminUsersORM is initialized and required regardless of separate Authentication Provider
@@ -365,8 +363,16 @@ func NewApplication(opts ApplicationOpts) (Application, error) {
workflowORM = workflowstore.NewDBStore(opts.DS, globalLogger, clockwork.NewRealClock())
)
+ promReporter := headreporter.NewPrometheusReporter(opts.DS, legacyEVMChains)
+ chainIDs := make([]*big.Int, legacyEVMChains.Len())
+ for i, chain := range legacyEVMChains.Slice() {
+ chainIDs[i] = chain.ID()
+ }
+ telemReporter := headreporter.NewTelemetryReporter(telemetryManager, globalLogger, chainIDs...)
+ headReporter := headreporter.NewHeadReporterService(opts.DS, globalLogger, promReporter, telemReporter)
+ srvcs = append(srvcs, headReporter)
for _, chain := range legacyEVMChains.Slice() {
- chain.HeadBroadcaster().Subscribe(promReporter)
+ chain.HeadBroadcaster().Subscribe(headReporter)
chain.TxManager().RegisterResumeCallback(pipelineRunner.ResumeRun)
}
diff --git a/core/services/chainlink/config_test.go b/core/services/chainlink/config_test.go
index e9ed223b9b..ebdfec7117 100644
--- a/core/services/chainlink/config_test.go
+++ b/core/services/chainlink/config_test.go
@@ -520,6 +520,7 @@ func TestConfig_Marshal(t *testing.T) {
LimitMax: ptr[uint64](17),
LimitMultiplier: mustDecimal("1.234"),
LimitTransfer: ptr[uint64](100),
+ EstimateLimit: ptr(false),
TipCapDefault: assets.NewWeiI(2),
TipCapMin: assets.NewWeiI(1),
PriceDefault: assets.NewWeiI(math.MaxInt64),
@@ -543,6 +544,9 @@ func TestConfig_Marshal(t *testing.T) {
EIP1559FeeCapBufferBlocks: ptr[uint16](13),
TransactionPercentile: ptr[uint16](15),
},
+ FeeHistory: evmcfg.FeeHistoryEstimator{
+ CacheTimeout: &second,
+ },
},
KeySpecific: []evmcfg.KeySpecific{
@@ -565,6 +569,7 @@ func TestConfig_Marshal(t *testing.T) {
NonceAutoSync: ptr(true),
NoNewHeadsThreshold: &minute,
OperatorFactoryAddress: mustAddress("0xa5B85635Be42F21f94F28034B7DA440EeFF0F418"),
+ LogBroadcasterEnabled: ptr(true),
RPCDefaultBatchSize: ptr[uint32](17),
RPCBlockQueryDelay: ptr[uint16](10),
NoNewFinalizedHeadsThreshold: &hour,
@@ -599,6 +604,7 @@ func TestConfig_Marshal(t *testing.T) {
FinalizedBlockPollInterval: &second,
EnforceRepeatableRead: ptr(true),
DeathDeclarationDelay: &minute,
+ NewHeadsPollInterval: &zeroSeconds,
Errors: evmcfg.ClientErrors{
NonceTooLow: ptr[string]("(: |^)nonce too low"),
NonceTooHigh: ptr[string]("(: |^)nonce too high"),
@@ -996,6 +1002,7 @@ MinContractPayment = '9.223372036854775807 link'
NonceAutoSync = true
NoNewHeadsThreshold = '1m0s'
OperatorFactoryAddress = '0xa5B85635Be42F21f94F28034B7DA440EeFF0F418'
+LogBroadcasterEnabled = true
RPCDefaultBatchSize = 17
RPCBlockQueryDelay = 10
FinalizedBlockOffset = 16
@@ -1024,6 +1031,7 @@ LimitDefault = 12
LimitMax = 17
LimitMultiplier = '1.234'
LimitTransfer = 100
+EstimateLimit = false
BumpMin = '100 wei'
BumpPercent = 10
BumpThreshold = 6
@@ -1049,6 +1057,9 @@ CheckInclusionPercentile = 19
EIP1559FeeCapBufferBlocks = 13
TransactionPercentile = 15
+[EVM.GasEstimator.FeeHistory]
+CacheTimeout = '1s'
+
[EVM.HeadTracker]
HistoryDepth = 15
MaxBufferSize = 17
@@ -1072,6 +1083,7 @@ NodeIsSyncingEnabled = true
FinalizedBlockPollInterval = '1s'
EnforceRepeatableRead = true
DeathDeclarationDelay = '1m0s'
+NewHeadsPollInterval = '0s'
[EVM.NodePool.Errors]
NonceTooLow = '(: |^)nonce too low'
@@ -1283,11 +1295,12 @@ func TestConfig_Validate(t *testing.T) {
- LDAP.RunUserGroupCN: invalid value (HeadReporter
+JobSpawner
PromReporter
-TelemetryManager
+ +```toml +AutoCreateKey = true +BlockBackfillDepth = 10 +BlockBackfillSkip = false +ChainType = 'optimismBedrock' +FinalityDepth = 2000 +FinalityTagEnabled = true +LogBackfillBatchSize = 1000 +LogPollInterval = '5s' +LogKeepBlocksDepth = 100000 +LogPrunePageSize = 10000 +BackupLogPollerBlockDelay = 100 +MinIncomingConfirmations = 3 +MinContractPayment = '0.00001 link' +NonceAutoSync = true +NoNewHeadsThreshold = '3m0s' +LogBroadcasterEnabled = true +RPCDefaultBatchSize = 250 +RPCBlockQueryDelay = 1 +FinalizedBlockOffset = 0 +NoNewFinalizedHeadsThreshold = '1h10m0s' + +[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 = '115792089237316195423570985008687907853269984665.640564039457584007913129639935 tether' +PriceMin = '1 gwei' +LimitDefault = 8000000 +LimitMax = 8000000 +LimitMultiplier = '1' +LimitTransfer = 21000 +EstimateLimit = false +BumpMin = '5 gwei' +BumpPercent = 20 +BumpThreshold = 3 +EIP1559DynamicFees = true +FeeCapDefault = '100 gwei' +TipCapDefault = '1 wei' +TipCapMin = '1 wei' + +[GasEstimator.BlockHistory] +BatchSize = 25 +BlockHistorySize = 100 +CheckInclusionBlocks = 12 +CheckInclusionPercentile = 90 +TransactionPercentile = 60 + +[GasEstimator.FeeHistory] +CacheTimeout = '2s' + +[HeadTracker] +HistoryDepth = 100 +MaxBufferSize = 3 +SamplingInterval = '1s' +MaxAllowedFinalityDepth = 10000 +FinalityTagBypass = true + +[NodePool] +PollFailureThreshold = 5 +PollInterval = '10s' +SelectionMode = 'HighestHead' +SyncThreshold = 5 +LeaseDuration = '0s' +NodeIsSyncingEnabled = false +FinalizedBlockPollInterval = '5s' +EnforceRepeatableRead = false +DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' + +[OCR] +ContractConfirmations = 4 +ContractTransmitterTransmitTimeout = '10s' +DatabaseTimeout = '10s' +DeltaCOverride = '168h0m0s' +DeltaCJitterOverride = '1h0m0s' +ObservationGracePeriod = '1s' + +[OCR2] +[OCR2.Automation] +GasLimit = 5400000 +``` + +
```toml @@ -3482,6 +3690,7 @@ MinIncomingConfirmations = 3 MinContractPayment = '0.00001 link' NonceAutoSync = true NoNewHeadsThreshold = '30s' +LogBroadcasterEnabled = true RPCDefaultBatchSize = 250 RPCBlockQueryDelay = 2 FinalizedBlockOffset = 0 @@ -3510,6 +3719,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 +EstimateLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -3525,6 +3735,9 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.FeeHistory] +CacheTimeout = '10s' + [HeadTracker] HistoryDepth = 100 MaxBufferSize = 3 @@ -3542,6 +3755,7 @@ NodeIsSyncingEnabled = false FinalizedBlockPollInterval = '5s' EnforceRepeatableRead = false DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' [OCR] ContractConfirmations = 4 @@ -3576,6 +3790,7 @@ MinIncomingConfirmations = 1 MinContractPayment = '0.00001 link' NonceAutoSync = true NoNewHeadsThreshold = '40s' +LogBroadcasterEnabled = true RPCDefaultBatchSize = 250 RPCBlockQueryDelay = 1 FinalizedBlockOffset = 0 @@ -3604,6 +3819,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 +EstimateLimit = false BumpMin = '100 wei' BumpPercent = 20 BumpThreshold = 3 @@ -3619,6 +3835,9 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.FeeHistory] +CacheTimeout = '10s' + [HeadTracker] HistoryDepth = 400 MaxBufferSize = 3 @@ -3636,6 +3855,7 @@ NodeIsSyncingEnabled = false FinalizedBlockPollInterval = '5s' EnforceRepeatableRead = false DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' [OCR] ContractConfirmations = 1 @@ -3670,6 +3890,7 @@ MinIncomingConfirmations = 1 MinContractPayment = '0.00001 link' NonceAutoSync = true NoNewHeadsThreshold = '1m0s' +LogBroadcasterEnabled = true RPCDefaultBatchSize = 250 RPCBlockQueryDelay = 1 FinalizedBlockOffset = 0 @@ -3698,6 +3919,7 @@ LimitDefault = 100000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 +EstimateLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -3713,6 +3935,9 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.FeeHistory] +CacheTimeout = '10s' + [HeadTracker] HistoryDepth = 50 MaxBufferSize = 3 @@ -3730,6 +3955,7 @@ NodeIsSyncingEnabled = false FinalizedBlockPollInterval = '5s' EnforceRepeatableRead = false DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' [OCR] ContractConfirmations = 4 @@ -3764,6 +3990,7 @@ MinIncomingConfirmations = 3 MinContractPayment = '0.00001 link' NonceAutoSync = true NoNewHeadsThreshold = '1m0s' +LogBroadcasterEnabled = true RPCDefaultBatchSize = 250 RPCBlockQueryDelay = 1 FinalizedBlockOffset = 0 @@ -3792,6 +4019,7 @@ LimitDefault = 2500000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 +EstimateLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -3807,6 +4035,9 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.FeeHistory] +CacheTimeout = '10s' + [HeadTracker] HistoryDepth = 250 MaxBufferSize = 3 @@ -3824,6 +4055,7 @@ NodeIsSyncingEnabled = false FinalizedBlockPollInterval = '5s' EnforceRepeatableRead = false DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' [OCR] ContractConfirmations = 4 @@ -3858,6 +4090,7 @@ MinIncomingConfirmations = 3 MinContractPayment = '0.00001 link' NonceAutoSync = true NoNewHeadsThreshold = '1m0s' +LogBroadcasterEnabled = true RPCDefaultBatchSize = 250 RPCBlockQueryDelay = 1 FinalizedBlockOffset = 0 @@ -3886,6 +4119,7 @@ LimitDefault = 2500000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 +EstimateLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -3901,6 +4135,9 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.FeeHistory] +CacheTimeout = '10s' + [HeadTracker] HistoryDepth = 1500 MaxBufferSize = 3 @@ -3918,6 +4155,7 @@ NodeIsSyncingEnabled = false FinalizedBlockPollInterval = '5s' EnforceRepeatableRead = false DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' [OCR] ContractConfirmations = 4 @@ -3953,6 +4191,7 @@ MinIncomingConfirmations = 1 MinContractPayment = '0.00001 link' NonceAutoSync = true NoNewHeadsThreshold = '40s' +LogBroadcasterEnabled = true RPCDefaultBatchSize = 250 RPCBlockQueryDelay = 1 FinalizedBlockOffset = 0 @@ -3981,6 +4220,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 +EstimateLimit = false BumpMin = '100 wei' BumpPercent = 20 BumpThreshold = 3 @@ -3996,6 +4236,9 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.FeeHistory] +CacheTimeout = '10s' + [HeadTracker] HistoryDepth = 300 MaxBufferSize = 3 @@ -4013,6 +4256,7 @@ NodeIsSyncingEnabled = false FinalizedBlockPollInterval = '5s' EnforceRepeatableRead = false DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' [OCR] ContractConfirmations = 1 @@ -4029,6 +4273,106 @@ GasLimit = 6500000
+ +```toml +AutoCreateKey = true +BlockBackfillDepth = 10 +BlockBackfillSkip = false +ChainType = 'optimismBedrock' +FinalityDepth = 2500 +FinalityTagEnabled = true +LogBackfillBatchSize = 1000 +LogPollInterval = '5s' +LogKeepBlocksDepth = 100000 +LogPrunePageSize = 10000 +BackupLogPollerBlockDelay = 100 +MinIncomingConfirmations = 3 +MinContractPayment = '0.00001 link' +NonceAutoSync = true +NoNewHeadsThreshold = '3m0s' +LogBroadcasterEnabled = true +RPCDefaultBatchSize = 250 +RPCBlockQueryDelay = 1 +FinalizedBlockOffset = 0 +NoNewFinalizedHeadsThreshold = '1h30m0s' + +[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 = '115792089237316195423570985008687907853269984665.640564039457584007913129639935 tether' +PriceMin = '1 gwei' +LimitDefault = 8000000 +LimitMax = 8000000 +LimitMultiplier = '1' +LimitTransfer = 21000 +EstimateLimit = false +BumpMin = '5 gwei' +BumpPercent = 20 +BumpThreshold = 3 +EIP1559DynamicFees = true +FeeCapDefault = '100 gwei' +TipCapDefault = '1 wei' +TipCapMin = '1 wei' + +[GasEstimator.BlockHistory] +BatchSize = 25 +BlockHistorySize = 100 +CheckInclusionBlocks = 12 +CheckInclusionPercentile = 90 +TransactionPercentile = 60 + +[GasEstimator.FeeHistory] +CacheTimeout = '2s' + +[HeadTracker] +HistoryDepth = 100 +MaxBufferSize = 3 +SamplingInterval = '1s' +MaxAllowedFinalityDepth = 10000 +FinalityTagBypass = true + +[NodePool] +PollFailureThreshold = 5 +PollInterval = '10s' +SelectionMode = 'HighestHead' +SyncThreshold = 5 +LeaseDuration = '0s' +NodeIsSyncingEnabled = false +FinalizedBlockPollInterval = '5s' +EnforceRepeatableRead = false +DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' + +[OCR] +ContractConfirmations = 4 +ContractTransmitterTransmitTimeout = '10s' +DatabaseTimeout = '10s' +DeltaCOverride = '168h0m0s' +DeltaCJitterOverride = '1h0m0s' +ObservationGracePeriod = '1s' + +[OCR2] +[OCR2.Automation] +GasLimit = 5400000 +``` + +
```toml @@ -4047,6 +4391,7 @@ MinIncomingConfirmations = 1 MinContractPayment = '0.00001 link' NonceAutoSync = true NoNewHeadsThreshold = '0s' +LogBroadcasterEnabled = true RPCDefaultBatchSize = 250 RPCBlockQueryDelay = 1 FinalizedBlockOffset = 0 @@ -4075,6 +4420,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 +EstimateLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -4090,6 +4436,9 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.FeeHistory] +CacheTimeout = '10s' + [HeadTracker] HistoryDepth = 100 MaxBufferSize = 3 @@ -4107,6 +4456,7 @@ NodeIsSyncingEnabled = false FinalizedBlockPollInterval = '5s' EnforceRepeatableRead = false DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' [OCR] ContractConfirmations = 1 @@ -4129,8 +4479,9 @@ GasLimit = 5400000 AutoCreateKey = true BlockBackfillDepth = 10 BlockBackfillSkip = false +ChainType = 'astar' FinalityDepth = 100 -FinalityTagEnabled = false +FinalityTagEnabled = true LogBackfillBatchSize = 1000 LogPollInterval = '6s' LogKeepBlocksDepth = 100000 @@ -4140,6 +4491,7 @@ MinIncomingConfirmations = 3 MinContractPayment = '0.00001 link' NonceAutoSync = true NoNewHeadsThreshold = '3m0s' +LogBroadcasterEnabled = true RPCDefaultBatchSize = 250 RPCBlockQueryDelay = 1 FinalizedBlockOffset = 0 @@ -4168,6 +4520,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 +EstimateLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -4183,6 +4536,9 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.FeeHistory] +CacheTimeout = '10s' + [HeadTracker] HistoryDepth = 100 MaxBufferSize = 3 @@ -4200,6 +4556,7 @@ NodeIsSyncingEnabled = false FinalizedBlockPollInterval = '5s' EnforceRepeatableRead = false DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' [OCR] ContractConfirmations = 4 @@ -4234,6 +4591,7 @@ MinIncomingConfirmations = 3 MinContractPayment = '0.00001 link' NonceAutoSync = true NoNewHeadsThreshold = '3m0s' +LogBroadcasterEnabled = true RPCDefaultBatchSize = 250 RPCBlockQueryDelay = 1 FinalizedBlockOffset = 0 @@ -4262,6 +4620,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 +EstimateLimit = false BumpMin = '100 wei' BumpPercent = 20 BumpThreshold = 60 @@ -4278,6 +4637,9 @@ CheckInclusionPercentile = 90 EIP1559FeeCapBufferBlocks = 0 TransactionPercentile = 60 +[GasEstimator.FeeHistory] +CacheTimeout = '10s' + [HeadTracker] HistoryDepth = 300 MaxBufferSize = 3 @@ -4295,6 +4657,7 @@ NodeIsSyncingEnabled = false FinalizedBlockPollInterval = '5s' EnforceRepeatableRead = false DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' [OCR] ContractConfirmations = 4 @@ -4328,6 +4691,7 @@ MinIncomingConfirmations = 1 MinContractPayment = '0.00001 link' NonceAutoSync = true NoNewHeadsThreshold = '30s' +LogBroadcasterEnabled = true RPCDefaultBatchSize = 250 RPCBlockQueryDelay = 1 FinalizedBlockOffset = 0 @@ -4356,6 +4720,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 +EstimateLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 5 @@ -4371,6 +4736,9 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.FeeHistory] +CacheTimeout = '10s' + [HeadTracker] HistoryDepth = 100 MaxBufferSize = 3 @@ -4388,6 +4756,7 @@ NodeIsSyncingEnabled = false FinalizedBlockPollInterval = '5s' EnforceRepeatableRead = false DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' [OCR] ContractConfirmations = 1 @@ -4410,7 +4779,7 @@ GasLimit = 5400000 AutoCreateKey = true BlockBackfillDepth = 10 BlockBackfillSkip = false -ChainType = 'metis' +ChainType = 'optimismBedrock' FinalityDepth = 10 FinalityTagEnabled = true LogBackfillBatchSize = 1000 @@ -4422,6 +4791,7 @@ MinIncomingConfirmations = 1 MinContractPayment = '0.00001 link' NonceAutoSync = true NoNewHeadsThreshold = '0s' +LogBroadcasterEnabled = true RPCDefaultBatchSize = 250 RPCBlockQueryDelay = 1 FinalizedBlockOffset = 0 @@ -4450,6 +4820,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 +EstimateLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -4465,6 +4836,9 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.FeeHistory] +CacheTimeout = '10s' + [HeadTracker] HistoryDepth = 100 MaxBufferSize = 3 @@ -4482,6 +4856,7 @@ NodeIsSyncingEnabled = false FinalizedBlockPollInterval = '5s' EnforceRepeatableRead = false DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' [OCR] ContractConfirmations = 1 @@ -4516,6 +4891,7 @@ MinIncomingConfirmations = 1 MinContractPayment = '0.00001 link' NonceAutoSync = true NoNewHeadsThreshold = '6m0s' +LogBroadcasterEnabled = true RPCDefaultBatchSize = 100 RPCBlockQueryDelay = 15 FinalizedBlockOffset = 0 @@ -4536,15 +4912,16 @@ Enabled = false Enabled = true [GasEstimator] -Mode = 'BlockHistory' +Mode = 'FeeHistory' PriceDefault = '20 gwei' PriceMax = '115792089237316195423570985008687907853269984665.640564039457584007913129639935 tether' -PriceMin = '100 mwei' +PriceMin = '0' LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 -BumpMin = '100 mwei' +EstimateLimit = false +BumpMin = '5 gwei' BumpPercent = 40 BumpThreshold = 3 EIP1559DynamicFees = false @@ -4554,11 +4931,14 @@ TipCapMin = '1 wei' [GasEstimator.BlockHistory] BatchSize = 25 -BlockHistorySize = 12 +BlockHistorySize = 8 CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.FeeHistory] +CacheTimeout = '4s' + [HeadTracker] HistoryDepth = 2000 MaxBufferSize = 3 @@ -4576,6 +4956,7 @@ NodeIsSyncingEnabled = false FinalizedBlockPollInterval = '5s' EnforceRepeatableRead = false DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' [OCR] ContractConfirmations = 1 @@ -4610,6 +4991,7 @@ MinIncomingConfirmations = 1 MinContractPayment = '0.00001 link' NonceAutoSync = true NoNewHeadsThreshold = '30s' +LogBroadcasterEnabled = true RPCDefaultBatchSize = 250 RPCBlockQueryDelay = 1 FinalizedBlockOffset = 0 @@ -4638,6 +5020,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 +EstimateLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -4653,6 +5036,9 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.FeeHistory] +CacheTimeout = '10s' + [HeadTracker] HistoryDepth = 100 MaxBufferSize = 3 @@ -4670,6 +5056,7 @@ NodeIsSyncingEnabled = false FinalizedBlockPollInterval = '5s' EnforceRepeatableRead = false DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' [OCR] ContractConfirmations = 1 @@ -4704,6 +5091,7 @@ MinIncomingConfirmations = 1 MinContractPayment = '0.00001 link' NonceAutoSync = true NoNewHeadsThreshold = '30s' +LogBroadcasterEnabled = true RPCDefaultBatchSize = 250 RPCBlockQueryDelay = 1 FinalizedBlockOffset = 0 @@ -4732,6 +5120,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 +EstimateLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -4747,6 +5136,9 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.FeeHistory] +CacheTimeout = '10s' + [HeadTracker] HistoryDepth = 100 MaxBufferSize = 3 @@ -4764,6 +5156,7 @@ NodeIsSyncingEnabled = false FinalizedBlockPollInterval = '5s' EnforceRepeatableRead = false DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' [OCR] ContractConfirmations = 1 @@ -4780,35 +5173,37 @@ GasLimit = 5400000
+
```toml
AutoCreateKey = true
BlockBackfillDepth = 10
BlockBackfillSkip = false
-FinalityDepth = 10
-FinalityTagEnabled = false
+ChainType = 'optimismBedrock'
+FinalityDepth = 2000
+FinalityTagEnabled = true
LogBackfillBatchSize = 1000
-LogPollInterval = '15s'
+LogPollInterval = '5s'
LogKeepBlocksDepth = 100000
LogPrunePageSize = 10000
BackupLogPollerBlockDelay = 100
-MinIncomingConfirmations = 1
-MinContractPayment = '100'
+MinIncomingConfirmations = 3
+MinContractPayment = '0.00001 link'
NonceAutoSync = true
-NoNewHeadsThreshold = '0s'
+NoNewHeadsThreshold = '3m0s'
+LogBroadcasterEnabled = true
RPCDefaultBatchSize = 250
RPCBlockQueryDelay = 1
FinalizedBlockOffset = 0
-NoNewFinalizedHeadsThreshold = '0s'
+NoNewFinalizedHeadsThreshold = '1h10m0s'
[Transactions]
ForwardersEnabled = false
MaxInFlight = 16
MaxQueued = 250
ReaperInterval = '1h0m0s'
-ReaperThreshold = '0s'
-ResendAfterThreshold = '0s'
+ReaperThreshold = '168h0m0s'
+ResendAfterThreshold = '1m0s'
[Transactions.AutoPurge]
Enabled = false
@@ -4817,33 +5212,37 @@ Enabled = false
Enabled = true
[GasEstimator]
-Mode = 'FixedPrice'
+Mode = 'FeeHistory'
PriceDefault = '20 gwei'
-PriceMax = '100 micro'
-PriceMin = '0'
+PriceMax = '115792089237316195423570985008687907853269984665.640564039457584007913129639935 tether'
+PriceMin = '1 gwei'
LimitDefault = 8000000
LimitMax = 8000000
LimitMultiplier = '1'
LimitTransfer = 21000
+EstimateLimit = false
BumpMin = '5 gwei'
BumpPercent = 20
-BumpThreshold = 0
-EIP1559DynamicFees = false
-FeeCapDefault = '100 micro'
+BumpThreshold = 3
+EIP1559DynamicFees = true
+FeeCapDefault = '100 gwei'
TipCapDefault = '1 wei'
TipCapMin = '1 wei'
[GasEstimator.BlockHistory]
BatchSize = 25
-BlockHistorySize = 8
+BlockHistorySize = 100
CheckInclusionBlocks = 12
CheckInclusionPercentile = 90
TransactionPercentile = 60
+[GasEstimator.FeeHistory]
+CacheTimeout = '2s'
+
[HeadTracker]
-HistoryDepth = 10
-MaxBufferSize = 100
-SamplingInterval = '0s'
+HistoryDepth = 100
+MaxBufferSize = 3
+SamplingInterval = '1s'
MaxAllowedFinalityDepth = 10000
FinalityTagBypass = true
@@ -4857,9 +5256,10 @@ NodeIsSyncingEnabled = false
FinalizedBlockPollInterval = '5s'
EnforceRepeatableRead = false
DeathDeclarationDelay = '10s'
+NewHeadsPollInterval = '0s'
[OCR]
-ContractConfirmations = 1
+ContractConfirmations = 4
ContractTransmitterTransmitTimeout = '10s'
DatabaseTimeout = '10s'
DeltaCOverride = '168h0m0s'
@@ -4873,28 +5273,29 @@ GasLimit = 5400000
+
```toml
AutoCreateKey = true
BlockBackfillDepth = 10
BlockBackfillSkip = false
-ChainType = 'zkevm'
-FinalityDepth = 500
-FinalityTagEnabled = false
+ChainType = 'optimismBedrock'
+FinalityDepth = 2000
+FinalityTagEnabled = true
LogBackfillBatchSize = 1000
-LogPollInterval = '30s'
+LogPollInterval = '5s'
LogKeepBlocksDepth = 100000
LogPrunePageSize = 10000
BackupLogPollerBlockDelay = 100
-MinIncomingConfirmations = 1
+MinIncomingConfirmations = 3
MinContractPayment = '0.00001 link'
NonceAutoSync = true
-NoNewHeadsThreshold = '12m0s'
-RPCDefaultBatchSize = 100
+NoNewHeadsThreshold = '3m0s'
+LogBroadcasterEnabled = true
+RPCDefaultBatchSize = 250
RPCBlockQueryDelay = 1
FinalizedBlockOffset = 0
-NoNewFinalizedHeadsThreshold = '0s'
+NoNewFinalizedHeadsThreshold = '10m0s'
[Transactions]
ForwardersEnabled = false
@@ -4902,7 +5303,7 @@ MaxInFlight = 16
MaxQueued = 250
ReaperInterval = '1h0m0s'
ReaperThreshold = '168h0m0s'
-ResendAfterThreshold = '3m0s'
+ResendAfterThreshold = '1m0s'
[Transactions.AutoPurge]
Enabled = false
@@ -4911,31 +5312,35 @@ Enabled = false
Enabled = true
[GasEstimator]
-Mode = 'BlockHistory'
+Mode = 'FeeHistory'
PriceDefault = '20 gwei'
PriceMax = '115792089237316195423570985008687907853269984665.640564039457584007913129639935 tether'
-PriceMin = '50 mwei'
+PriceMin = '1 gwei'
LimitDefault = 8000000
LimitMax = 8000000
LimitMultiplier = '1'
LimitTransfer = 21000
-BumpMin = '20 mwei'
-BumpPercent = 40
+EstimateLimit = false
+BumpMin = '5 gwei'
+BumpPercent = 20
BumpThreshold = 3
-EIP1559DynamicFees = false
+EIP1559DynamicFees = true
FeeCapDefault = '100 gwei'
TipCapDefault = '1 wei'
TipCapMin = '1 wei'
[GasEstimator.BlockHistory]
BatchSize = 25
-BlockHistorySize = 12
+BlockHistorySize = 100
CheckInclusionBlocks = 12
CheckInclusionPercentile = 90
TransactionPercentile = 60
+[GasEstimator.FeeHistory]
+CacheTimeout = '1s'
+
[HeadTracker]
-HistoryDepth = 2000
+HistoryDepth = 100
MaxBufferSize = 3
SamplingInterval = '1s'
MaxAllowedFinalityDepth = 10000
@@ -4951,6 +5356,106 @@ NodeIsSyncingEnabled = false
FinalizedBlockPollInterval = '5s'
EnforceRepeatableRead = false
DeathDeclarationDelay = '10s'
+NewHeadsPollInterval = '0s'
+
+[OCR]
+ContractConfirmations = 4
+ContractTransmitterTransmitTimeout = '10s'
+DatabaseTimeout = '10s'
+DeltaCOverride = '168h0m0s'
+DeltaCJitterOverride = '1h0m0s'
+ObservationGracePeriod = '1s'
+
+[OCR2]
+[OCR2.Automation]
+GasLimit = 5400000
+```
+
+
+
+```toml
+AutoCreateKey = true
+BlockBackfillDepth = 10
+BlockBackfillSkip = false
+FinalityDepth = 10
+FinalityTagEnabled = false
+LogBackfillBatchSize = 1000
+LogPollInterval = '15s'
+LogKeepBlocksDepth = 100000
+LogPrunePageSize = 10000
+BackupLogPollerBlockDelay = 100
+MinIncomingConfirmations = 1
+MinContractPayment = '100'
+NonceAutoSync = true
+NoNewHeadsThreshold = '0s'
+LogBroadcasterEnabled = true
+RPCDefaultBatchSize = 250
+RPCBlockQueryDelay = 1
+FinalizedBlockOffset = 0
+NoNewFinalizedHeadsThreshold = '0s'
+
+[Transactions]
+ForwardersEnabled = false
+MaxInFlight = 16
+MaxQueued = 250
+ReaperInterval = '1h0m0s'
+ReaperThreshold = '0s'
+ResendAfterThreshold = '0s'
+
+[Transactions.AutoPurge]
+Enabled = false
+
+[BalanceMonitor]
+Enabled = true
+
+[GasEstimator]
+Mode = 'FixedPrice'
+PriceDefault = '20 gwei'
+PriceMax = '100 micro'
+PriceMin = '0'
+LimitDefault = 8000000
+LimitMax = 8000000
+LimitMultiplier = '1'
+LimitTransfer = 21000
+EstimateLimit = false
+BumpMin = '5 gwei'
+BumpPercent = 20
+BumpThreshold = 0
+EIP1559DynamicFees = false
+FeeCapDefault = '100 micro'
+TipCapDefault = '1 wei'
+TipCapMin = '1 wei'
+
+[GasEstimator.BlockHistory]
+BatchSize = 25
+BlockHistorySize = 8
+CheckInclusionBlocks = 12
+CheckInclusionPercentile = 90
+TransactionPercentile = 60
+
+[GasEstimator.FeeHistory]
+CacheTimeout = '10s'
+
+[HeadTracker]
+HistoryDepth = 10
+MaxBufferSize = 100
+SamplingInterval = '0s'
+MaxAllowedFinalityDepth = 10000
+FinalityTagBypass = true
+
+[NodePool]
+PollFailureThreshold = 5
+PollInterval = '10s'
+SelectionMode = 'HighestHead'
+SyncThreshold = 5
+LeaseDuration = '0s'
+NodeIsSyncingEnabled = false
+FinalizedBlockPollInterval = '5s'
+EnforceRepeatableRead = false
+DeathDeclarationDelay = '10s'
+NewHeadsPollInterval = '0s'
[OCR]
ContractConfirmations = 1
@@ -4985,6 +5490,7 @@ MinIncomingConfirmations = 1
MinContractPayment = '0.00001 link'
NonceAutoSync = true
NoNewHeadsThreshold = '40s'
+LogBroadcasterEnabled = true
RPCDefaultBatchSize = 250
RPCBlockQueryDelay = 1
FinalizedBlockOffset = 0
@@ -5013,6 +5519,7 @@ LimitDefault = 8000000
LimitMax = 8000000
LimitMultiplier = '1'
LimitTransfer = 21000
+EstimateLimit = false
BumpMin = '100 wei'
BumpPercent = 20
BumpThreshold = 3
@@ -5028,6 +5535,9 @@ CheckInclusionBlocks = 12
CheckInclusionPercentile = 90
TransactionPercentile = 60
+[GasEstimator.FeeHistory]
+CacheTimeout = '10s'
+
[HeadTracker]
HistoryDepth = 400
MaxBufferSize = 3
@@ -5045,6 +5555,7 @@ NodeIsSyncingEnabled = false
FinalizedBlockPollInterval = '5s'
EnforceRepeatableRead = false
DeathDeclarationDelay = '10s'
+NewHeadsPollInterval = '0s'
[OCR]
ContractConfirmations = 1
@@ -5079,6 +5590,7 @@ MinIncomingConfirmations = 1
MinContractPayment = '0.00001 link'
NonceAutoSync = true
NoNewHeadsThreshold = '12m0s'
+LogBroadcasterEnabled = true
RPCDefaultBatchSize = 100
RPCBlockQueryDelay = 1
FinalizedBlockOffset = 0
@@ -5099,15 +5611,16 @@ Enabled = false
Enabled = true
[GasEstimator]
-Mode = 'BlockHistory'
+Mode = 'FeeHistory'
PriceDefault = '20 gwei'
PriceMax = '115792089237316195423570985008687907853269984665.640564039457584007913129639935 tether'
-PriceMin = '1 mwei'
+PriceMin = '0'
LimitDefault = 8000000
LimitMax = 8000000
LimitMultiplier = '1'
LimitTransfer = 21000
-BumpMin = '20 mwei'
+EstimateLimit = false
+BumpMin = '5 gwei'
BumpPercent = 40
BumpThreshold = 3
EIP1559DynamicFees = false
@@ -5117,11 +5630,14 @@ TipCapMin = '1 wei'
[GasEstimator.BlockHistory]
BatchSize = 25
-BlockHistorySize = 12
+BlockHistorySize = 8
CheckInclusionBlocks = 12
CheckInclusionPercentile = 90
TransactionPercentile = 60
+[GasEstimator.FeeHistory]
+CacheTimeout = '4s'
+
[HeadTracker]
HistoryDepth = 2000
MaxBufferSize = 3
@@ -5139,6 +5655,7 @@ NodeIsSyncingEnabled = false
FinalizedBlockPollInterval = '5s'
EnforceRepeatableRead = false
DeathDeclarationDelay = '10s'
+NewHeadsPollInterval = '0s'
[OCR]
ContractConfirmations = 1
@@ -5173,6 +5690,7 @@ MinIncomingConfirmations = 3
MinContractPayment = '0.00001 link'
NonceAutoSync = true
NoNewHeadsThreshold = '0s'
+LogBroadcasterEnabled = true
RPCDefaultBatchSize = 250
RPCBlockQueryDelay = 2
FinalizedBlockOffset = 0
@@ -5201,6 +5719,7 @@ LimitDefault = 8000000
LimitMax = 8000000
LimitMultiplier = '1'
LimitTransfer = 21000
+EstimateLimit = false
BumpMin = '5 gwei'
BumpPercent = 20
BumpThreshold = 3
@@ -5216,6 +5735,9 @@ CheckInclusionBlocks = 12
CheckInclusionPercentile = 90
TransactionPercentile = 60
+[GasEstimator.FeeHistory]
+CacheTimeout = '10s'
+
[HeadTracker]
HistoryDepth = 100
MaxBufferSize = 3
@@ -5233,6 +5755,7 @@ NodeIsSyncingEnabled = false
FinalizedBlockPollInterval = '5s'
EnforceRepeatableRead = false
DeathDeclarationDelay = '10s'
+NewHeadsPollInterval = '0s'
[OCR]
ContractConfirmations = 4
@@ -5249,6 +5772,106 @@ GasLimit = 3800000
+
+```toml
+AutoCreateKey = true
+BlockBackfillDepth = 10
+BlockBackfillSkip = false
+ChainType = 'optimismBedrock'
+FinalityDepth = 2500
+FinalityTagEnabled = true
+LogBackfillBatchSize = 1000
+LogPollInterval = '5s'
+LogKeepBlocksDepth = 100000
+LogPrunePageSize = 10000
+BackupLogPollerBlockDelay = 100
+MinIncomingConfirmations = 3
+MinContractPayment = '0.00001 link'
+NonceAutoSync = true
+NoNewHeadsThreshold = '3m0s'
+LogBroadcasterEnabled = true
+RPCDefaultBatchSize = 250
+RPCBlockQueryDelay = 1
+FinalizedBlockOffset = 0
+NoNewFinalizedHeadsThreshold = '1h30m0s'
+
+[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 = '115792089237316195423570985008687907853269984665.640564039457584007913129639935 tether'
+PriceMin = '1 gwei'
+LimitDefault = 8000000
+LimitMax = 8000000
+LimitMultiplier = '1'
+LimitTransfer = 21000
+EstimateLimit = false
+BumpMin = '5 gwei'
+BumpPercent = 20
+BumpThreshold = 3
+EIP1559DynamicFees = true
+FeeCapDefault = '100 gwei'
+TipCapDefault = '1 wei'
+TipCapMin = '1 wei'
+
+[GasEstimator.BlockHistory]
+BatchSize = 25
+BlockHistorySize = 100
+CheckInclusionBlocks = 12
+CheckInclusionPercentile = 90
+TransactionPercentile = 60
+
+[GasEstimator.FeeHistory]
+CacheTimeout = '2s'
+
+[HeadTracker]
+HistoryDepth = 100
+MaxBufferSize = 3
+SamplingInterval = '1s'
+MaxAllowedFinalityDepth = 10000
+FinalityTagBypass = true
+
+[NodePool]
+PollFailureThreshold = 5
+PollInterval = '10s'
+SelectionMode = 'HighestHead'
+SyncThreshold = 5
+LeaseDuration = '0s'
+NodeIsSyncingEnabled = false
+FinalizedBlockPollInterval = '5s'
+EnforceRepeatableRead = false
+DeathDeclarationDelay = '10s'
+NewHeadsPollInterval = '0s'
+
+[OCR]
+ContractConfirmations = 4
+ContractTransmitterTransmitTimeout = '10s'
+DatabaseTimeout = '10s'
+DeltaCOverride = '168h0m0s'
+DeltaCJitterOverride = '1h0m0s'
+ObservationGracePeriod = '1s'
+
+[OCR2]
+[OCR2.Automation]
+GasLimit = 5400000
+```
+
+
```toml
@@ -5267,6 +5890,7 @@ MinIncomingConfirmations = 1
MinContractPayment = '0.00001 link'
NonceAutoSync = true
NoNewHeadsThreshold = '0s'
+LogBroadcasterEnabled = true
RPCDefaultBatchSize = 250
RPCBlockQueryDelay = 1
FinalizedBlockOffset = 0
@@ -5295,6 +5919,7 @@ LimitDefault = 100000000
LimitMax = 8000000
LimitMultiplier = '1'
LimitTransfer = 21000
+EstimateLimit = false
BumpMin = '5 gwei'
BumpPercent = 20
BumpThreshold = 3
@@ -5311,6 +5936,9 @@ CheckInclusionPercentile = 90
EIP1559FeeCapBufferBlocks = 0
TransactionPercentile = 60
+[GasEstimator.FeeHistory]
+CacheTimeout = '10s'
+
[HeadTracker]
HistoryDepth = 600
MaxBufferSize = 3
@@ -5328,6 +5956,7 @@ NodeIsSyncingEnabled = false
FinalizedBlockPollInterval = '5s'
EnforceRepeatableRead = false
DeathDeclarationDelay = '10s'
+NewHeadsPollInterval = '0s'
[OCR]
ContractConfirmations = 4
@@ -5361,6 +5990,7 @@ MinIncomingConfirmations = 1
MinContractPayment = '0.00001 link'
NonceAutoSync = true
NoNewHeadsThreshold = '30s'
+LogBroadcasterEnabled = true
RPCDefaultBatchSize = 250
RPCBlockQueryDelay = 1
FinalizedBlockOffset = 0
@@ -5389,6 +6019,7 @@ LimitDefault = 8000000
LimitMax = 8000000
LimitMultiplier = '1'
LimitTransfer = 21000
+EstimateLimit = false
BumpMin = '5 gwei'
BumpPercent = 20
BumpThreshold = 5
@@ -5404,6 +6035,9 @@ CheckInclusionBlocks = 12
CheckInclusionPercentile = 90
TransactionPercentile = 60
+[GasEstimator.FeeHistory]
+CacheTimeout = '10s'
+
[HeadTracker]
HistoryDepth = 100
MaxBufferSize = 3
@@ -5421,6 +6055,7 @@ NodeIsSyncingEnabled = false
FinalizedBlockPollInterval = '5s'
EnforceRepeatableRead = false
DeathDeclarationDelay = '10s'
+NewHeadsPollInterval = '0s'
[OCR]
ContractConfirmations = 1
@@ -5455,6 +6090,7 @@ MinIncomingConfirmations = 1
MinContractPayment = '0.00001 link'
NonceAutoSync = true
NoNewHeadsThreshold = '40s'
+LogBroadcasterEnabled = true
RPCDefaultBatchSize = 250
RPCBlockQueryDelay = 1
FinalizedBlockOffset = 0
@@ -5483,6 +6119,7 @@ LimitDefault = 8000000
LimitMax = 8000000
LimitMultiplier = '1'
LimitTransfer = 21000
+EstimateLimit = false
BumpMin = '100 wei'
BumpPercent = 20
BumpThreshold = 3
@@ -5498,6 +6135,9 @@ CheckInclusionBlocks = 12
CheckInclusionPercentile = 90
TransactionPercentile = 60
+[GasEstimator.FeeHistory]
+CacheTimeout = '10s'
+
[HeadTracker]
HistoryDepth = 300
MaxBufferSize = 3
@@ -5515,6 +6155,7 @@ NodeIsSyncingEnabled = false
FinalizedBlockPollInterval = '5s'
EnforceRepeatableRead = false
DeathDeclarationDelay = '10s'
+NewHeadsPollInterval = '0s'
[OCR]
ContractConfirmations = 1
@@ -5549,6 +6190,7 @@ MinIncomingConfirmations = 3
MinContractPayment = '0.00001 link'
NonceAutoSync = true
NoNewHeadsThreshold = '3m0s'
+LogBroadcasterEnabled = true
RPCDefaultBatchSize = 250
RPCBlockQueryDelay = 1
FinalizedBlockOffset = 0
@@ -5577,6 +6219,7 @@ LimitDefault = 8000000
LimitMax = 8000000
LimitMultiplier = '1'
LimitTransfer = 21000
+EstimateLimit = false
BumpMin = '5 gwei'
BumpPercent = 20
BumpThreshold = 3
@@ -5592,6 +6235,9 @@ CheckInclusionBlocks = 12
CheckInclusionPercentile = 90
TransactionPercentile = 60
+[GasEstimator.FeeHistory]
+CacheTimeout = '10s'
+
[HeadTracker]
HistoryDepth = 100
MaxBufferSize = 3
@@ -5609,6 +6255,7 @@ NodeIsSyncingEnabled = false
FinalizedBlockPollInterval = '5s'
EnforceRepeatableRead = false
DeathDeclarationDelay = '10s'
+NewHeadsPollInterval = '0s'
[OCR]
ContractConfirmations = 4
@@ -5644,6 +6291,7 @@ MinIncomingConfirmations = 3
MinContractPayment = '0.00001 link'
NonceAutoSync = true
NoNewHeadsThreshold = '0s'
+LogBroadcasterEnabled = true
RPCDefaultBatchSize = 250
RPCBlockQueryDelay = 1
FinalizedBlockOffset = 0
@@ -5672,6 +6320,7 @@ LimitDefault = 8000000
LimitMax = 1000000000
LimitMultiplier = '1'
LimitTransfer = 21000
+EstimateLimit = false
BumpMin = '5 gwei'
BumpPercent = 20
BumpThreshold = 5
@@ -5687,6 +6336,9 @@ CheckInclusionBlocks = 12
CheckInclusionPercentile = 90
TransactionPercentile = 60
+[GasEstimator.FeeHistory]
+CacheTimeout = '10s'
+
[HeadTracker]
HistoryDepth = 100
MaxBufferSize = 3
@@ -5704,6 +6356,7 @@ NodeIsSyncingEnabled = false
FinalizedBlockPollInterval = '5s'
EnforceRepeatableRead = false
DeathDeclarationDelay = '10s'
+NewHeadsPollInterval = '0s'
[OCR]
ContractConfirmations = 1
@@ -5739,6 +6392,7 @@ MinIncomingConfirmations = 3
MinContractPayment = '0.00001 link'
NonceAutoSync = true
NoNewHeadsThreshold = '0s'
+LogBroadcasterEnabled = true
RPCDefaultBatchSize = 250
RPCBlockQueryDelay = 1
FinalizedBlockOffset = 0
@@ -5767,6 +6421,7 @@ LimitDefault = 8000000
LimitMax = 1000000000
LimitMultiplier = '1'
LimitTransfer = 21000
+EstimateLimit = false
BumpMin = '5 gwei'
BumpPercent = 20
BumpThreshold = 5
@@ -5782,6 +6437,9 @@ CheckInclusionBlocks = 12
CheckInclusionPercentile = 90
TransactionPercentile = 60
+[GasEstimator.FeeHistory]
+CacheTimeout = '10s'
+
[HeadTracker]
HistoryDepth = 100
MaxBufferSize = 3
@@ -5799,6 +6457,7 @@ NodeIsSyncingEnabled = false
FinalizedBlockPollInterval = '5s'
EnforceRepeatableRead = false
DeathDeclarationDelay = '10s'
+NewHeadsPollInterval = '0s'
[OCR]
ContractConfirmations = 1
@@ -5833,6 +6492,7 @@ MinIncomingConfirmations = 3
MinContractPayment = '0.00001 link'
NonceAutoSync = true
NoNewHeadsThreshold = '3m0s'
+LogBroadcasterEnabled = true
RPCDefaultBatchSize = 250
RPCBlockQueryDelay = 1
FinalizedBlockOffset = 0
@@ -5861,6 +6521,7 @@ LimitDefault = 8000000
LimitMax = 8000000
LimitMultiplier = '1'
LimitTransfer = 21000
+EstimateLimit = false
BumpMin = '100 wei'
BumpPercent = 20
BumpThreshold = 60
@@ -5877,6 +6538,9 @@ CheckInclusionPercentile = 90
EIP1559FeeCapBufferBlocks = 0
TransactionPercentile = 60
+[GasEstimator.FeeHistory]
+CacheTimeout = '10s'
+
[HeadTracker]
HistoryDepth = 300
MaxBufferSize = 3
@@ -5894,6 +6558,7 @@ NodeIsSyncingEnabled = false
FinalizedBlockPollInterval = '5s'
EnforceRepeatableRead = false
DeathDeclarationDelay = '10s'
+NewHeadsPollInterval = '0s'
[OCR]
ContractConfirmations = 4
@@ -5929,6 +6594,7 @@ MinIncomingConfirmations = 3
MinContractPayment = '0.00001 link'
NonceAutoSync = true
NoNewHeadsThreshold = '0s'
+LogBroadcasterEnabled = true
RPCDefaultBatchSize = 250
RPCBlockQueryDelay = 1
FinalizedBlockOffset = 0
@@ -5957,6 +6623,7 @@ LimitDefault = 8000000
LimitMax = 1000000000
LimitMultiplier = '1'
LimitTransfer = 21000
+EstimateLimit = false
BumpMin = '5 gwei'
BumpPercent = 20
BumpThreshold = 5
@@ -5972,6 +6639,9 @@ CheckInclusionBlocks = 12
CheckInclusionPercentile = 90
TransactionPercentile = 60
+[GasEstimator.FeeHistory]
+CacheTimeout = '10s'
+
[HeadTracker]
HistoryDepth = 100
MaxBufferSize = 3
@@ -5989,6 +6659,7 @@ NodeIsSyncingEnabled = false
FinalizedBlockPollInterval = '5s'
EnforceRepeatableRead = false
DeathDeclarationDelay = '10s'
+NewHeadsPollInterval = '0s'
[OCR]
ContractConfirmations = 1
@@ -6023,6 +6694,7 @@ MinIncomingConfirmations = 1
MinContractPayment = '0.00001 link'
NonceAutoSync = true
NoNewHeadsThreshold = '1m0s'
+LogBroadcasterEnabled = true
RPCDefaultBatchSize = 250
RPCBlockQueryDelay = 1
FinalizedBlockOffset = 0
@@ -6051,6 +6723,7 @@ LimitDefault = 8000000
LimitMax = 8000000
LimitMultiplier = '1'
LimitTransfer = 21000
+EstimateLimit = false
BumpMin = '2 gwei'
BumpPercent = 20
BumpThreshold = 3
@@ -6066,6 +6739,9 @@ CheckInclusionBlocks = 12
CheckInclusionPercentile = 90
TransactionPercentile = 60
+[GasEstimator.FeeHistory]
+CacheTimeout = '10s'
+
[HeadTracker]
HistoryDepth = 50
MaxBufferSize = 3
@@ -6083,6 +6759,7 @@ NodeIsSyncingEnabled = false
FinalizedBlockPollInterval = '5s'
EnforceRepeatableRead = false
DeathDeclarationDelay = '10s'
+NewHeadsPollInterval = '0s'
[OCR]
ContractConfirmations = 1
@@ -6117,6 +6794,7 @@ MinIncomingConfirmations = 1
MinContractPayment = '0.00001 link'
NonceAutoSync = true
NoNewHeadsThreshold = '30s'
+LogBroadcasterEnabled = true
RPCDefaultBatchSize = 250
RPCBlockQueryDelay = 2
FinalizedBlockOffset = 0
@@ -6145,6 +6823,7 @@ LimitDefault = 8000000
LimitMax = 8000000
LimitMultiplier = '1'
LimitTransfer = 21000
+EstimateLimit = false
BumpMin = '5 gwei'
BumpPercent = 20
BumpThreshold = 3
@@ -6160,6 +6839,9 @@ CheckInclusionBlocks = 12
CheckInclusionPercentile = 90
TransactionPercentile = 60
+[GasEstimator.FeeHistory]
+CacheTimeout = '10s'
+
[HeadTracker]
HistoryDepth = 100
MaxBufferSize = 3
@@ -6177,6 +6859,7 @@ NodeIsSyncingEnabled = false
FinalizedBlockPollInterval = '5s'
EnforceRepeatableRead = false
DeathDeclarationDelay = '10s'
+NewHeadsPollInterval = '0s'
[OCR]
ContractConfirmations = 1
@@ -6211,6 +6894,7 @@ MinIncomingConfirmations = 1
MinContractPayment = '0.00001 link'
NonceAutoSync = true
NoNewHeadsThreshold = '30s'
+LogBroadcasterEnabled = true
RPCDefaultBatchSize = 250
RPCBlockQueryDelay = 2
FinalizedBlockOffset = 0
@@ -6239,6 +6923,7 @@ LimitDefault = 8000000
LimitMax = 8000000
LimitMultiplier = '1'
LimitTransfer = 21000
+EstimateLimit = false
BumpMin = '5 gwei'
BumpPercent = 20
BumpThreshold = 3
@@ -6254,6 +6939,9 @@ CheckInclusionBlocks = 12
CheckInclusionPercentile = 90
TransactionPercentile = 60
+[GasEstimator.FeeHistory]
+CacheTimeout = '10s'
+
[HeadTracker]
HistoryDepth = 100
MaxBufferSize = 3
@@ -6271,6 +6959,7 @@ NodeIsSyncingEnabled = false
FinalizedBlockPollInterval = '5s'
EnforceRepeatableRead = false
DeathDeclarationDelay = '10s'
+NewHeadsPollInterval = '0s'
[OCR]
ContractConfirmations = 1
@@ -6305,6 +6994,7 @@ MinIncomingConfirmations = 1
MinContractPayment = '0.00001 link'
NonceAutoSync = true
NoNewHeadsThreshold = '1m0s'
+LogBroadcasterEnabled = true
RPCDefaultBatchSize = 250
RPCBlockQueryDelay = 1
FinalizedBlockOffset = 0
@@ -6333,6 +7023,7 @@ LimitDefault = 8000000
LimitMax = 8000000
LimitMultiplier = '1'
LimitTransfer = 21000
+EstimateLimit = false
BumpMin = '2 gwei'
BumpPercent = 20
BumpThreshold = 3
@@ -6348,6 +7039,9 @@ CheckInclusionBlocks = 12
CheckInclusionPercentile = 90
TransactionPercentile = 60
+[GasEstimator.FeeHistory]
+CacheTimeout = '10s'
+
[HeadTracker]
HistoryDepth = 50
MaxBufferSize = 3
@@ -6365,6 +7059,7 @@ NodeIsSyncingEnabled = false
FinalizedBlockPollInterval = '5s'
EnforceRepeatableRead = false
DeathDeclarationDelay = '10s'
+NewHeadsPollInterval = '0s'
[OCR]
ContractConfirmations = 1
@@ -6381,27 +7076,30 @@ GasLimit = 5400000
+
```toml
AutoCreateKey = true
BlockBackfillDepth = 10
BlockBackfillSkip = false
-FinalityDepth = 15
-FinalityTagEnabled = false
+ChainType = 'optimismBedrock'
+FinalityDepth = 1000
+FinalityTagEnabled = true
+LinkContractAddress = '0xDEE94506570cA186BC1e3516fCf4fd719C312cCD'
LogBackfillBatchSize = 1000
-LogPollInterval = '15s'
+LogPollInterval = '2s'
LogKeepBlocksDepth = 100000
LogPrunePageSize = 10000
BackupLogPollerBlockDelay = 100
MinIncomingConfirmations = 3
MinContractPayment = '0.00001 link'
NonceAutoSync = true
-NoNewHeadsThreshold = '0s'
+NoNewHeadsThreshold = '40s'
+LogBroadcasterEnabled = true
RPCDefaultBatchSize = 250
RPCBlockQueryDelay = 1
FinalizedBlockOffset = 0
-NoNewFinalizedHeadsThreshold = '0s'
+NoNewFinalizedHeadsThreshold = '15m0s'
[Transactions]
ForwardersEnabled = false
@@ -6409,10 +7107,12 @@ MaxInFlight = 16
MaxQueued = 250
ReaperInterval = '1h0m0s'
ReaperThreshold = '168h0m0s'
-ResendAfterThreshold = '3m0s'
+ResendAfterThreshold = '30s'
[Transactions.AutoPurge]
-Enabled = false
+Enabled = true
+Threshold = 90
+MinAttempts = 3
[BalanceMonitor]
Enabled = true
@@ -6421,11 +7121,214 @@ Enabled = true
Mode = 'BlockHistory'
PriceDefault = '20 gwei'
PriceMax = '115792089237316195423570985008687907853269984665.640564039457584007913129639935 tether'
-PriceMin = '1 gwei'
+PriceMin = '1 wei'
LimitDefault = 8000000
LimitMax = 8000000
LimitMultiplier = '1'
LimitTransfer = 21000
+EstimateLimit = false
+BumpMin = '100 wei'
+BumpPercent = 20
+BumpThreshold = 3
+EIP1559DynamicFees = true
+FeeCapDefault = '100 gwei'
+TipCapDefault = '1 wei'
+TipCapMin = '1 wei'
+
+[GasEstimator.BlockHistory]
+BatchSize = 25
+BlockHistorySize = 60
+CheckInclusionBlocks = 12
+CheckInclusionPercentile = 90
+TransactionPercentile = 60
+
+[GasEstimator.FeeHistory]
+CacheTimeout = '10s'
+
+[HeadTracker]
+HistoryDepth = 2000
+MaxBufferSize = 3
+SamplingInterval = '1s'
+MaxAllowedFinalityDepth = 10000
+FinalityTagBypass = true
+
+[NodePool]
+PollFailureThreshold = 5
+PollInterval = '10s'
+SelectionMode = 'HighestHead'
+SyncThreshold = 10
+LeaseDuration = '0s'
+NodeIsSyncingEnabled = false
+FinalizedBlockPollInterval = '5s'
+EnforceRepeatableRead = false
+DeathDeclarationDelay = '10s'
+NewHeadsPollInterval = '0s'
+
+[OCR]
+ContractConfirmations = 1
+ContractTransmitterTransmitTimeout = '10s'
+DatabaseTimeout = '10s'
+DeltaCOverride = '168h0m0s'
+DeltaCJitterOverride = '1h0m0s'
+ObservationGracePeriod = '1s'
+
+[OCR2]
+[OCR2.Automation]
+GasLimit = 6500000
+```
+
+
+
+```toml
+AutoCreateKey = true
+BlockBackfillDepth = 10
+BlockBackfillSkip = false
+ChainType = 'optimismBedrock'
+FinalityDepth = 1000
+FinalityTagEnabled = true
+LinkContractAddress = '0x5D6d033B4FbD2190D99D930719fAbAcB64d2439a'
+LogBackfillBatchSize = 1000
+LogPollInterval = '2s'
+LogKeepBlocksDepth = 100000
+LogPrunePageSize = 10000
+BackupLogPollerBlockDelay = 100
+MinIncomingConfirmations = 3
+MinContractPayment = '0.00001 link'
+NonceAutoSync = true
+NoNewHeadsThreshold = '40s'
+LogBroadcasterEnabled = true
+RPCDefaultBatchSize = 250
+RPCBlockQueryDelay = 1
+FinalizedBlockOffset = 0
+NoNewFinalizedHeadsThreshold = '15m0s'
+
+[Transactions]
+ForwardersEnabled = false
+MaxInFlight = 16
+MaxQueued = 250
+ReaperInterval = '1h0m0s'
+ReaperThreshold = '168h0m0s'
+ResendAfterThreshold = '30s'
+
+[Transactions.AutoPurge]
+Enabled = true
+Threshold = 90
+MinAttempts = 3
+
+[BalanceMonitor]
+Enabled = true
+
+[GasEstimator]
+Mode = 'BlockHistory'
+PriceDefault = '20 gwei'
+PriceMax = '115792089237316195423570985008687907853269984665.640564039457584007913129639935 tether'
+PriceMin = '1 wei'
+LimitDefault = 8000000
+LimitMax = 8000000
+LimitMultiplier = '1'
+LimitTransfer = 21000
+EstimateLimit = false
+BumpMin = '100 wei'
+BumpPercent = 20
+BumpThreshold = 3
+EIP1559DynamicFees = true
+FeeCapDefault = '100 gwei'
+TipCapDefault = '1 wei'
+TipCapMin = '1 wei'
+
+[GasEstimator.BlockHistory]
+BatchSize = 25
+BlockHistorySize = 24
+CheckInclusionBlocks = 12
+CheckInclusionPercentile = 90
+TransactionPercentile = 60
+
+[GasEstimator.FeeHistory]
+CacheTimeout = '10s'
+
+[HeadTracker]
+HistoryDepth = 2000
+MaxBufferSize = 3
+SamplingInterval = '1s'
+MaxAllowedFinalityDepth = 10000
+FinalityTagBypass = true
+
+[NodePool]
+PollFailureThreshold = 5
+PollInterval = '10s'
+SelectionMode = 'HighestHead'
+SyncThreshold = 10
+LeaseDuration = '0s'
+NodeIsSyncingEnabled = false
+FinalizedBlockPollInterval = '5s'
+EnforceRepeatableRead = false
+DeathDeclarationDelay = '10s'
+NewHeadsPollInterval = '0s'
+
+[OCR]
+ContractConfirmations = 1
+ContractTransmitterTransmitTimeout = '10s'
+DatabaseTimeout = '10s'
+DeltaCOverride = '168h0m0s'
+DeltaCJitterOverride = '1h0m0s'
+ObservationGracePeriod = '1s'
+
+[OCR2]
+[OCR2.Automation]
+GasLimit = 6500000
+```
+
+
+
+```toml
+AutoCreateKey = true
+BlockBackfillDepth = 10
+BlockBackfillSkip = false
+FinalityDepth = 15
+FinalityTagEnabled = false
+LogBackfillBatchSize = 1000
+LogPollInterval = '15s'
+LogKeepBlocksDepth = 100000
+LogPrunePageSize = 10000
+BackupLogPollerBlockDelay = 100
+MinIncomingConfirmations = 3
+MinContractPayment = '0.00001 link'
+NonceAutoSync = true
+NoNewHeadsThreshold = '0s'
+LogBroadcasterEnabled = true
+RPCDefaultBatchSize = 250
+RPCBlockQueryDelay = 1
+FinalizedBlockOffset = 0
+NoNewFinalizedHeadsThreshold = '0s'
+
+[Transactions]
+ForwardersEnabled = false
+MaxInFlight = 16
+MaxQueued = 250
+ReaperInterval = '1h0m0s'
+ReaperThreshold = '168h0m0s'
+ResendAfterThreshold = '3m0s'
+
+[Transactions.AutoPurge]
+Enabled = false
+
+[BalanceMonitor]
+Enabled = true
+
+[GasEstimator]
+Mode = 'BlockHistory'
+PriceDefault = '20 gwei'
+PriceMax = '115792089237316195423570985008687907853269984665.640564039457584007913129639935 tether'
+PriceMin = '1 gwei'
+LimitDefault = 8000000
+LimitMax = 8000000
+LimitMultiplier = '1'
+LimitTransfer = 21000
+EstimateLimit = false
BumpMin = '5 gwei'
BumpPercent = 40
BumpThreshold = 3
@@ -6441,6 +7344,9 @@ CheckInclusionBlocks = 12
CheckInclusionPercentile = 90
TransactionPercentile = 60
+[GasEstimator.FeeHistory]
+CacheTimeout = '10s'
+
[HeadTracker]
HistoryDepth = 100
MaxBufferSize = 3
@@ -6458,6 +7364,7 @@ NodeIsSyncingEnabled = false
FinalizedBlockPollInterval = '5s'
EnforceRepeatableRead = false
DeathDeclarationDelay = '10s'
+NewHeadsPollInterval = '0s'
[OCR]
ContractConfirmations = 4
@@ -6491,6 +7398,7 @@ MinIncomingConfirmations = 3
MinContractPayment = '0.00001 link'
NonceAutoSync = true
NoNewHeadsThreshold = '0s'
+LogBroadcasterEnabled = true
RPCDefaultBatchSize = 250
RPCBlockQueryDelay = 1
FinalizedBlockOffset = 0
@@ -6514,28 +7422,231 @@ Enabled = true
Mode = 'BlockHistory'
PriceDefault = '20 gwei'
PriceMax = '115792089237316195423570985008687907853269984665.640564039457584007913129639935 tether'
-PriceMin = '1 wei'
+PriceMin = '1 wei'
+LimitDefault = 8000000
+LimitMax = 8000000
+LimitMultiplier = '1'
+LimitTransfer = 21000
+EstimateLimit = false
+BumpMin = '5 gwei'
+BumpPercent = 20
+BumpThreshold = 3
+EIP1559DynamicFees = true
+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 = 1000
+MaxBufferSize = 3
+SamplingInterval = '1s'
+MaxAllowedFinalityDepth = 10000
+FinalityTagBypass = true
+
+[NodePool]
+PollFailureThreshold = 5
+PollInterval = '10s'
+SelectionMode = 'HighestHead'
+SyncThreshold = 5
+LeaseDuration = '0s'
+NodeIsSyncingEnabled = false
+FinalizedBlockPollInterval = '5s'
+EnforceRepeatableRead = false
+DeathDeclarationDelay = '10s'
+NewHeadsPollInterval = '0s'
+
+[OCR]
+ContractConfirmations = 4
+ContractTransmitterTransmitTimeout = '10s'
+DatabaseTimeout = '10s'
+DeltaCOverride = '168h0m0s'
+DeltaCJitterOverride = '1h0m0s'
+ObservationGracePeriod = '1s'
+
+[OCR2]
+[OCR2.Automation]
+GasLimit = 5400000
+```
+
+
+
+```toml
+AutoCreateKey = true
+BlockBackfillDepth = 10
+BlockBackfillSkip = false
+FinalityDepth = 300
+FinalityTagEnabled = false
+LogBackfillBatchSize = 1000
+LogPollInterval = '15s'
+LogKeepBlocksDepth = 100000
+LogPrunePageSize = 10000
+BackupLogPollerBlockDelay = 100
+MinIncomingConfirmations = 3
+MinContractPayment = '0.00001 link'
+NonceAutoSync = true
+NoNewHeadsThreshold = '0s'
+LogBroadcasterEnabled = true
+RPCDefaultBatchSize = 250
+RPCBlockQueryDelay = 1
+FinalizedBlockOffset = 0
+NoNewFinalizedHeadsThreshold = '0s'
+
+[Transactions]
+ForwardersEnabled = false
+MaxInFlight = 16
+MaxQueued = 250
+ReaperInterval = '1h0m0s'
+ReaperThreshold = '168h0m0s'
+ResendAfterThreshold = '3m0s'
+
+[Transactions.AutoPurge]
+Enabled = false
+
+[BalanceMonitor]
+Enabled = true
+
+[GasEstimator]
+Mode = 'BlockHistory'
+PriceDefault = '20 gwei'
+PriceMax = '115792089237316195423570985008687907853269984665.640564039457584007913129639935 tether'
+PriceMin = '400 mwei'
+LimitDefault = 8000000
+LimitMax = 8000000
+LimitMultiplier = '1'
+LimitTransfer = 21000
+EstimateLimit = false
+BumpMin = '5 gwei'
+BumpPercent = 40
+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 = 350
+MaxBufferSize = 3
+SamplingInterval = '1s'
+MaxAllowedFinalityDepth = 10000
+FinalityTagBypass = true
+
+[NodePool]
+PollFailureThreshold = 5
+PollInterval = '10s'
+SelectionMode = 'HighestHead'
+SyncThreshold = 5
+LeaseDuration = '0s'
+NodeIsSyncingEnabled = false
+FinalizedBlockPollInterval = '5s'
+EnforceRepeatableRead = false
+DeathDeclarationDelay = '10s'
+NewHeadsPollInterval = '0s'
+
+[OCR]
+ContractConfirmations = 4
+ContractTransmitterTransmitTimeout = '10s'
+DatabaseTimeout = '10s'
+DeltaCOverride = '168h0m0s'
+DeltaCJitterOverride = '1h0m0s'
+ObservationGracePeriod = '1s'
+
+[OCR2]
+[OCR2.Automation]
+GasLimit = 5400000
+```
+
+
+
+```toml
+AutoCreateKey = true
+BlockBackfillDepth = 10
+BlockBackfillSkip = false
+ChainType = 'optimismBedrock'
+FinalityDepth = 10
+FinalityTagEnabled = true
+LogBackfillBatchSize = 1000
+LogPollInterval = '15s'
+LogKeepBlocksDepth = 100000
+LogPrunePageSize = 10000
+BackupLogPollerBlockDelay = 100
+MinIncomingConfirmations = 1
+MinContractPayment = '0.00001 link'
+NonceAutoSync = true
+NoNewHeadsThreshold = '0s'
+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 = 'SuggestedPrice'
+PriceDefault = '20 gwei'
+PriceMax = '115792089237316195423570985008687907853269984665.640564039457584007913129639935 tether'
+PriceMin = '0'
LimitDefault = 8000000
LimitMax = 8000000
LimitMultiplier = '1'
LimitTransfer = 21000
+EstimateLimit = false
BumpMin = '5 gwei'
BumpPercent = 20
BumpThreshold = 3
-EIP1559DynamicFees = true
+EIP1559DynamicFees = false
FeeCapDefault = '100 gwei'
TipCapDefault = '1 wei'
TipCapMin = '1 wei'
[GasEstimator.BlockHistory]
BatchSize = 25
-BlockHistorySize = 8
+BlockHistorySize = 0
CheckInclusionBlocks = 12
CheckInclusionPercentile = 90
TransactionPercentile = 60
+[GasEstimator.FeeHistory]
+CacheTimeout = '10s'
+
[HeadTracker]
-HistoryDepth = 1000
+HistoryDepth = 100
MaxBufferSize = 3
SamplingInterval = '1s'
MaxAllowedFinalityDepth = 10000
@@ -6545,15 +7656,16 @@ FinalityTagBypass = true
PollFailureThreshold = 5
PollInterval = '10s'
SelectionMode = 'HighestHead'
-SyncThreshold = 5
+SyncThreshold = 10
LeaseDuration = '0s'
NodeIsSyncingEnabled = false
FinalizedBlockPollInterval = '5s'
EnforceRepeatableRead = false
DeathDeclarationDelay = '10s'
+NewHeadsPollInterval = '0s'
[OCR]
-ContractConfirmations = 4
+ContractConfirmations = 1
ContractTransmitterTransmitTimeout = '10s'
DatabaseTimeout = '10s'
DeltaCOverride = '168h0m0s'
@@ -6567,27 +7679,29 @@ GasLimit = 5400000
+
```toml
AutoCreateKey = true
BlockBackfillDepth = 10
BlockBackfillSkip = false
-FinalityDepth = 300
-FinalityTagEnabled = false
+ChainType = 'optimismBedrock'
+FinalityDepth = 900
+FinalityTagEnabled = true
LogBackfillBatchSize = 1000
-LogPollInterval = '15s'
+LogPollInterval = '5s'
LogKeepBlocksDepth = 100000
LogPrunePageSize = 10000
BackupLogPollerBlockDelay = 100
MinIncomingConfirmations = 3
MinContractPayment = '0.00001 link'
NonceAutoSync = true
-NoNewHeadsThreshold = '0s'
+NoNewHeadsThreshold = '3m0s'
+LogBroadcasterEnabled = true
RPCDefaultBatchSize = 250
RPCBlockQueryDelay = 1
FinalizedBlockOffset = 0
-NoNewFinalizedHeadsThreshold = '0s'
+NoNewFinalizedHeadsThreshold = '35m0s'
[Transactions]
ForwardersEnabled = false
@@ -6595,7 +7709,7 @@ MaxInFlight = 16
MaxQueued = 250
ReaperInterval = '1h0m0s'
ReaperThreshold = '168h0m0s'
-ResendAfterThreshold = '3m0s'
+ResendAfterThreshold = '1m0s'
[Transactions.AutoPurge]
Enabled = false
@@ -6604,31 +7718,35 @@ Enabled = false
Enabled = true
[GasEstimator]
-Mode = 'BlockHistory'
+Mode = 'FeeHistory'
PriceDefault = '20 gwei'
PriceMax = '115792089237316195423570985008687907853269984665.640564039457584007913129639935 tether'
-PriceMin = '400 mwei'
+PriceMin = '1 gwei'
LimitDefault = 8000000
LimitMax = 8000000
LimitMultiplier = '1'
LimitTransfer = 21000
+EstimateLimit = false
BumpMin = '5 gwei'
-BumpPercent = 40
+BumpPercent = 20
BumpThreshold = 3
-EIP1559DynamicFees = false
+EIP1559DynamicFees = true
FeeCapDefault = '100 gwei'
TipCapDefault = '1 wei'
TipCapMin = '1 wei'
[GasEstimator.BlockHistory]
BatchSize = 25
-BlockHistorySize = 8
+BlockHistorySize = 100
CheckInclusionBlocks = 12
CheckInclusionPercentile = 90
TransactionPercentile = 60
+[GasEstimator.FeeHistory]
+CacheTimeout = '2s'
+
[HeadTracker]
-HistoryDepth = 350
+HistoryDepth = 100
MaxBufferSize = 3
SamplingInterval = '1s'
MaxAllowedFinalityDepth = 10000
@@ -6644,6 +7762,7 @@ NodeIsSyncingEnabled = false
FinalizedBlockPollInterval = '5s'
EnforceRepeatableRead = false
DeathDeclarationDelay = '10s'
+NewHeadsPollInterval = '0s'
[OCR]
ContractConfirmations = 4
@@ -6660,17 +7779,17 @@ GasLimit = 5400000
+
```toml
AutoCreateKey = true
BlockBackfillDepth = 10
BlockBackfillSkip = false
-ChainType = 'metis'
-FinalityDepth = 10
-FinalityTagEnabled = true
+FinalityDepth = 1
+FinalityTagEnabled = false
+LinkContractAddress = '0x779877A7B0D9E8603169DdbD7836e478b4624789'
LogBackfillBatchSize = 1000
-LogPollInterval = '15s'
+LogPollInterval = '3s'
LogKeepBlocksDepth = 100000
LogPrunePageSize = 10000
BackupLogPollerBlockDelay = 100
@@ -6678,8 +7797,9 @@ MinIncomingConfirmations = 1
MinContractPayment = '0.00001 link'
NonceAutoSync = true
NoNewHeadsThreshold = '0s'
+LogBroadcasterEnabled = true
RPCDefaultBatchSize = 250
-RPCBlockQueryDelay = 1
+RPCBlockQueryDelay = 2
FinalizedBlockOffset = 0
NoNewFinalizedHeadsThreshold = '0s'
@@ -6698,14 +7818,15 @@ Enabled = false
Enabled = true
[GasEstimator]
-Mode = 'SuggestedPrice'
-PriceDefault = '20 gwei'
+Mode = 'BlockHistory'
+PriceDefault = '25 gwei'
PriceMax = '115792089237316195423570985008687907853269984665.640564039457584007913129639935 tether'
-PriceMin = '0'
+PriceMin = '25 gwei'
LimitDefault = 8000000
LimitMax = 8000000
LimitMultiplier = '1'
LimitTransfer = 21000
+EstimateLimit = false
BumpMin = '5 gwei'
BumpPercent = 20
BumpThreshold = 3
@@ -6716,11 +7837,14 @@ TipCapMin = '1 wei'
[GasEstimator.BlockHistory]
BatchSize = 25
-BlockHistorySize = 0
+BlockHistorySize = 24
CheckInclusionBlocks = 12
CheckInclusionPercentile = 90
TransactionPercentile = 60
+[GasEstimator.FeeHistory]
+CacheTimeout = '10s'
+
[HeadTracker]
HistoryDepth = 100
MaxBufferSize = 3
@@ -6732,12 +7856,13 @@ FinalityTagBypass = true
PollFailureThreshold = 5
PollInterval = '10s'
SelectionMode = 'HighestHead'
-SyncThreshold = 10
+SyncThreshold = 5
LeaseDuration = '0s'
NodeIsSyncingEnabled = false
FinalizedBlockPollInterval = '5s'
EnforceRepeatableRead = false
DeathDeclarationDelay = '10s'
+NewHeadsPollInterval = '0s'
[OCR]
ContractConfirmations = 1
@@ -6754,33 +7879,34 @@ GasLimit = 5400000
+
```toml
AutoCreateKey = true
BlockBackfillDepth = 10
BlockBackfillSkip = false
-FinalityDepth = 1
-FinalityTagEnabled = false
-LinkContractAddress = '0x779877A7B0D9E8603169DdbD7836e478b4624789'
+FinalityDepth = 500
+FinalityTagEnabled = true
+LinkContractAddress = '0x326C977E6efc84E512bB9C30f76E30c160eD06FB'
LogBackfillBatchSize = 1000
-LogPollInterval = '3s'
+LogPollInterval = '1s'
LogKeepBlocksDepth = 100000
LogPrunePageSize = 10000
BackupLogPollerBlockDelay = 100
-MinIncomingConfirmations = 1
+MinIncomingConfirmations = 5
MinContractPayment = '0.00001 link'
NonceAutoSync = true
-NoNewHeadsThreshold = '0s'
-RPCDefaultBatchSize = 250
-RPCBlockQueryDelay = 2
+NoNewHeadsThreshold = '30s'
+LogBroadcasterEnabled = true
+RPCDefaultBatchSize = 100
+RPCBlockQueryDelay = 10
FinalizedBlockOffset = 0
NoNewFinalizedHeadsThreshold = '0s'
[Transactions]
ForwardersEnabled = false
MaxInFlight = 16
-MaxQueued = 250
+MaxQueued = 5000
ReaperInterval = '1h0m0s'
ReaperThreshold = '168h0m0s'
ResendAfterThreshold = '1m0s'
@@ -6800,9 +7926,10 @@ LimitDefault = 8000000
LimitMax = 8000000
LimitMultiplier = '1'
LimitTransfer = 21000
-BumpMin = '5 gwei'
+EstimateLimit = false
+BumpMin = '20 gwei'
BumpPercent = 20
-BumpThreshold = 3
+BumpThreshold = 5
EIP1559DynamicFees = false
FeeCapDefault = '100 gwei'
TipCapDefault = '1 wei'
@@ -6815,8 +7942,11 @@ CheckInclusionBlocks = 12
CheckInclusionPercentile = 90
TransactionPercentile = 60
+[GasEstimator.FeeHistory]
+CacheTimeout = '10s'
+
[HeadTracker]
-HistoryDepth = 100
+HistoryDepth = 2000
MaxBufferSize = 3
SamplingInterval = '1s'
MaxAllowedFinalityDepth = 10000
@@ -6826,15 +7956,16 @@ FinalityTagBypass = true
PollFailureThreshold = 5
PollInterval = '10s'
SelectionMode = 'HighestHead'
-SyncThreshold = 5
+SyncThreshold = 10
LeaseDuration = '0s'
NodeIsSyncingEnabled = false
FinalizedBlockPollInterval = '5s'
EnforceRepeatableRead = false
DeathDeclarationDelay = '10s'
+NewHeadsPollInterval = '0s'
[OCR]
-ContractConfirmations = 1
+ContractConfirmations = 4
ContractTransmitterTransmitTimeout = '10s'
DatabaseTimeout = '10s'
DeltaCOverride = '168h0m0s'
@@ -6848,15 +7979,14 @@ GasLimit = 5400000
+
```toml
AutoCreateKey = true
BlockBackfillDepth = 10
BlockBackfillSkip = false
FinalityDepth = 500
-FinalityTagEnabled = true
-LinkContractAddress = '0x326C977E6efc84E512bB9C30f76E30c160eD06FB'
+FinalityTagEnabled = false
LogBackfillBatchSize = 1000
LogPollInterval = '1s'
LogKeepBlocksDepth = 100000
@@ -6866,10 +7996,11 @@ MinIncomingConfirmations = 5
MinContractPayment = '0.00001 link'
NonceAutoSync = true
NoNewHeadsThreshold = '30s'
+LogBroadcasterEnabled = true
RPCDefaultBatchSize = 100
RPCBlockQueryDelay = 10
FinalizedBlockOffset = 0
-NoNewFinalizedHeadsThreshold = '0s'
+NoNewFinalizedHeadsThreshold = '12m0s'
[Transactions]
ForwardersEnabled = false
@@ -6894,10 +8025,11 @@ LimitDefault = 8000000
LimitMax = 8000000
LimitMultiplier = '1'
LimitTransfer = 21000
+EstimateLimit = false
BumpMin = '20 gwei'
BumpPercent = 20
BumpThreshold = 5
-EIP1559DynamicFees = false
+EIP1559DynamicFees = true
FeeCapDefault = '100 gwei'
TipCapDefault = '1 wei'
TipCapMin = '1 wei'
@@ -6909,6 +8041,9 @@ CheckInclusionBlocks = 12
CheckInclusionPercentile = 90
TransactionPercentile = 60
+[GasEstimator.FeeHistory]
+CacheTimeout = '10s'
+
[HeadTracker]
HistoryDepth = 2000
MaxBufferSize = 3
@@ -6926,6 +8061,7 @@ NodeIsSyncingEnabled = false
FinalizedBlockPollInterval = '5s'
EnforceRepeatableRead = false
DeathDeclarationDelay = '10s'
+NewHeadsPollInterval = '0s'
[OCR]
ContractConfirmations = 4
@@ -6942,32 +8078,33 @@ GasLimit = 5400000
+
```toml
AutoCreateKey = true
BlockBackfillDepth = 10
BlockBackfillSkip = false
-FinalityDepth = 500
+FinalityDepth = 10
FinalityTagEnabled = false
LogBackfillBatchSize = 1000
-LogPollInterval = '1s'
+LogPollInterval = '10s'
LogKeepBlocksDepth = 100000
LogPrunePageSize = 10000
BackupLogPollerBlockDelay = 100
-MinIncomingConfirmations = 5
+MinIncomingConfirmations = 3
MinContractPayment = '0.00001 link'
NonceAutoSync = true
-NoNewHeadsThreshold = '30s'
-RPCDefaultBatchSize = 100
-RPCBlockQueryDelay = 10
+NoNewHeadsThreshold = '3m0s'
+LogBroadcasterEnabled = true
+RPCDefaultBatchSize = 250
+RPCBlockQueryDelay = 1
FinalizedBlockOffset = 0
-NoNewFinalizedHeadsThreshold = '12m0s'
+NoNewFinalizedHeadsThreshold = '5m0s'
[Transactions]
ForwardersEnabled = false
MaxInFlight = 16
-MaxQueued = 5000
+MaxQueued = 250
ReaperInterval = '1h0m0s'
ReaperThreshold = '168h0m0s'
ResendAfterThreshold = '1m0s'
@@ -6979,17 +8116,18 @@ Enabled = false
Enabled = true
[GasEstimator]
-Mode = 'BlockHistory'
-PriceDefault = '25 gwei'
+Mode = 'FeeHistory'
+PriceDefault = '20 gwei'
PriceMax = '115792089237316195423570985008687907853269984665.640564039457584007913129639935 tether'
-PriceMin = '25 gwei'
+PriceMin = '1 gwei'
LimitDefault = 8000000
LimitMax = 8000000
LimitMultiplier = '1'
LimitTransfer = 21000
-BumpMin = '20 gwei'
+EstimateLimit = false
+BumpMin = '5 gwei'
BumpPercent = 20
-BumpThreshold = 5
+BumpThreshold = 3
EIP1559DynamicFees = true
FeeCapDefault = '100 gwei'
TipCapDefault = '1 wei'
@@ -6997,13 +8135,16 @@ TipCapMin = '1 wei'
[GasEstimator.BlockHistory]
BatchSize = 25
-BlockHistorySize = 24
+BlockHistorySize = 100
CheckInclusionBlocks = 12
CheckInclusionPercentile = 90
TransactionPercentile = 60
+[GasEstimator.FeeHistory]
+CacheTimeout = '5s'
+
[HeadTracker]
-HistoryDepth = 2000
+HistoryDepth = 100
MaxBufferSize = 3
SamplingInterval = '1s'
MaxAllowedFinalityDepth = 10000
@@ -7013,12 +8154,13 @@ FinalityTagBypass = true
PollFailureThreshold = 5
PollInterval = '10s'
SelectionMode = 'HighestHead'
-SyncThreshold = 10
+SyncThreshold = 5
LeaseDuration = '0s'
NodeIsSyncingEnabled = false
FinalizedBlockPollInterval = '5s'
EnforceRepeatableRead = false
DeathDeclarationDelay = '10s'
+NewHeadsPollInterval = '0s'
[OCR]
ContractConfirmations = 4
@@ -7053,6 +8195,7 @@ MinIncomingConfirmations = 3
MinContractPayment = '0.00001 link'
NonceAutoSync = true
NoNewHeadsThreshold = '3m0s'
+LogBroadcasterEnabled = true
RPCDefaultBatchSize = 250
RPCBlockQueryDelay = 1
FinalizedBlockOffset = 0
@@ -7081,6 +8224,7 @@ LimitDefault = 8000000
LimitMax = 8000000
LimitMultiplier = '1'
LimitTransfer = 21000
+EstimateLimit = false
BumpMin = '100 wei'
BumpPercent = 20
BumpThreshold = 60
@@ -7097,6 +8241,9 @@ CheckInclusionPercentile = 90
EIP1559FeeCapBufferBlocks = 0
TransactionPercentile = 60
+[GasEstimator.FeeHistory]
+CacheTimeout = '10s'
+
[HeadTracker]
HistoryDepth = 300
MaxBufferSize = 3
@@ -7114,6 +8261,7 @@ NodeIsSyncingEnabled = false
FinalizedBlockPollInterval = '5s'
EnforceRepeatableRead = false
DeathDeclarationDelay = '10s'
+NewHeadsPollInterval = '0s'
[OCR]
ContractConfirmations = 4
@@ -7148,6 +8296,7 @@ MinIncomingConfirmations = 1
MinContractPayment = '0.00001 link'
NonceAutoSync = true
NoNewHeadsThreshold = '40s'
+LogBroadcasterEnabled = true
RPCDefaultBatchSize = 250
RPCBlockQueryDelay = 1
FinalizedBlockOffset = 0
@@ -7176,6 +8325,7 @@ LimitDefault = 8000000
LimitMax = 8000000
LimitMultiplier = '1'
LimitTransfer = 21000
+EstimateLimit = false
BumpMin = '100 wei'
BumpPercent = 20
BumpThreshold = 3
@@ -7191,6 +8341,9 @@ CheckInclusionBlocks = 12
CheckInclusionPercentile = 90
TransactionPercentile = 60
+[GasEstimator.FeeHistory]
+CacheTimeout = '10s'
+
[HeadTracker]
HistoryDepth = 300
MaxBufferSize = 3
@@ -7208,6 +8361,7 @@ NodeIsSyncingEnabled = false
FinalizedBlockPollInterval = '5s'
EnforceRepeatableRead = false
DeathDeclarationDelay = '10s'
+NewHeadsPollInterval = '0s'
[OCR]
ContractConfirmations = 1
@@ -7243,6 +8397,7 @@ MinIncomingConfirmations = 1
MinContractPayment = '0.00001 link'
NonceAutoSync = true
NoNewHeadsThreshold = '40s'
+LogBroadcasterEnabled = true
RPCDefaultBatchSize = 250
RPCBlockQueryDelay = 1
FinalizedBlockOffset = 0
@@ -7271,6 +8426,7 @@ LimitDefault = 8000000
LimitMax = 8000000
LimitMultiplier = '1'
LimitTransfer = 21000
+EstimateLimit = false
BumpMin = '100 wei'
BumpPercent = 20
BumpThreshold = 3
@@ -7286,6 +8442,9 @@ CheckInclusionBlocks = 12
CheckInclusionPercentile = 90
TransactionPercentile = 60
+[GasEstimator.FeeHistory]
+CacheTimeout = '10s'
+
[HeadTracker]
HistoryDepth = 300
MaxBufferSize = 3
@@ -7303,6 +8462,7 @@ NodeIsSyncingEnabled = false
FinalizedBlockPollInterval = '5s'
EnforceRepeatableRead = false
DeathDeclarationDelay = '10s'
+NewHeadsPollInterval = '0s'
[OCR]
ContractConfirmations = 1
@@ -7338,6 +8498,7 @@ MinIncomingConfirmations = 3
MinContractPayment = '0.00001 link'
NonceAutoSync = true
NoNewHeadsThreshold = '0s'
+LogBroadcasterEnabled = true
RPCDefaultBatchSize = 250
RPCBlockQueryDelay = 1
FinalizedBlockOffset = 0
@@ -7366,6 +8527,7 @@ LimitDefault = 8000000
LimitMax = 1000000000
LimitMultiplier = '1'
LimitTransfer = 21000
+EstimateLimit = false
BumpMin = '5 gwei'
BumpPercent = 20
BumpThreshold = 5
@@ -7381,6 +8543,9 @@ CheckInclusionBlocks = 12
CheckInclusionPercentile = 90
TransactionPercentile = 60
+[GasEstimator.FeeHistory]
+CacheTimeout = '10s'
+
[HeadTracker]
HistoryDepth = 100
MaxBufferSize = 3
@@ -7398,6 +8563,7 @@ NodeIsSyncingEnabled = false
FinalizedBlockPollInterval = '5s'
EnforceRepeatableRead = false
DeathDeclarationDelay = '10s'
+NewHeadsPollInterval = '0s'
[OCR]
ContractConfirmations = 1
@@ -7433,6 +8599,7 @@ MinIncomingConfirmations = 3
MinContractPayment = '0.00001 link'
NonceAutoSync = true
NoNewHeadsThreshold = '0s'
+LogBroadcasterEnabled = true
RPCDefaultBatchSize = 250
RPCBlockQueryDelay = 1
FinalizedBlockOffset = 0
@@ -7461,6 +8628,7 @@ LimitDefault = 8000000
LimitMax = 1000000000
LimitMultiplier = '1'
LimitTransfer = 21000
+EstimateLimit = false
BumpMin = '5 gwei'
BumpPercent = 20
BumpThreshold = 5
@@ -7476,6 +8644,9 @@ CheckInclusionBlocks = 12
CheckInclusionPercentile = 90
TransactionPercentile = 60
+[GasEstimator.FeeHistory]
+CacheTimeout = '10s'
+
[HeadTracker]
HistoryDepth = 100
MaxBufferSize = 3
@@ -7493,6 +8664,7 @@ NodeIsSyncingEnabled = false
FinalizedBlockPollInterval = '5s'
EnforceRepeatableRead = false
DeathDeclarationDelay = '10s'
+NewHeadsPollInterval = '0s'
[OCR]
ContractConfirmations = 1
@@ -7528,6 +8700,7 @@ MinIncomingConfirmations = 3
MinContractPayment = '0.00001 link'
NonceAutoSync = true
NoNewHeadsThreshold = '0s'
+LogBroadcasterEnabled = true
RPCDefaultBatchSize = 250
RPCBlockQueryDelay = 1
FinalizedBlockOffset = 0
@@ -7556,6 +8729,7 @@ LimitDefault = 8000000
LimitMax = 1000000000
LimitMultiplier = '1'
LimitTransfer = 21000
+EstimateLimit = false
BumpMin = '5 gwei'
BumpPercent = 20
BumpThreshold = 5
@@ -7571,6 +8745,9 @@ CheckInclusionBlocks = 12
CheckInclusionPercentile = 90
TransactionPercentile = 60
+[GasEstimator.FeeHistory]
+CacheTimeout = '10s'
+
[HeadTracker]
HistoryDepth = 100
MaxBufferSize = 3
@@ -7588,6 +8765,7 @@ NodeIsSyncingEnabled = false
FinalizedBlockPollInterval = '5s'
EnforceRepeatableRead = false
DeathDeclarationDelay = '10s'
+NewHeadsPollInterval = '0s'
[OCR]
ContractConfirmations = 1
@@ -7622,6 +8800,7 @@ MinIncomingConfirmations = 1
MinContractPayment = '0.00001 link'
NonceAutoSync = true
NoNewHeadsThreshold = '0s'
+LogBroadcasterEnabled = true
RPCDefaultBatchSize = 250
RPCBlockQueryDelay = 1
FinalizedBlockOffset = 0
@@ -7650,6 +8829,7 @@ LimitDefault = 8000000
LimitMax = 8000000
LimitMultiplier = '1'
LimitTransfer = 21000
+EstimateLimit = false
BumpMin = '1 gwei'
BumpPercent = 20
BumpThreshold = 3
@@ -7665,6 +8845,9 @@ CheckInclusionBlocks = 12
CheckInclusionPercentile = 90
TransactionPercentile = 60
+[GasEstimator.FeeHistory]
+CacheTimeout = '10s'
+
[HeadTracker]
HistoryDepth = 50
MaxBufferSize = 3
@@ -7682,6 +8865,7 @@ NodeIsSyncingEnabled = false
FinalizedBlockPollInterval = '5s'
EnforceRepeatableRead = false
DeathDeclarationDelay = '10s'
+NewHeadsPollInterval = '0s'
[OCR]
ContractConfirmations = 1
@@ -7716,6 +8900,7 @@ MinIncomingConfirmations = 1
MinContractPayment = '0.00001 link'
NonceAutoSync = true
NoNewHeadsThreshold = '0s'
+LogBroadcasterEnabled = true
RPCDefaultBatchSize = 250
RPCBlockQueryDelay = 1
FinalizedBlockOffset = 0
@@ -7744,6 +8929,7 @@ LimitDefault = 8000000
LimitMax = 8000000
LimitMultiplier = '1'
LimitTransfer = 21000
+EstimateLimit = false
BumpMin = '1 gwei'
BumpPercent = 20
BumpThreshold = 3
@@ -7759,6 +8945,9 @@ CheckInclusionBlocks = 12
CheckInclusionPercentile = 90
TransactionPercentile = 60
+[GasEstimator.FeeHistory]
+CacheTimeout = '10s'
+
[HeadTracker]
HistoryDepth = 50
MaxBufferSize = 3
@@ -7776,6 +8965,7 @@ NodeIsSyncingEnabled = false
FinalizedBlockPollInterval = '5s'
EnforceRepeatableRead = false
DeathDeclarationDelay = '10s'
+NewHeadsPollInterval = '0s'
[OCR]
ContractConfirmations = 1
@@ -7792,6 +8982,106 @@ GasLimit = 5400000
+
+```toml
+AutoCreateKey = true
+BlockBackfillDepth = 10
+BlockBackfillSkip = false
+ChainType = 'optimismBedrock'
+FinalityDepth = 900
+FinalityTagEnabled = true
+LogBackfillBatchSize = 1000
+LogPollInterval = '5s'
+LogKeepBlocksDepth = 100000
+LogPrunePageSize = 10000
+BackupLogPollerBlockDelay = 100
+MinIncomingConfirmations = 3
+MinContractPayment = '0.00001 link'
+NonceAutoSync = true
+NoNewHeadsThreshold = '3m0s'
+LogBroadcasterEnabled = true
+RPCDefaultBatchSize = 250
+RPCBlockQueryDelay = 1
+FinalizedBlockOffset = 0
+NoNewFinalizedHeadsThreshold = '35m0s'
+
+[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 = '115792089237316195423570985008687907853269984665.640564039457584007913129639935 tether'
+PriceMin = '1 gwei'
+LimitDefault = 8000000
+LimitMax = 8000000
+LimitMultiplier = '1'
+LimitTransfer = 21000
+EstimateLimit = false
+BumpMin = '5 gwei'
+BumpPercent = 20
+BumpThreshold = 3
+EIP1559DynamicFees = true
+FeeCapDefault = '100 gwei'
+TipCapDefault = '1 wei'
+TipCapMin = '1 wei'
+
+[GasEstimator.BlockHistory]
+BatchSize = 25
+BlockHistorySize = 100
+CheckInclusionBlocks = 12
+CheckInclusionPercentile = 90
+TransactionPercentile = 60
+
+[GasEstimator.FeeHistory]
+CacheTimeout = '2s'
+
+[HeadTracker]
+HistoryDepth = 100
+MaxBufferSize = 3
+SamplingInterval = '1s'
+MaxAllowedFinalityDepth = 10000
+FinalityTagBypass = true
+
+[NodePool]
+PollFailureThreshold = 5
+PollInterval = '10s'
+SelectionMode = 'HighestHead'
+SyncThreshold = 5
+LeaseDuration = '0s'
+NodeIsSyncingEnabled = false
+FinalizedBlockPollInterval = '5s'
+EnforceRepeatableRead = false
+DeathDeclarationDelay = '10s'
+NewHeadsPollInterval = '0s'
+
+[OCR]
+ContractConfirmations = 4
+ContractTransmitterTransmitTimeout = '10s'
+DatabaseTimeout = '10s'
+DeltaCOverride = '168h0m0s'
+DeltaCJitterOverride = '1h0m0s'
+ObservationGracePeriod = '1s'
+
+[OCR2]
+[OCR2.Automation]
+GasLimit = 5400000
+```
+
+
```toml
@@ -7810,6 +9100,7 @@ MinIncomingConfirmations = 3
MinContractPayment = '0.1 link'
NonceAutoSync = true
NoNewHeadsThreshold = '3m0s'
+LogBroadcasterEnabled = true
RPCDefaultBatchSize = 250
RPCBlockQueryDelay = 1
FinalizedBlockOffset = 0
@@ -7838,6 +9129,7 @@ LimitDefault = 8000000
LimitMax = 8000000
LimitMultiplier = '1'
LimitTransfer = 21000
+EstimateLimit = false
BumpMin = '5 gwei'
BumpPercent = 20
BumpThreshold = 3
@@ -7853,6 +9145,9 @@ CheckInclusionBlocks = 12
CheckInclusionPercentile = 90
TransactionPercentile = 50
+[GasEstimator.FeeHistory]
+CacheTimeout = '10s'
+
[HeadTracker]
HistoryDepth = 100
MaxBufferSize = 3
@@ -7870,6 +9165,7 @@ NodeIsSyncingEnabled = false
FinalizedBlockPollInterval = '5s'
EnforceRepeatableRead = false
DeathDeclarationDelay = '10s'
+NewHeadsPollInterval = '0s'
[OCR]
ContractConfirmations = 4
@@ -7904,6 +9200,7 @@ MinIncomingConfirmations = 1
MinContractPayment = '0.00001 link'
NonceAutoSync = true
NoNewHeadsThreshold = '40s'
+LogBroadcasterEnabled = true
RPCDefaultBatchSize = 250
RPCBlockQueryDelay = 1
FinalizedBlockOffset = 0
@@ -7932,6 +9229,7 @@ LimitDefault = 8000000
LimitMax = 8000000
LimitMultiplier = '1'
LimitTransfer = 21000
+EstimateLimit = false
BumpMin = '100 wei'
BumpPercent = 20
BumpThreshold = 3
@@ -7947,6 +9245,9 @@ CheckInclusionBlocks = 12
CheckInclusionPercentile = 90
TransactionPercentile = 60
+[GasEstimator.FeeHistory]
+CacheTimeout = '10s'
+
[HeadTracker]
HistoryDepth = 300
MaxBufferSize = 3
@@ -7964,6 +9265,7 @@ NodeIsSyncingEnabled = false
FinalizedBlockPollInterval = '5s'
EnforceRepeatableRead = false
DeathDeclarationDelay = '10s'
+NewHeadsPollInterval = '0s'
[OCR]
ContractConfirmations = 1
@@ -7998,6 +9300,7 @@ MinIncomingConfirmations = 3
MinContractPayment = '0.00001 link'
NonceAutoSync = true
NoNewHeadsThreshold = '3m0s'
+LogBroadcasterEnabled = true
RPCDefaultBatchSize = 250
RPCBlockQueryDelay = 1
FinalizedBlockOffset = 0
@@ -8026,6 +9329,7 @@ LimitDefault = 8000000
LimitMax = 8000000
LimitMultiplier = '1'
LimitTransfer = 21000
+EstimateLimit = false
BumpMin = '100 wei'
BumpPercent = 20
BumpThreshold = 60
@@ -8042,6 +9346,9 @@ CheckInclusionPercentile = 90
EIP1559FeeCapBufferBlocks = 0
TransactionPercentile = 60
+[GasEstimator.FeeHistory]
+CacheTimeout = '10s'
+
[HeadTracker]
HistoryDepth = 300
MaxBufferSize = 3
@@ -8059,6 +9366,7 @@ NodeIsSyncingEnabled = false
FinalizedBlockPollInterval = '5s'
EnforceRepeatableRead = false
DeathDeclarationDelay = '10s'
+NewHeadsPollInterval = '0s'
[OCR]
ContractConfirmations = 4
@@ -8093,6 +9401,7 @@ MinIncomingConfirmations = 1
MinContractPayment = '0.00001 link'
NonceAutoSync = true
NoNewHeadsThreshold = '30s'
+LogBroadcasterEnabled = true
RPCDefaultBatchSize = 250
RPCBlockQueryDelay = 1
FinalizedBlockOffset = 0
@@ -8121,6 +9430,7 @@ LimitDefault = 8000000
LimitMax = 8000000
LimitMultiplier = '1'
LimitTransfer = 21000
+EstimateLimit = false
BumpMin = '5 gwei'
BumpPercent = 20
BumpThreshold = 3
@@ -8136,6 +9446,9 @@ CheckInclusionBlocks = 12
CheckInclusionPercentile = 90
TransactionPercentile = 60
+[GasEstimator.FeeHistory]
+CacheTimeout = '10s'
+
[HeadTracker]
HistoryDepth = 100
MaxBufferSize = 3
@@ -8153,6 +9466,7 @@ NodeIsSyncingEnabled = false
FinalizedBlockPollInterval = '5s'
EnforceRepeatableRead = false
DeathDeclarationDelay = '10s'
+NewHeadsPollInterval = '0s'
[OCR]
ContractConfirmations = 4
@@ -8187,6 +9501,7 @@ MinIncomingConfirmations = 1
MinContractPayment = '0.00001 link'
NonceAutoSync = true
NoNewHeadsThreshold = '30s'
+LogBroadcasterEnabled = true
RPCDefaultBatchSize = 250
RPCBlockQueryDelay = 1
FinalizedBlockOffset = 0
@@ -8215,6 +9530,7 @@ LimitDefault = 8000000
LimitMax = 8000000
LimitMultiplier = '1'
LimitTransfer = 21000
+EstimateLimit = false
BumpMin = '5 gwei'
BumpPercent = 20
BumpThreshold = 3
@@ -8230,6 +9546,9 @@ CheckInclusionBlocks = 12
CheckInclusionPercentile = 90
TransactionPercentile = 60
+[GasEstimator.FeeHistory]
+CacheTimeout = '10s'
+
[HeadTracker]
HistoryDepth = 100
MaxBufferSize = 3
@@ -8247,6 +9566,7 @@ NodeIsSyncingEnabled = false
FinalizedBlockPollInterval = '5s'
EnforceRepeatableRead = false
DeathDeclarationDelay = '10s'
+NewHeadsPollInterval = '0s'
[OCR]
ContractConfirmations = 4
@@ -8452,6 +9772,12 @@ The latest finalized block on chain is 64, so block 63 is the latest finalized f
Block 64 will be treated as finalized by CL Node only when chain's latest finalized block is 65. As chain finalizes blocks in batches of 32,
CL Node has to wait for a whole new batch to be finalized to treat block 64 as finalized.
+### LogBroadcasterEnabled
+```toml
+LogBroadcasterEnabled = true # Default
+```
+LogBroadcasterEnabled is a feature flag for LogBroadcaster, by default it's true.
+
### NoNewFinalizedHeadsThreshold
```toml
NoNewFinalizedHeadsThreshold = '0' # Default
@@ -8577,6 +9903,7 @@ LimitDefault = 8_000_000 # Default
LimitMax = 8_000_000 # Default
LimitMultiplier = '1.0' # Default
LimitTransfer = 21_000 # Default
+EstimateLimit = false # Default
BumpMin = '5 gwei' # Default
BumpPercent = 20 # Default
BumpThreshold = 3 # Default
@@ -8671,6 +9998,12 @@ LimitTransfer = 21_000 # Default
```
LimitTransfer is the gas limit used for an ordinary ETH transfer.
+### EstimateLimit
+```toml
+EstimateLimit = false # Default
+```
+EstimateLimit enables estimating gas limits for transactions. This feature respects the gas limit provided during transaction creation as an upper bound.
+
### BumpMin
```toml
BumpMin = '5 gwei' # Default
@@ -8884,6 +10217,24 @@ Setting this number higher will cause the Chainlink node to select higher gas pr
Setting it lower will tend to set lower gas prices.
+## EVM.GasEstimator.FeeHistory
+```toml
+[EVM.GasEstimator.FeeHistory]
+CacheTimeout = '10s' # Default
+```
+
+
+### CacheTimeout
+```toml
+CacheTimeout = '10s' # Default
+```
+CacheTimeout is the time to wait in order to refresh the cached values stored in the FeeHistory estimator. A small jitter is applied so the timeout won't be exactly the same each time.
+
+You want this value to be close to the block time. For slower chains, like Ethereum, you can set it to 12s, the same as the block time. For faster chains you can skip a block or two
+and set it to two times the block time i.e. on Optimism you can set it to 4s. Ideally, you don't want to go lower than 1s since the RTT times of the RPC requests will be comparable to
+the timeout. The estimator is already adding a buffer to account for a potential increase in prices within one or two blocks. On the other hand, slower frequency will fail to refresh
+the prices and end up in stale values.
+
## EVM.HeadTracker
```toml
[EVM.HeadTracker]
@@ -8970,6 +10321,7 @@ NodeIsSyncingEnabled = false # Default
FinalizedBlockPollInterval = '5s' # Default
EnforceRepeatableRead = false # Default
DeathDeclarationDelay = '10s' # Default
+NewHeadsPollInterval = '0s' # Default
```
The node pool manages multiple RPC endpoints.
@@ -9062,6 +10414,14 @@ Larger values might be helpful to reduce the noisiness of health checks like `En
trigger declaration of `FinalizedBlockOutOfSync` due to insignificant network delays in broadcasting of the finalized state among RPCs.
RPC will not be picked to handle a request even if this option is set to a nonzero value.
+### NewHeadsPollInterval
+```toml
+NewHeadsPollInterval = '0s' # Default
+```
+NewHeadsPollInterval define an interval for polling new block periodically using http client rather than subscribe to ws feed
+
+Set to 0 to disable.
+
## EVM.NodePool.Errors
:warning: **_ADVANCED_**: _Do not change these settings unless you know what you are doing._
```toml
@@ -9240,7 +10600,7 @@ Name is a unique (per-chain) identifier for this node.
```toml
WSURL = 'wss://web.socket/test' # Example
```
-WSURL is the WS(S) endpoint for this node. Required for primary nodes.
+WSURL is the WS(S) endpoint for this node. Required for primary nodes when `LogBroadcasterEnabled` is `true`
### HTTPURL
```toml
diff --git a/go.mod b/go.mod
index 683793cb53..af1174beab 100644
--- a/go.mod
+++ b/go.mod
@@ -72,7 +72,7 @@ require (
github.com/scylladb/go-reflectx v1.0.1
github.com/shirou/gopsutil/v3 v3.24.3
github.com/shopspring/decimal v1.4.0
- github.com/smartcontractkit/chain-selectors v1.0.21
+ github.com/smartcontractkit/chain-selectors v1.0.27
github.com/smartcontractkit/chainlink-automation v1.0.4
github.com/smartcontractkit/chainlink-ccip v0.0.0-20240816163757-48726fd8165f
github.com/smartcontractkit/chainlink-common v0.2.1-0.20240717132349-ee5af9b79834
diff --git a/go.sum b/go.sum
index 5ad6772ecd..b81f4930a0 100644
--- a/go.sum
+++ b/go.sum
@@ -1023,8 +1023,8 @@ github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMB
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
-github.com/smartcontractkit/chain-selectors v1.0.21 h1:KCR9SA7PhOexaBzFieHoLv1WonwhVOPtOStpqTmLC4E=
-github.com/smartcontractkit/chain-selectors v1.0.21/go.mod h1:d4Hi+E1zqjy9HqMkjBE5q1vcG9VGgxf5VxiRHfzi2kE=
+github.com/smartcontractkit/chain-selectors v1.0.27 h1:VE/ftX9Aae4gnw67yR1raKi+30iWKL/sWq8uyiLHM8k=
+github.com/smartcontractkit/chain-selectors v1.0.27/go.mod h1:d4Hi+E1zqjy9HqMkjBE5q1vcG9VGgxf5VxiRHfzi2kE=
github.com/smartcontractkit/chainlink-automation v1.0.4 h1:iyW181JjKHLNMnDleI8umfIfVVlwC7+n5izbLSFgjw8=
github.com/smartcontractkit/chainlink-automation v1.0.4/go.mod h1:u4NbPZKJ5XiayfKHD/v3z3iflQWqvtdhj13jVZXj/cM=
github.com/smartcontractkit/chainlink-ccip v0.0.0-20240816163757-48726fd8165f h1:lQZBOjeYFpCdk0mGQUhbrJipd00tu49xK4zSijC/9Co=
diff --git a/integration-tests/go.mod b/integration-tests/go.mod
index 7abf19dc82..c26b3d27e9 100644
--- a/integration-tests/go.mod
+++ b/integration-tests/go.mod
@@ -34,7 +34,7 @@ require (
github.com/shopspring/decimal v1.4.0
github.com/slack-go/slack v0.12.2
github.com/smartcontractkit/ccip-owner-contracts v0.0.0-20240808195812-ae0378684685
- github.com/smartcontractkit/chain-selectors v1.0.21
+ github.com/smartcontractkit/chain-selectors v1.0.27
github.com/smartcontractkit/chainlink-automation v1.0.4
github.com/smartcontractkit/chainlink-ccip v0.0.0-20240816163757-48726fd8165f
github.com/smartcontractkit/chainlink-common v0.2.1-0.20240717132349-ee5af9b79834
diff --git a/integration-tests/go.sum b/integration-tests/go.sum
index babc3fc8c6..381abac5be 100644
--- a/integration-tests/go.sum
+++ b/integration-tests/go.sum
@@ -1390,8 +1390,8 @@ github.com/slack-go/slack v0.12.2 h1:x3OppyMyGIbbiyFhsBmpf9pwkUzMhthJMRNmNlA4LaQ
github.com/slack-go/slack v0.12.2/go.mod h1:hlGi5oXA+Gt+yWTPP0plCdRKmjsDxecdHxYQdlMQKOw=
github.com/smartcontractkit/ccip-owner-contracts v0.0.0-20240808195812-ae0378684685 h1:jakAsdhDxV4cMgRAcSvHraXjyePi8umG5SEUTGFvuy8=
github.com/smartcontractkit/ccip-owner-contracts v0.0.0-20240808195812-ae0378684685/go.mod h1:p7L/xNEQpHDdZtgFA6/FavuZHqvV3kYhQysxBywmq1k=
-github.com/smartcontractkit/chain-selectors v1.0.21 h1:KCR9SA7PhOexaBzFieHoLv1WonwhVOPtOStpqTmLC4E=
-github.com/smartcontractkit/chain-selectors v1.0.21/go.mod h1:d4Hi+E1zqjy9HqMkjBE5q1vcG9VGgxf5VxiRHfzi2kE=
+github.com/smartcontractkit/chain-selectors v1.0.27 h1:VE/ftX9Aae4gnw67yR1raKi+30iWKL/sWq8uyiLHM8k=
+github.com/smartcontractkit/chain-selectors v1.0.27/go.mod h1:d4Hi+E1zqjy9HqMkjBE5q1vcG9VGgxf5VxiRHfzi2kE=
github.com/smartcontractkit/chainlink-automation v1.0.4 h1:iyW181JjKHLNMnDleI8umfIfVVlwC7+n5izbLSFgjw8=
github.com/smartcontractkit/chainlink-automation v1.0.4/go.mod h1:u4NbPZKJ5XiayfKHD/v3z3iflQWqvtdhj13jVZXj/cM=
github.com/smartcontractkit/chainlink-ccip v0.0.0-20240816163757-48726fd8165f h1:lQZBOjeYFpCdk0mGQUhbrJipd00tu49xK4zSijC/9Co=
diff --git a/integration-tests/load/go.mod b/integration-tests/load/go.mod
index a90826f599..d086677434 100644
--- a/integration-tests/load/go.mod
+++ b/integration-tests/load/go.mod
@@ -374,7 +374,7 @@ require (
github.com/shoenig/go-m1cpu v0.1.6 // indirect
github.com/shopspring/decimal v1.4.0 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
- github.com/smartcontractkit/chain-selectors v1.0.21 // indirect
+ github.com/smartcontractkit/chain-selectors v1.0.27 // indirect
github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240718160222-2dc0c8136bfa // indirect
github.com/smartcontractkit/chainlink-feeds v0.0.0-20240710170203-5b41615da827 // indirect
github.com/smartcontractkit/chainlink-solana v1.0.3-0.20240712132946-267a37c5ac6e // indirect
diff --git a/integration-tests/load/go.sum b/integration-tests/load/go.sum
index 536b96f8b6..b181bfbaae 100644
--- a/integration-tests/load/go.sum
+++ b/integration-tests/load/go.sum
@@ -1354,8 +1354,8 @@ 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.12.2 h1:x3OppyMyGIbbiyFhsBmpf9pwkUzMhthJMRNmNlA4LaQ=
github.com/slack-go/slack v0.12.2/go.mod h1:hlGi5oXA+Gt+yWTPP0plCdRKmjsDxecdHxYQdlMQKOw=
-github.com/smartcontractkit/chain-selectors v1.0.21 h1:KCR9SA7PhOexaBzFieHoLv1WonwhVOPtOStpqTmLC4E=
-github.com/smartcontractkit/chain-selectors v1.0.21/go.mod h1:d4Hi+E1zqjy9HqMkjBE5q1vcG9VGgxf5VxiRHfzi2kE=
+github.com/smartcontractkit/chain-selectors v1.0.27 h1:VE/ftX9Aae4gnw67yR1raKi+30iWKL/sWq8uyiLHM8k=
+github.com/smartcontractkit/chain-selectors v1.0.27/go.mod h1:d4Hi+E1zqjy9HqMkjBE5q1vcG9VGgxf5VxiRHfzi2kE=
github.com/smartcontractkit/chainlink-automation v1.0.4 h1:iyW181JjKHLNMnDleI8umfIfVVlwC7+n5izbLSFgjw8=
github.com/smartcontractkit/chainlink-automation v1.0.4/go.mod h1:u4NbPZKJ5XiayfKHD/v3z3iflQWqvtdhj13jVZXj/cM=
github.com/smartcontractkit/chainlink-ccip v0.0.0-20240816163757-48726fd8165f h1:lQZBOjeYFpCdk0mGQUhbrJipd00tu49xK4zSijC/9Co=
diff --git a/integration-tests/testconfig/vrfv2plus/vrfv2plus.toml b/integration-tests/testconfig/vrfv2plus/vrfv2plus.toml
index 8f8aa9530e..cd089013db 100644
--- a/integration-tests/testconfig/vrfv2plus/vrfv2plus.toml
+++ b/integration-tests/testconfig/vrfv2plus/vrfv2plus.toml
@@ -19,6 +19,7 @@ MaxSize = '0b'
[WebServer]
AllowOrigins = '*'
HTTPPort = 6688
+HTTPWriteTimeout = '1m0s'
SecureCookies = false
[WebServer.RateLimit]
diff --git a/package.json b/package.json
index 541cfb1aaf..e86ba1ac33 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "ccip",
- "version": "2.14.0-ccip1.5.0",
+ "version": "2.14.0-ccip1.5.6",
"description": "node of the decentralized oracle network, bridging on and off-chain computation",
"main": "index.js",
"scripts": {
diff --git a/testdata/scripts/health/default.txtar b/testdata/scripts/health/default.txtar
index 1dbf6b8eb9..777d3e5e12 100644
--- a/testdata/scripts/health/default.txtar
+++ b/testdata/scripts/health/default.txtar
@@ -31,6 +31,7 @@ fj293fbBnlQ!f9vNs
HTTPPort = $PORT
-- out.txt --
+ok HeadReporter
ok JobSpawner
ok Mailbox.Monitor
ok Mercury.WSRPCPool
@@ -38,12 +39,20 @@ ok Mercury.WSRPCPool.CacheSet
ok PipelineORM
ok PipelineRunner
ok PipelineRunner.BridgeCache
-ok PromReporter
ok TelemetryManager
-- out.json --
{
"data": [
+ {
+ "type": "checks",
+ "id": "HeadReporter",
+ "attributes": {
+ "name": "HeadReporter",
+ "status": "passing",
+ "output": ""
+ }
+ },
{
"type": "checks",
"id": "JobSpawner",
@@ -107,15 +116,6 @@ ok TelemetryManager
"output": ""
}
},
- {
- "type": "checks",
- "id": "PromReporter",
- "attributes": {
- "name": "PromReporter",
- "status": "passing",
- "output": ""
- }
- },
{
"type": "checks",
"id": "TelemetryManager",
diff --git a/testdata/scripts/health/multi-chain.txtar b/testdata/scripts/health/multi-chain.txtar
index 8178f8e821..3bd15850af 100644
--- a/testdata/scripts/health/multi-chain.txtar
+++ b/testdata/scripts/health/multi-chain.txtar
@@ -75,6 +75,7 @@ ok EVM.1.Txm.BlockHistoryEstimator
ok EVM.1.Txm.Broadcaster
ok EVM.1.Txm.Confirmer
ok EVM.1.Txm.WrappedEvmEstimator
+ok HeadReporter
ok JobSpawner
ok Mailbox.Monitor
ok Mercury.WSRPCPool
@@ -82,7 +83,6 @@ ok Mercury.WSRPCPool.CacheSet
ok PipelineORM
ok PipelineRunner
ok PipelineRunner.BridgeCache
-ok PromReporter
ok Solana.Bar
ok StarkNet.Baz
ok TelemetryManager
@@ -216,6 +216,15 @@ ok TelemetryManager
"output": ""
}
},
+ {
+ "type": "checks",
+ "id": "HeadReporter",
+ "attributes": {
+ "name": "HeadReporter",
+ "status": "passing",
+ "output": ""
+ }
+ },
{
"type": "checks",
"id": "JobSpawner",
@@ -279,15 +288,6 @@ ok TelemetryManager
"output": ""
}
},
- {
- "type": "checks",
- "id": "PromReporter",
- "attributes": {
- "name": "PromReporter",
- "status": "passing",
- "output": ""
- }
- },
{
"type": "checks",
"id": "Solana.Bar",
diff --git a/testdata/scripts/node/validate/disk-based-logging-disabled.txtar b/testdata/scripts/node/validate/disk-based-logging-disabled.txtar
index fe522ca1bb..fb02650ee7 100644
--- a/testdata/scripts/node/validate/disk-based-logging-disabled.txtar
+++ b/testdata/scripts/node/validate/disk-based-logging-disabled.txtar
@@ -331,6 +331,7 @@ MinContractPayment = '0.1 link'
NonceAutoSync = true
NoNewHeadsThreshold = '3m0s'
OperatorFactoryAddress = '0x3E64Cd889482443324F91bFA9c84fE72A511f48A'
+LogBroadcasterEnabled = true
RPCDefaultBatchSize = 250
RPCBlockQueryDelay = 1
FinalizedBlockOffset = 0
@@ -359,6 +360,7 @@ LimitDefault = 8000000
LimitMax = 8000000
LimitMultiplier = '1'
LimitTransfer = 21000
+EstimateLimit = false
BumpMin = '5 gwei'
BumpPercent = 20
BumpThreshold = 3
@@ -374,6 +376,9 @@ CheckInclusionBlocks = 12
CheckInclusionPercentile = 90
TransactionPercentile = 50
+[EVM.GasEstimator.FeeHistory]
+CacheTimeout = '10s'
+
[EVM.HeadTracker]
HistoryDepth = 100
MaxBufferSize = 3
@@ -391,6 +396,7 @@ NodeIsSyncingEnabled = false
FinalizedBlockPollInterval = '5s'
EnforceRepeatableRead = false
DeathDeclarationDelay = '10s'
+NewHeadsPollInterval = '0s'
[EVM.OCR]
ContractConfirmations = 4
diff --git a/testdata/scripts/node/validate/disk-based-logging-no-dir.txtar b/testdata/scripts/node/validate/disk-based-logging-no-dir.txtar
index 5a5ce42d6b..8f4f2010de 100644
--- a/testdata/scripts/node/validate/disk-based-logging-no-dir.txtar
+++ b/testdata/scripts/node/validate/disk-based-logging-no-dir.txtar
@@ -331,6 +331,7 @@ MinContractPayment = '0.1 link'
NonceAutoSync = true
NoNewHeadsThreshold = '3m0s'
OperatorFactoryAddress = '0x3E64Cd889482443324F91bFA9c84fE72A511f48A'
+LogBroadcasterEnabled = true
RPCDefaultBatchSize = 250
RPCBlockQueryDelay = 1
FinalizedBlockOffset = 0
@@ -359,6 +360,7 @@ LimitDefault = 8000000
LimitMax = 8000000
LimitMultiplier = '1'
LimitTransfer = 21000
+EstimateLimit = false
BumpMin = '5 gwei'
BumpPercent = 20
BumpThreshold = 3
@@ -374,6 +376,9 @@ CheckInclusionBlocks = 12
CheckInclusionPercentile = 90
TransactionPercentile = 50
+[EVM.GasEstimator.FeeHistory]
+CacheTimeout = '10s'
+
[EVM.HeadTracker]
HistoryDepth = 100
MaxBufferSize = 3
@@ -391,6 +396,7 @@ NodeIsSyncingEnabled = false
FinalizedBlockPollInterval = '5s'
EnforceRepeatableRead = false
DeathDeclarationDelay = '10s'
+NewHeadsPollInterval = '0s'
[EVM.OCR]
ContractConfirmations = 4
diff --git a/testdata/scripts/node/validate/disk-based-logging.txtar b/testdata/scripts/node/validate/disk-based-logging.txtar
index 12773dc99c..8f2ac33f33 100644
--- a/testdata/scripts/node/validate/disk-based-logging.txtar
+++ b/testdata/scripts/node/validate/disk-based-logging.txtar
@@ -331,6 +331,7 @@ MinContractPayment = '0.1 link'
NonceAutoSync = true
NoNewHeadsThreshold = '3m0s'
OperatorFactoryAddress = '0x3E64Cd889482443324F91bFA9c84fE72A511f48A'
+LogBroadcasterEnabled = true
RPCDefaultBatchSize = 250
RPCBlockQueryDelay = 1
FinalizedBlockOffset = 0
@@ -359,6 +360,7 @@ LimitDefault = 8000000
LimitMax = 8000000
LimitMultiplier = '1'
LimitTransfer = 21000
+EstimateLimit = false
BumpMin = '5 gwei'
BumpPercent = 20
BumpThreshold = 3
@@ -374,6 +376,9 @@ CheckInclusionBlocks = 12
CheckInclusionPercentile = 90
TransactionPercentile = 50
+[EVM.GasEstimator.FeeHistory]
+CacheTimeout = '10s'
+
[EVM.HeadTracker]
HistoryDepth = 100
MaxBufferSize = 3
@@ -391,6 +396,7 @@ NodeIsSyncingEnabled = false
FinalizedBlockPollInterval = '5s'
EnforceRepeatableRead = false
DeathDeclarationDelay = '10s'
+NewHeadsPollInterval = '0s'
[EVM.OCR]
ContractConfirmations = 4
diff --git a/testdata/scripts/node/validate/invalid.txtar b/testdata/scripts/node/validate/invalid.txtar
index e709b24d5b..563f075bc0 100644
--- a/testdata/scripts/node/validate/invalid.txtar
+++ b/testdata/scripts/node/validate/invalid.txtar
@@ -321,6 +321,7 @@ MinContractPayment = '0.1 link'
NonceAutoSync = true
NoNewHeadsThreshold = '3m0s'
OperatorFactoryAddress = '0x3E64Cd889482443324F91bFA9c84fE72A511f48A'
+LogBroadcasterEnabled = true
RPCDefaultBatchSize = 250
RPCBlockQueryDelay = 1
FinalizedBlockOffset = 0
@@ -349,6 +350,7 @@ LimitDefault = 8000000
LimitMax = 8000000
LimitMultiplier = '1'
LimitTransfer = 21000
+EstimateLimit = false
BumpMin = '5 gwei'
BumpPercent = 20
BumpThreshold = 3
@@ -364,6 +366,9 @@ CheckInclusionBlocks = 12
CheckInclusionPercentile = 90
TransactionPercentile = 50
+[EVM.GasEstimator.FeeHistory]
+CacheTimeout = '10s'
+
[EVM.HeadTracker]
HistoryDepth = 100
MaxBufferSize = 3
@@ -381,6 +386,7 @@ NodeIsSyncingEnabled = false
FinalizedBlockPollInterval = '5s'
EnforceRepeatableRead = false
DeathDeclarationDelay = '10s'
+NewHeadsPollInterval = '0s'
[EVM.OCR]
ContractConfirmations = 4
diff --git a/testdata/scripts/node/validate/valid.txtar b/testdata/scripts/node/validate/valid.txtar
index 9b22da4850..c27cbd51e1 100644
--- a/testdata/scripts/node/validate/valid.txtar
+++ b/testdata/scripts/node/validate/valid.txtar
@@ -328,6 +328,7 @@ MinContractPayment = '0.1 link'
NonceAutoSync = true
NoNewHeadsThreshold = '3m0s'
OperatorFactoryAddress = '0x3E64Cd889482443324F91bFA9c84fE72A511f48A'
+LogBroadcasterEnabled = true
RPCDefaultBatchSize = 250
RPCBlockQueryDelay = 1
FinalizedBlockOffset = 0
@@ -356,6 +357,7 @@ LimitDefault = 8000000
LimitMax = 8000000
LimitMultiplier = '1'
LimitTransfer = 21000
+EstimateLimit = false
BumpMin = '5 gwei'
BumpPercent = 20
BumpThreshold = 3
@@ -371,6 +373,9 @@ CheckInclusionBlocks = 12
CheckInclusionPercentile = 90
TransactionPercentile = 50
+[EVM.GasEstimator.FeeHistory]
+CacheTimeout = '10s'
+
[EVM.HeadTracker]
HistoryDepth = 100
MaxBufferSize = 3
@@ -388,6 +393,7 @@ NodeIsSyncingEnabled = false
FinalizedBlockPollInterval = '5s'
EnforceRepeatableRead = false
DeathDeclarationDelay = '10s'
+NewHeadsPollInterval = '0s'
[EVM.OCR]
ContractConfirmations = 4
Bsquared Testnet (1123)
Polygon Zkevm Goerli (1442)
Unichain Testnet (1301)
Simulated (1337)
Worldchain Testnet (4801)
Mantle Sepolia (5003)
Linea Goerli (59140)
Zircuit Sepolia (48899)
Zircuit Mainnet (48900)
Linea Goerli (59140)
Linea Mainnet (59144)
Metis Sepolia (59902)
Linea Mainnet (59144)
BOB Mainnet (60808)
Metis Sepolia (59902)
Avalanche ANZ testnet (76578)
Avalanche ANZ testnet (76578)
Polygon Mumbai (80001)
Polygon Mumbai (80001)
Polygon Amoy (80002)
Polygon Amoy (80002)
Berachain Testnet (80084)
BOB Testnet (808813)
Ethereum Sepolia (11155111)