Skip to content

Commit

Permalink
BCFR-1099 sei custom log index (#15858)
Browse files Browse the repository at this point in the history
* add sei chain and error mapping

* fix changeset and config_test

* remove sei chain type

* add pricemax

* custom calculation of log's index for Sei

* fix lint issues & tests

---------

Co-authored-by: flodesi <[email protected]>
  • Loading branch information
2 people authored and stackman27 committed Jan 8, 2025
1 parent c9380a4 commit 0229ca9
Show file tree
Hide file tree
Showing 19 changed files with 538 additions and 110 deletions.
5 changes: 5 additions & 0 deletions .changeset/clever-knives-tap.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"chainlink": patch
---

#added Sei config and error mapping
18 changes: 18 additions & 0 deletions ccip/config/evm/Sei_Testnet_Atlantic.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
ChainID = '1328'
ChainType = 'sei'
# finality_depth: instant
FinalityDepth = 10
# block_time: ~0.4s, adding 1 second buffer
LogPollInterval = '2s'
# finality_depth * block_time / 60 secs = ~0.8 min (finality time)
NoNewFinalizedHeadsThreshold = '5m'
# "RPC node returned multiple missing blocks on query for block numbers [31592085 31592084] even though the WS subscription already sent us these blocks. It might help to increase EVM.RPCBlockQueryDelay (currently 1)"
RPCBlockQueryDelay = 5

[GasEstimator]
EIP1559DynamicFees = false
Mode = 'BlockHistory'
PriceMax = '3000 gwei' # recommended by ds&a

[GasEstimator.BlockHistory]
BlockHistorySize = 200
16 changes: 12 additions & 4 deletions common/headtracker/head_tracker.go
Original file line number Diff line number Diff line change
Expand Up @@ -434,10 +434,10 @@ func (ht *headTracker[HTH, S, ID, BLOCK_HASH]) backfill(ctx context.Context, hea
}

if head.BlockHash() != latestFinalizedHead.BlockHash() {
const errMsg = "expected finalized block to be present in canonical chain"
ht.log.With("finalized_block_number", latestFinalizedHead.BlockNumber(), "finalized_hash", latestFinalizedHead.BlockHash(),
"canonical_chain_block_number", head.BlockNumber(), "canonical_chain_hash", head.BlockHash()).Criticalf(errMsg)
return fmt.Errorf(errMsg)
ht.log.Criticalw("Finalized block missing from conical chain",
"finalized_block_number", latestFinalizedHead.BlockNumber(), "finalized_hash", latestFinalizedHead.BlockHash(),
"canonical_chain_block_number", head.BlockNumber(), "canonical_chain_hash", head.BlockHash())
return FinalizedMissingError[BLOCK_HASH]{latestFinalizedHead.BlockHash(), head.BlockHash()}
}

l = l.With("latest_finalized_block_hash", latestFinalizedHead.BlockHash(),
Expand All @@ -454,6 +454,14 @@ func (ht *headTracker[HTH, S, ID, BLOCK_HASH]) backfill(ctx context.Context, hea
return
}

type FinalizedMissingError[BLOCK_HASH types.Hashable] struct {
Finalized, Canonical BLOCK_HASH
}

func (e FinalizedMissingError[BLOCK_HASH]) Error() string {
return fmt.Sprintf("finalized block %s missing from canonical chain %s", e.Finalized, e.Canonical)
}

func (ht *headTracker[HTH, S, ID, BLOCK_HASH]) fetchAndSaveHead(ctx context.Context, n int64, hash BLOCK_HASH) (HTH, error) {
ht.log.Debugw("Fetching head", "blockHeight", n, "blockHash", hash)
head, err := ht.client.HeadByHash(ctx, hash)
Expand Down
3 changes: 3 additions & 0 deletions core/build/platform_arch_guard.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
//go:build !amd64 && !arm64
package build
"non-64-bits architectures are not supported"
12 changes: 11 additions & 1 deletion core/chains/evm/client/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -282,14 +282,24 @@ var gnosis = ClientErrors{
TransactionAlreadyInMempool: regexp.MustCompile(`(: |^)(alreadyknown)`),
}

var sei = ClientErrors{
// https://github.com/sei-protocol/sei-tendermint/blob/e9a22c961e83579d8a68cd045c532980d82fb2a0/types/mempool.go#L12
TransactionAlreadyInMempool: regexp.MustCompile("tx already exists in cache"),
// https://github.com/sei-protocol/sei-cosmos/blob/a4eb451c957b1ca7ca9118406682f93fe83d1f61/types/errors/errors.go#L50
// https://github.com/sei-protocol/sei-cosmos/blob/a4eb451c957b1ca7ca9118406682f93fe83d1f61/types/errors/errors.go#L56
// https://github.com/sei-protocol/sei-cosmos/blob/a4eb451c957b1ca7ca9118406682f93fe83d1f61/client/broadcast.go#L27
// https://github.com/sei-protocol/sei-cosmos/blob/a4eb451c957b1ca7ca9118406682f93fe83d1f61/types/errors/errors.go#L32
Fatal: regexp.MustCompile(`(: |^)'*out of gas|insufficient fee|Tx too large. Max size is \d+, but got \d+|: insufficient funds`),
}

const TerminallyStuckMsg = "transaction terminally stuck"

// Tx.Error messages that are set internally so they are not chain or client specific
var internal = ClientErrors{
TerminallyStuck: regexp.MustCompile(TerminallyStuckMsg),
}

var clients = []ClientErrors{parity, geth, arbitrum, metis, substrate, avalanche, nethermind, harmony, besu, erigon, klaytn, celo, zkSync, zkEvm, treasure, mantle, aStar, hedera, gnosis, internal}
var clients = []ClientErrors{parity, geth, arbitrum, metis, substrate, avalanche, nethermind, harmony, besu, erigon, klaytn, celo, zkSync, zkEvm, treasure, mantle, aStar, hedera, gnosis, sei, internal}

// ClientErrorRegexes returns a map of compiled regexes for each error type
func ClientErrorRegexes(errsRegex config.ClientErrors) *ClientErrors {
Expand Down
6 changes: 6 additions & 0 deletions core/chains/evm/client/errors_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ func Test_Eth_Errors(t *testing.T) {
{"ErrorObject { code: ServerError(3), message: \\\"known transaction. transaction with hash 0xf016…ad63 is already in the system\\\", data: Some(RawValue(\\\"0x\\\")) }", true, "zkSync"},
{"client error transaction already in mempool", true, "tomlConfig"},
{"alreadyknown", true, "Gnosis"},
{"tx already exists in cache", true, "Sei"},
}
for _, test := range tests {
err = evmclient.NewSendErrorS(test.message)
Expand Down Expand Up @@ -418,6 +419,11 @@ func Test_Eth_Errors_Fatal(t *testing.T) {
{"client error fatal", true, "tomlConfig"},
{"[Request ID: d9711488-4c1e-4af2-bc1f-7969913d7b60] Error invoking RPC: transaction [email protected] failed precheck with status INVALID_SIGNATURE", true, "hedera"},
{"invalid chain id for signer", true, "Treasure"},

{": out of gas", true, "Sei"},
{"Tx too large. Max size is 2048576, but got 2097431", true, "Sei"},
{": insufficient funds", true, "Sei"},
{"insufficient fee", true, "Sei"},
}

for _, test := range tests {
Expand Down
8 changes: 6 additions & 2 deletions core/chains/evm/client/helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"
"math/big"
"net/url"
"sync"
"testing"
"time"

Expand Down Expand Up @@ -216,6 +217,7 @@ const HeadResult = `{"difficulty":"0xf3a00","extraData":"0xd88301050384676574688
type mockSubscription struct {
unsubscribed bool
Errors chan error
unsub sync.Once
}

func NewMockSubscription() *mockSubscription {
Expand All @@ -225,8 +227,10 @@ func NewMockSubscription() *mockSubscription {
func (mes *mockSubscription) Err() <-chan error { return mes.Errors }

func (mes *mockSubscription) Unsubscribe() {
mes.unsubscribed = true
close(mes.Errors)
mes.unsub.Do(func() {
mes.unsubscribed = true
close(mes.Errors)
})
}

func ParseTestNodeConfigs(nodes []NodeConfig) ([]*toml.Node, error) {
Expand Down
51 changes: 47 additions & 4 deletions core/chains/evm/client/rpc_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"encoding/json"
"errors"
"fmt"
"math"
"math/big"
"net/url"
"strconv"
Expand Down Expand Up @@ -374,6 +375,10 @@ func (r *RPCClient) BatchCallContext(rootCtx context.Context, b []rpc.BatchElem)
var requestedFinalizedBlock bool
if r.chainType == chaintype.ChainAstar {
for _, el := range b {
if el.Method == "eth_getLogs" {
r.rpcLog.Critical("evmclient.BatchCallContext: eth_getLogs is not supported")
return errors.New("evmclient.BatchCallContext: eth_getLogs is not supported")
}
if !isRequestingFinalizedBlock(el) {
continue
}
Expand Down Expand Up @@ -482,10 +487,10 @@ func (r *RPCClient) SubscribeToHeads(ctx context.Context) (ch <-chan *evmtypes.H
}()

channel := make(chan *evmtypes.Head)
forwarder := newSubForwarder(channel, func(head *evmtypes.Head) *evmtypes.Head {
forwarder := newSubForwarder(channel, func(head *evmtypes.Head) (*evmtypes.Head, error) {
head.EVMChainID = ubig.New(r.chainID)
r.onNewHead(ctx, chStopInFlight, head)
return head
return head, nil
}, r.wrapRPCClientError)

err = forwarder.start(ws.rpc.EthSubscribe(ctx, forwarder.srcCh, args...))
Expand Down Expand Up @@ -1163,8 +1168,11 @@ func (r *RPCClient) FilterLogs(ctx context.Context, q ethereum.FilterQuery) (l [
l, err = ws.geth.FilterLogs(ctx, q)
err = r.wrapWS(err)
}
duration := time.Since(start)

if err == nil {
err = r.makeLogsValid(l)
}
duration := time.Since(start)
r.logResult(lggr, err, duration, r.getRPCDomain(), "FilterLogs",
"log", l,
)
Expand Down Expand Up @@ -1192,7 +1200,7 @@ func (r *RPCClient) SubscribeFilterLogs(ctx context.Context, q ethereum.FilterQu
r.logResult(lggr, err, duration, r.getRPCDomain(), "SubscribeFilterLogs")
err = r.wrapWS(err)
}()
sub := newSubForwarder(ch, nil, r.wrapRPCClientError)
sub := newSubForwarder(ch, r.makeLogValid, r.wrapRPCClientError)
err = sub.start(ws.geth.SubscribeFilterLogs(ctx, q, sub.srcCh))
if err != nil {
return
Expand Down Expand Up @@ -1416,3 +1424,38 @@ func ToBlockNumArg(number *big.Int) string {
}
return hexutil.EncodeBig(number)
}

func (r *RPCClient) makeLogsValid(logs []types.Log) error {
if r.chainType != chaintype.ChainSei {
return nil
}

for i := range logs {
var err error
logs[i], err = r.makeLogValid(logs[i])
if err != nil {
return err
}
}

return nil
}

func (r *RPCClient) makeLogValid(log types.Log) (types.Log, error) {
if r.chainType != chaintype.ChainSei {
return log, nil
}

if log.TxIndex > math.MaxUint32 {
return types.Log{}, fmt.Errorf("TxIndex of tx %s exceeds max supported value of %d", log.TxHash, math.MaxUint32)
}

if log.Index > math.MaxUint32 {
return types.Log{}, fmt.Errorf("log's index %d of tx %s exceeds max supported value of %d", log.Index, log.TxHash, math.MaxUint32)
}

// it's safe as we have a build guard to guarantee 64-bit system
newIndex := uint64(log.TxIndex<<32) | uint64(log.Index)
log.Index = uint(newIndex)
return log, nil
}
93 changes: 93 additions & 0 deletions core/chains/evm/client/rpc_client_internal_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package client

import (
"errors"
"math"
"testing"

ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/stretchr/testify/require"

commonclient "github.com/smartcontractkit/chainlink/v2/common/client"
"github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/chaintype"
"github.com/smartcontractkit/chainlink/v2/core/logger"
)

func TestRPCClient_MakeLogsValid(t *testing.T) {
testCases := []struct {
Name string
TxIndex uint
LogIndex uint
ExpectedLogIndex uint
ExpectedError error
}{
{
Name: "TxIndex = 0 LogIndex = 0",
TxIndex: 0,
LogIndex: 0,
ExpectedLogIndex: 0,
ExpectedError: nil,
},
{
Name: "TxIndex = 0 LogIndex = 1",
TxIndex: 0,
LogIndex: 1,
ExpectedLogIndex: 1,
ExpectedError: nil,
},
{
Name: "TxIndex = 0 LogIndex = MaxUint32",
TxIndex: 0,
LogIndex: math.MaxUint32,
ExpectedLogIndex: math.MaxUint32,
ExpectedError: nil,
},
{
Name: "LogIndex = MaxUint32 + 1 => returns an error",
TxIndex: 0,
LogIndex: math.MaxUint32 + 1,
ExpectedLogIndex: 0,
ExpectedError: errors.New("log's index 4294967296 of tx 0x0000000000000000000000000000000000000000000000000000000000000000 exceeds max supported value of 4294967295"),
},
{
Name: "TxIndex = 1 LogIndex = 0",
TxIndex: 1,
LogIndex: 0,
ExpectedLogIndex: math.MaxUint32 + 1,
ExpectedError: nil,
},
{
Name: "TxIndex = MaxUint32 LogIndex = MaxUint32",
TxIndex: math.MaxUint32,
LogIndex: math.MaxUint32,
ExpectedLogIndex: math.MaxUint64,
ExpectedError: nil,
},
{
Name: "TxIndex = MaxUint32 + 1 => returns an error",
TxIndex: math.MaxUint32 + 1,
LogIndex: 0,
ExpectedLogIndex: 0,
ExpectedError: errors.New("TxIndex of tx 0x0000000000000000000000000000000000000000000000000000000000000000 exceeds max supported value of 4294967295"),
},
}
for _, tc := range testCases {
t.Run(tc.Name, func(t *testing.T) {
rpc := NewRPCClient(TestNodePoolConfig{}, logger.TestLogger(t), nil, nil, "eth-primary-rpc-0", 0, nil, commonclient.Primary, commonclient.QueryTimeout, commonclient.QueryTimeout, "")
log, err := rpc.makeLogValid(ethtypes.Log{TxIndex: tc.TxIndex, Index: tc.LogIndex})
// non sei should return as is
require.NoError(t, err)
require.Equal(t, tc.TxIndex, log.TxIndex)
require.Equal(t, tc.LogIndex, log.Index)
seiRPC := NewRPCClient(TestNodePoolConfig{}, logger.TestLogger(t), nil, nil, "eth-primary-rpc-0", 0, nil, commonclient.Primary, commonclient.QueryTimeout, commonclient.QueryTimeout, chaintype.ChainSei)
log, err = seiRPC.makeLogValid(ethtypes.Log{TxIndex: tc.TxIndex, Index: tc.LogIndex})
if tc.ExpectedError != nil {
require.EqualError(t, err, tc.ExpectedError.Error())
return
}

require.Equal(t, tc.ExpectedLogIndex, log.Index)
require.Equal(t, tc.TxIndex, log.TxIndex)
})
}
}
Loading

0 comments on commit 0229ca9

Please sign in to comment.