Skip to content

rpc sharding by tx sender for autobahn#3438

Open
pompon0 wants to merge 77 commits into
mainfrom
gprusak-rpc-sharding9
Open

rpc sharding by tx sender for autobahn#3438
pompon0 wants to merge 77 commits into
mainfrom
gprusak-rpc-sharding9

Conversation

@pompon0
Copy link
Copy Markdown
Contributor

@pompon0 pompon0 commented May 14, 2026

For giga testnet we need every account to send transaction only to a single lane, so that they are included in nonce order. In this pr we are adding proxying of transactions to the correct lane (validator). How it works:

  • only evmrpc endpoint is handled
  • it is using http evmrpc connections for proxying (ws connections are also supported, but with the current impl they would be inefficient - see the comments in code)
  • [evm account -> lane] mapping is deterministic and defined by autobahn committee
  • evmrpc urls of validators of each lane are specified in autobahn config
  • whenever user calls sendRawTransaction or getTransactionCount rpcs to a node, the lane of the evm account is computed. If the lane is produced by the local node, it is added to the mempool. Otherwise url of the lane owner is fetched and the same evmrpc is called on that url.
  • If autobahn is not enabled, we fallback to the previous semantics.

Note

Medium Risk
Introduces request redirection for eth_sendRawTransaction and pending eth_getTransactionCount, which changes transaction submission/nonce semantics and adds cross-validator HTTP RPC dependencies driven by config.

Overview
Adds sender-based RPC sharding for Autobahn by introducing LocalClient.EvmProxy(sender) and wiring it through the Tendermint local client/environment and GigaRouter (deterministic shard selection via Committee.EvmShard).

evmrpc now redirects eth_sendRawTransaction and pending eth_getTransactionCount to the shard owner’s EVM RPC HTTP endpoint when the sender maps to a different validator, with a fallback to existing local behavior when no proxy is configured or Autobahn isn’t active. Adds a new OpenTelemetry counter evmrpc_redirected_requests_total.

Extends Autobahn config generation/consumption to include per-validator evmrpc URLs (new evmrpc_url.txt in localnode init, new evmrpc field in config with URL marshal/unmarshal) and updates/expands tests to cover proxy behavior and the shard→URL mapping.

Reviewed by Cursor Bugbot for commit 33794ff. Bugbot is set up for automated code reviews on this repo. Configure here.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 14, 2026

The latest Buf updates on your PR. Results from workflow Buf / buf (pull_request).

BuildFormatLintBreakingUpdated (UTC)
✅ passed✅ passed✅ passed✅ passedMay 15, 2026, 11:21 AM

@codecov
Copy link
Copy Markdown

codecov Bot commented May 14, 2026

Codecov Report

❌ Patch coverage is 53.84615% with 42 lines in your changes missing coverage. Please review.
✅ Project coverage is 59.31%. Comparing base (5060dcd) to head (33794ff).

Files with missing lines Patch % Lines
evmrpc/send.go 48.64% 15 Missing and 4 partials ⚠️
...int/cmd/tendermint/commands/gen_autobahn_config.go 0.00% 7 Missing ⚠️
evmrpc/tx.go 60.00% 2 Missing and 2 partials ⚠️
sei-tendermint/config/autobahn.go 63.63% 2 Missing and 2 partials ⚠️
sei-tendermint/internal/rpc/core/mempool.go 0.00% 4 Missing ⚠️
evmrpc/tests/mock_client.go 0.00% 2 Missing ⚠️
sei-tendermint/rpc/client/local/local.go 0.00% 2 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #3438      +/-   ##
==========================================
- Coverage   59.31%   59.31%   -0.01%     
==========================================
  Files        2120     2120              
  Lines      175523   175583      +60     
==========================================
+ Hits       104106   104139      +33     
- Misses      62338    62358      +20     
- Partials     9079     9086       +7     
Flag Coverage Δ
sei-chain-pr 67.89% <53.84%> (?)
sei-db 70.41% <ø> (-0.22%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
evmrpc/metrics.go 96.00% <100.00%> (+0.65%) ⬆️
sei-cosmos/client/context.go 86.07% <ø> (ø)
...ei-tendermint/internal/autobahn/types/committee.go 96.42% <100.00%> (+0.35%) ⬆️
sei-tendermint/internal/p2p/giga_router.go 69.75% <100.00%> (+0.75%) ⬆️
sei-tendermint/node/setup.go 69.67% <100.00%> (+0.09%) ⬆️
evmrpc/tests/mock_client.go 56.66% <0.00%> (-0.77%) ⬇️
sei-tendermint/rpc/client/local/local.go 55.17% <0.00%> (-0.78%) ⬇️
evmrpc/tx.go 84.68% <60.00%> (-0.80%) ⬇️
sei-tendermint/config/autobahn.go 44.00% <63.63%> (+15.42%) ⬆️
sei-tendermint/internal/rpc/core/mempool.go 63.82% <0.00%> (-2.84%) ⬇️
... and 2 more

... and 1 file with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Comment thread evmrpc/send.go Outdated
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

There are 2 total unresolved issues (including 1 from previous review).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 9c3516e. Configure here.

Comment thread evmrpc/send.go Outdated
SEI_NODE_ID=$(seid tendermint show-node-id)
NODE_IP=$(hostname -i | awk '{print $1}')
P2P_PORT=26656 # Must match [p2p] laddr in config.toml
EVMRPC_PORT=8545 # Must match the EVM RPC HTTP port (evmrpc DefaultConfig HTTPPort).
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: would be nice if both ports come from a common config

Comment thread evmrpc/metrics.go
)),
redirectedRequestCount: must(rpcTelemetryMeter.Int64Counter(
"evmrpc_redirected_requests_total",
metric.WithDescription("Number of EVM RPC requests redirected to another validator"),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: can you clarify whether this is request you forwarded to someone else or it is request you received from redirection?

Comment thread evmrpc/send.go
// the underlying TCP connection lifecycle is strictly bound to Dial -> Close calls.
client, err := rpc.DialContext(ctx, url.String())
if err != nil {
return hash, fmt.Errorf("rpc.DialContext(%q): %w", url.String(), err)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would we see one error log per tx when one validator is down? That would be too spammy

Comment thread evmrpc/send.go
// but we still need to handle it.
sender, senderErr := getSender(tx, s.keeper.ChainID(s.ctxProvider(LatestCtxHeight)))
if senderErr == nil {
if url, ok := s.tmClient.EvmProxy(sender); ok {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's our plan for handling the case where one validator is down?

Comment thread evmrpc/send.go
from, err = signer.Sender(tx)
return from, nil
case tx.Protected():
from, err := ethtypes.NewEIP155Signer(chainID).Sender(tx)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would we always have NewEIP155Signer in tx.Protected()? Would ethtypes.LatestSignerForChainID(chainID)) be better? So we accept non-1559 transactions?

Comment thread evmrpc/send.go
signer := ethtypes.NewLondonSigner(s.keeper.ChainID(s.ctxProvider(LatestCtxHeight)))
from, err = signer.Sender(tx)
func getSender(tx *ethtypes.Transaction, chainID *big.Int) (common.Address, error) {
switch {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we just use LatestSignerForChainID(chainID).Sender(tx) so we don't need to do the switch ourselves?

h := sha256.Sum256(addr[:])
x := new(big.Int).SetBytes(h[:])
i := int(x.Mod(x, big.NewInt(int64(c.replicas.Len()))).Int64())
return c.replicas.At(i)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't have that many validators so maybe we can use a cheaper algorithm?

i := int(binary.BigEndian.Uint64(addr[:8]) % uint64(c.replicas.Len()))

I suppose we will have a different algorithm when Autobahn hits mainnet, so we don't need to worry about attacker reverse-engineering this algorithm for now?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants