Skip to content

Commit

Permalink
Add zkSync support (#11162)
Browse files Browse the repository at this point in the history
* feat(zksync): add custom error messages

* feat(zksync): add custom chain type to ignore 0x71 transaction type and commit default node config

* fix: add exhaustive case stmt for zkSync ChainType

* docs: regenerate CONFIG.md

* test: add zksync ChainType to expected output

* test: add test for custom zkSync tx type

* fix: reduce HistoryDepth setting to 5

* ci: trigger pipelines

* fix(zksync): also ignore tx type 0xff

* docs(zksync): regenerate CONFIG.md
  • Loading branch information
friedemannf authored Nov 7, 2023
1 parent 38de9a6 commit 4e45a2a
Show file tree
Hide file tree
Showing 12 changed files with 255 additions and 8 deletions.
18 changes: 17 additions & 1 deletion core/chains/evm/client/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,23 @@ var harmony = ClientErrors{
Fatal: harmonyFatal,
}

var clients = []ClientErrors{parity, geth, arbitrum, metis, substrate, avalanche, nethermind, harmony, besu, erigon, klaytn, celo}
var zkSync = ClientErrors{
NonceTooLow: regexp.MustCompile(`(?:: |^)nonce too low\..+actual: \d*$`),
NonceTooHigh: regexp.MustCompile(`(?:: |^)nonce too high\..+actual: \d*$`),
TerminallyUnderpriced: regexp.MustCompile(`(?:: |^)max fee per gas less than block base fee$`),
InsufficientEth: regexp.MustCompile(`(?:: |^)(?:insufficient balance for transfer$|insufficient funds for gas + value)`),
TxFeeExceedsCap: regexp.MustCompile(`(?:: |^)max priority fee per gas higher than max fee per gas$`),
// intrinsic gas too low - gas limit less than 14700
// Not enough gas for transaction validation - gas limit less than L2 fee
// Failed to pay the fee to the operator - gas limit less than L2+L1 fee
// Error function_selector = 0x, data = 0x - contract call with gas limit of 0
// can't start a transaction from a non-account - trying to send from an invalid address, e.g. estimating a contract -> contract tx
// max fee per gas higher than 2^64-1 - uint64 overflow
// oversized data - data too large
Fatal: regexp.MustCompile(`(?:: |^)(?:exceeds block gas limit|intrinsic gas too low|Not enough gas for transaction validation|Failed to pay the fee to the operator|Error function_selector = 0x, data = 0x|invalid sender. can't start a transaction from a non-account|max(?: priority)? fee per (?:gas|pubdata byte) higher than 2\^64-1|oversized data. max: \d+; actual: \d+)$`),
}

var clients = []ClientErrors{parity, geth, arbitrum, metis, substrate, avalanche, nethermind, harmony, besu, erigon, klaytn, celo, zkSync}

func (s *SendError) is(errorType int) bool {
if s == nil || s.err == nil {
Expand Down
16 changes: 16 additions & 0 deletions core/chains/evm/client/errors_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ func Test_Eth_Errors(t *testing.T) {
{"call failed: nonce too low: address 0x0499BEA33347cb62D79A9C0b1EDA01d8d329894c current nonce (5833) > tx nonce (5511)", true, "Avalanche"},
{"call failed: OldNonce", true, "Nethermind"},
{"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"},
}

for _, test := range tests {
Expand All @@ -60,6 +61,7 @@ func Test_Eth_Errors(t *testing.T) {
{"nonce too high: address 0x336394A3219e71D9d9bd18201d34E95C1Bb7122C, tx: 8089 state: 8090", true, "Arbitrum"},
{"nonce too high", true, "Geth"},
{"nonce too high", true, "Erigon"},
{"nonce too high. allowed nonce range: 427 - 477, actual: 527", true, "zkSync"},
}

for _, test := range tests {
Expand Down Expand Up @@ -152,6 +154,7 @@ func Test_Eth_Errors(t *testing.T) {
{"FeeTooLowToCompete", true, "Nethermind"},
{"transaction underpriced", true, "Klaytn"},
{"intrinsic gas too low", true, "Klaytn"},
{"max fee per gas less than block base fee", true, "zkSync"},
}

for _, test := range tests {
Expand Down Expand Up @@ -194,6 +197,8 @@ func Test_Eth_Errors(t *testing.T) {
{"call failed: InsufficientFunds, Account balance: 4740799397601480913, cumulative cost: 22019342038993800000", true, "Nethermind"},
{"insufficient funds", true, "Klaytn"},
{"insufficient funds for gas * price + value + gatewayFee", true, "celo"},
{"insufficient balance for transfer", true, "zkSync"},
{"insufficient funds for gas + value. balance: 42719769622667482000, fee: 48098250000000, value: 42719769622667482000", true, "celo"},
}
for _, test := range tests {
err = evmclient.NewSendErrorS(test.message)
Expand All @@ -213,6 +218,7 @@ func Test_Eth_Errors(t *testing.T) {
{"invalid gas fee cap", true, "Klaytn"},
{"max fee per gas higher than max priority fee per gas", true, "Klaytn"},
{"tx fee (1.10 of currency celo) exceeds the configured cap (1.00 celo)", true, "celo"},
{"max priority fee per gas higher than max fee per gas", true, "zkSync"},
}
for _, test := range tests {
err = evmclient.NewSendErrorS(test.message)
Expand Down Expand Up @@ -329,6 +335,16 @@ func Test_Eth_Errors_Fatal(t *testing.T) {
{"`to` address of transaction in blacklist", true, "Harmony"},
{"`from` address of transaction in blacklist", true, "Harmony"},
{"staking message does not match directive message", true, "Harmony"},

{"intrinsic gas too low", true, "zkSync"},
{"failed to validate the transaction. reason: Validation revert: Account validation error: Not enough gas for transaction validation", true, "zkSync"},
{"failed to validate the transaction. reason: Validation revert: Failed to pay for the transaction: Failed to pay the fee to the operator", true, "zkSync"},
{"failed to validate the transaction. reason: Validation revert: Account validation error: Error function_selector = 0x, data = 0x", true, "zkSync"},
{"invalid sender. can't start a transaction from a non-account", true, "zkSync"},
{"Failed to serialize transaction: max fee per gas higher than 2^64-1", true, "zkSync"},
{"Failed to serialize transaction: max fee per pubdata byte higher than 2^64-1", true, "zkSync"},
{"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"},
}

for _, test := range tests {
Expand Down
14 changes: 14 additions & 0 deletions core/chains/evm/config/toml/defaults/zkSync_Goerli.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
ChainID = '280'
ChainType = 'zksync'
FinalityDepth = 1
LogPollInterval = '5s'
MinIncomingConfirmations = 1
NoNewHeadsThreshold = '1m'

[GasEstimator]
LimitDefault = 3_500_000
PriceMax = 18446744073709551615
PriceMin = 0

[HeadTracker]
HistoryDepth = 5
14 changes: 14 additions & 0 deletions core/chains/evm/config/toml/defaults/zkSync_Mainnet.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
ChainID = '324'
ChainType = 'zksync'
FinalityDepth = 1
LogPollInterval = '5s'
MinIncomingConfirmations = 1
NoNewHeadsThreshold = '1m'

[GasEstimator]
LimitDefault = 3_500_000
PriceMax = 18446744073709551615
PriceMin = 0

[HeadTracker]
HistoryDepth = 5
16 changes: 16 additions & 0 deletions core/chains/evm/gas/block_history_estimator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
evmclient "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client"
"github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas"
evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types"
"github.com/smartcontractkit/chainlink/v2/core/config"
"github.com/smartcontractkit/chainlink/v2/core/internal/cltest"
"github.com/smartcontractkit/chainlink/v2/core/internal/testutils"
"github.com/smartcontractkit/chainlink/v2/core/internal/testutils/evmtest"
Expand Down Expand Up @@ -1348,6 +1349,21 @@ func TestBlockHistoryEstimator_IsUsable(t *testing.T) {
assert.Equal(t, true, bhe.IsUsable(tx, block, cfg.ChainType(), geCfg.PriceMin(), logger.TestLogger(t)))
assert.Equal(t, true, bhe.IsUsable(tx2, block, cfg.ChainType(), geCfg.PriceMin(), logger.TestLogger(t)))
})

t.Run("returns false if transaction is of type 0x71 or 0xff only on zkSync", func(t *testing.T) {
cfg.ChainTypeF = string(config.ChainZkSync)
tx := evmtypes.Transaction{Type: 0x71, GasPrice: assets.NewWeiI(10), GasLimit: 42, Hash: utils.NewHash()}
assert.Equal(t, false, bhe.IsUsable(tx, block, cfg.ChainType(), geCfg.PriceMin(), logger.TestLogger(t)))

tx.Type = 0x02
assert.Equal(t, true, bhe.IsUsable(tx, block, cfg.ChainType(), geCfg.PriceMin(), logger.TestLogger(t)))

tx.Type = 0xff
assert.Equal(t, false, bhe.IsUsable(tx, block, cfg.ChainType(), geCfg.PriceMin(), logger.TestLogger(t)))

cfg.ChainTypeF = ""
assert.Equal(t, true, bhe.IsUsable(tx, block, cfg.ChainType(), geCfg.PriceMin(), logger.TestLogger(t)))
})
}

func TestBlockHistoryEstimator_EffectiveTipCap(t *testing.T) {
Expand Down
7 changes: 7 additions & 0 deletions core/chains/evm/gas/chain_specific.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,5 +49,12 @@ func chainSpecificIsUsable(tx evmtypes.Transaction, baseFee *assets.Wei, chainTy
return false
}
}
if chainType == config.ChainZkSync {
// zKSync specific type for contract deployment & priority transactions
// https://era.zksync.io/docs/reference/concepts/transactions.html#eip-712-0x71
if tx.Type == 0x71 || tx.Type == 0xff {
return false
}
}
return true
}
5 changes: 3 additions & 2 deletions core/config/chaintype.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,17 @@ const (
ChainCelo ChainType = "celo"
ChainWeMix ChainType = "wemix"
ChainKroma ChainType = "kroma"
ChainZkSync ChainType = "zksync"
)

var ErrInvalidChainType = fmt.Errorf("must be one of %s or omitted", strings.Join([]string{
string(ChainArbitrum), string(ChainMetis), string(ChainXDai), string(ChainOptimismBedrock), string(ChainCelo),
string(ChainKroma), string(ChainWeMix)}, ", "))
string(ChainKroma), string(ChainWeMix), string(ChainZkSync)}, ", "))

// IsValid returns true if the ChainType value is known or empty.
func (c ChainType) IsValid() bool {
switch c {
case "", ChainArbitrum, ChainMetis, ChainOptimismBedrock, ChainXDai, ChainCelo, ChainKroma, ChainWeMix:
case "", ChainArbitrum, ChainMetis, ChainOptimismBedrock, ChainXDai, ChainCelo, ChainKroma, ChainWeMix, ChainZkSync:
return true
}
return false
Expand Down
2 changes: 1 addition & 1 deletion core/config/docs/chains-evm.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ BlockBackfillDepth = 10 # Default
# BlockBackfillSkip enables skipping of very long backfills.
BlockBackfillSkip = false # Default
# ChainType is automatically detected from chain ID. Set this to force a certain chain type regardless of chain ID.
# Available types: arbitrum, metis, optimismBedrock, xdai, celo, kroma, wemix
# Available types: arbitrum, metis, optimismBedrock, xdai, celo, kroma, wemix, zksync
ChainType = 'arbitrum' # Example
# FinalityDepth is the number of blocks after which an ethereum transaction is considered "final". Note that the default is automatically set based on chain ID so it should not be necessary to change this under normal operation.
# BlocksConsideredFinal determines how deeply we look back to ensure that transactions are confirmed onto the longest chain
Expand Down
5 changes: 5 additions & 0 deletions core/scripts/common/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,11 @@ func explorerLinkPrefix(chainID int64) (prefix string) {
case 8453:
prefix = "https://basescan.org"

case 280: // zkSync Goerli testnet
prefix = "https://goerli.explorer.zksync.io"
case 324: // zkSync mainnet
prefix = "https://explorer.zksync.io"

default: // Unknown chain, return prefix as-is
prefix = ""
}
Expand Down
4 changes: 2 additions & 2 deletions core/services/chainlink/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1190,7 +1190,7 @@ func TestConfig_Validate(t *testing.T) {
- 1: 6 errors:
- ChainType: invalid value (Foo): must not be set with this chain id
- Nodes: missing: must have at least one node
- ChainType: invalid value (Foo): must be one of arbitrum, metis, xdai, optimismBedrock, celo, kroma, wemix or omitted
- ChainType: invalid value (Foo): must be one of arbitrum, metis, xdai, optimismBedrock, celo, kroma, wemix, zksync or omitted
- HeadTracker.HistoryDepth: invalid value (30): must be equal to or greater than FinalityDepth
- GasEstimator: 2 errors:
- FeeCapDefault: invalid value (101 wei): must be equal to PriceMax (99 wei) since you are using FixedPrice estimation with gas bumping disabled in EIP1559 mode - PriceMax will be used as the FeeCap for transactions instead of FeeCapDefault
Expand All @@ -1199,7 +1199,7 @@ func TestConfig_Validate(t *testing.T) {
- 2: 5 errors:
- ChainType: invalid value (Arbitrum): only "optimismBedrock" can be used with this chain id
- Nodes: missing: must have at least one node
- ChainType: invalid value (Arbitrum): must be one of arbitrum, metis, xdai, optimismBedrock, celo, kroma, wemix or omitted
- ChainType: invalid value (Arbitrum): must be one of arbitrum, metis, xdai, optimismBedrock, celo, kroma, wemix, zksync or omitted
- FinalityDepth: invalid value (0): must be greater than or equal to 1
- MinIncomingConfirmations: invalid value (0): must be greater than or equal to 1
- 3.Nodes: 5 errors:
Expand Down
2 changes: 1 addition & 1 deletion core/services/ocr/contract_tracker.go
Original file line number Diff line number Diff line change
Expand Up @@ -401,7 +401,7 @@ func (t *OCRContractTracker) LatestBlockHeight(ctx context.Context) (blockheight
// care about the block height; we have no way of getting the L1 block
// height anyway
return 0, nil
case "", config.ChainArbitrum, config.ChainCelo, config.ChainOptimismBedrock, config.ChainXDai, config.ChainKroma, config.ChainWeMix:
case "", config.ChainArbitrum, config.ChainCelo, config.ChainOptimismBedrock, config.ChainXDai, config.ChainKroma, config.ChainWeMix, config.ChainZkSync:
// continue
}
latestBlockHeight := t.getLatestBlockHeight()
Expand Down
160 changes: 159 additions & 1 deletion docs/CONFIG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2986,6 +2986,164 @@ GasLimit = 5300000

</p></details>

<details><summary>zkSync Goerli (280)</summary><p>

```toml
AutoCreateKey = true
BlockBackfillDepth = 10
BlockBackfillSkip = false
ChainType = 'zksync'
FinalityDepth = 1
FinalityTagEnabled = false
LogBackfillBatchSize = 1000
LogPollInterval = '5s'
LogKeepBlocksDepth = 100000
MinIncomingConfirmations = 1
MinContractPayment = '0.00001 link'
NonceAutoSync = true
NoNewHeadsThreshold = '1m0s'
RPCDefaultBatchSize = 250
RPCBlockQueryDelay = 1

[Transactions]
ForwardersEnabled = false
MaxInFlight = 16
MaxQueued = 250
ReaperInterval = '1h0m0s'
ReaperThreshold = '168h0m0s'
ResendAfterThreshold = '1m0s'

[BalanceMonitor]
Enabled = true

[GasEstimator]
Mode = 'BlockHistory'
PriceDefault = '20 gwei'
PriceMax = '18.446744073709551615 ether'
PriceMin = '0'
LimitDefault = 3500000
LimitMax = 500000
LimitMultiplier = '1'
LimitTransfer = 21000
BumpMin = '5 gwei'
BumpPercent = 20
BumpThreshold = 3
EIP1559DynamicFees = false
FeeCapDefault = '100 gwei'
TipCapDefault = '1 wei'
TipCapMin = '1 wei'

[GasEstimator.BlockHistory]
BatchSize = 25
BlockHistorySize = 8
CheckInclusionBlocks = 12
CheckInclusionPercentile = 90
TransactionPercentile = 60

[HeadTracker]
HistoryDepth = 5
MaxBufferSize = 3
SamplingInterval = '1s'

[NodePool]
PollFailureThreshold = 5
PollInterval = '10s'
SelectionMode = 'HighestHead'
SyncThreshold = 5
LeaseDuration = '0s'

[OCR]
ContractConfirmations = 4
ContractTransmitterTransmitTimeout = '10s'
DatabaseTimeout = '10s'
ObservationGracePeriod = '1s'

[OCR2]
[OCR2.Automation]
GasLimit = 5300000
```

</p></details>

<details><summary>zkSync Mainnet (324)</summary><p>

```toml
AutoCreateKey = true
BlockBackfillDepth = 10
BlockBackfillSkip = false
ChainType = 'zksync'
FinalityDepth = 1
FinalityTagEnabled = false
LogBackfillBatchSize = 1000
LogPollInterval = '5s'
LogKeepBlocksDepth = 100000
MinIncomingConfirmations = 1
MinContractPayment = '0.00001 link'
NonceAutoSync = true
NoNewHeadsThreshold = '1m0s'
RPCDefaultBatchSize = 250
RPCBlockQueryDelay = 1

[Transactions]
ForwardersEnabled = false
MaxInFlight = 16
MaxQueued = 250
ReaperInterval = '1h0m0s'
ReaperThreshold = '168h0m0s'
ResendAfterThreshold = '1m0s'

[BalanceMonitor]
Enabled = true

[GasEstimator]
Mode = 'BlockHistory'
PriceDefault = '20 gwei'
PriceMax = '18.446744073709551615 ether'
PriceMin = '0'
LimitDefault = 3500000
LimitMax = 500000
LimitMultiplier = '1'
LimitTransfer = 21000
BumpMin = '5 gwei'
BumpPercent = 20
BumpThreshold = 3
EIP1559DynamicFees = false
FeeCapDefault = '100 gwei'
TipCapDefault = '1 wei'
TipCapMin = '1 wei'

[GasEstimator.BlockHistory]
BatchSize = 25
BlockHistorySize = 8
CheckInclusionBlocks = 12
CheckInclusionPercentile = 90
TransactionPercentile = 60

[HeadTracker]
HistoryDepth = 5
MaxBufferSize = 3
SamplingInterval = '1s'

[NodePool]
PollFailureThreshold = 5
PollInterval = '10s'
SelectionMode = 'HighestHead'
SyncThreshold = 5
LeaseDuration = '0s'

[OCR]
ContractConfirmations = 4
ContractTransmitterTransmitTimeout = '10s'
DatabaseTimeout = '10s'
ObservationGracePeriod = '1s'

[OCR2]
[OCR2.Automation]
GasLimit = 5300000
```

</p></details>

<details><summary>Optimism Goerli (420)</summary><p>

```toml
Expand Down Expand Up @@ -5232,7 +5390,7 @@ BlockBackfillSkip enables skipping of very long backfills.
ChainType = 'arbitrum' # Example
```
ChainType is automatically detected from chain ID. Set this to force a certain chain type regardless of chain ID.
Available types: arbitrum, metis, optimismBedrock, xdai, celo, kroma, wemix
Available types: arbitrum, metis, optimismBedrock, xdai, celo, kroma, wemix, zksync

### FinalityDepth
```toml
Expand Down

0 comments on commit 4e45a2a

Please sign in to comment.