Skip to content
This repository was archived by the owner on Mar 14, 2025. It is now read-only.

Commit 0229ca9

Browse files
dhaidashenkoflodesi
authored andcommitted
BCFR-1099 sei custom log index (#15858)
* 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]>
1 parent c9380a4 commit 0229ca9

File tree

19 files changed

+538
-110
lines changed

19 files changed

+538
-110
lines changed

.changeset/clever-knives-tap.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"chainlink": patch
3+
---
4+
5+
#added Sei config and error mapping
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
ChainID = '1328'
2+
ChainType = 'sei'
3+
# finality_depth: instant
4+
FinalityDepth = 10
5+
# block_time: ~0.4s, adding 1 second buffer
6+
LogPollInterval = '2s'
7+
# finality_depth * block_time / 60 secs = ~0.8 min (finality time)
8+
NoNewFinalizedHeadsThreshold = '5m'
9+
# "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)"
10+
RPCBlockQueryDelay = 5
11+
12+
[GasEstimator]
13+
EIP1559DynamicFees = false
14+
Mode = 'BlockHistory'
15+
PriceMax = '3000 gwei' # recommended by ds&a
16+
17+
[GasEstimator.BlockHistory]
18+
BlockHistorySize = 200

common/headtracker/head_tracker.go

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -434,10 +434,10 @@ func (ht *headTracker[HTH, S, ID, BLOCK_HASH]) backfill(ctx context.Context, hea
434434
}
435435

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

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

457+
type FinalizedMissingError[BLOCK_HASH types.Hashable] struct {
458+
Finalized, Canonical BLOCK_HASH
459+
}
460+
461+
func (e FinalizedMissingError[BLOCK_HASH]) Error() string {
462+
return fmt.Sprintf("finalized block %s missing from canonical chain %s", e.Finalized, e.Canonical)
463+
}
464+
457465
func (ht *headTracker[HTH, S, ID, BLOCK_HASH]) fetchAndSaveHead(ctx context.Context, n int64, hash BLOCK_HASH) (HTH, error) {
458466
ht.log.Debugw("Fetching head", "blockHeight", n, "blockHash", hash)
459467
head, err := ht.client.HeadByHash(ctx, hash)

core/build/platform_arch_guard.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
//go:build !amd64 && !arm64
2+
package build
3+
"non-64-bits architectures are not supported"

core/chains/evm/client/errors.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -282,14 +282,24 @@ var gnosis = ClientErrors{
282282
TransactionAlreadyInMempool: regexp.MustCompile(`(: |^)(alreadyknown)`),
283283
}
284284

285+
var sei = ClientErrors{
286+
// https://github.com/sei-protocol/sei-tendermint/blob/e9a22c961e83579d8a68cd045c532980d82fb2a0/types/mempool.go#L12
287+
TransactionAlreadyInMempool: regexp.MustCompile("tx already exists in cache"),
288+
// https://github.com/sei-protocol/sei-cosmos/blob/a4eb451c957b1ca7ca9118406682f93fe83d1f61/types/errors/errors.go#L50
289+
// https://github.com/sei-protocol/sei-cosmos/blob/a4eb451c957b1ca7ca9118406682f93fe83d1f61/types/errors/errors.go#L56
290+
// https://github.com/sei-protocol/sei-cosmos/blob/a4eb451c957b1ca7ca9118406682f93fe83d1f61/client/broadcast.go#L27
291+
// https://github.com/sei-protocol/sei-cosmos/blob/a4eb451c957b1ca7ca9118406682f93fe83d1f61/types/errors/errors.go#L32
292+
Fatal: regexp.MustCompile(`(: |^)'*out of gas|insufficient fee|Tx too large. Max size is \d+, but got \d+|: insufficient funds`),
293+
}
294+
285295
const TerminallyStuckMsg = "transaction terminally stuck"
286296

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

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

294304
// ClientErrorRegexes returns a map of compiled regexes for each error type
295305
func ClientErrorRegexes(errsRegex config.ClientErrors) *ClientErrors {

core/chains/evm/client/errors_test.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ func Test_Eth_Errors(t *testing.T) {
142142
{"ErrorObject { code: ServerError(3), message: \\\"known transaction. transaction with hash 0xf016…ad63 is already in the system\\\", data: Some(RawValue(\\\"0x\\\")) }", true, "zkSync"},
143143
{"client error transaction already in mempool", true, "tomlConfig"},
144144
{"alreadyknown", true, "Gnosis"},
145+
{"tx already exists in cache", true, "Sei"},
145146
}
146147
for _, test := range tests {
147148
err = evmclient.NewSendErrorS(test.message)
@@ -418,6 +419,11 @@ func Test_Eth_Errors_Fatal(t *testing.T) {
418419
{"client error fatal", true, "tomlConfig"},
419420
{"[Request ID: d9711488-4c1e-4af2-bc1f-7969913d7b60] Error invoking RPC: transaction [email protected] failed precheck with status INVALID_SIGNATURE", true, "hedera"},
420421
{"invalid chain id for signer", true, "Treasure"},
422+
423+
{": out of gas", true, "Sei"},
424+
{"Tx too large. Max size is 2048576, but got 2097431", true, "Sei"},
425+
{": insufficient funds", true, "Sei"},
426+
{"insufficient fee", true, "Sei"},
421427
}
422428

423429
for _, test := range tests {

core/chains/evm/client/helpers_test.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"fmt"
55
"math/big"
66
"net/url"
7+
"sync"
78
"testing"
89
"time"
910

@@ -216,6 +217,7 @@ const HeadResult = `{"difficulty":"0xf3a00","extraData":"0xd88301050384676574688
216217
type mockSubscription struct {
217218
unsubscribed bool
218219
Errors chan error
220+
unsub sync.Once
219221
}
220222

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

227229
func (mes *mockSubscription) Unsubscribe() {
228-
mes.unsubscribed = true
229-
close(mes.Errors)
230+
mes.unsub.Do(func() {
231+
mes.unsubscribed = true
232+
close(mes.Errors)
233+
})
230234
}
231235

232236
func ParseTestNodeConfigs(nodes []NodeConfig) ([]*toml.Node, error) {

core/chains/evm/client/rpc_client.go

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"encoding/json"
66
"errors"
77
"fmt"
8+
"math"
89
"math/big"
910
"net/url"
1011
"strconv"
@@ -374,6 +375,10 @@ func (r *RPCClient) BatchCallContext(rootCtx context.Context, b []rpc.BatchElem)
374375
var requestedFinalizedBlock bool
375376
if r.chainType == chaintype.ChainAstar {
376377
for _, el := range b {
378+
if el.Method == "eth_getLogs" {
379+
r.rpcLog.Critical("evmclient.BatchCallContext: eth_getLogs is not supported")
380+
return errors.New("evmclient.BatchCallContext: eth_getLogs is not supported")
381+
}
377382
if !isRequestingFinalizedBlock(el) {
378383
continue
379384
}
@@ -482,10 +487,10 @@ func (r *RPCClient) SubscribeToHeads(ctx context.Context) (ch <-chan *evmtypes.H
482487
}()
483488

484489
channel := make(chan *evmtypes.Head)
485-
forwarder := newSubForwarder(channel, func(head *evmtypes.Head) *evmtypes.Head {
490+
forwarder := newSubForwarder(channel, func(head *evmtypes.Head) (*evmtypes.Head, error) {
486491
head.EVMChainID = ubig.New(r.chainID)
487492
r.onNewHead(ctx, chStopInFlight, head)
488-
return head
493+
return head, nil
489494
}, r.wrapRPCClientError)
490495

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

1172+
if err == nil {
1173+
err = r.makeLogsValid(l)
1174+
}
1175+
duration := time.Since(start)
11681176
r.logResult(lggr, err, duration, r.getRPCDomain(), "FilterLogs",
11691177
"log", l,
11701178
)
@@ -1192,7 +1200,7 @@ func (r *RPCClient) SubscribeFilterLogs(ctx context.Context, q ethereum.FilterQu
11921200
r.logResult(lggr, err, duration, r.getRPCDomain(), "SubscribeFilterLogs")
11931201
err = r.wrapWS(err)
11941202
}()
1195-
sub := newSubForwarder(ch, nil, r.wrapRPCClientError)
1203+
sub := newSubForwarder(ch, r.makeLogValid, r.wrapRPCClientError)
11961204
err = sub.start(ws.geth.SubscribeFilterLogs(ctx, q, sub.srcCh))
11971205
if err != nil {
11981206
return
@@ -1416,3 +1424,38 @@ func ToBlockNumArg(number *big.Int) string {
14161424
}
14171425
return hexutil.EncodeBig(number)
14181426
}
1427+
1428+
func (r *RPCClient) makeLogsValid(logs []types.Log) error {
1429+
if r.chainType != chaintype.ChainSei {
1430+
return nil
1431+
}
1432+
1433+
for i := range logs {
1434+
var err error
1435+
logs[i], err = r.makeLogValid(logs[i])
1436+
if err != nil {
1437+
return err
1438+
}
1439+
}
1440+
1441+
return nil
1442+
}
1443+
1444+
func (r *RPCClient) makeLogValid(log types.Log) (types.Log, error) {
1445+
if r.chainType != chaintype.ChainSei {
1446+
return log, nil
1447+
}
1448+
1449+
if log.TxIndex > math.MaxUint32 {
1450+
return types.Log{}, fmt.Errorf("TxIndex of tx %s exceeds max supported value of %d", log.TxHash, math.MaxUint32)
1451+
}
1452+
1453+
if log.Index > math.MaxUint32 {
1454+
return types.Log{}, fmt.Errorf("log's index %d of tx %s exceeds max supported value of %d", log.Index, log.TxHash, math.MaxUint32)
1455+
}
1456+
1457+
// it's safe as we have a build guard to guarantee 64-bit system
1458+
newIndex := uint64(log.TxIndex<<32) | uint64(log.Index)
1459+
log.Index = uint(newIndex)
1460+
return log, nil
1461+
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package client
2+
3+
import (
4+
"errors"
5+
"math"
6+
"testing"
7+
8+
ethtypes "github.com/ethereum/go-ethereum/core/types"
9+
"github.com/stretchr/testify/require"
10+
11+
commonclient "github.com/smartcontractkit/chainlink/v2/common/client"
12+
"github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/chaintype"
13+
"github.com/smartcontractkit/chainlink/v2/core/logger"
14+
)
15+
16+
func TestRPCClient_MakeLogsValid(t *testing.T) {
17+
testCases := []struct {
18+
Name string
19+
TxIndex uint
20+
LogIndex uint
21+
ExpectedLogIndex uint
22+
ExpectedError error
23+
}{
24+
{
25+
Name: "TxIndex = 0 LogIndex = 0",
26+
TxIndex: 0,
27+
LogIndex: 0,
28+
ExpectedLogIndex: 0,
29+
ExpectedError: nil,
30+
},
31+
{
32+
Name: "TxIndex = 0 LogIndex = 1",
33+
TxIndex: 0,
34+
LogIndex: 1,
35+
ExpectedLogIndex: 1,
36+
ExpectedError: nil,
37+
},
38+
{
39+
Name: "TxIndex = 0 LogIndex = MaxUint32",
40+
TxIndex: 0,
41+
LogIndex: math.MaxUint32,
42+
ExpectedLogIndex: math.MaxUint32,
43+
ExpectedError: nil,
44+
},
45+
{
46+
Name: "LogIndex = MaxUint32 + 1 => returns an error",
47+
TxIndex: 0,
48+
LogIndex: math.MaxUint32 + 1,
49+
ExpectedLogIndex: 0,
50+
ExpectedError: errors.New("log's index 4294967296 of tx 0x0000000000000000000000000000000000000000000000000000000000000000 exceeds max supported value of 4294967295"),
51+
},
52+
{
53+
Name: "TxIndex = 1 LogIndex = 0",
54+
TxIndex: 1,
55+
LogIndex: 0,
56+
ExpectedLogIndex: math.MaxUint32 + 1,
57+
ExpectedError: nil,
58+
},
59+
{
60+
Name: "TxIndex = MaxUint32 LogIndex = MaxUint32",
61+
TxIndex: math.MaxUint32,
62+
LogIndex: math.MaxUint32,
63+
ExpectedLogIndex: math.MaxUint64,
64+
ExpectedError: nil,
65+
},
66+
{
67+
Name: "TxIndex = MaxUint32 + 1 => returns an error",
68+
TxIndex: math.MaxUint32 + 1,
69+
LogIndex: 0,
70+
ExpectedLogIndex: 0,
71+
ExpectedError: errors.New("TxIndex of tx 0x0000000000000000000000000000000000000000000000000000000000000000 exceeds max supported value of 4294967295"),
72+
},
73+
}
74+
for _, tc := range testCases {
75+
t.Run(tc.Name, func(t *testing.T) {
76+
rpc := NewRPCClient(TestNodePoolConfig{}, logger.TestLogger(t), nil, nil, "eth-primary-rpc-0", 0, nil, commonclient.Primary, commonclient.QueryTimeout, commonclient.QueryTimeout, "")
77+
log, err := rpc.makeLogValid(ethtypes.Log{TxIndex: tc.TxIndex, Index: tc.LogIndex})
78+
// non sei should return as is
79+
require.NoError(t, err)
80+
require.Equal(t, tc.TxIndex, log.TxIndex)
81+
require.Equal(t, tc.LogIndex, log.Index)
82+
seiRPC := NewRPCClient(TestNodePoolConfig{}, logger.TestLogger(t), nil, nil, "eth-primary-rpc-0", 0, nil, commonclient.Primary, commonclient.QueryTimeout, commonclient.QueryTimeout, chaintype.ChainSei)
83+
log, err = seiRPC.makeLogValid(ethtypes.Log{TxIndex: tc.TxIndex, Index: tc.LogIndex})
84+
if tc.ExpectedError != nil {
85+
require.EqualError(t, err, tc.ExpectedError.Error())
86+
return
87+
}
88+
89+
require.Equal(t, tc.ExpectedLogIndex, log.Index)
90+
require.Equal(t, tc.TxIndex, log.TxIndex)
91+
})
92+
}
93+
}

0 commit comments

Comments
 (0)