From 1286e909d3890fdd88e533db8bb3ae69a318d40c Mon Sep 17 00:00:00 2001 From: Grzegorz Prusak Date: Fri, 10 Jul 2026 15:57:32 +0200 Subject: [PATCH 01/54] InitLastHeader --- app/app.go | 22 ++----------------- sei-cosmos/baseapp/abci.go | 8 +++++++ sei-cosmos/baseapp/baseapp.go | 14 ++++++++++++ sei-tendermint/abci/types/application.go | 5 +++++ .../abci/types/mocks/application.go | 6 +++++ .../internal/p2p/giga_router_common.go | 11 ++++++++++ sei-tendermint/internal/proxy/proxy.go | 5 +++++ 7 files changed, 51 insertions(+), 20 deletions(-) diff --git a/app/app.go b/app/app.go index a54473a593..2136676214 100644 --- a/app/app.go +++ b/app/app.go @@ -475,11 +475,6 @@ type App struct { forkInitializer func(sdk.Context) - httpServerStartSignal chan struct{} - wsServerStartSignal chan struct{} - httpServerStartSignalSent bool - wsServerStartSignalSent bool - // evmHTTPServer/evmWSServer hold the EVM JSON-RPC HTTP and WebSocket listeners // constructed in RegisterLocalServices so an embedding orchestrator (the // in-process harness) can Stop() them at teardown. Nil when the respective @@ -551,8 +546,6 @@ func New( encodingConfig: encodingConfig, legacyEncodingConfig: MakeLegacyEncodingConfig(), stateStore: stateStore, - httpServerStartSignal: make(chan struct{}, 1), - wsServerStartSignal: make(chan struct{}, 1), } for _, option := range appOptions { @@ -1929,17 +1922,6 @@ func (app *App) ProcessBlock(ctx sdk.Context, txs [][]byte, req *BlockProcessReq } }() - defer func() { - if !app.httpServerStartSignalSent { - app.httpServerStartSignalSent = true - app.httpServerStartSignal <- struct{}{} - } - if !app.wsServerStartSignalSent { - app.wsServerStartSignalSent = true - app.wsServerStartSignal <- struct{}{} - } - }() - ctx = ctx.WithIsOCCEnabled(app.OccEnabled()) blockSpanCtx, blockSpan := app.GetBaseApp().TracingInfo.Start("Block") @@ -2749,7 +2731,7 @@ func (app *App) RegisterLocalServices(node client.LocalClient, txConfig client.T } app.evmHTTPServer = evmHTTPServer go func() { - <-app.httpServerStartSignal + <-app.Initialized() if err := evmHTTPServer.Start(); err != nil { panic(err) } @@ -2764,7 +2746,7 @@ func (app *App) RegisterLocalServices(node client.LocalClient, txConfig client.T } app.evmWSServer = evmWSServer go func() { - <-app.wsServerStartSignal + <-app.Initialized() if err := evmWSServer.Start(); err != nil { panic(err) } diff --git a/sei-cosmos/baseapp/abci.go b/sei-cosmos/baseapp/abci.go index e6478fffbf..73a734e7ec 100644 --- a/sei-cosmos/baseapp/abci.go +++ b/sei-cosmos/baseapp/abci.go @@ -30,6 +30,11 @@ import ( grpcstatus "google.golang.org/grpc/status" ) +func (app *BaseApp) InitLastHeader(lastHeader *tmproto.Header) { + app.setCheckState(*lastHeader) + app.signalInitialized() +} + // InitChain implements the ABCI interface. It runs the initialization logic // directly on the CommitMultiStore. func (app *BaseApp) InitChain(ctx context.Context, req *abci.RequestInitChain) (*abci.ResponseInitChain, error) { @@ -65,12 +70,14 @@ func (app *BaseApp) InitChain(ctx context.Context, req *abci.RequestInitChain) ( app.SetDeliverStateToCommit() + defer app.signalInitialized() if app.initChainer == nil { return nil, nil } resp := app.initChainer(app.deliverState.ctx, *req) app.initChainer(app.processProposalState.ctx, *req) + app.initChainer(app.checkState.ctx, *req) // In the case of a new chain, AppHash will be the hash of an empty string. // During an upgrade, it'll be the hash of the last committed block. @@ -315,6 +322,7 @@ func (app *BaseApp) Commit(ctx context.Context) (res *abci.ResponseCommit, err e // empty/reset the deliver state app.resetStatesExceptCheckState() + defer app.signalInitialized() var halt bool diff --git a/sei-cosmos/baseapp/baseapp.go b/sei-cosmos/baseapp/baseapp.go index 69401993b9..655c488203 100644 --- a/sei-cosmos/baseapp/baseapp.go +++ b/sei-cosmos/baseapp/baseapp.go @@ -121,6 +121,8 @@ type BaseApp struct { processProposalState *state processProposalCleanCtx sdk.Context // snapshot before optimistic processing stateToCommit *state + initializedCh chan struct{} + initializedOnce *sync.Once // nextResultHash is the result hash (merkle root over the block's deterministic tx results) // computed in FinalizeBlock and handed to the commit store in Commit, mirroring nextBlockHash. @@ -289,6 +291,8 @@ func NewBaseApp( TracingInfo: tracing.NewTracingInfo(tr, tracingEnabled), commitLock: &sync.Mutex{}, checkTxStateLock: &sync.RWMutex{}, + initializedCh: make(chan struct{}), + initializedOnce: &sync.Once{}, deliverTxHooks: []DeliverTxHook{}, } @@ -340,6 +344,16 @@ func (app *BaseApp) OccEnabled() bool { return app.occEnabled } +func (app *BaseApp) Initialized() <-chan struct{} { + return app.initializedCh +} + +func (app *BaseApp) signalInitialized() { + app.initializedOnce.Do(func() { + close(app.initializedCh) + }) +} + // Version returns the application's version string. func (app *BaseApp) Version() string { return app.version diff --git a/sei-tendermint/abci/types/application.go b/sei-tendermint/abci/types/application.go index 5db2209dd2..bff338506f 100644 --- a/sei-tendermint/abci/types/application.go +++ b/sei-tendermint/abci/types/application.go @@ -5,6 +5,8 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/holiman/uint256" + + tmproto "github.com/sei-protocol/sei-chain/sei-tendermint/proto/tendermint/types" ) // Application is an interface that enables any finite, deterministic state machine @@ -29,6 +31,7 @@ type Application interface { // Consensus Connection InitChain(context.Context, *RequestInitChain) (*ResponseInitChain, error) // Initialize blockchain w validators/other info from TendermintCore + InitLastHeader(lastHeader *tmproto.Header) ProcessProposal(context.Context, *RequestProcessProposal) (*ResponseProcessProposal, error) // Commit the state and return the application Merkle root hash Commit(context.Context) (*ResponseCommit, error) @@ -67,6 +70,8 @@ func (BaseApplication) Query(_ context.Context, req *RequestQuery) (*ResponseQue return &ResponseQuery{Code: CodeTypeOK}, nil } +func (BaseApplication) InitLastHeader(lastHeader *tmproto.Header) {} + func (BaseApplication) InitChain(_ context.Context, req *RequestInitChain) (*ResponseInitChain, error) { return &ResponseInitChain{}, nil } diff --git a/sei-tendermint/abci/types/mocks/application.go b/sei-tendermint/abci/types/mocks/application.go index c474b32770..922edd3f95 100644 --- a/sei-tendermint/abci/types/mocks/application.go +++ b/sei-tendermint/abci/types/mocks/application.go @@ -10,6 +10,7 @@ import ( mock "github.com/stretchr/testify/mock" types "github.com/sei-protocol/sei-chain/sei-tendermint/abci/types" + tmproto "github.com/sei-protocol/sei-chain/sei-tendermint/proto/tendermint/types" uint256 "github.com/holiman/uint256" ) @@ -277,6 +278,11 @@ func (_m *Application) InitChain(_a0 context.Context, _a1 *types.RequestInitChai return r0, r1 } +// InitLastHeader provides a mock function with given fields: lastHeader +func (_m *Application) InitLastHeader(lastHeader *tmproto.Header) { + _m.Called(lastHeader) +} + // LastBlockHeight provides a mock function with no fields func (_m *Application) LastBlockHeight() int64 { ret := _m.Called() diff --git a/sei-tendermint/internal/p2p/giga_router_common.go b/sei-tendermint/internal/p2p/giga_router_common.go index 4ee2c58d11..274e1aa19d 100644 --- a/sei-tendermint/internal/p2p/giga_router_common.go +++ b/sei-tendermint/internal/p2p/giga_router_common.go @@ -373,6 +373,17 @@ func (r *gigaRouterCommon) runExecute(ctx context.Context) error { return fmt.Errorf("invalid GenDoc.InitialHeight = %v", r.cfg.GenDoc.InitialHeight) } } else { + b, err := r.data.GlobalBlock(ctx, last) + if err != nil { + return fmt.Errorf("r.data.GlobalBlock(): %w", err) + } + app.InitLastHeader((&types.Header{ + ChainID: r.cfg.GenDoc.ChainID, + Height: int64(b.GlobalNumber), // nolint:gosec // different representations of the same value + Time: b.Timestamp, + // TODO: for consistency we should also set proposerAddress here, + // but this is a placeholder solution so maybe we don't care. + }).ToProto()) // Re-commit the last finalized block's app hash to the equivocation guard before re-proposing it // for AppQC voting (PushAppHash below), mirroring executeBlock's commit-before-PushAppHash // ordering. On a normal restart this idempotently matches the hash recorded when `last` was diff --git a/sei-tendermint/internal/proxy/proxy.go b/sei-tendermint/internal/proxy/proxy.go index 60d57ea691..cd1f998abd 100644 --- a/sei-tendermint/internal/proxy/proxy.go +++ b/sei-tendermint/internal/proxy/proxy.go @@ -11,6 +11,7 @@ import ( "github.com/prometheus/client_golang/prometheus" "github.com/sei-protocol/sei-chain/sei-tendermint/abci/types" + tmproto "github.com/sei-protocol/sei-chain/sei-tendermint/proto/tendermint/types" ) // Proxy wraps an ABCI application and records ABCI method timings. @@ -80,6 +81,10 @@ func (app *Proxy) CheckTxSafe(ctx context.Context, req *types.RequestCheckTxV2) return res, nil } +func (app *Proxy) InitLastHeader(lastHeader *tmproto.Header) { + app.app.InitLastHeader(lastHeader) +} + func (app *Proxy) Info(ctx context.Context, req *types.RequestInfo) (*types.ResponseInfo, error) { defer addTimeSample(Global.MethodTimingAt("info", "sync"))() return app.app.Info(ctx, req) From 83ebfc9a108b2bf61365bf6ae24bbafd0adc3b0c Mon Sep 17 00:00:00 2001 From: Grzegorz Prusak Date: Fri, 10 Jul 2026 19:25:10 +0200 Subject: [PATCH 02/54] changed defaults --- sei-tendermint/cmd/tendermint/commands/gen_autobahn_config.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sei-tendermint/cmd/tendermint/commands/gen_autobahn_config.go b/sei-tendermint/cmd/tendermint/commands/gen_autobahn_config.go index 13736e876d..111371ec0c 100644 --- a/sei-tendermint/cmd/tendermint/commands/gen_autobahn_config.go +++ b/sei-tendermint/cmd/tendermint/commands/gen_autobahn_config.go @@ -83,8 +83,8 @@ Output is written to the file specified by --output.`, cfg := config.AutobahnFileConfig{ Validators: validators, - MaxTxsPerBlock: 5_000, - AllowEmptyBlocks: true, + MaxTxsPerBlock: 2_000, + AllowEmptyBlocks: false, BlockInterval: utils.Duration(400 * time.Millisecond), ViewTimeout: utils.Duration(1500 * time.Millisecond), DialInterval: utils.Duration(10 * time.Second), From 3ae26f409fd1c418341d5aa6e150b4a04118064e Mon Sep 17 00:00:00 2001 From: Grzegorz Prusak Date: Fri, 10 Jul 2026 19:49:49 +0200 Subject: [PATCH 03/54] init fix --- app/app.go | 26 ++++++++++++------------ sei-cosmos/baseapp/abci.go | 5 ++++- sei-cosmos/baseapp/baseapp.go | 3 +-- sei-tendermint/abci/types/application.go | 2 +- 4 files changed, 19 insertions(+), 17 deletions(-) diff --git a/app/app.go b/app/app.go index 2136676214..fc181b7015 100644 --- a/app/app.go +++ b/app/app.go @@ -533,19 +533,19 @@ func New( memKeys := sdk.NewMemoryStoreKeys(capabilitytypes.MemStoreKey, banktypes.DeferredCacheStoreKey, oracletypes.MemStoreKey) app := &App{ - BaseApp: bApp, - cdc: cdc, - appCodec: appCodec, - interfaceRegistry: interfaceRegistry, - keys: keys, - tkeys: tkeys, - memKeys: memKeys, - txDecoder: encodingConfig.TxConfig.TxDecoder(), - versionInfo: version.NewInfo(), - metricCounter: &map[string]float32{}, - encodingConfig: encodingConfig, - legacyEncodingConfig: MakeLegacyEncodingConfig(), - stateStore: stateStore, + BaseApp: bApp, + cdc: cdc, + appCodec: appCodec, + interfaceRegistry: interfaceRegistry, + keys: keys, + tkeys: tkeys, + memKeys: memKeys, + txDecoder: encodingConfig.TxConfig.TxDecoder(), + versionInfo: version.NewInfo(), + metricCounter: &map[string]float32{}, + encodingConfig: encodingConfig, + legacyEncodingConfig: MakeLegacyEncodingConfig(), + stateStore: stateStore, } for _, option := range appOptions { diff --git a/sei-cosmos/baseapp/abci.go b/sei-cosmos/baseapp/abci.go index 73a734e7ec..e75c515e16 100644 --- a/sei-cosmos/baseapp/abci.go +++ b/sei-cosmos/baseapp/abci.go @@ -77,7 +77,10 @@ func (app *BaseApp) InitChain(ctx context.Context, req *abci.RequestInitChain) ( resp := app.initChainer(app.deliverState.ctx, *req) app.initChainer(app.processProposalState.ctx, *req) - app.initChainer(app.checkState.ctx, *req) + // Genesis initialization needs deliver semantics even when populating the + // check state branch. Running it in CheckTx mode enables mempool-only fee + // checks and can reject valid genesis txs. + app.initChainer(app.checkState.ctx.WithIsCheckTx(false), *req) // In the case of a new chain, AppHash will be the hash of an empty string. // During an upgrade, it'll be the hash of the last committed block. diff --git a/sei-cosmos/baseapp/baseapp.go b/sei-cosmos/baseapp/baseapp.go index 655c488203..43d8e41cf1 100644 --- a/sei-cosmos/baseapp/baseapp.go +++ b/sei-cosmos/baseapp/baseapp.go @@ -122,7 +122,7 @@ type BaseApp struct { processProposalCleanCtx sdk.Context // snapshot before optimistic processing stateToCommit *state initializedCh chan struct{} - initializedOnce *sync.Once + initializedOnce sync.Once // nextResultHash is the result hash (merkle root over the block's deterministic tx results) // computed in FinalizeBlock and handed to the commit store in Commit, mirroring nextBlockHash. @@ -292,7 +292,6 @@ func NewBaseApp( commitLock: &sync.Mutex{}, checkTxStateLock: &sync.RWMutex{}, initializedCh: make(chan struct{}), - initializedOnce: &sync.Once{}, deliverTxHooks: []DeliverTxHook{}, } diff --git a/sei-tendermint/abci/types/application.go b/sei-tendermint/abci/types/application.go index bff338506f..af0e021cbf 100644 --- a/sei-tendermint/abci/types/application.go +++ b/sei-tendermint/abci/types/application.go @@ -5,7 +5,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/holiman/uint256" - + tmproto "github.com/sei-protocol/sei-chain/sei-tendermint/proto/tendermint/types" ) From bea7c5c32c1f5b4f3056d7703bf84875b17f1210 Mon Sep 17 00:00:00 2001 From: Grzegorz Prusak Date: Fri, 10 Jul 2026 20:23:36 +0200 Subject: [PATCH 04/54] changed readiness signal --- docker/localnode/scripts/step5_start_sei.sh | 11 +++++++++++ integration_test/startup/startup_test.yaml | 8 ++++---- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/docker/localnode/scripts/step5_start_sei.sh b/docker/localnode/scripts/step5_start_sei.sh index bb02f08354..ef27d270a6 100755 --- a/docker/localnode/scripts/step5_start_sei.sh +++ b/docker/localnode/scripts/step5_start_sei.sh @@ -9,5 +9,16 @@ mkdir -p $LOG_DIR echo "Starting the seid process for node $NODE_ID with invariant check interval=$INVARIANT_CHECK_INTERVAL..." seid start --chain-id sei --inv-check-period ${INVARIANT_CHECK_INTERVAL} > "$LOG_DIR/seid-$NODE_ID.log" 2>&1 & +SEID_PID=$! echo "Node $NODE_ID seid is started now" + +until seid status >/dev/null 2>&1 && seid q tendermint-validator-set >/dev/null 2>&1 +do + if ! kill -0 "$SEID_PID" 2>/dev/null; then + echo "seid exited before becoming ready; see $LOG_DIR/seid-$NODE_ID.log" + exit 1 + fi + sleep 1 +done + echo "Done" >> build/generated/launch.complete diff --git a/integration_test/startup/startup_test.yaml b/integration_test/startup/startup_test.yaml index bb9ff5fc61..c6fad56cba 100644 --- a/integration_test/startup/startup_test.yaml +++ b/integration_test/startup/startup_test.yaml @@ -7,11 +7,11 @@ - type: eval expr: RESULT == 4 -- name: Test block height should be greater than 0 +- name: Test node status should report the configured network inputs: - # Get the current block height - - cmd: seid status |jq -M -r .SyncInfo.latest_block_height + # Confirm status RPC is ready without relying on block production. + - cmd: seid status |jq -M -r .NodeInfo.network env: RESULT verifiers: - type: eval - expr: RESULT > 0 + expr: RESULT == "sei" From 0a3b50f022323541930359f6c6e424d0372a2e2c Mon Sep 17 00:00:00 2001 From: Grzegorz Prusak Date: Fri, 10 Jul 2026 20:47:43 +0200 Subject: [PATCH 05/54] fixes --- integration_test/contracts/create_tokenfactory_denoms.sh | 4 ++-- integration_test/contracts/deploy_dex_contract.sh | 2 +- .../contracts/deploy_timelocked_token_contract.sh | 2 +- integration_test/contracts/deploy_wasm_contracts.sh | 4 ++-- integration_test/seidb/flatkv_evm_test.yaml | 2 +- sei-cosmos/server/rollback_test.go | 2 ++ 6 files changed, 9 insertions(+), 7 deletions(-) diff --git a/integration_test/contracts/create_tokenfactory_denoms.sh b/integration_test/contracts/create_tokenfactory_denoms.sh index b90b20d7d2..58a8ecb09f 100755 --- a/integration_test/contracts/create_tokenfactory_denoms.sh +++ b/integration_test/contracts/create_tokenfactory_denoms.sh @@ -1,8 +1,8 @@ #!/bin/bash seidbin=$(which ~/go/bin/seid | tr -d '"') -keyname=$(printf "12345678\n" | $seidbin keys list --output json | jq ".[0].name" | tr -d '"') -keyaddress=$(printf "12345678\n" | $seidbin keys list --output json | jq ".[0].address" | tr -d '"') +keyname=admin +keyaddress=$(printf "12345678\n" | $seidbin keys show "$keyname" -a 2>/dev/null) chainid=$($seidbin status | jq ".NodeInfo.network" | tr -d '"') seihome=$(git rev-parse --show-toplevel | tr -d '"') diff --git a/integration_test/contracts/deploy_dex_contract.sh b/integration_test/contracts/deploy_dex_contract.sh index 2efc42ce3c..9d0236bc16 100755 --- a/integration_test/contracts/deploy_dex_contract.sh +++ b/integration_test/contracts/deploy_dex_contract.sh @@ -1,7 +1,7 @@ #!/bin/bash seidbin=$(which ~/go/bin/seid | tr -d '"') -keyname=$(printf "12345678\n" | $seidbin keys list --output json | jq ".[0].name" | tr -d '"') +keyname=admin chainid=$($seidbin status | jq ".NodeInfo.network" | tr -d '"') seihome=$(git rev-parse --show-toplevel | tr -d '"') contract_name=$1 diff --git a/integration_test/contracts/deploy_timelocked_token_contract.sh b/integration_test/contracts/deploy_timelocked_token_contract.sh index ff1b38e4f4..efc4e7c570 100755 --- a/integration_test/contracts/deploy_timelocked_token_contract.sh +++ b/integration_test/contracts/deploy_timelocked_token_contract.sh @@ -1,7 +1,7 @@ #!/bin/bash seidbin=$(which ~/go/bin/seid | tr -d '"') -keyname=$(printf "12345678\n" | $seidbin keys list --output json | jq ".[0].name" | tr -d '"') +keyname=admin chainid=$($seidbin status | jq ".NodeInfo.network" | tr -d '"') seihome=$(git rev-parse --show-toplevel | tr -d '"') migration=$1 diff --git a/integration_test/contracts/deploy_wasm_contracts.sh b/integration_test/contracts/deploy_wasm_contracts.sh index 0bf7271797..aff1885d33 100755 --- a/integration_test/contracts/deploy_wasm_contracts.sh +++ b/integration_test/contracts/deploy_wasm_contracts.sh @@ -1,8 +1,8 @@ #!/bin/bash seidbin=$(which ~/go/bin/seid | tr -d '"') -keyname=$(printf "12345678\n" | $seidbin keys list --output json | jq ".[0].name" | tr -d '"') -keyaddress=$(printf "12345678\n" | $seidbin keys list --output json | jq ".[0].address" | tr -d '"') +keyname=admin +keyaddress=$(printf "12345678\n" | $seidbin keys show "$keyname" -a 2>/dev/null) chainid=$($seidbin status | jq ".NodeInfo.network" | tr -d '"') seihome=$(git rev-parse --show-toplevel | tr -d '"') diff --git a/integration_test/seidb/flatkv_evm_test.yaml b/integration_test/seidb/flatkv_evm_test.yaml index 4cf716837f..cf5e970a2c 100644 --- a/integration_test/seidb/flatkv_evm_test.yaml +++ b/integration_test/seidb/flatkv_evm_test.yaml @@ -86,7 +86,7 @@ - name: Test non-EVM module remains queryable inputs: - - cmd: printf "12345678\n" | seid keys list --output json | jq -r ".[0].address" + - cmd: printf "12345678\n" | seid keys show admin -a env: SEI_ADDR - cmd: seid q bank balances $SEI_ADDR --output json | jq -r ".balances | length" env: BANK_BALANCE_COUNT diff --git a/sei-cosmos/server/rollback_test.go b/sei-cosmos/server/rollback_test.go index cecd249253..ff4c4394bb 100644 --- a/sei-cosmos/server/rollback_test.go +++ b/sei-cosmos/server/rollback_test.go @@ -68,6 +68,8 @@ func (m *mockApplication) InitChain(ctx context.Context, req *abci.RequestInitCh return &abci.ResponseInitChain{}, nil } +func (m *mockApplication) InitLastHeader(*tmproto.Header) {} + func (m *mockApplication) Query(ctx context.Context, req *abci.RequestQuery) (*abci.ResponseQuery, error) { return &abci.ResponseQuery{}, nil } From 4b6e2180cc829e240d84250777a350df6209c332 Mon Sep 17 00:00:00 2001 From: Grzegorz Prusak Date: Fri, 10 Jul 2026 21:36:11 +0200 Subject: [PATCH 06/54] conditional statesync --- .../rpcnode/scripts/step1_configure_init.sh | 26 +++++++++++++------ .../abci/types/mocks/application.go | 5 ++-- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/docker/rpcnode/scripts/step1_configure_init.sh b/docker/rpcnode/scripts/step1_configure_init.sh index 61613709a8..461187f916 100755 --- a/docker/rpcnode/scripts/step1_configure_init.sh +++ b/docker/rpcnode/scripts/step1_configure_init.sh @@ -148,16 +148,26 @@ if [ "$AUTOBAHN" = "true" ]; then echo "Autobahn config written to $AUTOBAHN_CONFIG (fullnode via mode=full)" fi -# Override state sync configs +# Override state sync configs. Autobahn startup can legitimately sit at +# height 0 until a tx drives the first finalized block, so state sync +# cannot be configured yet in that case because trust-height 0 is invalid. +# Fall back to normal peer sync from genesis until the cluster has a real +# block history to trust. STATE_SYNC_RPC="192.168.10.10:26657" STATE_SYNC_PEER="2f9846450b7a3dcf4af1ac0082e3279c16744df8@172.31.9.18:26656,ec98c4a28a2023f4f976828c8a8e7127bfef4e1b@172.31.4.96:26656,b03014d67384fb0ef6ad992c77cefe4f9d2c1640@172.31.4.219:26656" curl "$STATE_SYNC_RPC"/net_info |jq -r '.peers[] | .url' |sed -e 's#mconn://##' >> build/generated/PEERS STATE_SYNC_PEER=$(paste -s -d ',' build/generated/PEERS) LATEST_HEIGHT=$(curl -s $STATE_SYNC_RPC/block | jq -r .block.header.height) -SYNC_BLOCK_HEIGHT=$LATEST_HEIGHT -SYNC_BLOCK_HASH=$(curl -s "$STATE_SYNC_RPC/block?height=$SYNC_BLOCK_HEIGHT" | jq -r .block_id.hash) -sed -i.bak -e "s|^enable *=.*|enable = true|" ~/.sei/config/config.toml -sed -i.bak -e "s|^rpc-servers *=.*|rpc-servers = \"$STATE_SYNC_RPC,$STATE_SYNC_RPC\"|" ~/.sei/config/config.toml -sed -i.bak -e "s|^trust-height *=.*|trust-height = $SYNC_BLOCK_HEIGHT|" ~/.sei/config/config.toml -sed -i.bak -e "s|^trust-hash *=.*|trust-hash = \"$SYNC_BLOCK_HASH\"|" ~/.sei/config/config.toml -sed -i.bak -e "s|^persistent-peers *=.*|persistent-peers = \"$STATE_SYNC_PEER\"|" ~/.sei/config/config.toml +if [ -n "$LATEST_HEIGHT" ] && [ "$LATEST_HEIGHT" -gt 0 ] 2>/dev/null; then + SYNC_BLOCK_HEIGHT=$LATEST_HEIGHT + SYNC_BLOCK_HASH=$(curl -s "$STATE_SYNC_RPC/block?height=$SYNC_BLOCK_HEIGHT" | jq -r .block_id.hash) + sed -i.bak -e "s|^enable *=.*|enable = true|" ~/.sei/config/config.toml + sed -i.bak -e "s|^rpc-servers *=.*|rpc-servers = \"$STATE_SYNC_RPC,$STATE_SYNC_RPC\"|" ~/.sei/config/config.toml + sed -i.bak -e "s|^trust-height *=.*|trust-height = $SYNC_BLOCK_HEIGHT|" ~/.sei/config/config.toml + sed -i.bak -e "s|^trust-hash *=.*|trust-hash = \"$SYNC_BLOCK_HASH\"|" ~/.sei/config/config.toml + sed -i.bak -e "s|^persistent-peers *=.*|persistent-peers = \"$STATE_SYNC_PEER\"|" ~/.sei/config/config.toml +else + echo "State sync unavailable at height ${LATEST_HEIGHT:-unknown}; falling back to peer sync." + sed -i.bak -e "s|^enable *=.*|enable = false|" ~/.sei/config/config.toml + sed -i.bak -e "s|^persistent-peers *=.*|persistent-peers = \"$STATE_SYNC_PEER\"|" ~/.sei/config/config.toml +fi diff --git a/sei-tendermint/abci/types/mocks/application.go b/sei-tendermint/abci/types/mocks/application.go index 922edd3f95..1152d7655b 100644 --- a/sei-tendermint/abci/types/mocks/application.go +++ b/sei-tendermint/abci/types/mocks/application.go @@ -9,8 +9,9 @@ import ( mock "github.com/stretchr/testify/mock" + tenderminttypes "github.com/sei-protocol/sei-chain/sei-tendermint/proto/tendermint/types" + types "github.com/sei-protocol/sei-chain/sei-tendermint/abci/types" - tmproto "github.com/sei-protocol/sei-chain/sei-tendermint/proto/tendermint/types" uint256 "github.com/holiman/uint256" ) @@ -279,7 +280,7 @@ func (_m *Application) InitChain(_a0 context.Context, _a1 *types.RequestInitChai } // InitLastHeader provides a mock function with given fields: lastHeader -func (_m *Application) InitLastHeader(lastHeader *tmproto.Header) { +func (_m *Application) InitLastHeader(lastHeader *tenderminttypes.Header) { _m.Called(lastHeader) } From 938076d6425c246a451f7700bc0ef5746fe994a1 Mon Sep 17 00:00:00 2001 From: Grzegorz Prusak Date: Fri, 10 Jul 2026 23:33:41 +0200 Subject: [PATCH 07/54] updated tests --- .../scripts/step4_config_override.sh | 1 - .../rpcnode/scripts/step1_configure_init.sh | 1 - integration_test/autobahn/autobahn_test.go | 205 ++++++++++++------ 3 files changed, 139 insertions(+), 68 deletions(-) diff --git a/docker/localnode/scripts/step4_config_override.sh b/docker/localnode/scripts/step4_config_override.sh index c3b35cc3d5..b8a2ad7b96 100755 --- a/docker/localnode/scripts/step4_config_override.sh +++ b/docker/localnode/scripts/step4_config_override.sh @@ -165,7 +165,6 @@ if [ "$AUTOBAHN" = "true" ]; then # Generate autobahn config using seid seid tendermint gen-autobahn-config $NODE_DIRS --output "$AUTOBAHN_CONFIG" - # Inject autobahn config file path into config.toml # Must be placed before any [section] header so TOML parser reads it as a top-level key. if grep -q "autobahn-config-file" ~/.sei/config/config.toml; then diff --git a/docker/rpcnode/scripts/step1_configure_init.sh b/docker/rpcnode/scripts/step1_configure_init.sh index 461187f916..8982628900 100755 --- a/docker/rpcnode/scripts/step1_configure_init.sh +++ b/docker/rpcnode/scripts/step1_configure_init.sh @@ -137,7 +137,6 @@ if [ "$AUTOBAHN" = "true" ]; then done seid tendermint gen-autobahn-config $NODE_DIRS --output "$AUTOBAHN_CONFIG" - # Inject autobahn-config-file as a top-level key in config.toml. It must # precede any [section] header so the TOML parser sees it at root scope. if grep -q "autobahn-config-file" ~/.sei/config/config.toml; then diff --git a/integration_test/autobahn/autobahn_test.go b/integration_test/autobahn/autobahn_test.go index 906b3e4d22..f32718d74b 100644 --- a/integration_test/autobahn/autobahn_test.go +++ b/integration_test/autobahn/autobahn_test.go @@ -69,9 +69,6 @@ const ( // headroom without giving up another whole minute on every run. heightPoll = 1 * time.Second haltStableWindow = 20 * time.Second - // Initial fullnode sync can pause at a height briefly while Autobahn - // streams settle; require eventual progress rather than one fixed sample. - blockProductionAdvanceMax = 30 * time.Second // 2m / 90s give headroom for the fullnode catch-up backlog the // preceding subtest may have left (failover delay during // LivenessUnderMaxFaults can put the fullnode ~600 blocks behind, @@ -79,6 +76,7 @@ const ( // CI runners are slower than local; 1m was tight enough to flake. haltStableTimeout = 2 * time.Minute heightAdvanceMax = 90 * time.Second + txFinalizeMax = 30 * time.Second ) var ( @@ -107,10 +105,8 @@ func listRunningNodes(t *testing.T) []string { return strings.Fields(strings.TrimSpace(string(out))) } -// getHeight reads last_block_height from /abci_info and retries until the -// chain has produced at least one block (height > 0). ABCI returns 0 between -// InitChain and the first FinalizeBlock; we treat that as "not ready" since -// all callers assume a live, advancing chain. +// getHeight reads last_block_height from /abci_info and retries until a +// non-zero committed height is observed. // // Uses abci_info instead of /status because /status reads from the CometBFT // block store, which autobahn does not populate. @@ -128,6 +124,15 @@ func getHeight(t *testing.T) int64 { return 0 } +func currentHeight(t *testing.T) int64 { + t.Helper() + h, err := fetchHeight() + if err != nil { + t.Fatalf("fetch height: %v", err) + } + return h +} + // waitForStableHeight polls getHeight every heightPoll. It returns the // height once the value has stayed constant for at least `window`. Useful // after killing validators: cluster halt is observable through the rpc- @@ -233,6 +238,116 @@ func dockerExecAllowFail(container, script string) { _ = exec.Command("docker", "exec", container, "sh", "-c", script).Run() } +func createRecipient(t *testing.T, container, name string) string { + t.Helper() + createOut := dockerExec(t, container, + fmt.Sprintf("printf '12345678\\n12345678\\n' | seid keys add %s --output json 2>/dev/null", name)) + var key struct { + Address string `json:"address"` + } + if err := json.Unmarshal([]byte(strings.TrimSpace(createOut)), &key); err != nil { + t.Fatalf("parse recipient address: %v\noutput: %s", err, createOut) + } + return key.Address +} + +func waitForRecipientBalance(t *testing.T, container, address, want string, timeout time.Duration) { + t.Helper() + queryCmd := fmt.Sprintf("seid q bank balances %s --denom usei --output json 2>/dev/null", address) + deadline := time.Now().Add(timeout) + var balance string + for time.Now().Before(deadline) { + out, _ := exec.Command("docker", "exec", container, "sh", "-c", queryCmd).Output() + var b struct { + Amount string `json:"amount"` + } + if err := json.Unmarshal([]byte(strings.TrimSpace(string(out))), &b); err == nil { + balance = b.Amount + if balance == want { + return + } + } + time.Sleep(heightPoll) + } + t.Fatalf("expected balance %s for %s, got %s", want, address, balance) +} + +func sendBankTxAndWait(t *testing.T, container string) int64 { + t.Helper() + recipientName := fmt.Sprintf("autobahn_recipient_%d", time.Now().UnixNano()) + recipientAddr := createRecipient(t, container, recipientName) + baseHeight := currentHeight(t) + + var sendCmd string + if baseHeight == 0 { + sendCmd = fmt.Sprintf( + "recipient=%s; "+ + "printf '12345678\\n' | seid tx bank send node_admin \"$recipient\" 1000000usei "+ + "--chain-id sei --fees 2000usei --generate-only --output json > /tmp/autobahn_unsigned.json && "+ + "printf '12345678\\n' | seid tx sign /tmp/autobahn_unsigned.json --from node_admin "+ + "--chain-id sei --offline --account-number 0 --sequence 0 --output json > /tmp/autobahn_signed.json && "+ + "seid tx broadcast /tmp/autobahn_signed.json -b sync --output json", + recipientAddr, + ) + } else { + sendCmd = fmt.Sprintf( + "printf '12345678\\n' | seid tx bank send node_admin %s 1000000usei "+ + "--chain-id sei --fees 2000usei -b sync -y --output json", + recipientAddr, + ) + } + + sendOut := dockerExec(t, container, sendCmd) + var resp struct { + Code int `json:"code"` + RawLog string `json:"raw_log"` + } + if err := json.Unmarshal([]byte(strings.TrimSpace(sendOut)), &resp); err != nil { + t.Fatalf("parse send response: %v\noutput: %s", err, sendOut) + } + if resp.Code != 0 { + t.Fatalf("send tx rejected: code=%d raw_log=%s", resp.Code, resp.RawLog) + } + + height := waitForHeightAdvance(t, baseHeight, heightAdvanceMax) + waitForRecipientBalance(t, container, recipientAddr, "1000000", txFinalizeMax) + return height +} + +func sendBankTxExpectNoInclusion(t *testing.T, container string, baseHeight int64, timeout time.Duration) { + t.Helper() + recipientName := fmt.Sprintf("autobahn_halt_recipient_%d", time.Now().UnixNano()) + recipientAddr := createRecipient(t, container, recipientName) + sendCmd := fmt.Sprintf( + "printf '12345678\\n' | seid tx bank send node_admin %s 1000000usei "+ + "--chain-id sei --fees 2000usei -b sync -y --output json", + recipientAddr, + ) + sendOut := dockerExec(t, container, sendCmd) + var resp struct { + Code int `json:"code"` + RawLog string `json:"raw_log"` + } + if err := json.Unmarshal([]byte(strings.TrimSpace(sendOut)), &resp); err != nil { + t.Fatalf("parse send response: %v\noutput: %s", err, sendOut) + } + if resp.Code != 0 { + t.Fatalf("send tx rejected during halt check: code=%d raw_log=%s", resp.Code, resp.RawLog) + } + + deadline := time.Now().Add(timeout) + last := baseHeight + for time.Now().Before(deadline) { + h := currentHeight(t) + if h > baseHeight { + t.Fatalf("expected no inclusion after quorum loss, but height advanced from %d to %d", baseHeight, h) + } + last = h + time.Sleep(heightPoll) + } + t.Logf("height stayed at %d after submitted tx", last) +} + // TestMain brings up the autobahn docker cluster before the test runs and // tears it down afterward. The working directory is changed to the repo root // so the `make docker-cluster-*` targets resolve their relative paths. @@ -537,9 +652,12 @@ func testRecovery(t *testing.T) { target := clusterSize - 1 - maxFaults restartNode(t, target) - // Poll for the chain to advance. Give the restarted seid time to init - // and rejoin consensus, plus any fullnode failover. - hAfter := waitForHeightAdvance(t, hBefore, heightAdvanceMax) + // A committed tx is the liveness signal here: once quorum is restored, + // a new tx should finalize and advance height. + hAfter := sendBankTxAndWait(t, "sei-node-0") + if hAfter <= hBefore { + t.Fatalf("expected committed tx after recovery to advance height past %d, got %d", hBefore, hAfter) + } t.Logf("height after restart: %d", hAfter) // assertAutobahnEnabled greps every running container's log. The restarted @@ -551,17 +669,15 @@ func testRecovery(t *testing.T) { func testBlockProduction(t *testing.T) { assertAutobahnEnabled(t) - h1 := getHeight(t) - t.Logf("height: %d", h1) - h2 := waitForHeightAdvance(t, h1, blockProductionAdvanceMax) - t.Logf("height after progress: %d", h2) + h := sendBankTxAndWait(t, "sei-node-0") + t.Logf("height after committed tx: %d", h) - // Verify the Autobahn-routed tmRPC handlers serve real data at h2 (a + // Verify the Autobahn-routed tmRPC handlers serve real data at h (a // recently committed height — past tail of the chain, so historical // query paths are exercised without racing the producer). Each // endpoint asserts one observable property; a single mismatch fails // the test with the specific shape that broke. - assertTmRPCEndpoints(t, h2) + assertTmRPCEndpoints(t, h) } // assertTmRPCEndpoints exercises the tmRPC surface that PR #3310 wires up @@ -666,47 +782,8 @@ func fetchTmRPC[T any](t *testing.T, url string, into *T) { func testBankTransfer(t *testing.T) { assertAutobahnEnabled(t) - // Create recipient. stderr is redirected inside the container so stdout is pure JSON. - createOut := dockerExec(t, "sei-node-0", - "printf '12345678\n12345678\n' | seid keys add test_recipient --output json 2>/dev/null") - var key struct { - Address string `json:"address"` - } - if err := json.Unmarshal([]byte(strings.TrimSpace(createOut)), &key); err != nil { - t.Fatalf("parse recipient address: %v\noutput: %s", err, createOut) - } - t.Logf("recipient: %s", key.Address) - - // Send from node_admin (genesis account) to recipient. - // Use -b sync (not -b block) because CometBFT consensus is disabled in autobahn mode. - // TODO: support -b block once autobahn supports it. - sendCmd := fmt.Sprintf( - "printf '12345678\n' | seid tx bank send node_admin %s 1000000usei "+ - "--chain-id sei --fees 2000usei -b sync -y --output json", - key.Address) - dockerExec(t, "sei-node-0", sendCmd) - - // Poll for balance. Tolerate transient query failures before the tx finalizes. - t.Log("waiting for tx to finalize...") - queryCmd := fmt.Sprintf("seid q bank balances %s --denom usei --output json 2>/dev/null", key.Address) - var balance string - for attempt := 0; attempt < 15; attempt++ { - out, _ := exec.Command("docker", "exec", "sei-node-0", "sh", "-c", queryCmd).Output() - var b struct { - Amount string `json:"amount"` - } - if err := json.Unmarshal([]byte(strings.TrimSpace(string(out))), &b); err == nil { - balance = b.Amount - if balance == "1000000" { - break - } - } - time.Sleep(2 * time.Second) - } - t.Logf("balance: %s usei", balance) - if balance != "1000000" { - t.Fatalf("expected balance 1000000, got %s", balance) - } + h := sendBankTxAndWait(t, "sei-node-0") + t.Logf("bank transfer committed at height %d", h) } // killNode kills seid inside sei-node- via pkill. Tolerates non-zero exit @@ -732,7 +809,10 @@ func testLivenessUnderMaxFaults(t *testing.T) { for i := 0; i < maxFaults; i++ { killNode(t, clusterSize-1-i) } - hAfter := waitForHeightAdvance(t, hBefore, heightAdvanceMax) + hAfter := sendBankTxAndWait(t, "sei-node-0") + if hAfter <= hBefore { + t.Fatalf("expected committed tx with %d faults to advance height past %d, got %d", maxFaults, hBefore, hAfter) + } t.Logf("height after: %d", hAfter) } @@ -751,12 +831,5 @@ func testHaltsBeyondMaxFaults(t *testing.T) { killNode(t, clusterSize-1-maxFaults) hBefore := waitForStableHeight(t, haltStableWindow, haltStableTimeout) t.Logf("height: %d (expecting halt)", hBefore) - // waitForStableHeight already returned only after haltStableWindow of - // no movement; the sample we just took is the halted height. - hAfter := getHeight(t) - t.Logf("height after stability: %d", hAfter) - if hAfter != hBefore { - t.Fatalf("chain should halt with %d/%d validators (height changed: %d -> %d)", - clusterSize-maxFaults-1, clusterSize, hBefore, hAfter) - } + sendBankTxExpectNoInclusion(t, "sei-node-0", hBefore, haltStableWindow) } From cb430dc499d8d8a676a96212e6d0a64bffbd5228 Mon Sep 17 00:00:00 2001 From: Grzegorz Prusak Date: Sat, 11 Jul 2026 12:17:38 +0200 Subject: [PATCH 08/54] switched to evm handling --- integration_test/autobahn/autobahn_test.go | 178 +++++++++++---------- sei-cosmos/baseapp/abci.go | 1 - 2 files changed, 94 insertions(+), 85 deletions(-) diff --git a/integration_test/autobahn/autobahn_test.go b/integration_test/autobahn/autobahn_test.go index f32718d74b..c12b9fe9dc 100644 --- a/integration_test/autobahn/autobahn_test.go +++ b/integration_test/autobahn/autobahn_test.go @@ -77,6 +77,7 @@ const ( haltStableTimeout = 2 * time.Minute heightAdvanceMax = 90 * time.Second txFinalizeMax = 30 * time.Second + testRecipientEVM = "0x1000000000000000000000000000000000000001" ) var ( @@ -238,106 +239,111 @@ func dockerExecAllowFail(container, script string) { _ = exec.Command("docker", "exec", container, "sh", "-c", script).Run() } -func createRecipient(t *testing.T, container, name string) string { +func parseTxHash(t *testing.T, output string) string { t.Helper() - createOut := dockerExec(t, container, - fmt.Sprintf("printf '12345678\\n12345678\\n' | seid keys add %s --output json 2>/dev/null", name)) - var key struct { - Address string `json:"address"` - } - if err := json.Unmarshal([]byte(strings.TrimSpace(createOut)), &key); err != nil { - t.Fatalf("parse recipient address: %v\noutput: %s", err, createOut) + for _, line := range strings.Split(output, "\n") { + if hash, ok := strings.CutPrefix(strings.TrimSpace(line), "Transaction hash: "); ok { + return strings.TrimSpace(hash) + } } - return key.Address + t.Fatalf("transaction hash not found in output:\n%s", output) + return "" } -func waitForRecipientBalance(t *testing.T, container, address, want string, timeout time.Duration) { +func waitForReceiptBlockNumber(t *testing.T, txHash string, timeout time.Duration) int64 { t.Helper() - queryCmd := fmt.Sprintf("seid q bank balances %s --denom usei --output json 2>/dev/null", address) deadline := time.Now().Add(timeout) - var balance string for time.Now().Before(deadline) { - out, _ := exec.Command("docker", "exec", container, "sh", "-c", queryCmd).Output() - var b struct { - Amount string `json:"amount"` + resp, err := evmRPCInContainer(fullnodeContainer, "eth_getTransactionReceipt", []any{txHash}) + if err != nil { + time.Sleep(heightPoll) + continue } - if err := json.Unmarshal([]byte(strings.TrimSpace(string(out))), &b); err == nil { - balance = b.Amount - if balance == want { - return - } + if resp.Error != nil { + t.Fatalf("eth_getTransactionReceipt(%s): code=%d message=%s", txHash, resp.Error.Code, resp.Error.Message) + } + if string(resp.Result) == "null" || len(resp.Result) == 0 { + time.Sleep(heightPoll) + continue } - time.Sleep(heightPoll) - } - t.Fatalf("expected balance %s for %s, got %s", want, address, balance) -} -func sendBankTxAndWait(t *testing.T, container string) int64 { - t.Helper() - recipientName := fmt.Sprintf("autobahn_recipient_%d", time.Now().UnixNano()) - recipientAddr := createRecipient(t, container, recipientName) - baseHeight := currentHeight(t) + var receipt struct { + BlockNumber string `json:"blockNumber"` + Status string `json:"status"` + } + if err := json.Unmarshal(resp.Result, &receipt); err != nil { + t.Fatalf("decode receipt for %s: %v\nbody: %s", txHash, err, resp.Result) + } + if receipt.Status != "" && receipt.Status != "0x1" { + t.Fatalf("tx %s reverted with status %s", txHash, receipt.Status) + } + if receipt.BlockNumber == "" { + time.Sleep(heightPoll) + continue + } - var sendCmd string - if baseHeight == 0 { - sendCmd = fmt.Sprintf( - "recipient=%s; "+ - "printf '12345678\\n' | seid tx bank send node_admin \"$recipient\" 1000000usei "+ - "--chain-id sei --fees 2000usei --generate-only --output json > /tmp/autobahn_unsigned.json && "+ - "printf '12345678\\n' | seid tx sign /tmp/autobahn_unsigned.json --from node_admin "+ - "--chain-id sei --offline --account-number 0 --sequence 0 --output json > /tmp/autobahn_signed.json && "+ - "seid tx broadcast /tmp/autobahn_signed.json -b sync --output json", - recipientAddr, - ) - } else { - sendCmd = fmt.Sprintf( - "printf '12345678\\n' | seid tx bank send node_admin %s 1000000usei "+ - "--chain-id sei --fees 2000usei -b sync -y --output json", - recipientAddr, - ) + var height int64 + if _, err := fmt.Sscanf(receipt.BlockNumber, "0x%x", &height); err != nil { + t.Fatalf("parse receipt block number %q for %s: %v", receipt.BlockNumber, txHash, err) + } + return height } + t.Fatalf("receipt for %s not observed within %s", txHash, timeout) + return 0 +} - sendOut := dockerExec(t, container, sendCmd) - var resp struct { - Code int `json:"code"` - RawLog string `json:"raw_log"` +func evmBalanceHex(t *testing.T, address string) string { + t.Helper() + resp, err := evmRPCInContainer(fullnodeContainer, "eth_getBalance", []any{address, "latest"}) + if err != nil { + t.Fatalf("eth_getBalance(%s): %v", address, err) } - if err := json.Unmarshal([]byte(strings.TrimSpace(sendOut)), &resp); err != nil { - t.Fatalf("parse send response: %v\noutput: %s", err, sendOut) + if resp.Error != nil { + t.Fatalf("eth_getBalance(%s): code=%d message=%s", address, resp.Error.Code, resp.Error.Message) } - if resp.Code != 0 { - t.Fatalf("send tx rejected: code=%d raw_log=%s", resp.Code, resp.RawLog) + var balance string + if err := json.Unmarshal(resp.Result, &balance); err != nil { + t.Fatalf("decode balance for %s: %v\nbody: %s", address, err, resp.Result) } - - height := waitForHeightAdvance(t, baseHeight, heightAdvanceMax) - waitForRecipientBalance(t, container, recipientAddr, "1000000", txFinalizeMax) - return height + return balance } -func sendBankTxExpectNoInclusion(t *testing.T, container string, baseHeight int64, timeout time.Duration) { +func sendEvmTx(t *testing.T, container string) string { t.Helper() - recipientName := fmt.Sprintf("autobahn_halt_recipient_%d", time.Now().UnixNano()) - recipientAddr := createRecipient(t, container, recipientName) - sendCmd := fmt.Sprintf( - "printf '12345678\\n' | seid tx bank send node_admin %s 1000000usei "+ - "--chain-id sei --fees 2000usei -b sync -y --output json", - recipientAddr, + sendOut := dockerExec(t, container, + fmt.Sprintf( + "printf '12345678\\n' | seid tx evm send %s 1 --from node_admin --chain-id sei --evm-rpc %s -b sync -y 2>/dev/null", + testRecipientEVM, evmRPCURLOnContainerLocalhost, + ), ) - sendOut := dockerExec(t, container, sendCmd) - var resp struct { - Code int `json:"code"` - RawLog string `json:"raw_log"` - } - if err := json.Unmarshal([]byte(strings.TrimSpace(sendOut)), &resp); err != nil { - t.Fatalf("parse send response: %v\noutput: %s", err, sendOut) + return parseTxHash(t, sendOut) +} + +func sendEvmTxAndWait(t *testing.T, container string) int64 { + t.Helper() + baseHeight := currentHeight(t) + txHash := sendEvmTx(t, container) + receiptHeight := waitForReceiptBlockNumber(t, txHash, txFinalizeMax) + if receiptHeight <= baseHeight { + t.Fatalf("expected tx %s to land after height %d, got receipt at %d", txHash, baseHeight, receiptHeight) } - if resp.Code != 0 { - t.Fatalf("send tx rejected during halt check: code=%d raw_log=%s", resp.Code, resp.RawLog) + waitedHeight := waitForHeightAdvance(t, baseHeight, heightAdvanceMax) + if waitedHeight != receiptHeight { + t.Logf("height watcher observed %d while receipt reported %d", waitedHeight, receiptHeight) } + return receiptHeight +} +func sendEvmTxExpectNoInclusion(t *testing.T, container string, baseHeight int64, timeout time.Duration) { + t.Helper() + txHash := sendEvmTx(t, container) deadline := time.Now().Add(timeout) last := baseHeight for time.Now().Before(deadline) { + resp, err := evmRPCInContainer(fullnodeContainer, "eth_getTransactionReceipt", []any{txHash}) + if err == nil && resp != nil && resp.Error == nil && string(resp.Result) != "null" && len(resp.Result) > 0 { + t.Fatalf("expected no inclusion after quorum loss, but tx %s received receipt %s", txHash, resp.Result) + } h := currentHeight(t) if h > baseHeight { t.Fatalf("expected no inclusion after quorum loss, but height advanced from %d to %d", baseHeight, h) @@ -595,7 +601,7 @@ func TestAutobahn(t *testing.T) { t.Logf("cluster size = %d, max tolerated faults = %d (assuming equal weights)", clusterSize, maxFaults) t.Run("BlockProduction", testBlockProduction) - t.Run("BankTransfer", testBankTransfer) + t.Run("EVMTransfer", testEVMTransfer) t.Run("LivenessUnderMaxFaults", testLivenessUnderMaxFaults) t.Run("HaltsBeyondMaxFaults", testHaltsBeyondMaxFaults) t.Run("Recovery", testRecovery) @@ -654,7 +660,7 @@ func testRecovery(t *testing.T) { // A committed tx is the liveness signal here: once quorum is restored, // a new tx should finalize and advance height. - hAfter := sendBankTxAndWait(t, "sei-node-0") + hAfter := sendEvmTxAndWait(t, "sei-node-0") if hAfter <= hBefore { t.Fatalf("expected committed tx after recovery to advance height past %d, got %d", hBefore, hAfter) } @@ -669,8 +675,8 @@ func testRecovery(t *testing.T) { func testBlockProduction(t *testing.T) { assertAutobahnEnabled(t) - h := sendBankTxAndWait(t, "sei-node-0") - t.Logf("height after committed tx: %d", h) + h := sendEvmTxAndWait(t, "sei-node-0") + t.Logf("height after committed evm tx: %d", h) // Verify the Autobahn-routed tmRPC handlers serve real data at h (a // recently committed height — past tail of the chain, so historical @@ -779,11 +785,15 @@ func fetchTmRPC[T any](t *testing.T, url string, into *T) { } } -func testBankTransfer(t *testing.T) { +func testEVMTransfer(t *testing.T) { assertAutobahnEnabled(t) - - h := sendBankTxAndWait(t, "sei-node-0") - t.Logf("bank transfer committed at height %d", h) + before := evmBalanceHex(t, testRecipientEVM) + h := sendEvmTxAndWait(t, "sei-node-0") + after := evmBalanceHex(t, testRecipientEVM) + if before == after { + t.Fatalf("expected recipient %s balance to change after evm tx at height %d", testRecipientEVM, h) + } + t.Logf("evm transfer committed at height %d (balance %s -> %s)", h, before, after) } // killNode kills seid inside sei-node- via pkill. Tolerates non-zero exit @@ -809,7 +819,7 @@ func testLivenessUnderMaxFaults(t *testing.T) { for i := 0; i < maxFaults; i++ { killNode(t, clusterSize-1-i) } - hAfter := sendBankTxAndWait(t, "sei-node-0") + hAfter := sendEvmTxAndWait(t, "sei-node-0") if hAfter <= hBefore { t.Fatalf("expected committed tx with %d faults to advance height past %d, got %d", maxFaults, hBefore, hAfter) } @@ -831,5 +841,5 @@ func testHaltsBeyondMaxFaults(t *testing.T) { killNode(t, clusterSize-1-maxFaults) hBefore := waitForStableHeight(t, haltStableWindow, haltStableTimeout) t.Logf("height: %d (expecting halt)", hBefore) - sendBankTxExpectNoInclusion(t, "sei-node-0", hBefore, haltStableWindow) + sendEvmTxExpectNoInclusion(t, "sei-node-0", hBefore, haltStableWindow) } diff --git a/sei-cosmos/baseapp/abci.go b/sei-cosmos/baseapp/abci.go index e75c515e16..4e1980d6c7 100644 --- a/sei-cosmos/baseapp/abci.go +++ b/sei-cosmos/baseapp/abci.go @@ -714,7 +714,6 @@ func (app *BaseApp) CreateQueryContext(height int64, prove bool) (sdk.Context, e "failed to load state at height %d; %s (latest height: %d)", height, err, lastBlockHeight, ) } - checkStateCtx := app.checkState.Context() // branch the commit-multistore for safety ctx := sdk.NewContext( From 488d6866d148ba9a97cbab8c4aed39a78e39e0bb Mon Sep 17 00:00:00 2001 From: Grzegorz Prusak Date: Sat, 11 Jul 2026 12:40:26 +0200 Subject: [PATCH 09/54] pre-commit query fix --- sei-cosmos/baseapp/abci.go | 29 ++++++++++++++++++----------- sei-cosmos/baseapp/abci_test.go | 25 +++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 11 deletions(-) diff --git a/sei-cosmos/baseapp/abci.go b/sei-cosmos/baseapp/abci.go index 4e1980d6c7..5e5b4bec9f 100644 --- a/sei-cosmos/baseapp/abci.go +++ b/sei-cosmos/baseapp/abci.go @@ -700,21 +700,28 @@ func (app *BaseApp) CreateQueryContext(height int64, prove bool) (sdk.Context, e ) } + checkStateCtx := app.checkState.Context() var cacheMS types.CacheMultiStore - if height < app.migrationHeight && app.qms != nil { - cacheMS, err = app.qms.CacheMultiStoreWithVersion(height) + if lastBlockHeight == 0 && height == 0 && app.checkState != nil { + // Height 0 means "latest". Before the first Commit, the latest readable + // state lives only on checkState (e.g. InitChain writes). Querying the + // committed multistore at version 0 would incorrectly hide that state. + cacheMS = app.checkState.MultiStore().CacheMultiStore() } else { - cacheMS, err = app.cms.CacheMultiStoreWithVersion(height) - } + if height < app.migrationHeight && app.qms != nil { + cacheMS, err = app.qms.CacheMultiStoreWithVersion(height) + } else { + cacheMS, err = app.cms.CacheMultiStoreWithVersion(height) + } - if err != nil { - return sdk.Context{}, - sdkerrors.Wrapf( - sdkerrors.ErrInvalidRequest, - "failed to load state at height %d; %s (latest height: %d)", height, err, lastBlockHeight, - ) + if err != nil { + return sdk.Context{}, + sdkerrors.Wrapf( + sdkerrors.ErrInvalidRequest, + "failed to load state at height %d; %s (latest height: %d)", height, err, lastBlockHeight, + ) + } } - checkStateCtx := app.checkState.Context() // branch the commit-multistore for safety ctx := sdk.NewContext( cacheMS, checkStateCtx.BlockHeader(), true, diff --git a/sei-cosmos/baseapp/abci_test.go b/sei-cosmos/baseapp/abci_test.go index 99b2622962..50fff465a9 100644 --- a/sei-cosmos/baseapp/abci_test.go +++ b/sei-cosmos/baseapp/abci_test.go @@ -164,6 +164,31 @@ func TestBaseAppCreateQueryContext(t *testing.T) { } } +func TestCreateQueryContextUsesCheckStateBeforeFirstCommit(t *testing.T) { + t.Parallel() + + capKey := sdk.NewKVStoreKey("genesis") + app := newBaseApp(t.Name()) + app.MountStores(capKey1, capKey2, capKey) + app.SetParamStore(¶mStore{db: dbm.NewMemDB()}) + require.NoError(t, app.LoadLatestVersion()) + + key := []byte("hello") + value := []byte("world") + app.SetInitChainer(func(ctx sdk.Context, _ abci.RequestInitChain) abci.ResponseInitChain { + ctx.KVStore(capKey).Set(key, value) + return abci.ResponseInitChain{} + }) + + _, err := app.InitChain(context.Background(), &abci.RequestInitChain{ChainId: "sei"}) + require.NoError(t, err) + require.Equal(t, int64(0), app.LastBlockHeight()) + + ctx, err := app.CreateQueryContext(0, false) + require.NoError(t, err) + require.Equal(t, value, ctx.KVStore(capKey).Get(key)) +} + type paramStore struct { db *dbm.MemDB } From 7d59c9b273609b050032b8e4e5f5760299d952e5 Mon Sep 17 00:00:00 2001 From: Grzegorz Prusak Date: Sat, 11 Jul 2026 13:01:17 +0200 Subject: [PATCH 10/54] genesis tx fix --- sei-cosmos/baseapp/abci.go | 7 ++++++- sei-cosmos/baseapp/deliver_tx_test.go | 1 + 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/sei-cosmos/baseapp/abci.go b/sei-cosmos/baseapp/abci.go index 5e5b4bec9f..940cb63d85 100644 --- a/sei-cosmos/baseapp/abci.go +++ b/sei-cosmos/baseapp/abci.go @@ -54,9 +54,14 @@ func (app *BaseApp) InitChain(ctx context.Context, req *abci.RequestInitChain) ( } } + checkHeader := initHeader + if checkHeader.Height == 0 { + checkHeader.Height = 1 + } + // initialize the deliver state and check state with a correct header app.setDeliverState(initHeader) - app.setCheckState(initHeader) + app.setCheckState(checkHeader) app.setProcessProposalState(initHeader) // Store the consensus params in the BaseApp's paramstore. Note, this must be diff --git a/sei-cosmos/baseapp/deliver_tx_test.go b/sei-cosmos/baseapp/deliver_tx_test.go index 9e21813760..a03a120426 100644 --- a/sei-cosmos/baseapp/deliver_tx_test.go +++ b/sei-cosmos/baseapp/deliver_tx_test.go @@ -1157,6 +1157,7 @@ func TestInitChainer(t *testing.T) { chainID = app.checkState.ctx.ChainID() require.Equal(t, "test-chain-id", chainID, "ChainID in checkState not set correctly in InitChain") + require.Equal(t, int64(1), app.checkState.ctx.BlockHeight(), "checkState height should reflect the first executable block after InitChain") app.Commit(context.Background()) res, _ = app.Query(context.Background(), &query) From c71cc3dffa6d191669b486b4e112b3208976c19e Mon Sep 17 00:00:00 2001 From: Grzegorz Prusak Date: Sat, 11 Jul 2026 14:51:36 +0200 Subject: [PATCH 11/54] conditional proxying --- sei-tendermint/AGENTS.md | 7 +- sei-tendermint/internal/p2p/giga/pool.go | 9 ++ .../internal/p2p/giga_router_validator.go | 10 +- .../p2p/giga_router_validator_test.go | 94 ++++++++++++++----- 4 files changed, 91 insertions(+), 29 deletions(-) diff --git a/sei-tendermint/AGENTS.md b/sei-tendermint/AGENTS.md index 95abafb3fe..c666c129e4 100644 --- a/sei-tendermint/AGENTS.md +++ b/sei-tendermint/AGENTS.md @@ -2,7 +2,12 @@ Within sei-tendermint subdirectory * sei-tendermint is a root of the go module, but it is not the root of the repo. When refactoring check references across of the whole repo. * use sei-tendermint/libs/utils.Option for optional values, passing nil as function value is not allowed, unless explicitly documented (except for maps and slices, for which nil is a valid empty value). -* use sei-tendermint/libs/utils/scope.Run for structured concurrency (instead of waitgroup) +* use utils.Recv/utils.Send instead of select { case <-ctx.Done(); ... } +* use sei-tendermint/libs/utils/scope.Run for structured concurrency (instead of waitgroup). + * Don't spawn goroutines with plain "go func(){...}" + * using testing.T assertions (inclusing those from "require" library) is not allowed from non-main test goroutine. + In particular it is not allowed in Scope.Run() and goroutines spawned via Scope.Spawn (and related ones). On assertion error, return an error + or panic directly. If you are unsure if a test helper would be called from non-main test goroutine, dont use testing.T assertions. * use new golang features wherever possible, which is at least 1.25. In particular: * modern range iteration ("for range N", to iterate N times) * avoid capturing loop iterators ("tc := tc" is not needed) diff --git a/sei-tendermint/internal/p2p/giga/pool.go b/sei-tendermint/internal/p2p/giga/pool.go index 9c5917f734..6c29d1679f 100644 --- a/sei-tendermint/internal/p2p/giga/pool.go +++ b/sei-tendermint/internal/p2p/giga/pool.go @@ -66,6 +66,15 @@ func (p *Pool[K, V]) InsertAndRun(ctx context.Context, key K, val V, task func(c return err } +func (p *Pool[K, V]) Get(key K) (V, bool) { + for inner := range p.inner.Lock() { + if entry, ok := inner.byKey[key]; ok { + return entry.val, true + } + } + return utils.Zero[V](), false +} + func (p *Pool[K, V]) RunForEach(ctx context.Context, task func(context.Context, V) error) error { return scope.Run(ctx, func(ctx context.Context, s scope.Scope) error { var next poolEntry[V] diff --git a/sei-tendermint/internal/p2p/giga_router_validator.go b/sei-tendermint/internal/p2p/giga_router_validator.go index 4c65cd1538..b13d82da97 100644 --- a/sei-tendermint/internal/p2p/giga_router_validator.go +++ b/sei-tendermint/internal/p2p/giga_router_validator.go @@ -87,11 +87,17 @@ func (r *gigaValidatorRouter) Run(ctx context.Context) error { } // EvmProxy on the validator returns None when the sender's shard owner is -// us (handle locally via mempool, no HTTP round-trip to self). +// us (handle locally via mempool, no HTTP round-trip to self). For remote +// shards, we proxy only while the target validator is currently connected; +// otherwise we keep the tx local as a best-effort availability heuristic. func (r *gigaValidatorRouter) EvmProxy(sender common.Address) utils.Option[*url.URL] { shardValidator := r.data.Committee().EvmShard(sender) if r.validatorKey == shardValidator { return utils.None[*url.URL]() } - return utils.Some(r.cfg.ValidatorAddrs[shardValidator].EVMRPC) + target := r.cfg.ValidatorAddrs[shardValidator] + if _, ok := r.poolOut.Get(target.Key); !ok { + return utils.None[*url.URL]() + } + return utils.Some(target.EVMRPC) } diff --git a/sei-tendermint/internal/p2p/giga_router_validator_test.go b/sei-tendermint/internal/p2p/giga_router_validator_test.go index 78a5f6cc66..9507bd293c 100644 --- a/sei-tendermint/internal/p2p/giga_router_validator_test.go +++ b/sei-tendermint/internal/p2p/giga_router_validator_test.go @@ -3,6 +3,7 @@ package p2p import ( "context" "fmt" + "maps" "net/url" "testing" "time" @@ -14,6 +15,8 @@ import ( atypes "github.com/sei-protocol/sei-chain/sei-tendermint/autobahn/types" "github.com/sei-protocol/sei-chain/sei-tendermint/internal/autobahn/producer" "github.com/sei-protocol/sei-chain/sei-tendermint/internal/p2p/conn" + "github.com/sei-protocol/sei-chain/sei-tendermint/internal/p2p/giga" + "github.com/sei-protocol/sei-chain/sei-tendermint/internal/p2p/rpc" "github.com/sei-protocol/sei-chain/sei-tendermint/internal/proxy" "github.com/sei-protocol/sei-chain/sei-tendermint/libs/utils" "github.com/sei-protocol/sei-chain/sei-tendermint/libs/utils/require" @@ -199,8 +202,7 @@ func TestGigaRouter_EvmProxy(t *testing.T) { addrs := map[atypes.PublicKey]GigaNodeAddr{} urlByValidator := map[atypes.PublicKey]*url.URL{} // NewGigaRouter requires EVMRPC on every committee member in both - // validator and fullnode modes; the missing-URL branch of EvmProxy is - // unreachable. + // validator and fullnode modes. for i, validatorKey := range validatorKeys { nodeKey := makeKey(rng) nodeKeys = append(nodeKeys, nodeKey) @@ -244,34 +246,74 @@ func TestGigaRouter_EvmProxy(t *testing.T) { localURL, ok := urlByValidator[localValidator] require.True(t, ok) - expectedRemoteURLs := map[string]struct{}{} - for validator, rpcURL := range urlByValidator { - if validator == localValidator { - continue + err = scope.Run(t.Context(), func(ctx context.Context, s scope.Scope) error { + connectedRemote := map[atypes.PublicKey]struct{}{} + expectedRemoteURLs := map[string]struct{}{} + type noOpAPI = rpc.Client[giga.API] + for validator, addr := range addrs { + if validator == localValidator { + continue + } + if len(connectedRemote) >= 3 { + break + } + connectedRemote[validator] = struct{}{} + expectedRemoteURLs[addr.EVMRPC.String()] = struct{}{} + key := addr.Key + ready := make(chan struct{}) + s.SpawnBgNamed(fmt.Sprintf("poolOut[%s]", key), func() error { + var client noOpAPI + return utils.IgnoreCancel(router.poolOut.InsertAndRun(ctx, key, client, func(ctx context.Context) error { + close(ready) + <-ctx.Done() + return nil + })) + }) + if _, _, err := utils.RecvOrClosed(ctx, ready); err != nil { + return err + } } - expectedRemoteURLs[rpcURL.String()] = struct{}{} - } - returnedRemoteURLs := map[string]struct{}{} - for range 200 { - sender := common.BytesToAddress(utils.GenBytes(rng, common.AddressLength)) - shardValidator := router.data.Committee().EvmShard(sender) + returnedRemoteURLs := map[string]struct{}{} + seenDisconnected := false + for range 400 { + sender := common.BytesToAddress(utils.GenBytes(rng, common.AddressLength)) + shardValidator := router.data.Committee().EvmShard(sender) - proxyURL, ok := router.EvmProxy(sender).Get() - expectedURL := urlByValidator[shardValidator] + proxyURL, ok := router.EvmProxy(sender).Get() + expectedURL := urlByValidator[shardValidator] - if shardValidator == localValidator { - // Self-shard: validator short-circuits to local mempool. - require.False(t, ok) - require.Nil(t, proxyURL) - } else { - require.True(t, ok) - require.NotNil(t, proxyURL) - require.Equal(t, expectedURL.String(), proxyURL.String()) - require.NotEqual(t, localURL.String(), proxyURL.String()) - returnedRemoteURLs[proxyURL.String()] = struct{}{} + if shardValidator == localValidator { + // Self-shard: validator short-circuits to local mempool. + if ok || proxyURL != nil { + return fmt.Errorf("expected local shard %s to avoid proxying", shardValidator) + } + } else if _, connected := connectedRemote[shardValidator]; !connected { + if ok || proxyURL != nil { + return fmt.Errorf("expected disconnected shard %s to avoid proxying", shardValidator) + } + seenDisconnected = true + } else { + if !ok || proxyURL == nil { + return fmt.Errorf("expected connected shard %s to proxy", shardValidator) + } + if got := proxyURL.String(); got != expectedURL.String() { + return fmt.Errorf("proxy url = %s, want %s", got, expectedURL.String()) + } + if proxyURL.String() == localURL.String() { + return fmt.Errorf("connected remote shard %s unexpectedly proxied to local URL", shardValidator) + } + returnedRemoteURLs[proxyURL.String()] = struct{}{} + } } - } - require.Equal(t, expectedRemoteURLs, returnedRemoteURLs) + if !maps.Equal(expectedRemoteURLs, returnedRemoteURLs) { + return fmt.Errorf("returned remote urls = %v, want %v", returnedRemoteURLs, expectedRemoteURLs) + } + if !seenDisconnected { + return fmt.Errorf("expected at least one disconnected shard sample") + } + return nil + }) + require.NoError(t, err) } From 0e8f795b549c0d60439e9eeebe013b76122ff535 Mon Sep 17 00:00:00 2001 From: Grzegorz Prusak Date: Sat, 11 Jul 2026 14:58:42 +0200 Subject: [PATCH 12/54] test fix --- sei-cosmos/baseapp/abci.go | 13 +++++++------ sei-cosmos/baseapp/abci_test.go | 17 +++++++++-------- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/sei-cosmos/baseapp/abci.go b/sei-cosmos/baseapp/abci.go index 940cb63d85..a49c672692 100644 --- a/sei-cosmos/baseapp/abci.go +++ b/sei-cosmos/baseapp/abci.go @@ -54,14 +54,9 @@ func (app *BaseApp) InitChain(ctx context.Context, req *abci.RequestInitChain) ( } } - checkHeader := initHeader - if checkHeader.Height == 0 { - checkHeader.Height = 1 - } - // initialize the deliver state and check state with a correct header app.setDeliverState(initHeader) - app.setCheckState(checkHeader) + app.setCheckState(initHeader) app.setProcessProposalState(initHeader) // Store the consensus params in the BaseApp's paramstore. Note, this must be @@ -87,6 +82,12 @@ func (app *BaseApp) InitChain(ctx context.Context, req *abci.RequestInitChain) ( // checks and can reject valid genesis txs. app.initChainer(app.checkState.ctx.WithIsCheckTx(false), *req) + // After genesis initialization completes, CheckTx should use the first + // executable block height rather than height 0. + if initHeader.Height == 0 { + app.checkState.SetContext(app.checkState.ctx.WithBlockHeight(1)) + } + // In the case of a new chain, AppHash will be the hash of an empty string. // During an upgrade, it'll be the hash of the last committed block. var appHash []byte diff --git a/sei-cosmos/baseapp/abci_test.go b/sei-cosmos/baseapp/abci_test.go index 50fff465a9..7741543a4f 100644 --- a/sei-cosmos/baseapp/abci_test.go +++ b/sei-cosmos/baseapp/abci_test.go @@ -168,18 +168,19 @@ func TestCreateQueryContextUsesCheckStateBeforeFirstCommit(t *testing.T) { t.Parallel() capKey := sdk.NewKVStoreKey("genesis") - app := newBaseApp(t.Name()) + key := []byte("hello") + value := []byte("world") + initChainerOpt := func(bapp *BaseApp) { + bapp.SetInitChainer(func(ctx sdk.Context, _ abci.RequestInitChain) abci.ResponseInitChain { + ctx.KVStore(capKey).Set(key, value) + return abci.ResponseInitChain{} + }) + } + app := newBaseApp(t.Name(), initChainerOpt) app.MountStores(capKey1, capKey2, capKey) app.SetParamStore(¶mStore{db: dbm.NewMemDB()}) require.NoError(t, app.LoadLatestVersion()) - key := []byte("hello") - value := []byte("world") - app.SetInitChainer(func(ctx sdk.Context, _ abci.RequestInitChain) abci.ResponseInitChain { - ctx.KVStore(capKey).Set(key, value) - return abci.ResponseInitChain{} - }) - _, err := app.InitChain(context.Background(), &abci.RequestInitChain{ChainId: "sei"}) require.NoError(t, err) require.Equal(t, int64(0), app.LastBlockHeight()) From 60080e1a6a74a0cffe61d43e17ee167bdd354810 Mon Sep 17 00:00:00 2001 From: Grzegorz Prusak Date: Sat, 11 Jul 2026 22:14:21 +0200 Subject: [PATCH 13/54] more fixes --- contracts/test/EVMCompatabilityTest.js | 16 ++++---- .../contracts/deploy_wasm_contracts.sh | 2 + .../evm_module/scripts/evm_rpc_tests.sh | 38 +++++++++++++++++++ 3 files changed, 48 insertions(+), 8 deletions(-) diff --git a/contracts/test/EVMCompatabilityTest.js b/contracts/test/EVMCompatabilityTest.js index f191d3ce22..ee2443917c 100644 --- a/contracts/test/EVMCompatabilityTest.js +++ b/contracts/test/EVMCompatabilityTest.js @@ -198,14 +198,14 @@ describe("EVM Test", function () { }; const result = await ethers.provider.call(tx); - // wait for block to change - while(true){ - const bn = await ethers.provider.getBlockNumber(); - if(bn !== currentBlockNumber){ - break - } - await sleep(100) - } + // Force a real block instead of waiting for idle block production. + const advanceTx = await owner.sendTransaction({ + to: owner.address, + value: 1n, + gasPrice: ethers.parseUnits('100', 'gwei'), + }); + await advanceTx.wait(); + const result2 = await ethers.provider.call(tx); expect(result).to.equal(result2) }); diff --git a/integration_test/contracts/deploy_wasm_contracts.sh b/integration_test/contracts/deploy_wasm_contracts.sh index aff1885d33..2eda8b7cfc 100755 --- a/integration_test/contracts/deploy_wasm_contracts.sh +++ b/integration_test/contracts/deploy_wasm_contracts.sh @@ -11,6 +11,8 @@ source "$(dirname "$0")/../utils/_tx_helpers.sh" cd $seihome || exit echo "Deploying first set of contracts..." +bootstrap_block_height=$(bank_send_and_get_height "$keyname" "$keyaddress" "1usei") || exit 1 +wait_until_height_exceeds "$bootstrap_block_height" || exit 1 beginning_block_height=$($seidbin status | jq -r '.SyncInfo.latest_block_height') echo "$beginning_block_height" > $seihome/integration_test/contracts/wasm_beginning_block_height.txt echo "$keyaddress" > $seihome/integration_test/contracts/wasm_creator_id.txt diff --git a/integration_test/evm_module/scripts/evm_rpc_tests.sh b/integration_test/evm_module/scripts/evm_rpc_tests.sh index f73c66418b..7b073fcc6b 100755 --- a/integration_test/evm_module/scripts/evm_rpc_tests.sh +++ b/integration_test/evm_module/scripts/evm_rpc_tests.sh @@ -39,6 +39,12 @@ get_from_seq() { echo "$s" } +get_latest_height() { + local h + h=$(docker exec "$CONTAINER" curl -s http://localhost:26657/status 2>/dev/null | jq -r '.result.sync_info.latest_block_height // "0"' 2>/dev/null) || true + echo "${h:-0}" +} + # Wait until $FROM_ADDR's sequence advances past $1, with a 5s timeout. # Direct causal "previous tx committed" signal: the sender's sequence # advances atomically when its tx is included in a block, so by the @@ -57,7 +63,39 @@ wait_from_seq_advance() { done } +wait_until_height_exceeds() { + local prev="$1" + local deadline=$(($(date +%s) + 15)) + while [ "$(date +%s)" -lt "$deadline" ]; do + local cur; cur=$(get_latest_height) + if [[ "$cur" =~ ^[0-9]+$ ]] && [ "$cur" -gt "$prev" ]; then + return 0 + fi + sleep 0.5 + done + echo "timed out waiting for height to exceed ${prev}" >&2 + return 1 +} + +bump_chain_to_height() { + local target="$1" + local height; height=$(get_latest_height) + while [[ "$height" =~ ^[0-9]+$ ]] && [ "$height" -lt "$target" ]; do + local seq_before; seq_before=$(get_from_seq) + local prev_height="$height" + run seid tx evm send "$RECIPIENT" 1 --from "$FROM" "${KEYRING_ARGS[@]}" --chain-id sei --evm-rpc "$EVM_RPC_URL" -b sync -y >/dev/null + wait_from_seq_advance "$seq_before" + wait_until_height_exceeds "$prev_height" + height=$(get_latest_height) + done +} + # Seed chain with block/tx/contract; export SEI_EVM_IO_SEED_BLOCK so .iox __SEED__ tag resolves to deploy block. +# Static .io fixtures contain hard-coded historical block numbers up to 0x2d. +# Under Autobahn without empty blocks, create enough real blocks first so those +# requests stay within the available history. +bump_chain_to_height 100 + # CLI deploy expects hex file with no whitespace; write trimmed hex to a temp path in the container. docker exec "$CONTAINER" /bin/bash -c "tr -d '[:space:]' < \"$CONTRACT_HEX\" > /tmp/minimal_contract.hex" # Use -b sync (not -b block): under Autobahn the cosmos KV indexer that From 433c3da790f225d1236e80f86dfd06527426b7aa Mon Sep 17 00:00:00 2001 From: Grzegorz Prusak Date: Sun, 12 Jul 2026 12:07:09 +0200 Subject: [PATCH 14/54] more fixes --- evmrpc/simulate_test.go | 141 +++++++++++------- .../contracts/create_tokenfactory_denoms.sh | 3 +- .../contracts/deploy_wasm_contracts.sh | 3 +- .../evm_module/scripts/evm_rpc_tests.sh | 2 +- 4 files changed, 95 insertions(+), 54 deletions(-) diff --git a/evmrpc/simulate_test.go b/evmrpc/simulate_test.go index 831a7aa1d0..1f6c754ee7 100644 --- a/evmrpc/simulate_test.go +++ b/evmrpc/simulate_test.go @@ -663,42 +663,69 @@ func TestSimulationAPIRequestLimiter(t *testing.T) { t.Run("TestEthCallRateLimiting", func(t *testing.T) { tEnv := newTestEnv(t) // Test eth_call rate limiting with concurrent requests - numRequests := 10 // Much more than the limit of 2 - results := make(chan error, numRequests) + const ( + numRequests = 10 // Much more than the limit of 2 + maxAttempts = 5 + ) + runBurst := func() []error { + results := make(chan error, numRequests) + start := make(chan struct{}) + var wg sync.WaitGroup - // Start all requests concurrently to overwhelm the rate limiter - for i := 0; i < numRequests; i++ { - go func() { - _, err := tEnv.simAPI.Call(context.Background(), tEnv.args, nil, nil, nil) - results <- err - }() - } + // Release all requests at once to maximize contention on the limiter. + for range numRequests { + wg.Add(1) + go func() { + defer wg.Done() + <-start + _, err := tEnv.simAPI.Call(context.Background(), tEnv.args, nil, nil, nil) + results <- err + }() + } - // Collect all results - var errors []error - for i := 0; i < numRequests; i++ { - errors = append(errors, <-results) + close(start) + wg.Wait() + close(results) + + var errors []error + for err := range results { + errors = append(errors, err) + } + return errors } - // Count successful vs rejected requests - successCount := 0 - rejectedCount := 0 - for _, err := range errors { - if err == nil { - successCount++ - } else if strings.Contains(err.Error(), "eth_call rejected due to rate limit: server busy") { - rejectedCount++ - } else { - t.Logf("Unexpected error: %v", err) + var ( + successCount int + rejectedCount int + attemptsUsed int + observedRejection bool + ) + for attempt := 1; attempt <= maxAttempts; attempt++ { + attemptsUsed = attempt + successCount = 0 + rejectedCount = 0 + + for _, err := range runBurst() { + if err == nil { + successCount++ + } else if strings.Contains(err.Error(), "eth_call rejected due to rate limit: server busy") { + rejectedCount++ + } else { + t.Logf("Unexpected error: %v", err) + } + } + + require.Equalf(t, numRequests, successCount+rejectedCount, "All requests should be accounted for (attempt %d)", attempt) + if rejectedCount > 0 { + observedRejection = true + break } } - // With only 2 concurrent slots and 10 requests, we should have rejections - require.Greater(t, rejectedCount, 0, "Should have rejected requests due to rate limiting") + require.Truef(t, observedRejection, "Should have rejected requests due to rate limiting (last burst: %d successful, %d rejected)", successCount, rejectedCount) require.Greater(t, successCount, 0, "Should have some successful requests") - require.Equal(t, numRequests, successCount+rejectedCount, "All requests should be accounted for") - t.Logf("eth_call rate limiting: %d successful, %d rejected out of %d total", successCount, rejectedCount, numRequests) + t.Logf("eth_call rate limiting (attempt %d/%d): %d successful, %d rejected out of %d total", attemptsUsed, maxAttempts, successCount, rejectedCount, numRequests) }) t.Run("TestEstimateGasRateLimiting", func(t *testing.T) { @@ -952,32 +979,46 @@ func TestSimulationAPIRequestLimiter(t *testing.T) { t.Run("TestRateLimitErrorFormat", func(t *testing.T) { tEnv := newTestEnv(t) // Test the error message format by overwhelming the rate limiter - numRequests := 20 - results := make(chan error, numRequests) + const ( + numRequests = 20 + maxAttempts = 5 + ) + var ( + rateLimitErrors []error + attemptsUsed int + ) + for attempt := 1; attempt <= maxAttempts; attempt++ { + attemptsUsed = attempt + results := make(chan error, numRequests) + start := make(chan struct{}) + var wg sync.WaitGroup - // Start requests concurrently to trigger rate limiting - var wg sync.WaitGroup - for range numRequests { - wg.Add(1) - go func() { - defer wg.Done() - _, err := tEnv.simAPI.Call(context.Background(), tEnv.args, nil, nil, nil) - results <- err - }() - } - wg.Wait() - close(results) - - // Collect results and check error messages - var rateLimitErrors []error - for err := range results { - if err != nil && strings.Contains(err.Error(), "rejected due to rate limit") { - rateLimitErrors = append(rateLimitErrors, err) + // Release all requests at once to reliably saturate the limiter. + for range numRequests { + wg.Add(1) + go func() { + defer wg.Done() + <-start + _, err := tEnv.simAPI.Call(context.Background(), tEnv.args, nil, nil, nil) + results <- err + }() + } + close(start) + wg.Wait() + close(results) + + rateLimitErrors = rateLimitErrors[:0] + for err := range results { + if err != nil && strings.Contains(err.Error(), "rejected due to rate limit") { + rateLimitErrors = append(rateLimitErrors, err) + } + } + if len(rateLimitErrors) > 0 { + break } } - // Should have at least one rate limit error - require.Greater(t, len(rateLimitErrors), 0, "Should have at least one rate limit error") + require.Greaterf(t, len(rateLimitErrors), 0, "Should have at least one rate limit error (attempts %d)", attemptsUsed) // Verify error message format for _, err := range rateLimitErrors { @@ -985,7 +1026,7 @@ func TestSimulationAPIRequestLimiter(t *testing.T) { require.Contains(t, err.Error(), "server busy") } - t.Logf("Found %d rate limit errors with correct format", len(rateLimitErrors)) + t.Logf("Found %d rate limit errors with correct format (attempt %d/%d)", len(rateLimitErrors), attemptsUsed, maxAttempts) }) } diff --git a/integration_test/contracts/create_tokenfactory_denoms.sh b/integration_test/contracts/create_tokenfactory_denoms.sh index 58a8ecb09f..4e7518dfe8 100755 --- a/integration_test/contracts/create_tokenfactory_denoms.sh +++ b/integration_test/contracts/create_tokenfactory_denoms.sh @@ -11,7 +11,8 @@ source "$(dirname "$0")/../utils/_tx_helpers.sh" cd $seihome || exit echo "Deploying first set of tokenfactory denoms..." -beginning_block_height=$($seidbin status | jq -r '.SyncInfo.latest_block_height') +bootstrap_block_height=$(bank_send_and_get_height "$keyname" "$keyaddress" "1usei") || exit 1 +beginning_block_height="$bootstrap_block_height" echo "$beginning_block_height" > $seihome/integration_test/contracts/tfk_beginning_block_height.txt echo "$keyaddress" > $seihome/integration_test/contracts/tfk_creator_id.txt diff --git a/integration_test/contracts/deploy_wasm_contracts.sh b/integration_test/contracts/deploy_wasm_contracts.sh index 2eda8b7cfc..67afb313fd 100755 --- a/integration_test/contracts/deploy_wasm_contracts.sh +++ b/integration_test/contracts/deploy_wasm_contracts.sh @@ -12,8 +12,7 @@ cd $seihome || exit echo "Deploying first set of contracts..." bootstrap_block_height=$(bank_send_and_get_height "$keyname" "$keyaddress" "1usei") || exit 1 -wait_until_height_exceeds "$bootstrap_block_height" || exit 1 -beginning_block_height=$($seidbin status | jq -r '.SyncInfo.latest_block_height') +beginning_block_height="$bootstrap_block_height" echo "$beginning_block_height" > $seihome/integration_test/contracts/wasm_beginning_block_height.txt echo "$keyaddress" > $seihome/integration_test/contracts/wasm_creator_id.txt diff --git a/integration_test/evm_module/scripts/evm_rpc_tests.sh b/integration_test/evm_module/scripts/evm_rpc_tests.sh index 7b073fcc6b..730cd5acc5 100755 --- a/integration_test/evm_module/scripts/evm_rpc_tests.sh +++ b/integration_test/evm_module/scripts/evm_rpc_tests.sh @@ -41,7 +41,7 @@ get_from_seq() { get_latest_height() { local h - h=$(docker exec "$CONTAINER" curl -s http://localhost:26657/status 2>/dev/null | jq -r '.result.sync_info.latest_block_height // "0"' 2>/dev/null) || true + h=$(run seid status 2>/dev/null | jq -r '.SyncInfo.latest_block_height // "0"' 2>/dev/null) || true echo "${h:-0}" } From 70dc88c00efd55719226924467711f4e7ef5683d Mon Sep 17 00:00:00 2001 From: Grzegorz Prusak Date: Sun, 12 Jul 2026 20:36:58 +0200 Subject: [PATCH 15/54] more fixes --- contracts/test/EVMCompatabilityTest.js | 31 +++++----- contracts/test/lib.js | 30 ++++------ integration_test/autobahn/autobahn_test.go | 39 ++----------- .../evm_module/ws_test/ws_test.go | 57 ++++++++++++++++++- 4 files changed, 88 insertions(+), 69 deletions(-) diff --git a/contracts/test/EVMCompatabilityTest.js b/contracts/test/EVMCompatabilityTest.js index ee2443917c..c13356693d 100644 --- a/contracts/test/EVMCompatabilityTest.js +++ b/contracts/test/EVMCompatabilityTest.js @@ -4,7 +4,7 @@ const {uniq} = require("lodash"); const hre = require('hardhat'); const { ethers, upgrades } = hre; const { getImplementationAddress } = require('@openzeppelin/upgrades-core'); -const { deployEvmContract, setupSigners, fundAddress, waitForBaseFeeToEq, waitForBaseFeeToBeGt} = require("./lib") +const { deployEvmContract, setupSigners, fundAddress } = require("./lib") const axios = require("axios"); const { default: BigNumber } = require("bignumber.js"); @@ -21,6 +21,15 @@ function debug(msg) { // console.log(msg) } +async function mineTransferBlock(sender) { + const tx = await sender.sendTransaction({ + to: sender.address, + value: 1n, + gasPrice: ethers.parseUnits('100', 'gwei'), + }); + return await tx.wait(); +} + async function sendTransactionAndCheckGas(sender, recipient, amount) { // Get the balance of the sender before the transaction const balanceBefore = await ethers.provider.getBalance(sender.address); @@ -673,7 +682,7 @@ describe("EVM Test", function () { it("Check base fee on the right block", async function () { await delay() - // check if base fee is 1gwei, otherwise wait + // Drive real blocks until base fee decays back to 1 gwei. let iterations = 0 while (true) { const block = await ethers.provider.getBlock("latest"); @@ -685,7 +694,7 @@ describe("EVM Test", function () { if(iterations > 10) { throw new Error("base fee hasn't dropped to 1gwei in 10 iterations") } - await sleep(1000); + await mineTransferBlock(owner); } // use at least 1000000 gas to increase base fee const txResponse = await evmTester.useGas(1000000, { gasPrice: ethers.parseUnits('100', 'gwei') }); @@ -697,15 +706,9 @@ describe("EVM Test", function () { const block = await ethers.provider.getBlock(blockHeight); const baseFee = Number(block.baseFeePerGas); expect(baseFee).to.equal(oneGwei); - // wait for the next block - while (true) { - const bn = await ethers.provider.getBlockNumber(); - if(bn !== blockHeight){ - break - } - await sleep(500) - } - const nextBlock = await ethers.provider.getBlock(blockHeight + 1); + const nextReceipt = await mineTransferBlock(owner); + expect(nextReceipt.blockNumber).to.be.greaterThan(blockHeight); + const nextBlock = await ethers.provider.getBlock(nextReceipt.blockNumber); const nextBaseFee = Number(nextBlock.baseFeePerGas); expect(nextBaseFee).to.be.greaterThan(oneGwei); }); @@ -1011,8 +1014,8 @@ describe("EVM Test", function () { let blockEnd; let numTxs = 5; before(async function() { - await sleep(5000); // wait for a block to pass so we get a fresh block number - blockStart = await ethers.provider.getBlockNumber(); + const startReceipt = await mineTransferBlock(owner); + blockStart = startReceipt.blockNumber; // Emit an event by making a transaction for (let i = 0; i < numTxs; i++) { diff --git a/contracts/test/lib.js b/contracts/test/lib.js index 640889a790..bba8376c73 100644 --- a/contracts/test/lib.js +++ b/contracts/test/lib.js @@ -93,26 +93,13 @@ async function tryGetReceipt(provider, txHash) { } } -// (tx still in mempool, lands one block later). -async function waitForBlocks(blocks=2, timeoutMs=15000) { - const start = await ethers.provider.getBlockNumber() - const deadline = Date.now() + timeoutMs - while (Date.now() < deadline) { - const cur = await ethers.provider.getBlockNumber() - if (cur >= start + blocks) return - await sleep(50) - } - throw new Error(`block didn't advance by ${blocks} in ${timeoutMs}ms (start=${start})`) -} - // Poll an arbitrary side-effect check until it returns truthy. Used by // helpers that need to wait for a -b sync tx to take effect: instead of // polling `seid q tx ` (which doesn't work under Autobahn — the -// cosmos tx indexer isn't wired) or `waitForBlocks` (temporal, not -// causal), each caller passes a closure that queries the actual state -// it cares about (e.g. account balance, denom existence). Works under -// both Autobahn and legacy because the check goes through whatever query -// path the caller already relies on. +// cosmos tx indexer isn't wired), each caller passes a closure that +// queries the actual state it cares about (e.g. account balance, denom +// existence). Works under both Autobahn and legacy because the check +// goes through whatever query path the caller already relies on. async function waitForCondition(check, description, timeoutMs=30000, intervalMs=200) { const deadline = Date.now() + timeoutMs while (Date.now() < deadline) { @@ -266,12 +253,15 @@ async function getKeySeiAddress(name) { // address is already associated. async function associateKey(keyName) { try { + const seiAddress = await getKeySeiAddress(keyName) // seid tx evm associate-address has a custom (non-cosmos-JSON) output // format. The try/catch already tolerates failure here, and subsequent - // associate calls will succeed once the chain catches up, so a temporal - // wait is acceptable. + // associate calls will succeed once the chain catches up. await execute(`seid tx evm associate-address --from ${keyName} -b sync`) - await waitForBlocks() + await waitForCondition( + async () => (await getEvmAddressAssociation(seiAddress)).associated === true, + `${seiAddress} to have an associated EVM address`, + ) } catch (e) { console.log(`skipping associate for ${keyName}`) console.log(e) diff --git a/integration_test/autobahn/autobahn_test.go b/integration_test/autobahn/autobahn_test.go index c12b9fe9dc..219d86174a 100644 --- a/integration_test/autobahn/autobahn_test.go +++ b/integration_test/autobahn/autobahn_test.go @@ -59,14 +59,12 @@ const ( // checks (the rpc-node's 8545 isn't host-published). evmRPCURLOnContainerLocalhost = "http://localhost:8545" - // heightPoll governs both waitForStableHeight and waitForHeightAdvance: - // the fullnode's read of /abci_info trails the cluster while - // runExecute drains buffered blocks, and a killed-peer failover - // (DialInterval-bounded, ~10s) holds height static for that long. - // Polling lets each test absorb whatever combination of those delays - // actually applies, instead of guessing a sleep duration. - // Bounds are 2× the expected failover+drain so a slow CI runner has - // headroom without giving up another whole minute on every run. + // heightPoll governs waitForStableHeight: the fullnode's read of + // /abci_info trails the cluster while runExecute drains buffered + // blocks, and a killed-peer failover (DialInterval-bounded, ~10s) + // holds height static for that long. Polling lets each test absorb + // whatever combination of those delays actually applies, instead of + // guessing a sleep duration. heightPoll = 1 * time.Second haltStableWindow = 20 * time.Second // 2m / 90s give headroom for the fullnode catch-up backlog the @@ -75,7 +73,6 @@ const ( // which takes ~60s to drain on top of the halt-detection window). // CI runners are slower than local; 1m was tight enough to flake. haltStableTimeout = 2 * time.Minute - heightAdvanceMax = 90 * time.Second txFinalizeMax = 30 * time.Second testRecipientEVM = "0x1000000000000000000000000000000000000001" ) @@ -161,26 +158,6 @@ func waitForStableHeight(t *testing.T, window, timeout time.Duration) int64 { return 0 } -// waitForHeightAdvance polls getHeight every heightPoll, returning the -// first observed value strictly greater than `base`. Used by liveness -// tests where progress is expected but may be delayed by the fullnode's -// block-sync failover from a killed peer. Fails the test on timeout. -func waitForHeightAdvance(t *testing.T, base int64, timeout time.Duration) int64 { - t.Helper() - deadline := time.Now().Add(timeout) - last := base - for time.Now().Before(deadline) { - h := getHeight(t) - if h > base { - return h - } - last = h - time.Sleep(heightPoll) - } - t.Fatalf("height did not advance past %d within %s (last seen %d)", base, timeout, last) - return 0 -} - func fetchHeight() (int64, error) { resp, err := heightClient.Get(abciInfoURL) if err != nil { @@ -327,10 +304,6 @@ func sendEvmTxAndWait(t *testing.T, container string) int64 { if receiptHeight <= baseHeight { t.Fatalf("expected tx %s to land after height %d, got receipt at %d", txHash, baseHeight, receiptHeight) } - waitedHeight := waitForHeightAdvance(t, baseHeight, heightAdvanceMax) - if waitedHeight != receiptHeight { - t.Logf("height watcher observed %d while receipt reported %d", waitedHeight, receiptHeight) - } return receiptHeight } diff --git a/integration_test/evm_module/ws_test/ws_test.go b/integration_test/evm_module/ws_test/ws_test.go index 13e9896508..58011bb6cb 100644 --- a/integration_test/evm_module/ws_test/ws_test.go +++ b/integration_test/evm_module/ws_test/ws_test.go @@ -13,7 +13,10 @@ package ws_test import ( + "context" + "fmt" "os" + "os/exec" "testing" "time" @@ -27,6 +30,53 @@ func wsURL() string { return "ws://127.0.0.1:8546" } +func triggerHead(t *testing.T) { + t.Helper() + + container := os.Getenv("SEI_EVM_WS_TX_CONTAINER") + if container == "" { + container = "sei-node-0" + } + password := os.Getenv("SEI_EVM_WS_TX_PASSWORD") + if password == "" { + password = "12345678" + } + from := os.Getenv("SEI_EVM_WS_TX_FROM") + if from == "" { + from = "admin" + } + recipient := os.Getenv("SEI_EVM_WS_TX_RECIPIENT") + if recipient == "" { + recipient = "0xF87A299e6bC7bEba58dbBe5a5Aa21d49bCD16D52" + } + evmRPCURL := os.Getenv("SEI_EVM_WS_TX_EVM_RPC_URL") + if evmRPCURL == "" { + evmRPCURL = "http://localhost:8545" + } + + ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) + defer cancel() + cmd := exec.CommandContext( + ctx, + "docker", "exec", + "--env", fmt.Sprintf("SEI_EVM_WS_PASSWORD=%s", password), + container, + "/bin/bash", "-c", + `export PATH=$PATH:/root/go/bin && printf "%s\n" "$SEI_EVM_WS_PASSWORD" | "$@"`, + "bash", + "seid", "tx", "evm", "send", recipient, "1", + "--from", from, + "--chain-id", "sei", + "--evm-rpc", evmRPCURL, + "-b", "sync", + "-y", + ) + out, err := cmd.CombinedOutput() + if err != nil { + t.Fatalf("trigger head tx: %v\n%s", err, out) + } +} + func TestEthSubscribeNewHeads(t *testing.T) { if os.Getenv("SEI_EVM_WS_RUN_INTEGRATION") != "1" { t.Skip("EVM WS integration tests skipped (set SEI_EVM_WS_RUN_INTEGRATION=1 to run)") @@ -68,8 +118,11 @@ func TestEthSubscribeNewHeads(t *testing.T) { } t.Logf("subscription id: %s", ack.Result) - // Wait for at least one head notification. At Sei's block cadence - // this should arrive within a few seconds; allow generous slack. + // Drive a real block after subscribing so Autobahn does not depend on + // idle block production to emit a new head notification. + triggerHead(t) + + // Wait for the resulting head notification. if err = conn.SetReadDeadline(time.Now().Add(15 * time.Second)); err != nil { t.Fatalf("set deadline: %v", err) } From 155c33876b1aa34c9e7a35d2a4b13ba526467efc Mon Sep 17 00:00:00 2001 From: Grzegorz Prusak Date: Sun, 12 Jul 2026 22:27:29 +0200 Subject: [PATCH 16/54] heavier tx? --- contracts/test/EVMCompatabilityTest.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/contracts/test/EVMCompatabilityTest.js b/contracts/test/EVMCompatabilityTest.js index c13356693d..b47378c180 100644 --- a/contracts/test/EVMCompatabilityTest.js +++ b/contracts/test/EVMCompatabilityTest.js @@ -696,8 +696,9 @@ describe("EVM Test", function () { } await mineTransferBlock(owner); } - // use at least 1000000 gas to increase base fee - const txResponse = await evmTester.useGas(1000000, { gasPrice: ethers.parseUnits('100', 'gwei') }); + // Fill the parent block heavily enough that the next block's base fee + // must rise above the 1 gwei floor. + const txResponse = await evmTester.useGas(9500000, { gasPrice: ethers.parseUnits('100', 'gwei') }); const receipt = await txResponse.wait(); const blockHeight = receipt.blockNumber; From 269dfded3c38b0d3b4f7e56b1f5fc80708a9ec04 Mon Sep 17 00:00:00 2001 From: Grzegorz Prusak Date: Mon, 13 Jul 2026 09:59:34 +0200 Subject: [PATCH 17/54] some bullshit --- contracts/test/lib.js | 11 ++++- .../contracts/verify_flatkv_evm_migrate.sh | 25 ++++-------- .../gov_module/gov_proposal_test.yaml | 20 ++++------ .../gov_module/staking_proposal_test.yaml | 7 ++-- .../scripts/proposal_wait_for_pass.sh | 21 ++-------- integration_test/utils/_tx_helpers.sh | 40 +++++++++++++++++++ 6 files changed, 71 insertions(+), 53 deletions(-) diff --git a/contracts/test/lib.js b/contracts/test/lib.js index bba8376c73..b9764ac541 100644 --- a/contracts/test/lib.js +++ b/contracts/test/lib.js @@ -771,16 +771,23 @@ async function passProposal(proposalId, desposit="200000000usei", fees="200000u } else { await execute(`seid tx gov vote ${proposalId} yes --from ${from} -b sync -y --fees ${fees}`) } + const adminAddr = await getKeySeiAddress(adminKeyName) + let deadlineKickSent = false // Poll for proposal status with shorter delay for faster tests for(let i=0; i<200; i++) { - const proposal = await execute(`seid q gov proposal ${proposalId} -o json`) - const status = JSON.parse(proposal).status + const proposal = JSON.parse(await execute(`seid q gov proposal ${proposalId} -o json`)) + const status = proposal.status if(status === "PROPOSAL_STATUS_PASSED") { return proposalId } if(status === "PROPOSAL_STATUS_REJECTED" || status === "PROPOSAL_STATUS_FAILED") { throw new Error(`Proposal ${proposalId} was rejected/failed with status: ${status}`) } + const votingEndMs = Date.parse(proposal.voting_end_time ?? "") + if (!deadlineKickSent && Number.isFinite(votingEndMs) && Date.now() >= votingEndMs + 1000) { + await bankSend(adminAddr, adminKeyName, "1", "usei") + deadlineKickSent = true + } await sleep(250) // Poll every 250ms instead of 1s for faster feedback } throw new Error("could not pass proposal "+proposalId) diff --git a/integration_test/contracts/verify_flatkv_evm_migrate.sh b/integration_test/contracts/verify_flatkv_evm_migrate.sh index 071a7070cb..c306b9f3ef 100755 --- a/integration_test/contracts/verify_flatkv_evm_migrate.sh +++ b/integration_test/contracts/verify_flatkv_evm_migrate.sh @@ -758,24 +758,13 @@ EOF" done echo "Voted yes on proposal $proposal_id from a quorum of validators; waiting for it to pass..." - # Poll for PASSED rather than a fixed sleep so a slow voting period does - # not flake the test (and a rejection fails fast). - local elapsed=0 - local status="" - while [ "$elapsed" -lt "$GOV_PASS_TIMEOUT" ]; do - status=$(docker exec "sei-node-0" build/seid q gov proposal "$proposal_id" --output json 2>/dev/null \ - | jq -r '.status // ""' 2>/dev/null || true) - if [ "$status" = "PROPOSAL_STATUS_PASSED" ]; then - break - fi - if [ "$status" = "PROPOSAL_STATUS_REJECTED" ] || [ "$status" = "PROPOSAL_STATUS_FAILED" ]; then - echo "ERROR: migration param-change proposal $proposal_id ended in status $status" >&2 - exit 1 - fi - echo "Waiting for proposal $proposal_id to pass (elapsed=${elapsed}s/${GOV_PASS_TIMEOUT}s, status=${status:-unknown})" - sleep 5 - elapsed=$((elapsed + 5)) - done + local status + status=$(docker exec "sei-node-0" bash -lc " + cd /sei-protocol/sei-chain + seidbin=build/seid chainid=sei + source integration_test/utils/_tx_helpers.sh + wait_for_proposal_status '$proposal_id' PROPOSAL_STATUS_PASSED '$GOV_PASS_TIMEOUT' admin + " 2>/dev/null || true) if [ "$status" != "PROPOSAL_STATUS_PASSED" ]; then echo "ERROR: migration param-change proposal $proposal_id did not pass within ${GOV_PASS_TIMEOUT}s (last status=$status)" >&2 exit 1 diff --git a/integration_test/gov_module/gov_proposal_test.yaml b/integration_test/gov_module/gov_proposal_test.yaml index 23d867bf8d..a0eb07ec28 100644 --- a/integration_test/gov_module/gov_proposal_test.yaml +++ b/integration_test/gov_module/gov_proposal_test.yaml @@ -18,9 +18,8 @@ - cmd: seid q gov proposal $PROPOSAL_ID --output json | jq -r .status - cmd: seidbin=seid; chainid=sei; source integration_test/utils/_tx_helpers.sh && submit_tx_and_wait node_admin gov vote $PROPOSAL_ID yes --fees 2000usei node: sei-node-1 - # since quorum is 0.5, we only need 2/4 votes and expect proposal to pass after 35 seconds - - cmd: sleep 35 - - cmd: seid q gov proposal $PROPOSAL_ID --output json | jq -r .status + # since quorum is 0.5, we only need 2/4 votes; drive progress until it passes + - cmd: seidbin=seid; chainid=sei; source integration_test/utils/_tx_helpers.sh && wait_for_proposal_status $PROPOSAL_ID PROPOSAL_STATUS_PASSED 120 admin env: PROPOSAL_STATUS # Get the tally params again after proposal is passed - cmd: seid q gov params --output json | jq -r .tally_params.quorum @@ -62,9 +61,8 @@ - cmd: seid q gov proposal $PROPOSAL_ID --output json | jq -r .status - cmd: seidbin=seid; chainid=sei; source integration_test/utils/_tx_helpers.sh && submit_tx_and_wait node_admin gov vote $PROPOSAL_ID yes --fees 2000usei node: sei-node-1 - # since quorum is 0.5, we only need 2/4 votes and expect proposal to pass after 35 seconds - - cmd: sleep 35 - - cmd: seid q gov proposal $PROPOSAL_ID --output json | jq -r .status + # since quorum is 0.5, we only need 2/4 votes; drive progress until it passes + - cmd: seidbin=seid; chainid=sei; source integration_test/utils/_tx_helpers.sh && wait_for_proposal_status $PROPOSAL_ID PROPOSAL_STATUS_PASSED 120 admin env: PROPOSAL_STATUS # Get the migration batch size param again after proposal is passed - cmd: seid q params subspace migration NumKeysToMigratePerBlock --output json | jq -r .value | tr -d "\"" @@ -107,9 +105,8 @@ - cmd: seid q gov proposal $PROPOSAL_ID --output json | jq -r .status - cmd: seidbin=seid; chainid=sei; source integration_test/utils/_tx_helpers.sh && submit_tx_and_wait node_admin gov vote $PROPOSAL_ID yes --fees 2000usei node: sei-node-3 - # since expedited quorum is 0.9, we only need 4/4 votes and expect expedited proposal to pass after 20 seconds - - cmd: sleep 20 - - cmd: seid q gov proposal $PROPOSAL_ID --output json | jq -r .status + # since expedited quorum is 0.9, we need 4/4 votes; drive progress until it passes + - cmd: seidbin=seid; chainid=sei; source integration_test/utils/_tx_helpers.sh && wait_for_proposal_status $PROPOSAL_ID PROPOSAL_STATUS_PASSED 120 admin env: PROPOSAL_STATUS # Get the tally params again after proposal is passed - cmd: seid q gov params --output json | jq -r .tally_params.expedited_quorum @@ -135,9 +132,8 @@ # only sei-node-0 vote yes - cmd: seidbin=seid; chainid=sei; source integration_test/utils/_tx_helpers.sh && submit_tx_and_wait node_admin gov vote $PROPOSAL_ID yes --fees 2000usei node: sei-node-0 - # since expedited quorum is 0.75, we expect it to be rejected and burn tokens, the since expected proposal will auto convert to normal proposal, we need to wait 35 seconds - - cmd: sleep 35 - - cmd: seid q gov proposal $PROPOSAL_ID --output json | jq -r .status + # the expedited proposal auto-converts to regular; drive progress until it reaches a terminal status + - cmd: seidbin=seid; chainid=sei; source integration_test/utils/_tx_helpers.sh && wait_for_proposal_status $PROPOSAL_ID PROPOSAL_STATUS_REJECTED 120 admin env: PROPOSAL_STATUS # Get the tally params again after proposal is passed - cmd: seid q gov params --output json | jq -r .tally_params.expedited_quorum diff --git a/integration_test/gov_module/staking_proposal_test.yaml b/integration_test/gov_module/staking_proposal_test.yaml index 84ec6235b1..0f076b096a 100644 --- a/integration_test/gov_module/staking_proposal_test.yaml +++ b/integration_test/gov_module/staking_proposal_test.yaml @@ -26,9 +26,8 @@ - cmd: seid q gov proposal $PROPOSAL_ID --output json | jq -r .status - cmd: seidbin=seid; chainid=sei; source integration_test/utils/_tx_helpers.sh && submit_tx_and_wait node_admin gov vote $PROPOSAL_ID yes --fees 2000usei node: sei-node-3 - # since expedited quorum is 0.9, we only need 4/4 votes and expect expedited proposal to pass after 20 seconds - - cmd: sleep 20 - - cmd: seid q gov proposal $PROPOSAL_ID --output json | jq -r .status + # since expedited quorum is 0.9, we need 4/4 votes; drive progress until it passes + - cmd: seidbin=seid; chainid=sei; source integration_test/utils/_tx_helpers.sh && wait_for_proposal_status $PROPOSAL_ID PROPOSAL_STATUS_PASSED 120 admin env: PROPOSAL_STATUS # Get the params again after proposal is passed - cmd: seid q params subspace staking UnbondingTime --output json | jq -r .value | tr -d "\"" @@ -40,4 +39,4 @@ - type: eval expr: NEW_PARAM == 1814400000000000 - type: eval - expr: NEW_PARAM_FROM_STAKING == "1814400s" \ No newline at end of file + expr: NEW_PARAM_FROM_STAKING == "1814400s" diff --git a/integration_test/upgrade_module/scripts/proposal_wait_for_pass.sh b/integration_test/upgrade_module/scripts/proposal_wait_for_pass.sh index 84135cccb5..eb6e2ca5fb 100755 --- a/integration_test/upgrade_module/scripts/proposal_wait_for_pass.sh +++ b/integration_test/upgrade_module/scripts/proposal_wait_for_pass.sh @@ -2,21 +2,8 @@ PROPOSAL_ID=$1 TIMEOUT=300 # total wait time in seconds -INTERVAL=1 # time between checks in seconds -TRIES=$((TIMEOUT / INTERVAL)) # number of tries +seidbin=seid +source integration_test/utils/_tx_helpers.sh -# Loop until the proposal status is PROPOSAL_STATUS_PASSED or we timeout -for ((i=1; i<=TRIES; i++)); do - STATUS=$(seid query gov proposal $PROPOSAL_ID --output json | jq -r ".status") - - if [ "$STATUS" == "PROPOSAL_STATUS_PASSED" ]; then - echo "Proposal $PROPOSAL_ID has passed!" - exit 0 - else - echo "Waiting for proposal $PROPOSAL_ID to pass... ($i/$TRIES)" - sleep $INTERVAL - fi -done - -echo "Timeout reached. Exiting." -exit 1 +wait_for_proposal_status "$PROPOSAL_ID" "PROPOSAL_STATUS_PASSED" "$TIMEOUT" "admin" >/dev/null +echo "Proposal $PROPOSAL_ID has passed!" diff --git a/integration_test/utils/_tx_helpers.sh b/integration_test/utils/_tx_helpers.sh index 33cdc5c027..355fc51c48 100644 --- a/integration_test/utils/_tx_helpers.sh +++ b/integration_test/utils/_tx_helpers.sh @@ -110,6 +110,46 @@ wait_until_height_exceeds() { "[ \$($seidbin status | jq -r .SyncInfo.latest_block_height) -gt $min_height ]" } +get_proposal_status() { + $seidbin q gov proposal "$1" --output json 2>/dev/null | jq -r '.status // ""' +} + +wait_for_proposal_status() { + local proposal_id="$1" + local target_status="$2" + local timeout_secs="${3:-120}" + local from_key="${4:-admin}" + local from_addr; from_addr=$(printf "12345678\n" | $seidbin keys show "$from_key" -a 2>/dev/null) + local deadline=$(($(date +%s) + timeout_secs)) + local deadline_kick_sent=0 + local status="" + while [ "$(date +%s)" -lt "$deadline" ]; do + local raw; raw=$($seidbin q gov proposal "$proposal_id" --output json 2>/dev/null || true) + status=$(echo "$raw" | jq -r '.status // ""' 2>/dev/null) + if [ "$status" = "$target_status" ]; then + echo "$status" + return 0 + fi + if [ "$status" = "PROPOSAL_STATUS_REJECTED" ] || [ "$status" = "PROPOSAL_STATUS_FAILED" ]; then + echo "proposal $proposal_id reached terminal status $status while waiting for $target_status" >&2 + return 1 + fi + local voting_end + voting_end=$(echo "$raw" | jq -r '.voting_end_time // ""' 2>/dev/null) + local voting_end_epoch="" + if [ -n "$voting_end" ]; then + voting_end_epoch=$(date -d "$voting_end" +%s 2>/dev/null || true) + fi + if [ "$deadline_kick_sent" -eq 0 ] && [ -n "$voting_end_epoch" ] && [ "$(date +%s)" -ge $((voting_end_epoch + 1)) ]; then + bank_send_and_wait "$from_key" "$from_addr" "1usei" >/dev/null || return 1 + deadline_kick_sent=1 + fi + sleep 1 + done + echo "timed out waiting for proposal $proposal_id to reach $target_status (last status=${status:-unknown})" >&2 + return 1 +} + # Submit `tx ` via -b sync from , wait for # that sender's account sequence to advance, and echo the CheckTx code # (0 on success). On CheckTx rejection the rejection log is written to From a649a1c28653301896848a4f2be1d9d00039e328 Mon Sep 17 00:00:00 2001 From: Grzegorz Prusak Date: Mon, 13 Jul 2026 11:41:04 +0200 Subject: [PATCH 18/54] helpers dedup --- contracts/test/EVMCompatabilityTest.js | 11 +--- contracts/test/lib.js | 66 +++++++++++++------ integration_test/autobahn/autobahn_test.go | 34 +++++----- .../evm_module/ws_test/ws_test.go | 45 +------------ integration_test/utils/_tx_helpers.sh | 12 ++-- 5 files changed, 74 insertions(+), 94 deletions(-) diff --git a/contracts/test/EVMCompatabilityTest.js b/contracts/test/EVMCompatabilityTest.js index b47378c180..8a9224c27d 100644 --- a/contracts/test/EVMCompatabilityTest.js +++ b/contracts/test/EVMCompatabilityTest.js @@ -4,7 +4,7 @@ const {uniq} = require("lodash"); const hre = require('hardhat'); const { ethers, upgrades } = hre; const { getImplementationAddress } = require('@openzeppelin/upgrades-core'); -const { deployEvmContract, setupSigners, fundAddress } = require("./lib") +const { deployEvmContract, setupSigners, fundAddress, mineTransferBlock } = require("./lib") const axios = require("axios"); const { default: BigNumber } = require("bignumber.js"); @@ -21,15 +21,6 @@ function debug(msg) { // console.log(msg) } -async function mineTransferBlock(sender) { - const tx = await sender.sendTransaction({ - to: sender.address, - value: 1n, - gasPrice: ethers.parseUnits('100', 'gwei'), - }); - return await tx.wait(); -} - async function sendTransactionAndCheckGas(sender, recipient, amount) { // Get the balance of the sender before the transaction const balanceBefore = await ethers.provider.getBalance(sender.address); diff --git a/contracts/test/lib.js b/contracts/test/lib.js index b9764ac541..ea36404027 100644 --- a/contracts/test/lib.js +++ b/contracts/test/lib.js @@ -78,6 +78,15 @@ async function delay() { await sleep(1000) } +async function mineTransferBlock(sender) { + const tx = await sender.sendTransaction({ + to: sender.address, + value: 1n, + gasPrice: ethers.parseUnits('100', 'gwei'), + }) + return await tx.wait() +} + // Default 2 because the very next block after submit can be empty // Like provider.getTransactionReceipt, but treats the Autobahn-specific // "requested height N is not yet available; safe latest is N-1" @@ -249,6 +258,39 @@ async function getKeySeiAddress(name) { return (await execute(`seid keys show ${name} -a`)).trim() } +async function waitForProposalStatus( + proposalId, + targetStatus, + { timeoutMs = 50000, pollIntervalMs = 250, kickKeyName = adminKeyName } = {}, +) { + const kickAddr = await getKeySeiAddress(kickKeyName) + const deadline = Date.now() + timeoutMs + let deadlineKickSent = false + + while (Date.now() < deadline) { + const proposal = JSON.parse(await execute(`seid q gov proposal ${proposalId} -o json`)) + const status = proposal.status + if (status === targetStatus) { + return proposal + } + if ( + (status === "PROPOSAL_STATUS_REJECTED" || status === "PROPOSAL_STATUS_FAILED") && + status !== targetStatus + ) { + throw new Error(`Proposal ${proposalId} was rejected/failed with status: ${status}`) + } + + const votingEndMs = Date.parse(proposal.voting_end_time ?? "") + if (!deadlineKickSent && Number.isFinite(votingEndMs) && Date.now() >= votingEndMs + 1000) { + await bankSend(kickAddr, kickKeyName, "1", "usei") + deadlineKickSent = true + } + await sleep(pollIntervalMs) + } + + throw new Error(`could not observe proposal ${proposalId} reaching ${targetStatus}`) +} + // Best-effort helper for idempotent bootstrap paths that may run after an // address is already associated. async function associateKey(keyName) { @@ -771,26 +813,8 @@ async function passProposal(proposalId, desposit="200000000usei", fees="200000u } else { await execute(`seid tx gov vote ${proposalId} yes --from ${from} -b sync -y --fees ${fees}`) } - const adminAddr = await getKeySeiAddress(adminKeyName) - let deadlineKickSent = false - // Poll for proposal status with shorter delay for faster tests - for(let i=0; i<200; i++) { - const proposal = JSON.parse(await execute(`seid q gov proposal ${proposalId} -o json`)) - const status = proposal.status - if(status === "PROPOSAL_STATUS_PASSED") { - return proposalId - } - if(status === "PROPOSAL_STATUS_REJECTED" || status === "PROPOSAL_STATUS_FAILED") { - throw new Error(`Proposal ${proposalId} was rejected/failed with status: ${status}`) - } - const votingEndMs = Date.parse(proposal.voting_end_time ?? "") - if (!deadlineKickSent && Number.isFinite(votingEndMs) && Date.now() >= votingEndMs + 1000) { - await bankSend(adminAddr, adminKeyName, "1", "usei") - deadlineKickSent = true - } - await sleep(250) // Poll every 250ms instead of 1s for faster feedback - } - throw new Error("could not pass proposal "+proposalId) + await waitForProposalStatus(proposalId, "PROPOSAL_STATUS_PASSED") + return proposalId } async function registerPointerForERC20(erc20Address, fees="20000usei", from=adminKeyName) { @@ -1194,5 +1218,7 @@ module.exports = { waitForBaseFeeToEq, waitForBaseFeeToBeGt, waitForCondition, + waitForProposalStatus, getAccountSequence, + mineTransferBlock, }; diff --git a/integration_test/autobahn/autobahn_test.go b/integration_test/autobahn/autobahn_test.go index 219d86174a..e87766ddd9 100644 --- a/integration_test/autobahn/autobahn_test.go +++ b/integration_test/autobahn/autobahn_test.go @@ -13,6 +13,7 @@ package autobahn import ( "bufio" + "context" "encoding/json" "fmt" "io" @@ -24,6 +25,7 @@ import ( "testing" "time" + "github.com/sei-protocol/sei-chain/integration_test/internal/evmtest" tmjson "github.com/sei-protocol/sei-chain/sei-tendermint/libs/json" "github.com/sei-protocol/sei-chain/sei-tendermint/rpc/coretypes" ) @@ -216,17 +218,6 @@ func dockerExecAllowFail(container, script string) { _ = exec.Command("docker", "exec", container, "sh", "-c", script).Run() } -func parseTxHash(t *testing.T, output string) string { - t.Helper() - for _, line := range strings.Split(output, "\n") { - if hash, ok := strings.CutPrefix(strings.TrimSpace(line), "Transaction hash: "); ok { - return strings.TrimSpace(hash) - } - } - t.Fatalf("transaction hash not found in output:\n%s", output) - return "" -} - func waitForReceiptBlockNumber(t *testing.T, txHash string, timeout time.Duration) int64 { t.Helper() deadline := time.Now().Add(timeout) @@ -287,13 +278,20 @@ func evmBalanceHex(t *testing.T, address string) string { func sendEvmTx(t *testing.T, container string) string { t.Helper() - sendOut := dockerExec(t, container, - fmt.Sprintf( - "printf '12345678\\n' | seid tx evm send %s 1 --from node_admin --chain-id sei --evm-rpc %s -b sync -y 2>/dev/null", - testRecipientEVM, evmRPCURLOnContainerLocalhost, - ), - ) - return parseTxHash(t, sendOut) + ctx, cancel := context.WithTimeout(context.Background(), txFinalizeMax) + defer cancel() + txHash, err := evmtest.SendTinyEvmTx(ctx, evmtest.DockerTxConfig{ + Container: container, + Password: "12345678", + From: "node_admin", + Recipient: testRecipientEVM, + ChainID: "sei", + EVMRPCURL: evmRPCURLOnContainerLocalhost, + }) + if err != nil { + t.Fatalf("send evm tx: %v", err) + } + return txHash } func sendEvmTxAndWait(t *testing.T, container string) int64 { diff --git a/integration_test/evm_module/ws_test/ws_test.go b/integration_test/evm_module/ws_test/ws_test.go index 58011bb6cb..c7bfe9f19d 100644 --- a/integration_test/evm_module/ws_test/ws_test.go +++ b/integration_test/evm_module/ws_test/ws_test.go @@ -14,13 +14,12 @@ package ws_test import ( "context" - "fmt" "os" - "os/exec" "testing" "time" "github.com/gorilla/websocket" + "github.com/sei-protocol/sei-chain/integration_test/internal/evmtest" ) func wsURL() string { @@ -32,48 +31,10 @@ func wsURL() string { func triggerHead(t *testing.T) { t.Helper() - - container := os.Getenv("SEI_EVM_WS_TX_CONTAINER") - if container == "" { - container = "sei-node-0" - } - password := os.Getenv("SEI_EVM_WS_TX_PASSWORD") - if password == "" { - password = "12345678" - } - from := os.Getenv("SEI_EVM_WS_TX_FROM") - if from == "" { - from = "admin" - } - recipient := os.Getenv("SEI_EVM_WS_TX_RECIPIENT") - if recipient == "" { - recipient = "0xF87A299e6bC7bEba58dbBe5a5Aa21d49bCD16D52" - } - evmRPCURL := os.Getenv("SEI_EVM_WS_TX_EVM_RPC_URL") - if evmRPCURL == "" { - evmRPCURL = "http://localhost:8545" - } - ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) defer cancel() - cmd := exec.CommandContext( - ctx, - "docker", "exec", - "--env", fmt.Sprintf("SEI_EVM_WS_PASSWORD=%s", password), - container, - "/bin/bash", "-c", - `export PATH=$PATH:/root/go/bin && printf "%s\n" "$SEI_EVM_WS_PASSWORD" | "$@"`, - "bash", - "seid", "tx", "evm", "send", recipient, "1", - "--from", from, - "--chain-id", "sei", - "--evm-rpc", evmRPCURL, - "-b", "sync", - "-y", - ) - out, err := cmd.CombinedOutput() - if err != nil { - t.Fatalf("trigger head tx: %v\n%s", err, out) + if _, err := evmtest.SendTinyEvmTx(ctx, evmtest.ConfigFromEnv("SEI_EVM_WS_TX_")); err != nil { + t.Fatalf("trigger head tx: %v", err) } } diff --git a/integration_test/utils/_tx_helpers.sh b/integration_test/utils/_tx_helpers.sh index 355fc51c48..51e847e7de 100644 --- a/integration_test/utils/_tx_helpers.sh +++ b/integration_test/utils/_tx_helpers.sh @@ -20,6 +20,10 @@ _get_account_sequence() { $seidbin q account "$1" -o json 2>/dev/null | jq -r '.sequence // 0' } +_get_key_address() { + printf "12345678\n" | $seidbin keys show "$1" -a 2>/dev/null +} + # Cosmos account sequence for an address at a historical height. Echoes # the sequence number (0 if the account didn't exist yet at ), # or empty on query failure — distinct from 0 so the caller can retry @@ -57,7 +61,7 @@ bank_send_and_get_height() { local from_key="$1" local to_addr="$2" local amount_denom="$3" - local from_addr; from_addr=$(printf "12345678\n" | $seidbin keys show "$from_key" -a 2>/dev/null) + local from_addr; from_addr=$(_get_key_address "$from_key") local seq_before; seq_before=$(_get_account_sequence "$from_addr") local resp; resp=$(printf "12345678\n" | $seidbin tx bank send "$from_key" "$to_addr" "$amount_denom" \ -y --chain-id="$chainid" --gas=5000000 --fees=1000000usei \ @@ -119,7 +123,7 @@ wait_for_proposal_status() { local target_status="$2" local timeout_secs="${3:-120}" local from_key="${4:-admin}" - local from_addr; from_addr=$(printf "12345678\n" | $seidbin keys show "$from_key" -a 2>/dev/null) + local from_addr; from_addr=$(_get_key_address "$from_key") local deadline=$(($(date +%s) + timeout_secs)) local deadline_kick_sent=0 local status="" @@ -159,7 +163,7 @@ wait_for_proposal_status() { # Usage: code=$(submit_tx_and_wait ) submit_tx_and_wait() { local from_key="$1"; shift - local from_addr; from_addr=$(printf "12345678\n" | $seidbin keys show "$from_key" -a 2>/dev/null) + local from_addr; from_addr=$(_get_key_address "$from_key") local seq_before; seq_before=$(_get_account_sequence "$from_addr") local resp; resp=$(printf "12345678\n" | $seidbin tx "$@" --from "$from_key" \ -y --chain-id="$chainid" --broadcast-mode=sync --output=json) @@ -241,7 +245,7 @@ bank_send_and_wait() { local from_key="$1" local to_addr="$2" local amount_denom="$3" - local from_addr; from_addr=$(printf "12345678\n" | $seidbin keys show "$from_key" -a 2>/dev/null) + local from_addr; from_addr=$(_get_key_address "$from_key") local seq_before; seq_before=$(_get_account_sequence "$from_addr") local resp; resp=$(printf "12345678\n" | $seidbin tx bank send "$from_key" "$to_addr" "$amount_denom" \ -y --chain-id="$chainid" --gas=5000000 --fees=1000000usei \ From f93a5d51a336f4c68af5f49bac61cd8902432ef7 Mon Sep 17 00:00:00 2001 From: Grzegorz Prusak Date: Mon, 13 Jul 2026 11:46:24 +0200 Subject: [PATCH 19/54] simplify --- contracts/test/EVMCompatabilityTest.js | 2 + contracts/test/lib.js | 55 +++++++++++++------ integration_test/autobahn/autobahn_test.go | 4 ++ .../evm_module/scripts/evm_rpc_tests.sh | 2 + .../evm_module/ws_test/ws_test.go | 2 + integration_test/utils/_tx_helpers.sh | 7 ++- 6 files changed, 53 insertions(+), 19 deletions(-) diff --git a/contracts/test/EVMCompatabilityTest.js b/contracts/test/EVMCompatabilityTest.js index 8a9224c27d..2f1e5b5694 100644 --- a/contracts/test/EVMCompatabilityTest.js +++ b/contracts/test/EVMCompatabilityTest.js @@ -698,6 +698,8 @@ describe("EVM Test", function () { const block = await ethers.provider.getBlock(blockHeight); const baseFee = Number(block.baseFeePerGas); expect(baseFee).to.equal(oneGwei); + // Progress-only block: the assertion is about the next block's base + // fee, so force that block to exist explicitly. const nextReceipt = await mineTransferBlock(owner); expect(nextReceipt.blockNumber).to.be.greaterThan(blockHeight); const nextBlock = await ethers.provider.getBlock(nextReceipt.blockNumber); diff --git a/contracts/test/lib.js b/contracts/test/lib.js index ea36404027..ffa53d80f5 100644 --- a/contracts/test/lib.js +++ b/contracts/test/lib.js @@ -79,6 +79,8 @@ async function delay() { } async function mineTransferBlock(sender) { + // Progress-only self-transfer: under allow_empty_blocks=false, tests must + // actively create a block when they need "the next block" to exist. const tx = await sender.sendTransaction({ to: sender.address, value: 1n, @@ -261,34 +263,51 @@ async function getKeySeiAddress(name) { async function waitForProposalStatus( proposalId, targetStatus, - { timeoutMs = 50000, pollIntervalMs = 250, kickKeyName = adminKeyName } = {}, + { kickKeyName = adminKeyName } = {}, ) { - const kickAddr = await getKeySeiAddress(kickKeyName) - const deadline = Date.now() + timeoutMs - let deadlineKickSent = false - - while (Date.now() < deadline) { - const proposal = JSON.parse(await execute(`seid q gov proposal ${proposalId} -o json`)) - const status = proposal.status - if (status === targetStatus) { - return proposal - } + const readProposal = async () => JSON.parse(await execute(`seid q gov proposal ${proposalId} -o json`)) + const ensureNotFailed = (status) => { if ( (status === "PROPOSAL_STATUS_REJECTED" || status === "PROPOSAL_STATUS_FAILED") && status !== targetStatus ) { throw new Error(`Proposal ${proposalId} was rejected/failed with status: ${status}`) } + } - const votingEndMs = Date.parse(proposal.voting_end_time ?? "") - if (!deadlineKickSent && Number.isFinite(votingEndMs) && Date.now() >= votingEndMs + 1000) { - await bankSend(kickAddr, kickKeyName, "1", "usei") - deadlineKickSent = true - } - await sleep(pollIntervalMs) + let proposal = await readProposal() + if (proposal.status === targetStatus) { + return proposal } + ensureNotFailed(proposal.status) - throw new Error(`could not observe proposal ${proposalId} reaching ${targetStatus}`) + const votingEndMs = Date.parse(proposal.voting_end_time ?? "") + if (!Number.isFinite(votingEndMs)) { + throw new Error(`Proposal ${proposalId} is missing a valid voting_end_time`) + } + + const waitMs = votingEndMs + 1000 - Date.now() + if (waitMs > 0) { + await sleep(waitMs) + } + + proposal = await readProposal() + if (proposal.status === targetStatus) { + return proposal + } + ensureNotFailed(proposal.status) + + const kickAddr = await getKeySeiAddress(kickKeyName) + // Progress-only bank send: if voting_end_time has passed but no tx has + // arrived to trigger the tally block yet, force one committed block so the + // proposal can move to its terminal status. + await bankSend(kickAddr, kickKeyName, "1", "usei") + proposal = await readProposal() + if (proposal.status === targetStatus) { + return proposal + } + ensureNotFailed(proposal.status) + throw new Error(`Proposal ${proposalId} did not reach ${targetStatus}; current status: ${proposal.status}`) } // Best-effort helper for idempotent bootstrap paths that may run after an diff --git a/integration_test/autobahn/autobahn_test.go b/integration_test/autobahn/autobahn_test.go index e87766ddd9..ba70761b7d 100644 --- a/integration_test/autobahn/autobahn_test.go +++ b/integration_test/autobahn/autobahn_test.go @@ -278,6 +278,8 @@ func evmBalanceHex(t *testing.T, address string) string { func sendEvmTx(t *testing.T, container string) string { t.Helper() + // Progress-only tx: these subtests use "a tx finalized in a new block" as + // the observable signal that Autobahn is live or halted. ctx, cancel := context.WithTimeout(context.Background(), txFinalizeMax) defer cancel() txHash, err := evmtest.SendTinyEvmTx(ctx, evmtest.DockerTxConfig{ @@ -307,6 +309,8 @@ func sendEvmTxAndWait(t *testing.T, container string) int64 { func sendEvmTxExpectNoInclusion(t *testing.T, container string, baseHeight int64, timeout time.Duration) { t.Helper() + // Progress-only tx: after quorum loss, this should remain uncommitted and + // height should stay fixed, proving that no new block can be produced. txHash := sendEvmTx(t, container) deadline := time.Now().Add(timeout) last := baseHeight diff --git a/integration_test/evm_module/scripts/evm_rpc_tests.sh b/integration_test/evm_module/scripts/evm_rpc_tests.sh index 730cd5acc5..6939efedff 100755 --- a/integration_test/evm_module/scripts/evm_rpc_tests.sh +++ b/integration_test/evm_module/scripts/evm_rpc_tests.sh @@ -83,6 +83,8 @@ bump_chain_to_height() { while [[ "$height" =~ ^[0-9]+$ ]] && [ "$height" -lt "$target" ]; do local seq_before; seq_before=$(get_from_seq) local prev_height="$height" + # Progress-only EVM send: these historical RPC fixtures need real block + # history to exist first when empty blocks are disabled. run seid tx evm send "$RECIPIENT" 1 --from "$FROM" "${KEYRING_ARGS[@]}" --chain-id sei --evm-rpc "$EVM_RPC_URL" -b sync -y >/dev/null wait_from_seq_advance "$seq_before" wait_until_height_exceeds "$prev_height" diff --git a/integration_test/evm_module/ws_test/ws_test.go b/integration_test/evm_module/ws_test/ws_test.go index c7bfe9f19d..d730ba66c8 100644 --- a/integration_test/evm_module/ws_test/ws_test.go +++ b/integration_test/evm_module/ws_test/ws_test.go @@ -31,6 +31,8 @@ func wsURL() string { func triggerHead(t *testing.T) { t.Helper() + // Progress-only tx: newHeads needs one real block after subscription, and + // under allow_empty_blocks=false we must create that block explicitly. ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) defer cancel() if _, err := evmtest.SendTinyEvmTx(ctx, evmtest.ConfigFromEnv("SEI_EVM_WS_TX_")); err != nil { diff --git a/integration_test/utils/_tx_helpers.sh b/integration_test/utils/_tx_helpers.sh index 51e847e7de..7c2edae4ad 100644 --- a/integration_test/utils/_tx_helpers.sh +++ b/integration_test/utils/_tx_helpers.sh @@ -52,7 +52,9 @@ _wait_until() { # Submit `bank send` via -b sync and echo the exact block height at # which the tx committed. Useful when callers need the inclusion # height for state-at-height queries (e.g., historical balance lookups -# at height-1 vs height to validate per-block granularity). +# at height-1 vs height to validate per-block granularity). Some +# callers intentionally self-send a dust amount purely to force the +# chain to produce a real block under allow_empty_blocks=false. # Implementation: after the sender's sequence advances, walk back from # the observed height querying historical sequence — the largest H # where sequence(H) is still pre-tx is one below the inclusion height. @@ -145,6 +147,9 @@ wait_for_proposal_status() { voting_end_epoch=$(date -d "$voting_end" +%s 2>/dev/null || true) fi if [ "$deadline_kick_sent" -eq 0 ] && [ -n "$voting_end_epoch" ] && [ "$(date +%s)" -ge $((voting_end_epoch + 1)) ]; then + # Progress-only self-send: once voting has ended, a proposal may + # still need one more committed block to be tallied. Force that + # block explicitly instead of waiting for unrelated traffic. bank_send_and_wait "$from_key" "$from_addr" "1usei" >/dev/null || return 1 deadline_kick_sent=1 fi From c931ea974a42052ca2442454fa4e8dc51e569215 Mon Sep 17 00:00:00 2001 From: Grzegorz Prusak Date: Mon, 13 Jul 2026 12:20:21 +0200 Subject: [PATCH 20/54] golang improvements --- docker/localnode/scripts/step5_start_sei.sh | 6 +++- evmrpc/simulate_test.go | 32 ++++++++------------- integration_test/autobahn/autobahn_test.go | 2 +- 3 files changed, 18 insertions(+), 22 deletions(-) diff --git a/docker/localnode/scripts/step5_start_sei.sh b/docker/localnode/scripts/step5_start_sei.sh index ef27d270a6..4474d768f5 100755 --- a/docker/localnode/scripts/step5_start_sei.sh +++ b/docker/localnode/scripts/step5_start_sei.sh @@ -12,7 +12,11 @@ seid start --chain-id sei --inv-check-period ${INVARIANT_CHECK_INTERVAL} > "$LOG SEID_PID=$! echo "Node $NODE_ID seid is started now" -until seid status >/dev/null 2>&1 && seid q tendermint-validator-set >/dev/null 2>&1 +# launch.complete means the node's query surface is available, not merely that +# the process has started. The specific query here is arbitrary; any simple +# query would do. We use tendermint-validator-set because startup tests already +# rely on it and it exercises the CLI query path we need to be live. +until seid q tendermint-validator-set >/dev/null 2>&1 do if ! kill -0 "$SEID_PID" 2>/dev/null; then echo "seid exited before becoming ready; see $LOG_DIR/seid-$NODE_ID.log" diff --git a/evmrpc/simulate_test.go b/evmrpc/simulate_test.go index 1f6c754ee7..ea96756488 100644 --- a/evmrpc/simulate_test.go +++ b/evmrpc/simulate_test.go @@ -674,13 +674,11 @@ func TestSimulationAPIRequestLimiter(t *testing.T) { // Release all requests at once to maximize contention on the limiter. for range numRequests { - wg.Add(1) - go func() { - defer wg.Done() + wg.Go(func() { <-start - _, err := tEnv.simAPI.Call(context.Background(), tEnv.args, nil, nil, nil) + _, err := tEnv.simAPI.Call(t.Context(), tEnv.args, nil, nil, nil) results <- err - }() + }) } close(start) @@ -909,22 +907,18 @@ func TestSimulationAPIRequestLimiter(t *testing.T) { // Start mixed requests and release them at once to maximize contention. for range numCallRequests { - wg.Add(1) - go func() { - defer wg.Done() + wg.Go(func() { <-start - _, err := tEnv.simAPI.Call(context.Background(), tEnv.args, nil, nil, nil) + _, err := tEnv.simAPI.Call(t.Context(), tEnv.args, nil, nil, nil) results <- err - }() + }) } for range numEstimateRequests { - wg.Add(1) - go func() { - defer wg.Done() + wg.Go(func() { <-start - _, err := tEnv.simAPI.EstimateGas(context.Background(), tEnv.args, nil, nil) + _, err := tEnv.simAPI.EstimateGas(t.Context(), tEnv.args, nil, nil) results <- err - }() + }) } close(start) @@ -995,13 +989,11 @@ func TestSimulationAPIRequestLimiter(t *testing.T) { // Release all requests at once to reliably saturate the limiter. for range numRequests { - wg.Add(1) - go func() { - defer wg.Done() + wg.Go(func() { <-start - _, err := tEnv.simAPI.Call(context.Background(), tEnv.args, nil, nil, nil) + _, err := tEnv.simAPI.Call(t.Context(), tEnv.args, nil, nil, nil) results <- err - }() + }) } close(start) wg.Wait() diff --git a/integration_test/autobahn/autobahn_test.go b/integration_test/autobahn/autobahn_test.go index ba70761b7d..858f1f2dd7 100644 --- a/integration_test/autobahn/autobahn_test.go +++ b/integration_test/autobahn/autobahn_test.go @@ -280,7 +280,7 @@ func sendEvmTx(t *testing.T, container string) string { t.Helper() // Progress-only tx: these subtests use "a tx finalized in a new block" as // the observable signal that Autobahn is live or halted. - ctx, cancel := context.WithTimeout(context.Background(), txFinalizeMax) + ctx, cancel := context.WithTimeout(t.Context(), txFinalizeMax) defer cancel() txHash, err := evmtest.SendTinyEvmTx(ctx, evmtest.DockerTxConfig{ Container: container, From c6f98c4a44cf8081c6d7a761399934bf79da084f Mon Sep 17 00:00:00 2001 From: Grzegorz Prusak Date: Mon, 13 Jul 2026 14:00:30 +0200 Subject: [PATCH 21/54] updates --- evmrpc/simulate_test.go | 134 ++++++------------ integration_test/autobahn/autobahn_test.go | 44 +++--- .../contracts/create_tokenfactory_denoms.sh | 4 + .../contracts/deploy_wasm_contracts.sh | 4 + .../contracts/verify_flatkv_evm_migrate.sh | 2 +- .../evm_module/scripts/evm_rpc_tests.sh | 10 +- .../gov_module/gov_proposal_test.yaml | 8 +- .../gov_module/staking_proposal_test.yaml | 2 +- .../scripts/proposal_wait_for_pass.sh | 3 +- integration_test/utils/_tx_helpers.sh | 82 ++++++----- sei-tendermint/AGENTS.md | 9 +- 11 files changed, 134 insertions(+), 168 deletions(-) diff --git a/evmrpc/simulate_test.go b/evmrpc/simulate_test.go index ea96756488..7d976f60f5 100644 --- a/evmrpc/simulate_test.go +++ b/evmrpc/simulate_test.go @@ -663,10 +663,7 @@ func TestSimulationAPIRequestLimiter(t *testing.T) { t.Run("TestEthCallRateLimiting", func(t *testing.T) { tEnv := newTestEnv(t) // Test eth_call rate limiting with concurrent requests - const ( - numRequests = 10 // Much more than the limit of 2 - maxAttempts = 5 - ) + const numRequests = 10 // Much more than the limit of 2 runBurst := func() []error { results := make(chan error, numRequests) start := make(chan struct{}) @@ -692,38 +689,23 @@ func TestSimulationAPIRequestLimiter(t *testing.T) { return errors } - var ( - successCount int - rejectedCount int - attemptsUsed int - observedRejection bool - ) - for attempt := 1; attempt <= maxAttempts; attempt++ { - attemptsUsed = attempt - successCount = 0 - rejectedCount = 0 - - for _, err := range runBurst() { - if err == nil { - successCount++ - } else if strings.Contains(err.Error(), "eth_call rejected due to rate limit: server busy") { - rejectedCount++ - } else { - t.Logf("Unexpected error: %v", err) - } - } - - require.Equalf(t, numRequests, successCount+rejectedCount, "All requests should be accounted for (attempt %d)", attempt) - if rejectedCount > 0 { - observedRejection = true - break + successCount := 0 + rejectedCount := 0 + for _, err := range runBurst() { + if err == nil { + successCount++ + } else if strings.Contains(err.Error(), "eth_call rejected due to rate limit: server busy") { + rejectedCount++ + } else { + t.Logf("Unexpected error: %v", err) } } - require.Truef(t, observedRejection, "Should have rejected requests due to rate limiting (last burst: %d successful, %d rejected)", successCount, rejectedCount) + require.Equal(t, numRequests, successCount+rejectedCount, "All requests should be accounted for") + require.Greaterf(t, rejectedCount, 0, "Should have rejected requests due to rate limiting (burst: %d successful, %d rejected)", successCount, rejectedCount) require.Greater(t, successCount, 0, "Should have some successful requests") - t.Logf("eth_call rate limiting (attempt %d/%d): %d successful, %d rejected out of %d total", attemptsUsed, maxAttempts, successCount, rejectedCount, numRequests) + t.Logf("eth_call rate limiting: %d successful, %d rejected out of %d total", successCount, rejectedCount, numRequests) }) t.Run("TestEstimateGasRateLimiting", func(t *testing.T) { @@ -891,12 +873,9 @@ func TestSimulationAPIRequestLimiter(t *testing.T) { t.Run("TestDifferentMethodsShareSameLimiter", func(t *testing.T) { // Test that different simulation methods share the same rate limiter. - // A single burst can occasionally avoid contention on overloaded CI workers, - // so retry a synchronized burst a few times. const ( numCallRequests = 20 numEstimateRequests = 20 - maxAttempts = 5 ) totalRequests := numCallRequests + numEstimateRequests @@ -937,33 +916,19 @@ func TestSimulationAPIRequestLimiter(t *testing.T) { return successCount, rejectedCount } - var ( - lastSuccess int - lastRejected int - attemptsUsed int - observedRejection bool - ) - for attempt := 1; attempt <= maxAttempts; attempt++ { - attemptsUsed = attempt - lastSuccess, lastRejected = runMixedBurst(newTestEnv(t)) - require.Equalf(t, totalRequests, lastSuccess+lastRejected, "All mixed method requests should be accounted for (attempt %d)", attempt) - if lastRejected > 0 { - observedRejection = true - break - } - } + lastSuccess, lastRejected := runMixedBurst(newTestEnv(t)) + require.Equal(t, totalRequests, lastSuccess+lastRejected, "All mixed method requests should be accounted for") - require.Truef( + require.Greaterf( t, - observedRejection, - "Different methods should share the same rate limiter (last burst: %d successful, %d rejected)", + lastRejected, + 0, + "Different methods should share the same rate limiter (burst: %d successful, %d rejected)", lastSuccess, lastRejected, ) t.Logf( - "Mixed methods rate limiting (attempt %d/%d): %d successful, %d rejected out of %d total", - attemptsUsed, - maxAttempts, + "Mixed methods rate limiting: %d successful, %d rejected out of %d total", lastSuccess, lastRejected, totalRequests, @@ -973,44 +938,31 @@ func TestSimulationAPIRequestLimiter(t *testing.T) { t.Run("TestRateLimitErrorFormat", func(t *testing.T) { tEnv := newTestEnv(t) // Test the error message format by overwhelming the rate limiter - const ( - numRequests = 20 - maxAttempts = 5 - ) - var ( - rateLimitErrors []error - attemptsUsed int - ) - for attempt := 1; attempt <= maxAttempts; attempt++ { - attemptsUsed = attempt - results := make(chan error, numRequests) - start := make(chan struct{}) - var wg sync.WaitGroup - - // Release all requests at once to reliably saturate the limiter. - for range numRequests { - wg.Go(func() { - <-start - _, err := tEnv.simAPI.Call(t.Context(), tEnv.args, nil, nil, nil) - results <- err - }) - } - close(start) - wg.Wait() - close(results) - - rateLimitErrors = rateLimitErrors[:0] - for err := range results { - if err != nil && strings.Contains(err.Error(), "rejected due to rate limit") { - rateLimitErrors = append(rateLimitErrors, err) - } - } - if len(rateLimitErrors) > 0 { - break + const numRequests = 20 + results := make(chan error, numRequests) + start := make(chan struct{}) + var wg sync.WaitGroup + + // Release all requests at once to reliably saturate the limiter. + for range numRequests { + wg.Go(func() { + <-start + _, err := tEnv.simAPI.Call(t.Context(), tEnv.args, nil, nil, nil) + results <- err + }) + } + close(start) + wg.Wait() + close(results) + + var rateLimitErrors []error + for err := range results { + if err != nil && strings.Contains(err.Error(), "rejected due to rate limit") { + rateLimitErrors = append(rateLimitErrors, err) } } - require.Greaterf(t, len(rateLimitErrors), 0, "Should have at least one rate limit error (attempts %d)", attemptsUsed) + require.Greater(t, len(rateLimitErrors), 0, "Should have at least one rate limit error") // Verify error message format for _, err := range rateLimitErrors { @@ -1018,7 +970,7 @@ func TestSimulationAPIRequestLimiter(t *testing.T) { require.Contains(t, err.Error(), "server busy") } - t.Logf("Found %d rate limit errors with correct format (attempt %d/%d)", len(rateLimitErrors), attemptsUsed, maxAttempts) + t.Logf("Found %d rate limit errors with correct format", len(rateLimitErrors)) }) } diff --git a/integration_test/autobahn/autobahn_test.go b/integration_test/autobahn/autobahn_test.go index 858f1f2dd7..2df63486dd 100644 --- a/integration_test/autobahn/autobahn_test.go +++ b/integration_test/autobahn/autobahn_test.go @@ -13,7 +13,6 @@ package autobahn import ( "bufio" - "context" "encoding/json" "fmt" "io" @@ -75,7 +74,6 @@ const ( // which takes ~60s to drain on top of the halt-detection window). // CI runners are slower than local; 1m was tight enough to flake. haltStableTimeout = 2 * time.Minute - txFinalizeMax = 30 * time.Second testRecipientEVM = "0x1000000000000000000000000000000000000001" ) @@ -218,10 +216,12 @@ func dockerExecAllowFail(container, script string) { _ = exec.Command("docker", "exec", container, "sh", "-c", script).Run() } -func waitForReceiptBlockNumber(t *testing.T, txHash string, timeout time.Duration) int64 { +func waitForReceiptBlockNumber(t *testing.T, txHash string) int64 { t.Helper() - deadline := time.Now().Add(timeout) - for time.Now().Before(deadline) { + for { + if err := t.Context().Err(); err != nil { + t.Fatalf("receipt for %s not observed before test context ended: %v", txHash, err) + } resp, err := evmRPCInContainer(fullnodeContainer, "eth_getTransactionReceipt", []any{txHash}) if err != nil { time.Sleep(heightPoll) @@ -256,8 +256,6 @@ func waitForReceiptBlockNumber(t *testing.T, txHash string, timeout time.Duratio } return height } - t.Fatalf("receipt for %s not observed within %s", txHash, timeout) - return 0 } func evmBalanceHex(t *testing.T, address string) string { @@ -280,9 +278,7 @@ func sendEvmTx(t *testing.T, container string) string { t.Helper() // Progress-only tx: these subtests use "a tx finalized in a new block" as // the observable signal that Autobahn is live or halted. - ctx, cancel := context.WithTimeout(t.Context(), txFinalizeMax) - defer cancel() - txHash, err := evmtest.SendTinyEvmTx(ctx, evmtest.DockerTxConfig{ + txHash, err := evmtest.SendTinyEvmTx(t.Context(), evmtest.DockerTxConfig{ Container: container, Password: "12345678", From: "node_admin", @@ -300,33 +296,27 @@ func sendEvmTxAndWait(t *testing.T, container string) int64 { t.Helper() baseHeight := currentHeight(t) txHash := sendEvmTx(t, container) - receiptHeight := waitForReceiptBlockNumber(t, txHash, txFinalizeMax) + receiptHeight := waitForReceiptBlockNumber(t, txHash) if receiptHeight <= baseHeight { t.Fatalf("expected tx %s to land after height %d, got receipt at %d", txHash, baseHeight, receiptHeight) } return receiptHeight } -func sendEvmTxExpectNoInclusion(t *testing.T, container string, baseHeight int64, timeout time.Duration) { +func sendEvmTxExpectNoInclusion(t *testing.T, container string, baseHeight int64) { t.Helper() // Progress-only tx: after quorum loss, this should remain uncommitted and // height should stay fixed, proving that no new block can be produced. txHash := sendEvmTx(t, container) - deadline := time.Now().Add(timeout) - last := baseHeight - for time.Now().Before(deadline) { - resp, err := evmRPCInContainer(fullnodeContainer, "eth_getTransactionReceipt", []any{txHash}) - if err == nil && resp != nil && resp.Error == nil && string(resp.Result) != "null" && len(resp.Result) > 0 { - t.Fatalf("expected no inclusion after quorum loss, but tx %s received receipt %s", txHash, resp.Result) - } - h := currentHeight(t) - if h > baseHeight { - t.Fatalf("expected no inclusion after quorum loss, but height advanced from %d to %d", baseHeight, h) - } - last = h - time.Sleep(heightPoll) + hAfter := waitForStableHeight(t, haltStableWindow, haltStableTimeout) + if hAfter != baseHeight { + t.Fatalf("expected no inclusion after quorum loss, but height advanced from %d to %d", baseHeight, hAfter) + } + resp, err := evmRPCInContainer(fullnodeContainer, "eth_getTransactionReceipt", []any{txHash}) + if err == nil && resp != nil && resp.Error == nil && string(resp.Result) != "null" && len(resp.Result) > 0 { + t.Fatalf("expected no inclusion after quorum loss, but tx %s received receipt %s", txHash, resp.Result) } - t.Logf("height stayed at %d after submitted tx", last) + t.Logf("height stayed at %d after submitted tx", hAfter) } // TestMain brings up the autobahn docker cluster before the test runs and @@ -816,5 +806,5 @@ func testHaltsBeyondMaxFaults(t *testing.T) { killNode(t, clusterSize-1-maxFaults) hBefore := waitForStableHeight(t, haltStableWindow, haltStableTimeout) t.Logf("height: %d (expecting halt)", hBefore) - sendEvmTxExpectNoInclusion(t, "sei-node-0", hBefore, haltStableWindow) + sendEvmTxExpectNoInclusion(t, "sei-node-0", hBefore) } diff --git a/integration_test/contracts/create_tokenfactory_denoms.sh b/integration_test/contracts/create_tokenfactory_denoms.sh index 4e7518dfe8..dc036224be 100755 --- a/integration_test/contracts/create_tokenfactory_denoms.sh +++ b/integration_test/contracts/create_tokenfactory_denoms.sh @@ -11,6 +11,10 @@ source "$(dirname "$0")/../utils/_tx_helpers.sh" cd $seihome || exit echo "Deploying first set of tokenfactory denoms..." +# Capture a committed baseline height before the first denom creation. This +# script later records and compares state around "beginning block height", so +# under allow_empty_blocks=false we must force one real block and remember the +# exact inclusion height instead of sampling status at height 0 / an idle tip. bootstrap_block_height=$(bank_send_and_get_height "$keyname" "$keyaddress" "1usei") || exit 1 beginning_block_height="$bootstrap_block_height" echo "$beginning_block_height" > $seihome/integration_test/contracts/tfk_beginning_block_height.txt diff --git a/integration_test/contracts/deploy_wasm_contracts.sh b/integration_test/contracts/deploy_wasm_contracts.sh index 67afb313fd..038a8c6a0a 100755 --- a/integration_test/contracts/deploy_wasm_contracts.sh +++ b/integration_test/contracts/deploy_wasm_contracts.sh @@ -11,6 +11,10 @@ source "$(dirname "$0")/../utils/_tx_helpers.sh" cd $seihome || exit echo "Deploying first set of contracts..." +# Capture a committed baseline height before storing the first wasm contracts. +# The follow-up checks use this "beginning block height" as the last state +# before any contract writes, so under allow_empty_blocks=false we force one +# real block and persist its exact inclusion height. bootstrap_block_height=$(bank_send_and_get_height "$keyname" "$keyaddress" "1usei") || exit 1 beginning_block_height="$bootstrap_block_height" echo "$beginning_block_height" > $seihome/integration_test/contracts/wasm_beginning_block_height.txt diff --git a/integration_test/contracts/verify_flatkv_evm_migrate.sh b/integration_test/contracts/verify_flatkv_evm_migrate.sh index c306b9f3ef..36bad31ce8 100755 --- a/integration_test/contracts/verify_flatkv_evm_migrate.sh +++ b/integration_test/contracts/verify_flatkv_evm_migrate.sh @@ -763,7 +763,7 @@ EOF" cd /sei-protocol/sei-chain seidbin=build/seid chainid=sei source integration_test/utils/_tx_helpers.sh - wait_for_proposal_status '$proposal_id' PROPOSAL_STATUS_PASSED '$GOV_PASS_TIMEOUT' admin + wait_for_proposal_status '$proposal_id' PROPOSAL_STATUS_PASSED admin " 2>/dev/null || true) if [ "$status" != "PROPOSAL_STATUS_PASSED" ]; then echo "ERROR: migration param-change proposal $proposal_id did not pass within ${GOV_PASS_TIMEOUT}s (last status=$status)" >&2 diff --git a/integration_test/evm_module/scripts/evm_rpc_tests.sh b/integration_test/evm_module/scripts/evm_rpc_tests.sh index 6939efedff..1087560ec9 100755 --- a/integration_test/evm_module/scripts/evm_rpc_tests.sh +++ b/integration_test/evm_module/scripts/evm_rpc_tests.sh @@ -45,7 +45,7 @@ get_latest_height() { echo "${h:-0}" } -# Wait until $FROM_ADDR's sequence advances past $1, with a 5s timeout. +# Wait until $FROM_ADDR's sequence advances past $1. # Direct causal "previous tx committed" signal: the sender's sequence # advances atomically when its tx is included in a block, so by the # time this returns the next CLI's pre-flight `q account` will read @@ -53,8 +53,7 @@ get_latest_height() { wait_from_seq_advance() { local prev="$1" if [ -z "$FROM_ADDR" ] || [ -z "$prev" ]; then return 0; fi - local deadline=$(($(date +%s) + 5)) - while [ "$(date +%s)" -lt "$deadline" ]; do + while true; do local cur; cur=$(get_from_seq) if [[ "$cur" =~ ^[0-9]+$ ]] && [ "$cur" -gt "$prev" ]; then return 0 @@ -65,16 +64,13 @@ wait_from_seq_advance() { wait_until_height_exceeds() { local prev="$1" - local deadline=$(($(date +%s) + 15)) - while [ "$(date +%s)" -lt "$deadline" ]; do + while true; do local cur; cur=$(get_latest_height) if [[ "$cur" =~ ^[0-9]+$ ]] && [ "$cur" -gt "$prev" ]; then return 0 fi sleep 0.5 done - echo "timed out waiting for height to exceed ${prev}" >&2 - return 1 } bump_chain_to_height() { diff --git a/integration_test/gov_module/gov_proposal_test.yaml b/integration_test/gov_module/gov_proposal_test.yaml index a0eb07ec28..afb276b165 100644 --- a/integration_test/gov_module/gov_proposal_test.yaml +++ b/integration_test/gov_module/gov_proposal_test.yaml @@ -19,7 +19,7 @@ - cmd: seidbin=seid; chainid=sei; source integration_test/utils/_tx_helpers.sh && submit_tx_and_wait node_admin gov vote $PROPOSAL_ID yes --fees 2000usei node: sei-node-1 # since quorum is 0.5, we only need 2/4 votes; drive progress until it passes - - cmd: seidbin=seid; chainid=sei; source integration_test/utils/_tx_helpers.sh && wait_for_proposal_status $PROPOSAL_ID PROPOSAL_STATUS_PASSED 120 admin + - cmd: seidbin=seid; chainid=sei; source integration_test/utils/_tx_helpers.sh && wait_for_proposal_status $PROPOSAL_ID PROPOSAL_STATUS_PASSED admin env: PROPOSAL_STATUS # Get the tally params again after proposal is passed - cmd: seid q gov params --output json | jq -r .tally_params.quorum @@ -62,7 +62,7 @@ - cmd: seidbin=seid; chainid=sei; source integration_test/utils/_tx_helpers.sh && submit_tx_and_wait node_admin gov vote $PROPOSAL_ID yes --fees 2000usei node: sei-node-1 # since quorum is 0.5, we only need 2/4 votes; drive progress until it passes - - cmd: seidbin=seid; chainid=sei; source integration_test/utils/_tx_helpers.sh && wait_for_proposal_status $PROPOSAL_ID PROPOSAL_STATUS_PASSED 120 admin + - cmd: seidbin=seid; chainid=sei; source integration_test/utils/_tx_helpers.sh && wait_for_proposal_status $PROPOSAL_ID PROPOSAL_STATUS_PASSED admin env: PROPOSAL_STATUS # Get the migration batch size param again after proposal is passed - cmd: seid q params subspace migration NumKeysToMigratePerBlock --output json | jq -r .value | tr -d "\"" @@ -106,7 +106,7 @@ - cmd: seidbin=seid; chainid=sei; source integration_test/utils/_tx_helpers.sh && submit_tx_and_wait node_admin gov vote $PROPOSAL_ID yes --fees 2000usei node: sei-node-3 # since expedited quorum is 0.9, we need 4/4 votes; drive progress until it passes - - cmd: seidbin=seid; chainid=sei; source integration_test/utils/_tx_helpers.sh && wait_for_proposal_status $PROPOSAL_ID PROPOSAL_STATUS_PASSED 120 admin + - cmd: seidbin=seid; chainid=sei; source integration_test/utils/_tx_helpers.sh && wait_for_proposal_status $PROPOSAL_ID PROPOSAL_STATUS_PASSED admin env: PROPOSAL_STATUS # Get the tally params again after proposal is passed - cmd: seid q gov params --output json | jq -r .tally_params.expedited_quorum @@ -133,7 +133,7 @@ - cmd: seidbin=seid; chainid=sei; source integration_test/utils/_tx_helpers.sh && submit_tx_and_wait node_admin gov vote $PROPOSAL_ID yes --fees 2000usei node: sei-node-0 # the expedited proposal auto-converts to regular; drive progress until it reaches a terminal status - - cmd: seidbin=seid; chainid=sei; source integration_test/utils/_tx_helpers.sh && wait_for_proposal_status $PROPOSAL_ID PROPOSAL_STATUS_REJECTED 120 admin + - cmd: seidbin=seid; chainid=sei; source integration_test/utils/_tx_helpers.sh && wait_for_proposal_status $PROPOSAL_ID PROPOSAL_STATUS_REJECTED admin env: PROPOSAL_STATUS # Get the tally params again after proposal is passed - cmd: seid q gov params --output json | jq -r .tally_params.expedited_quorum diff --git a/integration_test/gov_module/staking_proposal_test.yaml b/integration_test/gov_module/staking_proposal_test.yaml index 0f076b096a..e8701d261c 100644 --- a/integration_test/gov_module/staking_proposal_test.yaml +++ b/integration_test/gov_module/staking_proposal_test.yaml @@ -27,7 +27,7 @@ - cmd: seidbin=seid; chainid=sei; source integration_test/utils/_tx_helpers.sh && submit_tx_and_wait node_admin gov vote $PROPOSAL_ID yes --fees 2000usei node: sei-node-3 # since expedited quorum is 0.9, we need 4/4 votes; drive progress until it passes - - cmd: seidbin=seid; chainid=sei; source integration_test/utils/_tx_helpers.sh && wait_for_proposal_status $PROPOSAL_ID PROPOSAL_STATUS_PASSED 120 admin + - cmd: seidbin=seid; chainid=sei; source integration_test/utils/_tx_helpers.sh && wait_for_proposal_status $PROPOSAL_ID PROPOSAL_STATUS_PASSED admin env: PROPOSAL_STATUS # Get the params again after proposal is passed - cmd: seid q params subspace staking UnbondingTime --output json | jq -r .value | tr -d "\"" diff --git a/integration_test/upgrade_module/scripts/proposal_wait_for_pass.sh b/integration_test/upgrade_module/scripts/proposal_wait_for_pass.sh index eb6e2ca5fb..0647ab9e43 100755 --- a/integration_test/upgrade_module/scripts/proposal_wait_for_pass.sh +++ b/integration_test/upgrade_module/scripts/proposal_wait_for_pass.sh @@ -1,9 +1,8 @@ #!/bin/bash PROPOSAL_ID=$1 -TIMEOUT=300 # total wait time in seconds seidbin=seid source integration_test/utils/_tx_helpers.sh -wait_for_proposal_status "$PROPOSAL_ID" "PROPOSAL_STATUS_PASSED" "$TIMEOUT" "admin" >/dev/null +wait_for_proposal_status "$PROPOSAL_ID" "PROPOSAL_STATUS_PASSED" "admin" >/dev/null echo "Proposal $PROPOSAL_ID has passed!" diff --git a/integration_test/utils/_tx_helpers.sh b/integration_test/utils/_tx_helpers.sh index 7c2edae4ad..0910246710 100644 --- a/integration_test/utils/_tx_helpers.sh +++ b/integration_test/utils/_tx_helpers.sh @@ -123,39 +123,57 @@ get_proposal_status() { wait_for_proposal_status() { local proposal_id="$1" local target_status="$2" - local timeout_secs="${3:-120}" - local from_key="${4:-admin}" + local from_key="${3:-admin}" local from_addr; from_addr=$(_get_key_address "$from_key") - local deadline=$(($(date +%s) + timeout_secs)) - local deadline_kick_sent=0 - local status="" - while [ "$(date +%s)" -lt "$deadline" ]; do - local raw; raw=$($seidbin q gov proposal "$proposal_id" --output json 2>/dev/null || true) - status=$(echo "$raw" | jq -r '.status // ""' 2>/dev/null) - if [ "$status" = "$target_status" ]; then - echo "$status" - return 0 - fi - if [ "$status" = "PROPOSAL_STATUS_REJECTED" ] || [ "$status" = "PROPOSAL_STATUS_FAILED" ]; then - echo "proposal $proposal_id reached terminal status $status while waiting for $target_status" >&2 - return 1 - fi - local voting_end - voting_end=$(echo "$raw" | jq -r '.voting_end_time // ""' 2>/dev/null) - local voting_end_epoch="" - if [ -n "$voting_end" ]; then - voting_end_epoch=$(date -d "$voting_end" +%s 2>/dev/null || true) - fi - if [ "$deadline_kick_sent" -eq 0 ] && [ -n "$voting_end_epoch" ] && [ "$(date +%s)" -ge $((voting_end_epoch + 1)) ]; then - # Progress-only self-send: once voting has ended, a proposal may - # still need one more committed block to be tallied. Force that - # block explicitly instead of waiting for unrelated traffic. - bank_send_and_wait "$from_key" "$from_addr" "1usei" >/dev/null || return 1 - deadline_kick_sent=1 - fi - sleep 1 - done - echo "timed out waiting for proposal $proposal_id to reach $target_status (last status=${status:-unknown})" >&2 + local raw; raw=$($seidbin q gov proposal "$proposal_id" --output json 2>/dev/null || true) + local status; status=$(echo "$raw" | jq -r '.status // ""' 2>/dev/null) + if [ "$status" = "$target_status" ]; then + echo "$status" + return 0 + fi + if [ "$status" = "PROPOSAL_STATUS_REJECTED" ] || [ "$status" = "PROPOSAL_STATUS_FAILED" ]; then + echo "proposal $proposal_id reached terminal status $status while waiting for $target_status" >&2 + return 1 + fi + local voting_end + voting_end=$(echo "$raw" | jq -r '.voting_end_time // ""' 2>/dev/null) + local voting_end_epoch="" + if [ -n "$voting_end" ]; then + voting_end_epoch=$(date -d "$voting_end" +%s 2>/dev/null || true) + fi + if [ -z "$voting_end_epoch" ]; then + echo "proposal $proposal_id missing valid voting_end_time while waiting for $target_status" >&2 + return 1 + fi + local wait_secs=$((voting_end_epoch + 1 - $(date +%s))) + if [ "$wait_secs" -gt 0 ]; then + sleep "$wait_secs" + fi + raw=$($seidbin q gov proposal "$proposal_id" --output json 2>/dev/null || true) + status=$(echo "$raw" | jq -r '.status // ""' 2>/dev/null) + if [ "$status" = "$target_status" ]; then + echo "$status" + return 0 + fi + if [ "$status" = "PROPOSAL_STATUS_REJECTED" ] || [ "$status" = "PROPOSAL_STATUS_FAILED" ]; then + echo "proposal $proposal_id reached terminal status $status while waiting for $target_status" >&2 + return 1 + fi + # Progress-only self-send: once voting has ended, a proposal may + # still need one more committed block to be tallied. Force that + # block explicitly instead of waiting for unrelated traffic. + bank_send_and_wait "$from_key" "$from_addr" "1usei" >/dev/null || return 1 + raw=$($seidbin q gov proposal "$proposal_id" --output json 2>/dev/null || true) + status=$(echo "$raw" | jq -r '.status // ""' 2>/dev/null) + if [ "$status" = "$target_status" ]; then + echo "$status" + return 0 + fi + if [ "$status" = "PROPOSAL_STATUS_REJECTED" ] || [ "$status" = "PROPOSAL_STATUS_FAILED" ]; then + echo "proposal $proposal_id reached terminal status $status while waiting for $target_status" >&2 + return 1 + fi + echo "proposal $proposal_id did not reach $target_status (current status=${status:-unknown})" >&2 return 1 } diff --git a/sei-tendermint/AGENTS.md b/sei-tendermint/AGENTS.md index bd4c9e0141..495c209979 100644 --- a/sei-tendermint/AGENTS.md +++ b/sei-tendermint/AGENTS.md @@ -2,7 +2,10 @@ Within sei-tendermint subdirectory * sei-tendermint is a root of the go module, but it is not the root of the repo. When refactoring check references across of the whole repo. * use sei-tendermint/libs/utils.Option for optional values, passing nil as function value is not allowed, unless explicitly documented (except for maps and slices, for which nil is a valid empty value). +* struct fields are assumed to be non-nil by default. Do not add defensive nil-checks in internal logic. + External data, like proto generated code for example, might require nil checks. * use utils.Recv/utils.Send instead of select { case <-ctx.Done(); ... } +* use strongly typed utils/require asserts instead of testify/require * use sei-tendermint/libs/utils/scope.Run for structured concurrency (instead of waitgroup). * Don't spawn goroutines with plain "go func(){...}" * using testing.T assertions (inclusing those from "require" library) is not allowed from non-main test goroutine. @@ -20,16 +23,16 @@ Within sei-tendermint subdirectory you can use private fields directly in case it is not accessible via public api. * when writing tests, focus on asserting the publically visible properties, especially the API contract. Avoid asserting implementation details. * Avoid sleeping and active polling in tests. Prefer waiting for updates (for example by using sei-tendermint/libs/utils.Watch or AtomicWatch). +* Do not introduce artificial timeouts in tests because they are a source of flakiness. Let the test hang and make the user run go tests with explicit + timeout instead. * Avoid checking human readable error messages in tests. If programatic error type check is requested, use errors.Is/As/AsType instead. * After introducing changes, you may Use `go test --count=0` to quickly check if tests compile. You may run `go test` to actually run the tests afterwards. Prefer running modified tests selectively and run the whole test suite only if requested or looking for failures. -* Proto fields: use `optional` for all scalar and message fields (required for protobuf3 presence - detection and wireguard bounds checking). Annotate semantically required fields with `// required` +* Proto fields: For hashable messages, use explicit "optional" for all signular fields and annotate semantically required fields with `// required` and truly optional fields with `// optional`. Example: optional uint64 index = 1; // required optional AppProposal app = 4; // optional Required fields must be nil-checked at the boundary (constructor and proto Decode) and trusted - everywhere else — do not add defensive nil-checks in internal logic. * Avoid removing comments and logs which are not obviously obsolete. Keep the original wording, only fixing mistakes or obsolete parts. * TestRng instance should be one per test, constructed directly in the test function. In case of nested/table tests, each nested test should create its own instance. Use TestRng.Split() (before spawning) if you need to pass entropy source to a spawned goroutine From 02ad2d89aff398b212f9f60572ac14175944b6d7 Mon Sep 17 00:00:00 2001 From: Grzegorz Prusak Date: Mon, 13 Jul 2026 14:15:42 +0200 Subject: [PATCH 22/54] missing files --- evmrpc/genesis_latest_state_test.go | 165 ++++++++++++++++++ integration_test/autobahn/autobahn_test.go | 2 +- .../evm_module/ws_test/ws_test.go | 2 +- sei-cosmos/server/start_test.go | 47 +++++ testutil/evmtest/docker.go | 87 +++++++++ 5 files changed, 301 insertions(+), 2 deletions(-) create mode 100644 evmrpc/genesis_latest_state_test.go create mode 100644 sei-cosmos/server/start_test.go create mode 100644 testutil/evmtest/docker.go diff --git a/evmrpc/genesis_latest_state_test.go b/evmrpc/genesis_latest_state_test.go new file mode 100644 index 0000000000..6a384befc1 --- /dev/null +++ b/evmrpc/genesis_latest_state_test.go @@ -0,0 +1,165 @@ +package evmrpc_test + +import ( + "context" + "net/url" + "sync" + "testing" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/rpc" + "github.com/sei-protocol/sei-chain/app" + "github.com/sei-protocol/sei-chain/app/legacyabci" + "github.com/sei-protocol/sei-chain/evmrpc" + "github.com/sei-protocol/sei-chain/sei-cosmos/client" + sdk "github.com/sei-protocol/sei-chain/sei-cosmos/types" + "github.com/sei-protocol/sei-chain/sei-tendermint/libs/bytes" + "github.com/sei-protocol/sei-chain/sei-tendermint/libs/utils" + "github.com/sei-protocol/sei-chain/sei-tendermint/rpc/client/mock" + "github.com/sei-protocol/sei-chain/sei-tendermint/rpc/coretypes" + tmtypes "github.com/sei-protocol/sei-chain/sei-tendermint/types" + testkeeper "github.com/sei-protocol/sei-chain/testutil/keeper" + evmstate "github.com/sei-protocol/sei-chain/x/evm/state" + evmtypes "github.com/sei-protocol/sei-chain/x/evm/types" + "github.com/stretchr/testify/require" +) + +type freshChainClient struct { + mock.Client +} + +func (*freshChainClient) EvmNextPendingNonce(common.Address) uint64 { + return 0 +} + +func (*freshChainClient) EvmTxByHash(common.Hash) (tmtypes.Tx, bool) { + return nil, false +} + +func (*freshChainClient) EvmProxy(common.Address) utils.Option[*url.URL] { + return utils.None[*url.URL]() +} + +func (*freshChainClient) Status(context.Context) (*coretypes.ResultStatus, error) { + return &coretypes.ResultStatus{ + SyncInfo: coretypes.SyncInfo{ + LatestBlockHeight: 0, + EarliestBlockHeight: 1, + }, + }, nil +} + +func (*freshChainClient) Genesis(context.Context) (*coretypes.ResultGenesis, error) { + return &coretypes.ResultGenesis{Genesis: &tmtypes.GenesisDoc{InitialHeight: 1}}, nil +} + +func (*freshChainClient) Block(context.Context, *int64) (*coretypes.ResultBlock, error) { + return &coretypes.ResultBlock{Block: &tmtypes.Block{Header: tmtypes.Header{Height: 0}}}, nil +} + +func (*freshChainClient) BlockByHash(context.Context, bytes.HexBytes) (*coretypes.ResultBlock, error) { + return &coretypes.ResultBlock{Block: &tmtypes.Block{Header: tmtypes.Header{Height: 0}}}, nil +} + +func latestLikeTags() []rpc.BlockNumber { + return []rpc.BlockNumber{ + rpc.LatestBlockNumber, + rpc.SafeBlockNumber, + rpc.FinalizedBlockNumber, + rpc.PendingBlockNumber, + } +} + +func TestStateAPILatestLikeTagsUseGenesisCheckStateBeforeFirstCommit(t *testing.T) { + testApp := app.Setup(t, false, false, false) + checkCtx := testApp.GetCheckCtx() + _, address := testkeeper.MockAddressPair() + key := common.BytesToHash([]byte("key")) + value := common.BytesToHash([]byte("value")) + code := []byte{0xaa, 0xbb, 0xcc} + amount := sdk.NewInt(1234) + + coins := sdk.NewCoins(sdk.NewCoin(testApp.EvmKeeper.GetBaseDenom(checkCtx), amount)) + require.NoError(t, testApp.EvmKeeper.BankKeeper().MintCoins(checkCtx, evmtypes.ModuleName, coins)) + require.NoError(t, testApp.EvmKeeper.BankKeeper().SendCoinsFromModuleToAccount(checkCtx, evmtypes.ModuleName, sdk.AccAddress(address[:]), coins)) + testApp.EvmKeeper.SetCode(checkCtx, address, code) + testApp.EvmKeeper.SetState(checkCtx, address, key, value) + + ctxProvider := func(height int64) sdk.Context { + if height == evmrpc.LatestCtxHeight { + return testApp.GetCheckCtx() + } + queryCtx, err := testApp.CreateQueryContext(height, false) + require.NoError(t, err) + return queryCtx + } + tmClient := &freshChainClient{} + watermarks := evmrpc.NewWatermarkManager(tmClient, ctxProvider, nil, testApp.EvmKeeper.ReceiptStore()) + api := evmrpc.NewStateAPI(tmClient, &testApp.EvmKeeper, ctxProvider, evmrpc.ConnectionTypeHTTP, watermarks) + expectedBalance := evmstate.NewDBImpl(testApp.GetCheckCtx(), &testApp.EvmKeeper, true).GetBalance(address).ToBig().String() + + for _, tag := range latestLikeTags() { + blockRef := rpc.BlockNumberOrHashWithNumber(tag) + + balance, err := api.GetBalance(t.Context(), address, blockRef) + require.NoError(t, err) + require.Equal(t, expectedBalance, (*balance).ToInt().String()) + + gotCode, err := api.GetCode(t.Context(), address, blockRef) + require.NoError(t, err) + require.Equal(t, code, []byte(gotCode)) + + storage, err := api.GetStorageAt(t.Context(), address, key.Hex(), blockRef) + require.NoError(t, err) + require.Equal(t, value[:], []byte(storage)) + } +} + +func TestSimulationBackendLatestLikeTagsUseGenesisCheckStateBeforeFirstCommit(t *testing.T) { + testApp := app.Setup(t, false, false, false) + checkCtx := testApp.GetCheckCtx() + _, address := testkeeper.MockAddressPair() + code := []byte{0xde, 0xad, 0xbe, 0xef} + amount := sdk.NewInt(4321) + + coins := sdk.NewCoins(sdk.NewCoin(testApp.EvmKeeper.GetBaseDenom(checkCtx), amount)) + require.NoError(t, testApp.EvmKeeper.BankKeeper().MintCoins(checkCtx, evmtypes.ModuleName, coins)) + require.NoError(t, testApp.EvmKeeper.BankKeeper().SendCoinsFromModuleToAccount(checkCtx, evmtypes.ModuleName, sdk.AccAddress(address[:]), coins)) + testApp.EvmKeeper.SetCode(checkCtx, address, code) + + tmClient := &freshChainClient{} + ctxProvider := func(height int64) sdk.Context { + if height == evmrpc.LatestCtxHeight { + return testApp.GetCheckCtx() + } + queryCtx, err := testApp.CreateQueryContext(height, false) + require.NoError(t, err) + return queryCtx + } + watermarks := evmrpc.NewWatermarkManager(tmClient, ctxProvider, nil, testApp.EvmKeeper.ReceiptStore()) + backend := evmrpc.NewBackend( + ctxProvider, + &testApp.EvmKeeper, + legacyabci.BeginBlockKeepers{}, + func(int64) client.TxConfig { return app.MakeEncodingConfig().TxConfig }, + tmClient, + &evmrpc.SimulateConfig{GasCap: 1_000_000, EVMTimeout: time.Second}, + testApp.BaseApp, + testApp.TracerAnteHandler, + evmrpc.NewBlockCache(16), + &sync.Mutex{}, + watermarks, + ) + expectedBalance := evmstate.NewDBImpl(testApp.GetCheckCtx(), &testApp.EvmKeeper, true).GetBalance(address).ToBig().String() + + for _, tag := range latestLikeTags() { + statedb, header, err := backend.StateAndHeaderByNumberOrHash(context.Background(), rpc.BlockNumberOrHashWithNumber(tag)) + require.NoError(t, err) + require.Equal(t, checkCtx.BlockHeight(), header.Number.Int64()) + require.Equal(t, code, statedb.GetCode(address)) + + db := evmstate.GetDBImpl(statedb) + require.Equal(t, expectedBalance, db.GetBalance(address).ToBig().String()) + } +} diff --git a/integration_test/autobahn/autobahn_test.go b/integration_test/autobahn/autobahn_test.go index 2df63486dd..2857ebde36 100644 --- a/integration_test/autobahn/autobahn_test.go +++ b/integration_test/autobahn/autobahn_test.go @@ -24,9 +24,9 @@ import ( "testing" "time" - "github.com/sei-protocol/sei-chain/integration_test/internal/evmtest" tmjson "github.com/sei-protocol/sei-chain/sei-tendermint/libs/json" "github.com/sei-protocol/sei-chain/sei-tendermint/rpc/coretypes" + "github.com/sei-protocol/sei-chain/testutil/evmtest" ) const ( diff --git a/integration_test/evm_module/ws_test/ws_test.go b/integration_test/evm_module/ws_test/ws_test.go index d730ba66c8..2db984a66f 100644 --- a/integration_test/evm_module/ws_test/ws_test.go +++ b/integration_test/evm_module/ws_test/ws_test.go @@ -19,7 +19,7 @@ import ( "time" "github.com/gorilla/websocket" - "github.com/sei-protocol/sei-chain/integration_test/internal/evmtest" + "github.com/sei-protocol/sei-chain/testutil/evmtest" ) func wsURL() string { diff --git a/sei-cosmos/server/start_test.go b/sei-cosmos/server/start_test.go new file mode 100644 index 0000000000..9fe943805f --- /dev/null +++ b/sei-cosmos/server/start_test.go @@ -0,0 +1,47 @@ +package server + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/sei-protocol/sei-chain/sei-cosmos/telemetry" +) + +func TestInjectTelemetryChainID(t *testing.T) { + t.Run("appends chain id when missing", func(t *testing.T) { + cfg := telemetry.Config{ + GlobalLabels: [][]string{{"foo", "bar"}}, + } + + got := injectTelemetryChainID(cfg, "sei-test-1") + + require.Equal(t, [][]string{ + {"foo", "bar"}, + {"chain_id", "sei-test-1"}, + }, got.GlobalLabels) + }) + + t.Run("preserves existing chain id label", func(t *testing.T) { + cfg := telemetry.Config{ + GlobalLabels: [][]string{ + {"foo", "bar"}, + {"chain_id", "existing-chain"}, + }, + } + + got := injectTelemetryChainID(cfg, "sei-test-1") + + require.Equal(t, cfg.GlobalLabels, got.GlobalLabels) + }) + + t.Run("ignores empty chain id", func(t *testing.T) { + cfg := telemetry.Config{ + GlobalLabels: [][]string{{"foo", "bar"}}, + } + + got := injectTelemetryChainID(cfg, "") + + require.Equal(t, cfg.GlobalLabels, got.GlobalLabels) + }) +} diff --git a/testutil/evmtest/docker.go b/testutil/evmtest/docker.go new file mode 100644 index 0000000000..98da02d2b7 --- /dev/null +++ b/testutil/evmtest/docker.go @@ -0,0 +1,87 @@ +package evmtest + +import ( + "context" + "fmt" + "os" + "os/exec" + "strings" +) + +type DockerTxConfig struct { + Container string + Password string + From string + Recipient string + ChainID string + EVMRPCURL string +} + +func DefaultDockerTxConfig() DockerTxConfig { + return DockerTxConfig{ + Container: "sei-node-0", + Password: "12345678", + From: "admin", + Recipient: "0xF87A299e6bC7bEba58dbBe5a5Aa21d49bCD16D52", + ChainID: "sei", + EVMRPCURL: "http://localhost:8545", + } +} + +func ConfigFromEnv(prefix string) DockerTxConfig { + cfg := DefaultDockerTxConfig() + if v := os.Getenv(prefix + "CONTAINER"); v != "" { + cfg.Container = v + } + if v := os.Getenv(prefix + "PASSWORD"); v != "" { + cfg.Password = v + } + if v := os.Getenv(prefix + "FROM"); v != "" { + cfg.From = v + } + if v := os.Getenv(prefix + "RECIPIENT"); v != "" { + cfg.Recipient = v + } + if v := os.Getenv(prefix + "CHAIN_ID"); v != "" { + cfg.ChainID = v + } + if v := os.Getenv(prefix + "EVM_RPC_URL"); v != "" { + cfg.EVMRPCURL = v + } + return cfg +} + +// SendTinyEvmTx submits a dust EVM transfer whose only purpose is to force one +// real block under allow_empty_blocks=false. Callers use it as a liveness/head +// trigger, not to validate transfer semantics. +func SendTinyEvmTx(ctx context.Context, cfg DockerTxConfig) (string, error) { + cmd := exec.CommandContext( + ctx, + "docker", "exec", + "--env", fmt.Sprintf("SEI_EVM_PASSWORD=%s", cfg.Password), + cfg.Container, + "/bin/bash", "-c", + `export PATH=$PATH:/root/go/bin && printf "%s\n" "$SEI_EVM_PASSWORD" | "$@"`, + "bash", + "seid", "tx", "evm", "send", cfg.Recipient, "1", + "--from", cfg.From, + "--chain-id", cfg.ChainID, + "--evm-rpc", cfg.EVMRPCURL, + "-b", "sync", + "-y", + ) + out, err := cmd.CombinedOutput() + if err != nil { + return "", fmt.Errorf("send tiny evm tx: %w\n%s", err, out) + } + return parseTxHash(string(out)) +} + +func parseTxHash(output string) (string, error) { + for _, line := range strings.Split(output, "\n") { + if hash, ok := strings.CutPrefix(strings.TrimSpace(line), "Transaction hash: "); ok { + return strings.TrimSpace(hash), nil + } + } + return "", fmt.Errorf("transaction hash not found in output:\n%s", output) +} From eb4c28b1631a128d5206fc6e3b170ce1d3c62658 Mon Sep 17 00:00:00 2001 From: Grzegorz Prusak Date: Mon, 13 Jul 2026 14:25:34 +0200 Subject: [PATCH 23/54] addressed comments --- contracts/test/lib.js | 49 +++++------ .../evm_module/scripts/evm_rpc_tests.sh | 10 ++- integration_test/utils/_tx_helpers.sh | 84 ++++++++----------- 3 files changed, 62 insertions(+), 81 deletions(-) diff --git a/contracts/test/lib.js b/contracts/test/lib.js index ffa53d80f5..22b740015e 100644 --- a/contracts/test/lib.js +++ b/contracts/test/lib.js @@ -263,7 +263,7 @@ async function getKeySeiAddress(name) { async function waitForProposalStatus( proposalId, targetStatus, - { kickKeyName = adminKeyName } = {}, + { kickKeyName = adminKeyName, pollIntervalMs = 1000 } = {}, ) { const readProposal = async () => JSON.parse(await execute(`seid q gov proposal ${proposalId} -o json`)) const ensureNotFailed = (status) => { @@ -275,39 +275,30 @@ async function waitForProposalStatus( } } - let proposal = await readProposal() - if (proposal.status === targetStatus) { - return proposal - } - ensureNotFailed(proposal.status) + const kickAddr = await getKeySeiAddress(kickKeyName) - const votingEndMs = Date.parse(proposal.voting_end_time ?? "") - if (!Number.isFinite(votingEndMs)) { - throw new Error(`Proposal ${proposalId} is missing a valid voting_end_time`) - } + while (true) { + const proposal = await readProposal() + if (proposal.status === targetStatus) { + return proposal + } + ensureNotFailed(proposal.status) - const waitMs = votingEndMs + 1000 - Date.now() - if (waitMs > 0) { - await sleep(waitMs) - } + const votingEndMs = Date.parse(proposal.voting_end_time ?? "") + if (!Number.isFinite(votingEndMs)) { + throw new Error(`Proposal ${proposalId} is missing a valid voting_end_time`) + } - proposal = await readProposal() - if (proposal.status === targetStatus) { - return proposal - } - ensureNotFailed(proposal.status) + if (Date.now() >= votingEndMs + 1000) { + // Progress-only bank send: once the currently observed voting end + // time has passed, force one committed block so tallying can + // advance. Expedited proposals can convert to regular and extend + // voting_end_time, so re-read proposal state after every kick. + await bankSend(kickAddr, kickKeyName, "1", "usei") + } - const kickAddr = await getKeySeiAddress(kickKeyName) - // Progress-only bank send: if voting_end_time has passed but no tx has - // arrived to trigger the tally block yet, force one committed block so the - // proposal can move to its terminal status. - await bankSend(kickAddr, kickKeyName, "1", "usei") - proposal = await readProposal() - if (proposal.status === targetStatus) { - return proposal + await sleep(pollIntervalMs) } - ensureNotFailed(proposal.status) - throw new Error(`Proposal ${proposalId} did not reach ${targetStatus}; current status: ${proposal.status}`) } // Best-effort helper for idempotent bootstrap paths that may run after an diff --git a/integration_test/evm_module/scripts/evm_rpc_tests.sh b/integration_test/evm_module/scripts/evm_rpc_tests.sh index 1087560ec9..f76433cc8f 100755 --- a/integration_test/evm_module/scripts/evm_rpc_tests.sh +++ b/integration_test/evm_module/scripts/evm_rpc_tests.sh @@ -53,24 +53,30 @@ get_latest_height() { wait_from_seq_advance() { local prev="$1" if [ -z "$FROM_ADDR" ] || [ -z "$prev" ]; then return 0; fi - while true; do + local deadline=$(($(date +%s) + 30)) + while [ "$(date +%s)" -lt "$deadline" ]; do local cur; cur=$(get_from_seq) if [[ "$cur" =~ ^[0-9]+$ ]] && [ "$cur" -gt "$prev" ]; then return 0 fi sleep 0.5 done + echo "timed out waiting for sequence to advance past ${prev}" >&2 + return 1 } wait_until_height_exceeds() { local prev="$1" - while true; do + local deadline=$(($(date +%s) + 30)) + while [ "$(date +%s)" -lt "$deadline" ]; do local cur; cur=$(get_latest_height) if [[ "$cur" =~ ^[0-9]+$ ]] && [ "$cur" -gt "$prev" ]; then return 0 fi sleep 0.5 done + echo "timed out waiting for height to exceed ${prev}" >&2 + return 1 } bump_chain_to_height() { diff --git a/integration_test/utils/_tx_helpers.sh b/integration_test/utils/_tx_helpers.sh index 0910246710..a432bb8a70 100644 --- a/integration_test/utils/_tx_helpers.sh +++ b/integration_test/utils/_tx_helpers.sh @@ -123,57 +123,41 @@ get_proposal_status() { wait_for_proposal_status() { local proposal_id="$1" local target_status="$2" - local from_key="${3:-admin}" + local timeout_secs="${3:-120}" + local from_key="${4:-admin}" local from_addr; from_addr=$(_get_key_address "$from_key") - local raw; raw=$($seidbin q gov proposal "$proposal_id" --output json 2>/dev/null || true) - local status; status=$(echo "$raw" | jq -r '.status // ""' 2>/dev/null) - if [ "$status" = "$target_status" ]; then - echo "$status" - return 0 - fi - if [ "$status" = "PROPOSAL_STATUS_REJECTED" ] || [ "$status" = "PROPOSAL_STATUS_FAILED" ]; then - echo "proposal $proposal_id reached terminal status $status while waiting for $target_status" >&2 - return 1 - fi - local voting_end - voting_end=$(echo "$raw" | jq -r '.voting_end_time // ""' 2>/dev/null) - local voting_end_epoch="" - if [ -n "$voting_end" ]; then - voting_end_epoch=$(date -d "$voting_end" +%s 2>/dev/null || true) - fi - if [ -z "$voting_end_epoch" ]; then - echo "proposal $proposal_id missing valid voting_end_time while waiting for $target_status" >&2 - return 1 - fi - local wait_secs=$((voting_end_epoch + 1 - $(date +%s))) - if [ "$wait_secs" -gt 0 ]; then - sleep "$wait_secs" - fi - raw=$($seidbin q gov proposal "$proposal_id" --output json 2>/dev/null || true) - status=$(echo "$raw" | jq -r '.status // ""' 2>/dev/null) - if [ "$status" = "$target_status" ]; then - echo "$status" - return 0 - fi - if [ "$status" = "PROPOSAL_STATUS_REJECTED" ] || [ "$status" = "PROPOSAL_STATUS_FAILED" ]; then - echo "proposal $proposal_id reached terminal status $status while waiting for $target_status" >&2 - return 1 - fi - # Progress-only self-send: once voting has ended, a proposal may - # still need one more committed block to be tallied. Force that - # block explicitly instead of waiting for unrelated traffic. - bank_send_and_wait "$from_key" "$from_addr" "1usei" >/dev/null || return 1 - raw=$($seidbin q gov proposal "$proposal_id" --output json 2>/dev/null || true) - status=$(echo "$raw" | jq -r '.status // ""' 2>/dev/null) - if [ "$status" = "$target_status" ]; then - echo "$status" - return 0 - fi - if [ "$status" = "PROPOSAL_STATUS_REJECTED" ] || [ "$status" = "PROPOSAL_STATUS_FAILED" ]; then - echo "proposal $proposal_id reached terminal status $status while waiting for $target_status" >&2 - return 1 - fi - echo "proposal $proposal_id did not reach $target_status (current status=${status:-unknown})" >&2 + local deadline=$(($(date +%s) + timeout_secs)) + while [ "$(date +%s)" -lt "$deadline" ]; do + local raw; raw=$($seidbin q gov proposal "$proposal_id" --output json 2>/dev/null || true) + local status; status=$(echo "$raw" | jq -r '.status // ""' 2>/dev/null) + if [ "$status" = "$target_status" ]; then + echo "$status" + return 0 + fi + if [ "$status" = "PROPOSAL_STATUS_REJECTED" ] || [ "$status" = "PROPOSAL_STATUS_FAILED" ]; then + echo "proposal $proposal_id reached terminal status $status while waiting for $target_status" >&2 + return 1 + fi + local voting_end + voting_end=$(echo "$raw" | jq -r '.voting_end_time // ""' 2>/dev/null) + local voting_end_epoch="" + if [ -n "$voting_end" ]; then + voting_end_epoch=$(date -d "$voting_end" +%s 2>/dev/null || true) + fi + if [ -z "$voting_end_epoch" ]; then + echo "proposal $proposal_id missing valid voting_end_time while waiting for $target_status" >&2 + return 1 + fi + if [ "$(date +%s)" -ge $((voting_end_epoch + 1)) ]; then + # Progress-only self-send: once the currently observed voting end + # time has passed, force one committed block so tallying can + # advance. Expedited proposals can convert to regular and extend + # voting_end_time, so re-read proposal state after every kick. + bank_send_and_wait "$from_key" "$from_addr" "1usei" >/dev/null || return 1 + fi + sleep 1 + done + echo "timed out waiting for proposal $proposal_id to reach $target_status (last status=${status:-unknown})" >&2 return 1 } From b5d3490960367d8d5cbe0c14fad0da50a3e43c79 Mon Sep 17 00:00:00 2001 From: Grzegorz Prusak Date: Mon, 13 Jul 2026 14:33:59 +0200 Subject: [PATCH 24/54] applied comments --- evmrpc/simulate_test.go | 31 ++++++++++++++----- .../contracts/verify_flatkv_evm_migrate.sh | 12 +++---- .../scripts/proposal_wait_for_pass.sh | 3 +- integration_test/utils/_tx_helpers.sh | 7 +++-- sei-cosmos/baseapp/abci.go | 2 +- 5 files changed, 37 insertions(+), 18 deletions(-) diff --git a/evmrpc/simulate_test.go b/evmrpc/simulate_test.go index 7d976f60f5..7040becaae 100644 --- a/evmrpc/simulate_test.go +++ b/evmrpc/simulate_test.go @@ -873,9 +873,12 @@ func TestSimulationAPIRequestLimiter(t *testing.T) { t.Run("TestDifferentMethodsShareSameLimiter", func(t *testing.T) { // Test that different simulation methods share the same rate limiter. + // A single burst can occasionally avoid contention on overloaded CI workers, + // so retry a synchronized burst a few times. const ( numCallRequests = 20 numEstimateRequests = 20 + maxAttempts = 5 ) totalRequests := numCallRequests + numEstimateRequests @@ -916,19 +919,33 @@ func TestSimulationAPIRequestLimiter(t *testing.T) { return successCount, rejectedCount } - lastSuccess, lastRejected := runMixedBurst(newTestEnv(t)) - require.Equal(t, totalRequests, lastSuccess+lastRejected, "All mixed method requests should be accounted for") + var ( + lastSuccess int + lastRejected int + attemptsUsed int + observedRejection bool + ) + for attempt := 1; attempt <= maxAttempts; attempt++ { + attemptsUsed = attempt + lastSuccess, lastRejected = runMixedBurst(newTestEnv(t)) + require.Equalf(t, totalRequests, lastSuccess+lastRejected, "All mixed method requests should be accounted for (attempt %d)", attempt) + if lastRejected > 0 { + observedRejection = true + break + } + } - require.Greaterf( + require.Truef( t, - lastRejected, - 0, - "Different methods should share the same rate limiter (burst: %d successful, %d rejected)", + observedRejection, + "Different methods should share the same rate limiter (last burst: %d successful, %d rejected)", lastSuccess, lastRejected, ) t.Logf( - "Mixed methods rate limiting: %d successful, %d rejected out of %d total", + "Mixed methods rate limiting (attempt %d/%d): %d successful, %d rejected out of %d total", + attemptsUsed, + maxAttempts, lastSuccess, lastRejected, totalRequests, diff --git a/integration_test/contracts/verify_flatkv_evm_migrate.sh b/integration_test/contracts/verify_flatkv_evm_migrate.sh index 36bad31ce8..8c02557d29 100755 --- a/integration_test/contracts/verify_flatkv_evm_migrate.sh +++ b/integration_test/contracts/verify_flatkv_evm_migrate.sh @@ -759,12 +759,12 @@ EOF" echo "Voted yes on proposal $proposal_id from a quorum of validators; waiting for it to pass..." local status - status=$(docker exec "sei-node-0" bash -lc " - cd /sei-protocol/sei-chain - seidbin=build/seid chainid=sei - source integration_test/utils/_tx_helpers.sh - wait_for_proposal_status '$proposal_id' PROPOSAL_STATUS_PASSED admin - " 2>/dev/null || true) + status=$(docker exec "sei-node-0" bash -lc " + cd /sei-protocol/sei-chain + seidbin=build/seid chainid=sei + source integration_test/utils/_tx_helpers.sh + wait_for_proposal_status '$proposal_id' PROPOSAL_STATUS_PASSED admin '$GOV_PASS_TIMEOUT' + " 2>/dev/null || true) if [ "$status" != "PROPOSAL_STATUS_PASSED" ]; then echo "ERROR: migration param-change proposal $proposal_id did not pass within ${GOV_PASS_TIMEOUT}s (last status=$status)" >&2 exit 1 diff --git a/integration_test/upgrade_module/scripts/proposal_wait_for_pass.sh b/integration_test/upgrade_module/scripts/proposal_wait_for_pass.sh index 0647ab9e43..0b3d1aabe2 100755 --- a/integration_test/upgrade_module/scripts/proposal_wait_for_pass.sh +++ b/integration_test/upgrade_module/scripts/proposal_wait_for_pass.sh @@ -1,8 +1,9 @@ #!/bin/bash PROPOSAL_ID=$1 +TIMEOUT=300 seidbin=seid source integration_test/utils/_tx_helpers.sh -wait_for_proposal_status "$PROPOSAL_ID" "PROPOSAL_STATUS_PASSED" "admin" >/dev/null +wait_for_proposal_status "$PROPOSAL_ID" "PROPOSAL_STATUS_PASSED" "admin" "$TIMEOUT" >/dev/null echo "Proposal $PROPOSAL_ID has passed!" diff --git a/integration_test/utils/_tx_helpers.sh b/integration_test/utils/_tx_helpers.sh index a432bb8a70..3ba9a2330e 100644 --- a/integration_test/utils/_tx_helpers.sh +++ b/integration_test/utils/_tx_helpers.sh @@ -123,13 +123,14 @@ get_proposal_status() { wait_for_proposal_status() { local proposal_id="$1" local target_status="$2" - local timeout_secs="${3:-120}" - local from_key="${4:-admin}" + local from_key="${3:-admin}" + local timeout_secs="${4:-120}" local from_addr; from_addr=$(_get_key_address "$from_key") local deadline=$(($(date +%s) + timeout_secs)) + local status="" while [ "$(date +%s)" -lt "$deadline" ]; do local raw; raw=$($seidbin q gov proposal "$proposal_id" --output json 2>/dev/null || true) - local status; status=$(echo "$raw" | jq -r '.status // ""' 2>/dev/null) + status=$(echo "$raw" | jq -r '.status // ""' 2>/dev/null) if [ "$status" = "$target_status" ]; then echo "$status" return 0 diff --git a/sei-cosmos/baseapp/abci.go b/sei-cosmos/baseapp/abci.go index a49c672692..7ac14cb1d2 100644 --- a/sei-cosmos/baseapp/abci.go +++ b/sei-cosmos/baseapp/abci.go @@ -708,7 +708,7 @@ func (app *BaseApp) CreateQueryContext(height int64, prove bool) (sdk.Context, e checkStateCtx := app.checkState.Context() var cacheMS types.CacheMultiStore - if lastBlockHeight == 0 && height == 0 && app.checkState != nil { + if lastBlockHeight == 0 && height == 0 { // Height 0 means "latest". Before the first Commit, the latest readable // state lives only on checkState (e.g. InitChain writes). Querying the // committed multistore at version 0 would incorrectly hide that state. From 41dfedb186b3c464d7013886770a0416a23bfcba Mon Sep 17 00:00:00 2001 From: Grzegorz Prusak Date: Mon, 13 Jul 2026 15:09:05 +0200 Subject: [PATCH 25/54] more fixes --- evmrpc/simulate.go | 24 ++++++++++++++++-------- evmrpc/watermark_manager.go | 10 +++++++++- sei-tendermint/AGENTS.md | 1 - testutil/evmtest/docker.go | 2 +- 4 files changed, 26 insertions(+), 11 deletions(-) diff --git a/evmrpc/simulate.go b/evmrpc/simulate.go index 6315a1e51b..6ea364159a 100644 --- a/evmrpc/simulate.go +++ b/evmrpc/simulate.go @@ -318,9 +318,13 @@ func (b *Backend) StateAndHeaderByNumberOrHash(ctx context.Context, blockNrOrHas if err != nil { return nil, nil, err } - height := tmBlock.Block.Height + ctxHeight := LatestCtxHeight + if !isLatestBlock { + ctxHeight = tmBlock.Block.Height + } isWasmdCall, ok := ctx.Value(CtxIsWasmdPrecompileCallKey).(bool) - sdkCtx := b.ctxProvider(height).WithIsEVM(true).WithEVMEntryViaWasmdPrecompile(ok && isWasmdCall) + + sdkCtx := b.ctxProvider(ctxHeight).WithIsEVM(true).WithEVMEntryViaWasmdPrecompile(ok && isWasmdCall) if !isLatestBlock { // no need to check version for latest block if err := CheckVersion(sdkCtx, b.keeper); err != nil { @@ -328,6 +332,9 @@ func (b *Backend) StateAndHeaderByNumberOrHash(ctx context.Context, blockNrOrHas } } header := b.getHeader(ctx, tmBlock) + if isLatestBlock && tmBlock.Block.Height != sdkCtx.BlockHeight() { + header = b.syntheticHeaderFromCtx(sdkCtx) + } header.BaseFee = b.keeper.GetNextBaseFeePerGas(b.ctxProvider(LatestCtxHeight)).TruncateInt().BigInt() return state.NewDBImpl(sdkCtx, b.keeper, true), header, nil } @@ -678,7 +685,7 @@ func (b *Backend) CurrentHeader() *ethtypes.Header { if tmBlock, err := blockByNumberRespectingWatermarks(ctx, b.tmClient, b.watermarks, &height, 1); err == nil { header = b.getHeader(ctx, tmBlock) } else { - header = b.fallbackToEthHeaderOnly(height) + header = b.syntheticHeaderFromCtx(b.ctxProvider(LatestCtxHeight)) } header.BaseFee = b.keeper.GetNextBaseFeePerGas(b.ctxProvider(LatestCtxHeight)).TruncateInt().BigInt() return header @@ -724,15 +731,16 @@ func (b *Backend) getBlockByNumberOrHash(ctx context.Context, blockNrOrHash rpc. return block, isLatestBlock, nil } -// fallbackToEthHeaderOnly builds a minimal header when the block cannot be loaded -// (e.g. CurrentHeader when Block RPC fails). BaseFee is overwritten by CurrentHeader afterward. -func (b *Backend) fallbackToEthHeaderOnly(height int64) *ethtypes.Header { +// syntheticHeaderFromCtx builds a minimal header directly from the selected SDK +// context when no coherent Tendermint block/header is available for that state +// (e.g. latest-like queries before the first Commit). +func (b *Backend) syntheticHeaderFromCtx(sdkCtx sdk.Context) *ethtypes.Header { zeroExcessBlobGas := uint64(0) return ðtypes.Header{ Difficulty: common.Big0, - Number: big.NewInt(height), + Number: big.NewInt(sdkCtx.BlockHeight()), GasLimit: keeper.DefaultBlockGasLimit, - Time: toUint64(time.Now().Unix()), //nolint:gosec + Time: toUint64(sdkCtx.BlockTime().Unix()), //nolint:gosec ExcessBlobGas: &zeroExcessBlobGas, } } diff --git a/evmrpc/watermark_manager.go b/evmrpc/watermark_manager.go index 0bbc8edfb4..8dcd2ad5c2 100644 --- a/evmrpc/watermark_manager.go +++ b/evmrpc/watermark_manager.go @@ -338,7 +338,15 @@ func (m *WatermarkManager) fetchTendermintWatermarks(ctx context.Context) (int64 return 0, 0, err } TraceTendermintIfApplicable(ctx, "Status", []string{}, status) - return status.SyncInfo.LatestBlockHeight, status.SyncInfo.EarliestBlockHeight, nil + latest := status.SyncInfo.LatestBlockHeight + earliest := status.SyncInfo.EarliestBlockHeight + if latest == 0 { + // Before the first Commit, Tendermint can still report InitialHeight as + // the earliest block height even though the only readable "latest" state + // is the synthetic height-0 genesis/checkState branch. + earliest = 0 + } + return latest, earliest, nil } func (m *WatermarkManager) fetchStateStoreWatermarks() (int64, int64, bool) { diff --git a/sei-tendermint/AGENTS.md b/sei-tendermint/AGENTS.md index 495c209979..ee38c7c81e 100644 --- a/sei-tendermint/AGENTS.md +++ b/sei-tendermint/AGENTS.md @@ -32,7 +32,6 @@ Within sei-tendermint subdirectory and truly optional fields with `// optional`. Example: optional uint64 index = 1; // required optional AppProposal app = 4; // optional - Required fields must be nil-checked at the boundary (constructor and proto Decode) and trusted * Avoid removing comments and logs which are not obviously obsolete. Keep the original wording, only fixing mistakes or obsolete parts. * TestRng instance should be one per test, constructed directly in the test function. In case of nested/table tests, each nested test should create its own instance. Use TestRng.Split() (before spawning) if you need to pass entropy source to a spawned goroutine diff --git a/testutil/evmtest/docker.go b/testutil/evmtest/docker.go index 98da02d2b7..933231be0c 100644 --- a/testutil/evmtest/docker.go +++ b/testutil/evmtest/docker.go @@ -55,7 +55,7 @@ func ConfigFromEnv(prefix string) DockerTxConfig { // real block under allow_empty_blocks=false. Callers use it as a liveness/head // trigger, not to validate transfer semantics. func SendTinyEvmTx(ctx context.Context, cfg DockerTxConfig) (string, error) { - cmd := exec.CommandContext( + cmd := exec.CommandContext( //nolint:gosec // test-only helper invokes docker with fixed argv shape ctx, "docker", "exec", "--env", fmt.Sprintf("SEI_EVM_PASSWORD=%s", cfg.Password), From c088f11db1eced4de392e6f0b7c80d60f0ff81b0 Mon Sep 17 00:00:00 2001 From: Grzegorz Prusak Date: Mon, 13 Jul 2026 15:40:16 +0200 Subject: [PATCH 26/54] fixes --- evmrpc/simulate.go | 49 +++++++++++++++++++++++++++------------------- 1 file changed, 29 insertions(+), 20 deletions(-) diff --git a/evmrpc/simulate.go b/evmrpc/simulate.go index 6ea364159a..4f18824da2 100644 --- a/evmrpc/simulate.go +++ b/evmrpc/simulate.go @@ -314,28 +314,18 @@ func (b *Backend) SetTraceContextProvider(provider TraceContextProvider) { } func (b *Backend) StateAndHeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (vm.StateDB, *ethtypes.Header, error) { - tmBlock, isLatestBlock, err := b.getBlockByNumberOrHash(ctx, blockNrOrHash) + isWasmdCall, ok := ctx.Value(CtxIsWasmdPrecompileCallKey).(bool) + sdkCtx, header, isLatestBlock, err := b.resolveStateAndHeaderByNumberOrHash(ctx, blockNrOrHash) if err != nil { return nil, nil, err } - ctxHeight := LatestCtxHeight - if !isLatestBlock { - ctxHeight = tmBlock.Block.Height - } - isWasmdCall, ok := ctx.Value(CtxIsWasmdPrecompileCallKey).(bool) - - sdkCtx := b.ctxProvider(ctxHeight).WithIsEVM(true).WithEVMEntryViaWasmdPrecompile(ok && isWasmdCall) + sdkCtx = sdkCtx.WithIsEVM(true).WithEVMEntryViaWasmdPrecompile(ok && isWasmdCall) if !isLatestBlock { // no need to check version for latest block if err := CheckVersion(sdkCtx, b.keeper); err != nil { return nil, nil, err } } - header := b.getHeader(ctx, tmBlock) - if isLatestBlock && tmBlock.Block.Height != sdkCtx.BlockHeight() { - header = b.syntheticHeaderFromCtx(sdkCtx) - } - header.BaseFee = b.keeper.GetNextBaseFeePerGas(b.ctxProvider(LatestCtxHeight)).TruncateInt().BigInt() return state.NewDBImpl(sdkCtx, b.keeper, true), header, nil } @@ -679,15 +669,10 @@ func (b *Backend) GetEVM(_ context.Context, msg *core.Message, stateDB vm.StateD } func (b *Backend) CurrentHeader() *ethtypes.Header { - height := b.ctxProvider(LatestCtxHeight).BlockHeight() - ctx := context.Background() - var header *ethtypes.Header - if tmBlock, err := blockByNumberRespectingWatermarks(ctx, b.tmClient, b.watermarks, &height, 1); err == nil { - header = b.getHeader(ctx, tmBlock) - } else { + _, header, _, err := b.resolveStateAndHeaderByNumberOrHash(context.Background(), rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber)) + if err != nil { header = b.syntheticHeaderFromCtx(b.ctxProvider(LatestCtxHeight)) } - header.BaseFee = b.keeper.GetNextBaseFeePerGas(b.ctxProvider(LatestCtxHeight)).TruncateInt().BigInt() return header } @@ -731,14 +716,38 @@ func (b *Backend) getBlockByNumberOrHash(ctx context.Context, blockNrOrHash rpc. return block, isLatestBlock, nil } +// resolveStateAndHeaderByNumberOrHash normalizes a block reference into the +// SDK context and Ethereum header that should be paired together. For latest-like +// requests before the first Commit, this intentionally prefers the initialized +// latest checkState context over the height-0 Tendermint block so callers receive +// a coherent (state, header) view. +func (b *Backend) resolveStateAndHeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (sdk.Context, *ethtypes.Header, bool, error) { + tmBlock, isLatestBlock, err := b.getBlockByNumberOrHash(ctx, blockNrOrHash) + if err != nil { + return sdk.Context{}, nil, false, err + } + ctxHeight := tmBlock.Block.Height + if isLatestBlock { + ctxHeight = LatestCtxHeight + } + sdkCtx := b.ctxProvider(ctxHeight) + header := b.getHeader(ctx, tmBlock) + if isLatestBlock && tmBlock.Block.Height != sdkCtx.BlockHeight() { + header = b.syntheticHeaderFromCtx(sdkCtx) + } + return sdkCtx, header, isLatestBlock, nil +} + // syntheticHeaderFromCtx builds a minimal header directly from the selected SDK // context when no coherent Tendermint block/header is available for that state // (e.g. latest-like queries before the first Commit). func (b *Backend) syntheticHeaderFromCtx(sdkCtx sdk.Context) *ethtypes.Header { zeroExcessBlobGas := uint64(0) + baseFee := b.keeper.GetNextBaseFeePerGas(sdkCtx).TruncateInt().BigInt() return ðtypes.Header{ Difficulty: common.Big0, Number: big.NewInt(sdkCtx.BlockHeight()), + BaseFee: baseFee, GasLimit: keeper.DefaultBlockGasLimit, Time: toUint64(sdkCtx.BlockTime().Unix()), //nolint:gosec ExcessBlobGas: &zeroExcessBlobGas, From 3867df21a1551b64b0d069362ac070344b37c679 Mon Sep 17 00:00:00 2001 From: Grzegorz Prusak Date: Mon, 13 Jul 2026 17:16:15 +0200 Subject: [PATCH 27/54] debug --- .../evm_module/scripts/evm_rpc_tests.sh | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/integration_test/evm_module/scripts/evm_rpc_tests.sh b/integration_test/evm_module/scripts/evm_rpc_tests.sh index f76433cc8f..a8814f645f 100755 --- a/integration_test/evm_module/scripts/evm_rpc_tests.sh +++ b/integration_test/evm_module/scripts/evm_rpc_tests.sh @@ -85,9 +85,16 @@ bump_chain_to_height() { while [[ "$height" =~ ^[0-9]+$ ]] && [ "$height" -lt "$target" ]; do local seq_before; seq_before=$(get_from_seq) local prev_height="$height" - # Progress-only EVM send: these historical RPC fixtures need real block - # history to exist first when empty blocks are disabled. - run seid tx evm send "$RECIPIENT" 1 --from "$FROM" "${KEYRING_ARGS[@]}" --chain-id sei --evm-rpc "$EVM_RPC_URL" -b sync -y >/dev/null + # Debug path: restore the original progress-only EVM send so we can inspect + # the -b sync response if it fails before inclusion. + local resp + resp=$(run seid tx evm send "$RECIPIENT" 1 --from "$FROM" "${KEYRING_ARGS[@]}" --chain-id sei --evm-rpc "$EVM_RPC_URL" -b sync -y 2>&1) || true + local code + code=$(echo "$resp" | jq -r '.code // empty' 2>/dev/null || true) + if [ -n "$code" ] && [ "$code" != "0" ]; then + echo "bump_chain_to_height CheckTx rejected: $(echo "$resp" | jq -r '.raw_log // .logs // .error // .message // "unknown error"' 2>/dev/null)" >&2 + return 1 + fi wait_from_seq_advance "$seq_before" wait_until_height_exceeds "$prev_height" height=$(get_latest_height) From 7543d79a3a5d1027fb4e44481582b8d8a6c3bf79 Mon Sep 17 00:00:00 2001 From: Grzegorz Prusak Date: Mon, 13 Jul 2026 18:29:10 +0200 Subject: [PATCH 28/54] fix --- .../evm_module/scripts/evm_rpc_tests.sh | 54 +++++++++---------- 1 file changed, 25 insertions(+), 29 deletions(-) diff --git a/integration_test/evm_module/scripts/evm_rpc_tests.sh b/integration_test/evm_module/scripts/evm_rpc_tests.sh index a8814f645f..f12443a3c4 100755 --- a/integration_test/evm_module/scripts/evm_rpc_tests.sh +++ b/integration_test/evm_module/scripts/evm_rpc_tests.sh @@ -79,25 +79,33 @@ wait_until_height_exceeds() { return 1 } +wait_for_receipt_field() { + local tx_hash="$1" + local field="$2" + local deadline=$(($(date +%s) + 30)) + while [ "$(date +%s)" -lt "$deadline" ]; do + local resp + resp=$(docker exec "$CONTAINER" curl -s -X POST -H "Content-Type: application/json" -d "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"eth_getTransactionReceipt\",\"params\":[\"$tx_hash\"]}" "$EVM_RPC_URL" 2>/dev/null) || true + local value + value=$(echo "$resp" | jq -r --arg field "$field" '.result[$field] // empty' 2>/dev/null) || true + if [[ -n "$value" && "$value" != "null" ]]; then + echo "$value" + return 0 + fi + sleep 1 + done + echo "timed out waiting for executed receipt field ${field} for ${tx_hash}" >&2 + return 1 +} + bump_chain_to_height() { local target="$1" local height; height=$(get_latest_height) while [[ "$height" =~ ^[0-9]+$ ]] && [ "$height" -lt "$target" ]; do - local seq_before; seq_before=$(get_from_seq) - local prev_height="$height" - # Debug path: restore the original progress-only EVM send so we can inspect - # the -b sync response if it fails before inclusion. - local resp - resp=$(run seid tx evm send "$RECIPIENT" 1 --from "$FROM" "${KEYRING_ARGS[@]}" --chain-id sei --evm-rpc "$EVM_RPC_URL" -b sync -y 2>&1) || true - local code - code=$(echo "$resp" | jq -r '.code // empty' 2>/dev/null || true) - if [ -n "$code" ] && [ "$code" != "0" ]; then - echo "bump_chain_to_height CheckTx rejected: $(echo "$resp" | jq -r '.raw_log // .logs // .error // .message // "unknown error"' 2>/dev/null)" >&2 - return 1 - fi - wait_from_seq_advance "$seq_before" - wait_until_height_exceeds "$prev_height" - height=$(get_latest_height) + # Progress-only EVM send: these fixtures need real historical blocks first. + local tx_hash + tx_hash=$(run seid tx evm send "$RECIPIENT" 1 --from "$FROM" "${KEYRING_ARGS[@]}" --chain-id sei --evm-rpc "$EVM_RPC_URL" -b sync -y | grep -oE '0x[a-fA-F0-9]{64}' | head -1) + height=$(wait_for_receipt_field "$tx_hash" blockNumber) done } @@ -137,13 +145,7 @@ SEQ_BEFORE_MINIMAL=$(get_from_seq) DEPLOY_OUT=$(run seid tx evm deploy /tmp/minimal_contract.hex --from "$FROM" "${KEYRING_ARGS[@]}" --chain-id sei --evm-rpc "$EVM_RPC_URL" -b sync -y 2>&1) || true DEPLOY_TX=$(echo "$DEPLOY_OUT" | grep -oE '0x[a-fA-F0-9]{64}' | head -1) if [[ -n "$DEPLOY_TX" ]]; then - sleep 2 - for _ in 1 2 3 4 5 6 7 8 9 10; do - RESP=$(docker exec "$CONTAINER" curl -s -X POST -H "Content-Type: application/json" -d "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"eth_getTransactionReceipt\",\"params\":[\"$DEPLOY_TX\"]}" "$EVM_RPC_URL" 2>/dev/null) || true - SEED=$(echo "$RESP" | grep -o '"blockNumber":"[^"]*"' | head -1 | cut -d'"' -f4) - [[ -n "$SEED" ]] && break - sleep 1 - done + SEED=$(wait_for_receipt_field "$DEPLOY_TX" blockNumber) || true if [[ -n "$SEED" ]]; then export SEI_EVM_IO_SEED_BLOCK="$SEED" export SEI_EVM_IO_DEPLOY_TX_HASH="$DEPLOY_TX" @@ -161,13 +163,7 @@ wait_from_seq_advance "$SEQ_BEFORE_MINIMAL" REVERTER_OUT=$(run seid tx evm deploy /tmp/reverter_contract.hex --from "$FROM" "${KEYRING_ARGS[@]}" --chain-id sei --evm-rpc "$EVM_RPC_URL" -b sync -y 2>&1) || true REVERTER_TX=$(echo "$REVERTER_OUT" | grep -oE '0x[a-fA-F0-9]{64}' | head -1) if [[ -n "$REVERTER_TX" ]]; then - sleep 2 - for _ in 1 2 3 4 5 6 7 8 9 10; do - RRESP=$(docker exec "$CONTAINER" curl -s -X POST -H "Content-Type: application/json" -d "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"eth_getTransactionReceipt\",\"params\":[\"$REVERTER_TX\"]}" "$EVM_RPC_URL" 2>/dev/null) || true - REVERTER_ADDR=$(echo "$RRESP" | grep -o '"contractAddress":"[^"]*"' | head -1 | cut -d'"' -f4) - [[ -n "$REVERTER_ADDR" ]] && break - sleep 1 - done + REVERTER_ADDR=$(wait_for_receipt_field "$REVERTER_TX" contractAddress) || true if [[ -n "$REVERTER_ADDR" ]]; then export SEI_EVM_IO_REVERTER_ADDRESS="$REVERTER_ADDR" fi From 5b44c299d3c965215fedc16c6c5d6c67d3e6126e Mon Sep 17 00:00:00 2001 From: Grzegorz Prusak Date: Mon, 13 Jul 2026 19:06:14 +0200 Subject: [PATCH 29/54] again --- .../evm_module/scripts/evm_rpc_tests.sh | 72 +++---------------- 1 file changed, 10 insertions(+), 62 deletions(-) diff --git a/integration_test/evm_module/scripts/evm_rpc_tests.sh b/integration_test/evm_module/scripts/evm_rpc_tests.sh index f12443a3c4..6117f4b132 100755 --- a/integration_test/evm_module/scripts/evm_rpc_tests.sh +++ b/integration_test/evm_module/scripts/evm_rpc_tests.sh @@ -22,49 +22,12 @@ run() { 'export PATH=$PATH:/root/go/bin && printf "%s\n" "$SEI_EVM_IO_PASSWORD" | "$@"' bash "$@" } -# Resolve sender's cosmos address once. On any failure (transient -# docker hiccup, container start race, missing key) FROM_ADDR stays -# empty and the wait helper below becomes a no-op — the script falls -# back to the original unprotected behavior rather than crashing -# under set -e. -FROM_ADDR=$(run seid keys show "$FROM" "${KEYRING_ARGS[@]}" -a 2>/dev/null) || FROM_ADDR= - -# Cosmos account sequence for $FROM_ADDR (or empty on any error). -# Always exits 0 so the calling `local cur=$(get_from_seq)` doesn't -# trip set -e in command substitution. -get_from_seq() { - if [ -z "$FROM_ADDR" ]; then echo; return 0; fi - local s - s=$(run seid q account "$FROM_ADDR" -o json 2>/dev/null | jq -r '.sequence // ""' 2>/dev/null) || true - echo "$s" -} - get_latest_height() { local h h=$(run seid status 2>/dev/null | jq -r '.SyncInfo.latest_block_height // "0"' 2>/dev/null) || true echo "${h:-0}" } -# Wait until $FROM_ADDR's sequence advances past $1. -# Direct causal "previous tx committed" signal: the sender's sequence -# advances atomically when its tx is included in a block, so by the -# time this returns the next CLI's pre-flight `q account` will read -# the post-tx sequence. No-op if FROM_ADDR resolution failed. -wait_from_seq_advance() { - local prev="$1" - if [ -z "$FROM_ADDR" ] || [ -z "$prev" ]; then return 0; fi - local deadline=$(($(date +%s) + 30)) - while [ "$(date +%s)" -lt "$deadline" ]; do - local cur; cur=$(get_from_seq) - if [[ "$cur" =~ ^[0-9]+$ ]] && [ "$cur" -gt "$prev" ]; then - return 0 - fi - sleep 0.5 - done - echo "timed out waiting for sequence to advance past ${prev}" >&2 - return 1 -} - wait_until_height_exceeds() { local prev="$1" local deadline=$(($(date +%s) + 30)) @@ -123,25 +86,16 @@ docker exec "$CONTAINER" /bin/bash -c "tr -d '[:space:]' < \"$CONTRACT_HEX\" > / # downstream eth_getTransactionReceipt polling handles inclusion # confirmation independently. # -# Poll the sender's sequence between each pair of consecutive -b sync -# submissions so the next CLI's pre-flight `q account` doesn't race -# the mempool's still-pending prior tx (otherwise both sign with the -# same sequence and the second's CheckTx rejects with "incorrect -# account sequence"). For the send line that's required because it -# has no `|| true` and a rejection would crash the script under set -e; -# for the deploy line it's required because a silent CheckTx rejection -# leaves SEI_EVM_IO_SEED_BLOCK unset and skips the __SEED__ fixtures. -# The reverter deploy further down only needs protection if the -# minimal deploy's receipt-poll loop didn't already wait (which it -# only skips when DEPLOY_TX is empty — i.e. minimal already failed, -# in which case reverter racing is no worse). -SEQ_BEFORE_ASSOC=$(get_from_seq) -run seid tx evm associate-address --from "$FROM" "${KEYRING_ARGS[@]}" --chain-id sei -b sync -y 2>/dev/null || true -wait_from_seq_advance "$SEQ_BEFORE_ASSOC" -SEQ_BEFORE_SEND=$(get_from_seq) -run seid tx evm send "$RECIPIENT" 1 --from "$FROM" "${KEYRING_ARGS[@]}" --chain-id sei --evm-rpc "$EVM_RPC_URL" -b sync -y -wait_from_seq_advance "$SEQ_BEFORE_SEND" -SEQ_BEFORE_MINIMAL=$(get_from_seq) +# These are EVM transactions, so receipt polling is the reliable +# inclusion signal. Cosmos account sequence waits are not appropriate here. +SEND_OUT=$(run seid tx evm send "$RECIPIENT" 1 --from "$FROM" "${KEYRING_ARGS[@]}" --chain-id sei --evm-rpc "$EVM_RPC_URL" -b sync -y 2>&1) +SEND_TX=$(echo "$SEND_OUT" | grep -oE '0x[a-fA-F0-9]{64}' | head -1) +if [[ -z "$SEND_TX" ]]; then + echo "Failed to extract seed transfer tx hash:" >&2 + printf "%s\n" "$SEND_OUT" >&2 + exit 1 +fi +wait_for_receipt_field "$SEND_TX" blockNumber >/dev/null DEPLOY_OUT=$(run seid tx evm deploy /tmp/minimal_contract.hex --from "$FROM" "${KEYRING_ARGS[@]}" --chain-id sei --evm-rpc "$EVM_RPC_URL" -b sync -y 2>&1) || true DEPLOY_TX=$(echo "$DEPLOY_OUT" | grep -oE '0x[a-fA-F0-9]{64}' | head -1) if [[ -n "$DEPLOY_TX" ]]; then @@ -153,13 +107,7 @@ if [[ -n "$DEPLOY_TX" ]]; then fi # Deploy reverter contract (reverts with Error("user error")); export SEI_EVM_IO_REVERTER_ADDRESS for .iox __REVERTER__ tag. -# wait_from_seq_advance even though minimal's receipt-poll loop above -# usually does the same job — when the grep fails to extract DEPLOY_TX -# from a successful CheckTx response (CLI format quirk, partial output), -# the receipt poll is skipped entirely and reverter would otherwise -# fire with a stale sequence. docker exec "$CONTAINER" /bin/bash -c "tr -d '[:space:]' < \"$REVERTER_HEX\" > /tmp/reverter_contract.hex" -wait_from_seq_advance "$SEQ_BEFORE_MINIMAL" REVERTER_OUT=$(run seid tx evm deploy /tmp/reverter_contract.hex --from "$FROM" "${KEYRING_ARGS[@]}" --chain-id sei --evm-rpc "$EVM_RPC_URL" -b sync -y 2>&1) || true REVERTER_TX=$(echo "$REVERTER_OUT" | grep -oE '0x[a-fA-F0-9]{64}' | head -1) if [[ -n "$REVERTER_TX" ]]; then From 65d4d7b4eb6905d46ff9c79c862a05c018a16893 Mon Sep 17 00:00:00 2001 From: Grzegorz Prusak Date: Mon, 13 Jul 2026 19:15:01 +0200 Subject: [PATCH 30/54] again --- evmrpc/simulate.go | 27 ++++++++++++++++--- evmrpc/simulate_test.go | 6 ++++- .../evm_module/scripts/evm_rpc_tests.sh | 1 + .../scripts/proposal_wait_for_pass.sh | 8 ++++-- 4 files changed, 36 insertions(+), 6 deletions(-) diff --git a/evmrpc/simulate.go b/evmrpc/simulate.go index 4f18824da2..4a8af00e23 100644 --- a/evmrpc/simulate.go +++ b/evmrpc/simulate.go @@ -732,23 +732,44 @@ func (b *Backend) resolveStateAndHeaderByNumberOrHash(ctx context.Context, block } sdkCtx := b.ctxProvider(ctxHeight) header := b.getHeader(ctx, tmBlock) - if isLatestBlock && tmBlock.Block.Height != sdkCtx.BlockHeight() { - header = b.syntheticHeaderFromCtx(sdkCtx) + if isLatestBlock { + header = b.latestLikeHeaderFromCtx(tmBlock, sdkCtx, header) } return sdkCtx, header, isLatestBlock, nil } +// latestLikeHeaderFromCtx normalizes the latest/safe/finalized/pending header +// view to the execution context callers will use for simulation. For post-commit +// heads we keep the real Tendermint block metadata but use the latest check-state +// base fee (the fee for the next block), matching eth_call/CurrentHeader's +// historical behavior and eth_gasPrice. Before the first Commit, there is no +// coherent committed block/header for the latest check-state, so we synthesize +// one directly from sdkCtx instead. +func (b *Backend) latestLikeHeaderFromCtx(tmBlock *coretypes.ResultBlock, sdkCtx sdk.Context, header *ethtypes.Header) *ethtypes.Header { + if tmBlock.Block.Height != sdkCtx.BlockHeight() { + return b.syntheticHeaderFromCtx(sdkCtx) + } + header.BaseFee = b.keeper.GetNextBaseFeePerGas(sdkCtx).TruncateInt().BigInt() + return header +} + // syntheticHeaderFromCtx builds a minimal header directly from the selected SDK // context when no coherent Tendermint block/header is available for that state // (e.g. latest-like queries before the first Commit). func (b *Backend) syntheticHeaderFromCtx(sdkCtx sdk.Context) *ethtypes.Header { zeroExcessBlobGas := uint64(0) baseFee := b.keeper.GetNextBaseFeePerGas(sdkCtx).TruncateInt().BigInt() + var gasLimit uint64 + if cp := sdkCtx.ConsensusParams(); cp != nil && cp.Block != nil { + gasLimit = uint64(cp.Block.MaxGas) //nolint:gosec + } else { + gasLimit = keeper.DefaultBlockGasLimit + } return ðtypes.Header{ Difficulty: common.Big0, Number: big.NewInt(sdkCtx.BlockHeight()), BaseFee: baseFee, - GasLimit: keeper.DefaultBlockGasLimit, + GasLimit: gasLimit, Time: toUint64(sdkCtx.BlockTime().Unix()), //nolint:gosec ExcessBlobGas: &zeroExcessBlobGas, } diff --git a/evmrpc/simulate_test.go b/evmrpc/simulate_test.go index 7040becaae..abe61109bf 100644 --- a/evmrpc/simulate_test.go +++ b/evmrpc/simulate_test.go @@ -570,6 +570,8 @@ func TestSimulateBackendBlockResolutionCoverage(t *testing.T) { require.NotNil(t, h) require.Equal(t, int64(1), h.Number.Int64()) require.Equal(t, common.BytesToHash(MockBlockID.Hash), h.ParentHash) + expectedBaseFee := testApp.EvmKeeper.GetNextBaseFeePerGas(ctxProvider(evmrpc.LatestCtxHeight)).TruncateInt().BigInt() + require.Equal(t, 0, h.BaseFee.Cmp(expectedBaseFee)) }) t.Run("CurrentHeader_fallback_gas_limit_when_block_unavailable", func(t *testing.T) { @@ -581,7 +583,9 @@ func TestSimulateBackendBlockResolutionCoverage(t *testing.T) { h := b2.CurrentHeader() require.NotNil(t, h) require.Equal(t, int64(1), h.Number.Int64()) - require.Equal(t, uint64(10_000_000), h.GasLimit) + require.Equal(t, uint64(baseCtx.ConsensusParams().Block.MaxGas), h.GasLimit) + expectedBaseFee := testApp.EvmKeeper.GetNextBaseFeePerGas(ctxProvider(evmrpc.LatestCtxHeight)).TruncateInt().BigInt() + require.Equal(t, 0, h.BaseFee.Cmp(expectedBaseFee)) require.Equal(t, common.Hash{}, h.ParentHash) }) } diff --git a/integration_test/evm_module/scripts/evm_rpc_tests.sh b/integration_test/evm_module/scripts/evm_rpc_tests.sh index 6117f4b132..3d595bd728 100755 --- a/integration_test/evm_module/scripts/evm_rpc_tests.sh +++ b/integration_test/evm_module/scripts/evm_rpc_tests.sh @@ -69,6 +69,7 @@ bump_chain_to_height() { local tx_hash tx_hash=$(run seid tx evm send "$RECIPIENT" 1 --from "$FROM" "${KEYRING_ARGS[@]}" --chain-id sei --evm-rpc "$EVM_RPC_URL" -b sync -y | grep -oE '0x[a-fA-F0-9]{64}' | head -1) height=$(wait_for_receipt_field "$tx_hash" blockNumber) + height=$((height)) done } diff --git a/integration_test/upgrade_module/scripts/proposal_wait_for_pass.sh b/integration_test/upgrade_module/scripts/proposal_wait_for_pass.sh index 0b3d1aabe2..3b2d5ee96b 100755 --- a/integration_test/upgrade_module/scripts/proposal_wait_for_pass.sh +++ b/integration_test/upgrade_module/scripts/proposal_wait_for_pass.sh @@ -3,7 +3,11 @@ PROPOSAL_ID=$1 TIMEOUT=300 seidbin=seid +chainid=sei source integration_test/utils/_tx_helpers.sh -wait_for_proposal_status "$PROPOSAL_ID" "PROPOSAL_STATUS_PASSED" "admin" "$TIMEOUT" >/dev/null -echo "Proposal $PROPOSAL_ID has passed!" +if wait_for_proposal_status "$PROPOSAL_ID" "PROPOSAL_STATUS_PASSED" "admin" "$TIMEOUT" >/dev/null; then + echo "Proposal $PROPOSAL_ID has passed!" +else + exit 1 +fi From 646608e81972bd82618c47582347884bac0872ff Mon Sep 17 00:00:00 2001 From: Grzegorz Prusak Date: Mon, 13 Jul 2026 19:27:39 +0200 Subject: [PATCH 31/54] again --- contracts/test/EVMCompatabilityTest.js | 4 ++-- contracts/test/lib.js | 2 +- evmrpc/simulate.go | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/contracts/test/EVMCompatabilityTest.js b/contracts/test/EVMCompatabilityTest.js index 2f1e5b5694..8acf0b44da 100644 --- a/contracts/test/EVMCompatabilityTest.js +++ b/contracts/test/EVMCompatabilityTest.js @@ -4,7 +4,7 @@ const {uniq} = require("lodash"); const hre = require('hardhat'); const { ethers, upgrades } = hre; const { getImplementationAddress } = require('@openzeppelin/upgrades-core'); -const { deployEvmContract, setupSigners, fundAddress, mineTransferBlock } = require("./lib") +const { deployEvmContract, setupSigners, fundAddress, mineTransferBlock, waitForReceipt } = require("./lib") const axios = require("axios"); const { default: BigNumber } = require("bignumber.js"); @@ -980,7 +980,7 @@ describe("EVM Test", function () { // stops working once the chain grows past that (notably under // Autobahn's faster block production). const tx = await evmTester.emitDummyEvent("test", 0, { gasPrice: ethers.parseUnits('100', 'gwei') }); - const receipt = await tx.wait(); + const receipt = await waitForReceipt(tx.hash); const filter = { fromBlock: receipt.blockNumber, toBlock: receipt.blockNumber, diff --git a/contracts/test/lib.js b/contracts/test/lib.js index 22b740015e..7c05bd494f 100644 --- a/contracts/test/lib.js +++ b/contracts/test/lib.js @@ -86,7 +86,7 @@ async function mineTransferBlock(sender) { value: 1n, gasPrice: ethers.parseUnits('100', 'gwei'), }) - return await tx.wait() + return await waitForReceipt(tx.hash) } // Default 2 because the very next block after submit can be empty diff --git a/evmrpc/simulate.go b/evmrpc/simulate.go index 4a8af00e23..be3005426a 100644 --- a/evmrpc/simulate.go +++ b/evmrpc/simulate.go @@ -760,7 +760,7 @@ func (b *Backend) syntheticHeaderFromCtx(sdkCtx sdk.Context) *ethtypes.Header { zeroExcessBlobGas := uint64(0) baseFee := b.keeper.GetNextBaseFeePerGas(sdkCtx).TruncateInt().BigInt() var gasLimit uint64 - if cp := sdkCtx.ConsensusParams(); cp != nil && cp.Block != nil { + if cp := sdkCtx.ConsensusParams(); cp != nil && cp.Block != nil && cp.Block.MaxGas > 0 { gasLimit = uint64(cp.Block.MaxGas) //nolint:gosec } else { gasLimit = keeper.DefaultBlockGasLimit @@ -784,7 +784,7 @@ func (b *Backend) getHeader(ctx context.Context, tmBlock *coretypes.ResultBlock) baseFee = nil } var gasLimit uint64 - if cp := sdkCtx.ConsensusParams(); cp != nil && cp.Block != nil { + if cp := sdkCtx.ConsensusParams(); cp != nil && cp.Block != nil && cp.Block.MaxGas > 0 { gasLimit = uint64(cp.Block.MaxGas) //nolint:gosec } else { gasLimit = keeper.DefaultBlockGasLimit From a519f9f3c1da77e41c443f28da7092201dc688ee Mon Sep 17 00:00:00 2001 From: Grzegorz Prusak Date: Mon, 13 Jul 2026 20:04:07 +0200 Subject: [PATCH 32/54] again --- contracts/test/EVMCompatabilityTest.js | 14 +++++++--- .../evm_module/scripts/evm_rpc_tests.sh | 27 ++++++++++++++++--- 2 files changed, 34 insertions(+), 7 deletions(-) diff --git a/contracts/test/EVMCompatabilityTest.js b/contracts/test/EVMCompatabilityTest.js index 8acf0b44da..66f3aeb6af 100644 --- a/contracts/test/EVMCompatabilityTest.js +++ b/contracts/test/EVMCompatabilityTest.js @@ -4,7 +4,7 @@ const {uniq} = require("lodash"); const hre = require('hardhat'); const { ethers, upgrades } = hre; const { getImplementationAddress } = require('@openzeppelin/upgrades-core'); -const { deployEvmContract, setupSigners, fundAddress, mineTransferBlock, waitForReceipt } = require("./lib") +const { deployEvmContract, setupSigners, fundAddress, mineTransferBlock, waitForReceipt, waitForCondition } = require("./lib") const axios = require("axios"); const { default: BigNumber } = require("bignumber.js"); @@ -698,11 +698,17 @@ describe("EVM Test", function () { const block = await ethers.provider.getBlock(blockHeight); const baseFee = Number(block.baseFeePerGas); expect(baseFee).to.equal(oneGwei); - // Progress-only block: the assertion is about the next block's base - // fee, so force that block to exist explicitly. + // Progress-only block: the assertion is specifically about child + // block H+1 of the heavy tx's block H, so force progress and then + // read that exact child block once it exists. const nextReceipt = await mineTransferBlock(owner); expect(nextReceipt.blockNumber).to.be.greaterThan(blockHeight); - const nextBlock = await ethers.provider.getBlock(nextReceipt.blockNumber); + const nextBlockHeight = blockHeight + 1; + await waitForCondition( + async () => (await ethers.provider.getBlock(nextBlockHeight)) !== null, + `block ${nextBlockHeight} to exist`, + ); + const nextBlock = await ethers.provider.getBlock(nextBlockHeight); const nextBaseFee = Number(nextBlock.baseFeePerGas); expect(nextBaseFee).to.be.greaterThan(oneGwei); }); diff --git a/integration_test/evm_module/scripts/evm_rpc_tests.sh b/integration_test/evm_module/scripts/evm_rpc_tests.sh index 3d595bd728..d04211e9a7 100755 --- a/integration_test/evm_module/scripts/evm_rpc_tests.sh +++ b/integration_test/evm_module/scripts/evm_rpc_tests.sh @@ -64,12 +64,33 @@ wait_for_receipt_field() { bump_chain_to_height() { local target="$1" local height; height=$(get_latest_height) - while [[ "$height" =~ ^[0-9]+$ ]] && [ "$height" -lt "$target" ]; do + if ! [[ "$height" =~ ^[0-9]+$ ]]; then + echo "failed to read numeric chain height before preseeding: ${height}" >&2 + return 1 + fi + local max_iters=$((target - height + 5)) + if [ "$max_iters" -lt 1 ]; then + return 0 + fi + local iter=0 + while [ "$height" -lt "$target" ]; do + iter=$((iter + 1)) + if [ "$iter" -gt "$max_iters" ]; then + echo "timed out preseeding historical blocks: height=${height}, target=${target}, attempts=${iter}" >&2 + return 1 + fi # Progress-only EVM send: these fixtures need real historical blocks first. local tx_hash tx_hash=$(run seid tx evm send "$RECIPIENT" 1 --from "$FROM" "${KEYRING_ARGS[@]}" --chain-id sei --evm-rpc "$EVM_RPC_URL" -b sync -y | grep -oE '0x[a-fA-F0-9]{64}' | head -1) - height=$(wait_for_receipt_field "$tx_hash" blockNumber) - height=$((height)) + if [[ -z "$tx_hash" ]]; then + echo "failed to extract progress tx hash while preseeding historical blocks" >&2 + return 1 + fi + local receipt_height + if ! receipt_height=$(wait_for_receipt_field "$tx_hash" blockNumber); then + return 1 + fi + height=$((receipt_height)) done } From e70dd2e08a8d2d9ef43e94e4a147317609062298 Mon Sep 17 00:00:00 2001 From: Grzegorz Prusak Date: Mon, 13 Jul 2026 20:14:38 +0200 Subject: [PATCH 33/54] again --- evmrpc/genesis_latest_state_test.go | 2 +- evmrpc/simulate.go | 88 ++++++++++++++--------------- evmrpc/simulate_test.go | 2 +- 3 files changed, 44 insertions(+), 48 deletions(-) diff --git a/evmrpc/genesis_latest_state_test.go b/evmrpc/genesis_latest_state_test.go index 6a384befc1..0585c1d964 100644 --- a/evmrpc/genesis_latest_state_test.go +++ b/evmrpc/genesis_latest_state_test.go @@ -55,7 +55,7 @@ func (*freshChainClient) Genesis(context.Context) (*coretypes.ResultGenesis, err } func (*freshChainClient) Block(context.Context, *int64) (*coretypes.ResultBlock, error) { - return &coretypes.ResultBlock{Block: &tmtypes.Block{Header: tmtypes.Header{Height: 0}}}, nil + return nil, coretypes.ErrZeroOrNegativeHeight } func (*freshChainClient) BlockByHash(context.Context, bytes.HexBytes) (*coretypes.ResultBlock, error) { diff --git a/evmrpc/simulate.go b/evmrpc/simulate.go index be3005426a..ba9ac2e4b4 100644 --- a/evmrpc/simulate.go +++ b/evmrpc/simulate.go @@ -520,7 +520,7 @@ func (b *Backend) Engine() consensus.Engine { } func (b *Backend) HeaderByNumber(ctx context.Context, bn rpc.BlockNumber) (*ethtypes.Header, error) { - tmBlock, _, err := b.getBlockByNumberOrHash(ctx, rpc.BlockNumberOrHashWithNumber(bn)) + tmBlock, err := b.getBlockByNumberOrHash(ctx, rpc.BlockNumberOrHashWithNumber(bn)) if err != nil { return nil, err } @@ -682,38 +682,43 @@ func (b *Backend) SuggestGasTipCap(context.Context) (*big.Int, error) { // getBlockByNumberOrHash resolves blockNrOrHash to a Tendermint ResultBlock in one RPC path // (by hash or by number, including latest). Callers pass the result to getHeader. -func (b *Backend) getBlockByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*coretypes.ResultBlock, bool, error) { - var ( - block *coretypes.ResultBlock - err error - isLatestBlock bool - ) - +func (b *Backend) getBlockByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*coretypes.ResultBlock, error) { if blockNrOrHash.BlockHash != nil { - block, err = blockByHashRespectingWatermarks(ctx, b.tmClient, b.watermarks, blockNrOrHash.BlockHash[:], 1) + block, err := blockByHashRespectingWatermarks(ctx, b.tmClient, b.watermarks, blockNrOrHash.BlockHash[:], 1) if err != nil { - return nil, false, err + return nil, err } - return block, false, nil + return block, nil } var blockNumberPtr *int64 + var err error if blockNrOrHash.BlockNumber != nil { blockNumberPtr, err = getBlockNumber(ctx, b.tmClient, *blockNrOrHash.BlockNumber) if err != nil { - return nil, false, err - } - if blockNumberPtr == nil { - isLatestBlock = true + return nil, err } - } else { - isLatestBlock = true } - block, err = blockByNumberRespectingWatermarks(ctx, b.tmClient, b.watermarks, blockNumberPtr, 1) + block, err := blockByNumberRespectingWatermarks(ctx, b.tmClient, b.watermarks, blockNumberPtr, 1) if err != nil { - return nil, false, err + return nil, err + } + return block, nil +} + +func isLatestLikeBlockRef(blockNrOrHash rpc.BlockNumberOrHash) bool { + if blockNrOrHash.BlockHash != nil { + return false + } + if blockNrOrHash.BlockNumber == nil { + return true + } + switch *blockNrOrHash.BlockNumber { + case rpc.SafeBlockNumber, rpc.FinalizedBlockNumber, rpc.LatestBlockNumber, rpc.PendingBlockNumber: + return true + default: + return false } - return block, isLatestBlock, nil } // resolveStateAndHeaderByNumberOrHash normalizes a block reference into the @@ -722,35 +727,26 @@ func (b *Backend) getBlockByNumberOrHash(ctx context.Context, blockNrOrHash rpc. // latest checkState context over the height-0 Tendermint block so callers receive // a coherent (state, header) view. func (b *Backend) resolveStateAndHeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (sdk.Context, *ethtypes.Header, bool, error) { - tmBlock, isLatestBlock, err := b.getBlockByNumberOrHash(ctx, blockNrOrHash) - if err != nil { - return sdk.Context{}, nil, false, err - } - ctxHeight := tmBlock.Block.Height - if isLatestBlock { - ctxHeight = LatestCtxHeight - } - sdkCtx := b.ctxProvider(ctxHeight) - header := b.getHeader(ctx, tmBlock) - if isLatestBlock { - header = b.latestLikeHeaderFromCtx(tmBlock, sdkCtx, header) + if isLatestLikeBlockRef(blockNrOrHash) { + sdkCtx := b.ctxProvider(LatestCtxHeight) + tmBlock, err := b.getBlockByNumberOrHash(ctx, blockNrOrHash) + if err != nil { + return sdkCtx, b.syntheticHeaderFromCtx(sdkCtx), true, nil + } + header := b.getHeader(ctx, tmBlock) + if tmBlock.Block.Height != sdkCtx.BlockHeight() { + return sdkCtx, b.syntheticHeaderFromCtx(sdkCtx), true, nil + } + header.BaseFee = b.keeper.GetNextBaseFeePerGas(sdkCtx).TruncateInt().BigInt() + return sdkCtx, header, true, nil } - return sdkCtx, header, isLatestBlock, nil -} -// latestLikeHeaderFromCtx normalizes the latest/safe/finalized/pending header -// view to the execution context callers will use for simulation. For post-commit -// heads we keep the real Tendermint block metadata but use the latest check-state -// base fee (the fee for the next block), matching eth_call/CurrentHeader's -// historical behavior and eth_gasPrice. Before the first Commit, there is no -// coherent committed block/header for the latest check-state, so we synthesize -// one directly from sdkCtx instead. -func (b *Backend) latestLikeHeaderFromCtx(tmBlock *coretypes.ResultBlock, sdkCtx sdk.Context, header *ethtypes.Header) *ethtypes.Header { - if tmBlock.Block.Height != sdkCtx.BlockHeight() { - return b.syntheticHeaderFromCtx(sdkCtx) + tmBlock, err := b.getBlockByNumberOrHash(ctx, blockNrOrHash) + if err != nil { + return sdk.Context{}, nil, false, err } - header.BaseFee = b.keeper.GetNextBaseFeePerGas(sdkCtx).TruncateInt().BigInt() - return header + sdkCtx := b.ctxProvider(tmBlock.Block.Height) + return sdkCtx, b.getHeader(ctx, tmBlock), false, nil } // syntheticHeaderFromCtx builds a minimal header directly from the selected SDK diff --git a/evmrpc/simulate_test.go b/evmrpc/simulate_test.go index abe61109bf..45def61493 100644 --- a/evmrpc/simulate_test.go +++ b/evmrpc/simulate_test.go @@ -295,7 +295,7 @@ func TestCreateAccessList(t *testing.T) { result := resObj["result"].(map[string]any) require.Equal(t, []any{}, result["accessList"]) // the code uses MSTORE which does not trace access list - resObj = sendRequestBad(t, "createAccessList", txArgs, "latest") + resObj = sendRequestBad(t, "createAccessList", txArgs, "0x1") result = resObj["error"].(map[string]any) require.Equal(t, "error block", result["message"]) From e38febe038876190a6a17e17784a2d12bf0ea104 Mon Sep 17 00:00:00 2001 From: Grzegorz Prusak Date: Tue, 14 Jul 2026 12:51:28 +0200 Subject: [PATCH 34/54] fix --- contracts/test/lib.js | 2 +- evmrpc/simulate.go | 15 ++++++++------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/contracts/test/lib.js b/contracts/test/lib.js index 7c05bd494f..8d539559ce 100644 --- a/contracts/test/lib.js +++ b/contracts/test/lib.js @@ -140,7 +140,7 @@ async function evmSend(addr, fromKey, amount="10000000000000000000000000") { // seid tx evm send prints "Transaction hash: 0x..." on its own format // (not the standard cosmos JSON response), so we extract from text and // wait via the JSON-RPC receipt — semantically equivalent to -b block. - const output = await execute(`seid tx evm send ${addr} ${amount} --from ${fromKey} -b sync -y`); + const output = await execute(`seid tx evm send ${addr} ${amount} --from ${fromKey} --evm-rpc=http://localhost:8545 -b sync -y`); const evmTxHash = output.replace(/.*0x/, "0x").trim() await waitForReceipt(evmTxHash) return evmTxHash diff --git a/evmrpc/simulate.go b/evmrpc/simulate.go index ba9ac2e4b4..ded516904a 100644 --- a/evmrpc/simulate.go +++ b/evmrpc/simulate.go @@ -234,7 +234,7 @@ func (e *RevertError) ErrorCode() int { } // ErrorData returns the hex encoded revert reason. -func (e *RevertError) ErrorData() interface{} { +func (e *RevertError) ErrorData() any { return e.reason } @@ -447,7 +447,7 @@ func (b Backend) BlockByNumber(ctx context.Context, bn rpc.BlockNumber) (*ethtyp }) } } - header := b.getHeader(ctx, tmBlock) + header := b.getHeader(tmBlock) block := ðtypes.Block{ Header_: header, Txs: txs, @@ -524,7 +524,7 @@ func (b *Backend) HeaderByNumber(ctx context.Context, bn rpc.BlockNumber) (*etht if err != nil { return nil, err } - return b.getHeader(ctx, tmBlock), nil + return b.getHeader(tmBlock), nil } func (b *Backend) StateAtTransaction(ctx context.Context, block *ethtypes.Block, txIndex int, reexec uint64) (*ethtypes.Transaction, vm.BlockContext, vm.StateDB, tracers.StateReleaseFunc, error) { @@ -671,7 +671,8 @@ func (b *Backend) GetEVM(_ context.Context, msg *core.Message, stateDB vm.StateD func (b *Backend) CurrentHeader() *ethtypes.Header { _, header, _, err := b.resolveStateAndHeaderByNumberOrHash(context.Background(), rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber)) if err != nil { - header = b.syntheticHeaderFromCtx(b.ctxProvider(LatestCtxHeight)) + // resolveStateAndHeaderByNumberOrHash never returns an error for LatestBlockNumber + panic(err) } return header } @@ -733,7 +734,7 @@ func (b *Backend) resolveStateAndHeaderByNumberOrHash(ctx context.Context, block if err != nil { return sdkCtx, b.syntheticHeaderFromCtx(sdkCtx), true, nil } - header := b.getHeader(ctx, tmBlock) + header := b.getHeader(tmBlock) if tmBlock.Block.Height != sdkCtx.BlockHeight() { return sdkCtx, b.syntheticHeaderFromCtx(sdkCtx), true, nil } @@ -746,7 +747,7 @@ func (b *Backend) resolveStateAndHeaderByNumberOrHash(ctx context.Context, block return sdk.Context{}, nil, false, err } sdkCtx := b.ctxProvider(tmBlock.Block.Height) - return sdkCtx, b.getHeader(ctx, tmBlock), false, nil + return sdkCtx, b.getHeader(tmBlock), false, nil } // syntheticHeaderFromCtx builds a minimal header directly from the selected SDK @@ -771,7 +772,7 @@ func (b *Backend) syntheticHeaderFromCtx(sdkCtx sdk.Context) *ethtypes.Header { } } -func (b *Backend) getHeader(ctx context.Context, tmBlock *coretypes.ResultBlock) *ethtypes.Header { +func (b *Backend) getHeader(tmBlock *coretypes.ResultBlock) *ethtypes.Header { height := tmBlock.Block.Height zeroExcessBlobGas := uint64(0) baseFee := b.keeper.GetNextBaseFeePerGas(b.ctxProvider(height - 1)).TruncateInt().BigInt() From 6c766e0bb1badb256c060ae2b00c8e37763fc24f Mon Sep 17 00:00:00 2001 From: Grzegorz Prusak Date: Tue, 14 Jul 2026 13:55:30 +0200 Subject: [PATCH 35/54] logger --- evmrpc/filter.go | 7 +++++++ evmrpc/simulate.go | 18 ++++++++++-------- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/evmrpc/filter.go b/evmrpc/filter.go index 09baf1f2b0..69272c9374 100644 --- a/evmrpc/filter.go +++ b/evmrpc/filter.go @@ -6,6 +6,7 @@ import ( "encoding/json" "errors" "fmt" + "log/slog" "sort" "sync" "time" @@ -13,6 +14,7 @@ import ( "github.com/ethereum/go-ethereum/common" ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/eth/filters" + gethlog "github.com/ethereum/go-ethereum/log" ethrpc "github.com/ethereum/go-ethereum/rpc" "github.com/hashicorp/golang-lru/v2/expirable" evmrpcconfig "github.com/sei-protocol/sei-chain/evmrpc/config" @@ -30,6 +32,11 @@ import ( var logger = seilog.NewLogger("evmrpc") +func init() { + handler := logger.Handler().WithAttrs([]slog.Attr{slog.String("logger", "evmrpc/geth")}) + gethlog.SetDefault(gethlog.NewLogger(handler)) +} + const ( // DB Concurrency Read Limit MaxDBReadConcurrency = 16 diff --git a/evmrpc/simulate.go b/evmrpc/simulate.go index ded516904a..f517de8b55 100644 --- a/evmrpc/simulate.go +++ b/evmrpc/simulate.go @@ -670,9 +670,8 @@ func (b *Backend) GetEVM(_ context.Context, msg *core.Message, stateDB vm.StateD func (b *Backend) CurrentHeader() *ethtypes.Header { _, header, _, err := b.resolveStateAndHeaderByNumberOrHash(context.Background(), rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber)) - if err != nil { - // resolveStateAndHeaderByNumberOrHash never returns an error for LatestBlockNumber - panic(err) + if err!=nil { + return b.syntheticHeaderFromCtx(b.ctxProvider(LatestCtxHeight)) } return header } @@ -730,14 +729,17 @@ func isLatestLikeBlockRef(blockNrOrHash rpc.BlockNumberOrHash) bool { func (b *Backend) resolveStateAndHeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (sdk.Context, *ethtypes.Header, bool, error) { if isLatestLikeBlockRef(blockNrOrHash) { sdkCtx := b.ctxProvider(LatestCtxHeight) - tmBlock, err := b.getBlockByNumberOrHash(ctx, blockNrOrHash) - if err != nil { + h := sdkCtx.BlockHeight() + if wmHeight,err := b.watermarks.LatestHeight(ctx); err!=nil { + return sdkCtx,nil,false,fmt.Errorf("b.watermarks.LatestHeight(): %w", err) + } else if wmHeight!=h { return sdkCtx, b.syntheticHeaderFromCtx(sdkCtx), true, nil } - header := b.getHeader(tmBlock) - if tmBlock.Block.Height != sdkCtx.BlockHeight() { - return sdkCtx, b.syntheticHeaderFromCtx(sdkCtx), true, nil + tmBlock, err := b.tmClient.Block(ctx, &h) + if err!=nil { + return sdkCtx,nil,false,fmt.Errorf("b.tmClient.Block(): %w",err) } + header := b.getHeader(tmBlock) header.BaseFee = b.keeper.GetNextBaseFeePerGas(sdkCtx).TruncateInt().BigInt() return sdkCtx, header, true, nil } From 1d5e17674bd64329b693d5ed27b4ede5ccc4ef96 Mon Sep 17 00:00:00 2001 From: Grzegorz Prusak Date: Tue, 14 Jul 2026 14:07:44 +0200 Subject: [PATCH 36/54] removed ctx from InitChain --- app/eth_replay.go | 4 +- app/test_helpers.go | 75 +++++++++---------- evmrpc/simulate.go | 12 +-- giga/deps/testutil/processblock/common.go | 2 +- sei-cosmos/baseapp/abci.go | 2 +- sei-cosmos/baseapp/abci_test.go | 6 +- sei-cosmos/baseapp/deliver_tx_batch_test.go | 4 +- sei-cosmos/baseapp/deliver_tx_test.go | 37 +++++---- sei-cosmos/server/mock/app_test.go | 4 +- sei-cosmos/server/rollback_test.go | 2 +- sei-cosmos/x/auth/module_test.go | 10 +-- sei-cosmos/x/distribution/module_test.go | 10 +-- sei-cosmos/x/gov/genesis_test.go | 9 +-- sei-cosmos/x/gov/module_test.go | 10 +-- sei-cosmos/x/staking/module_test.go | 10 +-- sei-cosmos/x/upgrade/abci_test.go | 2 +- sei-ibc-go/testing/simapp/test_helpers.go | 22 +++--- .../abci/example/kvstore/kvstore.go | 2 +- .../abci/example/kvstore/kvstore_test.go | 2 +- sei-tendermint/abci/types/application.go | 4 +- .../abci/types/mocks/application.go | 18 ++--- .../internal/consensus/byzantine_test.go | 2 +- .../internal/consensus/common_test.go | 4 +- .../internal/consensus/reactor_test.go | 2 +- sei-tendermint/internal/consensus/replay.go | 2 +- .../internal/consensus/replay_test.go | 6 +- .../consensus/state_empty_valset_test.go | 4 +- .../internal/p2p/giga_router_common.go | 2 +- .../p2p/giga_router_testhelper_test.go | 4 +- sei-tendermint/internal/proxy/proxy.go | 4 +- sei-tendermint/test/e2e/app/app.go | 2 +- sei-wasmd/app/app_test.go | 2 +- sei-wasmd/app/test_helpers.go | 12 ++- sei-wasmd/benchmarks/app_test.go | 10 +-- testutil/processblock/common.go | 2 +- 35 files changed, 139 insertions(+), 166 deletions(-) diff --git a/app/eth_replay.go b/app/eth_replay.go index 9a29330749..732ff58fe2 100644 --- a/app/eth_replay.go +++ b/app/eth_replay.go @@ -35,7 +35,7 @@ func Replay(a *App) { if err != nil { panic(err) } - _, err = a.InitChain(context.Background(), &abci.RequestInitChain{ + _, err = a.InitChain(&abci.RequestInitChain{ Time: time.Now(), ChainId: gendoc.ChainID, AppStateBytes: gendoc.AppState, @@ -119,7 +119,7 @@ func BlockTest(a *App, bt *ethtests.BlockTest) { if err != nil { panic(err) } - _, err = a.InitChain(context.Background(), &abci.RequestInitChain{ + _, err = a.InitChain(&abci.RequestInitChain{ Time: time.Now(), ChainId: gendoc.ChainID, AppStateBytes: gendoc.AppState, diff --git a/app/test_helpers.go b/app/test_helpers.go index 8d25f981b0..79efcb31ee 100644 --- a/app/test_helpers.go +++ b/app/test_helpers.go @@ -404,12 +404,11 @@ func SetupWithAppOptsAndDefaultHome(isCheckTx bool, appOpts TestAppOpts, enableE // TODO: remove once init chain works with SC defer func() { _ = recover() }() - _, err = res.InitChain( - context.Background(), &abci.RequestInitChain{ - ConsensusParams: DefaultConsensusParams, - ChainId: "sei-test", - AppStateBytes: stateBytes, - }, + _, err = res.InitChain(&abci.RequestInitChain{ + ConsensusParams: DefaultConsensusParams, + ChainId: "sei-test", + AppStateBytes: stateBytes, + }, ) if err != nil { panic(err) @@ -473,12 +472,11 @@ func SetupWithDB(tb testing.TB, db dbm.DB, isCheckTx bool, enableEVMCustomPrecom panic(err) } - _, err = res.InitChain( - context.Background(), &abci.RequestInitChain{ - ConsensusParams: DefaultConsensusParams, - ChainId: "sei-test", - AppStateBytes: stateBytes, - }, + _, err = res.InitChain(&abci.RequestInitChain{ + ConsensusParams: DefaultConsensusParams, + ChainId: "sei-test", + AppStateBytes: stateBytes, + }, ) if err != nil { panic(err) @@ -521,12 +519,11 @@ func SetupWithScReceiptFromOpts(t *testing.T, isCheckTx bool, enableEVMCustomPre defer func() { _ = recover() }() - _, err = res.InitChain( - context.Background(), &abci.RequestInitChain{ - ConsensusParams: DefaultConsensusParams, - ChainId: "sei-test", - AppStateBytes: stateBytes, - }, + _, err = res.InitChain(&abci.RequestInitChain{ + ConsensusParams: DefaultConsensusParams, + ChainId: "sei-test", + AppStateBytes: stateBytes, + }, ) if err != nil { panic(err) @@ -577,11 +574,10 @@ func SetupWithSc(t *testing.T, isCheckTx bool, enableEVMCustomPrecompiles bool, // TODO: remove once init chain works with SC defer func() { _ = recover() }() - _, err = res.InitChain( - context.Background(), &abci.RequestInitChain{ - ConsensusParams: DefaultConsensusParams, - AppStateBytes: stateBytes, - }, + _, err = res.InitChain(&abci.RequestInitChain{ + ConsensusParams: DefaultConsensusParams, + AppStateBytes: stateBytes, + }, ) if err != nil { panic(err) @@ -621,12 +617,11 @@ func SetupTestingAppWithLevelDb(t *testing.T, isCheckTx bool, enableEVMCustomPre panic(err) } - _, err = app.InitChain( - context.Background(), &abci.RequestInitChain{ - ConsensusParams: DefaultConsensusParams, - ChainId: "sei-test", - AppStateBytes: stateBytes, - }, + _, err = app.InitChain(&abci.RequestInitChain{ + ConsensusParams: DefaultConsensusParams, + ChainId: "sei-test", + AppStateBytes: stateBytes, + }, ) if err != nil { panic(err) @@ -748,12 +743,11 @@ func SetupWithGenesisValSet(t *testing.T, valSet *tmtypes.ValidatorSet, genAccs require.NoError(t, err) // init chain will set the validator set and initialize the genesis accounts - _, _ = app.InitChain( - context.Background(), &abci.RequestInitChain{ - ConsensusParams: DefaultConsensusParams, - ChainId: "sei-test", - AppStateBytes: stateBytes, - }, + _, _ = app.InitChain(&abci.RequestInitChain{ + ConsensusParams: DefaultConsensusParams, + ChainId: "sei-test", + AppStateBytes: stateBytes, + }, ) // commit genesis changes @@ -790,12 +784,11 @@ func SetupWithGenesisAccounts(t *testing.T, genAccs []authtypes.GenesisAccount, panic(err) } - _, _ = app.InitChain( - context.Background(), &abci.RequestInitChain{ - ConsensusParams: DefaultConsensusParams, - ChainId: "sei-test", - AppStateBytes: stateBytes, - }, + _, _ = app.InitChain(&abci.RequestInitChain{ + ConsensusParams: DefaultConsensusParams, + ChainId: "sei-test", + AppStateBytes: stateBytes, + }, ) _, _ = app.Commit(context.Background()) diff --git a/evmrpc/simulate.go b/evmrpc/simulate.go index f517de8b55..e53b53943a 100644 --- a/evmrpc/simulate.go +++ b/evmrpc/simulate.go @@ -670,7 +670,7 @@ func (b *Backend) GetEVM(_ context.Context, msg *core.Message, stateDB vm.StateD func (b *Backend) CurrentHeader() *ethtypes.Header { _, header, _, err := b.resolveStateAndHeaderByNumberOrHash(context.Background(), rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber)) - if err!=nil { + if err != nil { return b.syntheticHeaderFromCtx(b.ctxProvider(LatestCtxHeight)) } return header @@ -730,14 +730,14 @@ func (b *Backend) resolveStateAndHeaderByNumberOrHash(ctx context.Context, block if isLatestLikeBlockRef(blockNrOrHash) { sdkCtx := b.ctxProvider(LatestCtxHeight) h := sdkCtx.BlockHeight() - if wmHeight,err := b.watermarks.LatestHeight(ctx); err!=nil { - return sdkCtx,nil,false,fmt.Errorf("b.watermarks.LatestHeight(): %w", err) - } else if wmHeight!=h { + if wmHeight, err := b.watermarks.LatestHeight(ctx); err != nil { + return sdkCtx, nil, false, fmt.Errorf("b.watermarks.LatestHeight(): %w", err) + } else if wmHeight != h { return sdkCtx, b.syntheticHeaderFromCtx(sdkCtx), true, nil } tmBlock, err := b.tmClient.Block(ctx, &h) - if err!=nil { - return sdkCtx,nil,false,fmt.Errorf("b.tmClient.Block(): %w",err) + if err != nil { + return sdkCtx, nil, false, fmt.Errorf("b.tmClient.Block(): %w", err) } header := b.getHeader(tmBlock) header.BaseFee = b.keeper.GetNextBaseFeePerGas(sdkCtx).TruncateInt().BigInt() diff --git a/giga/deps/testutil/processblock/common.go b/giga/deps/testutil/processblock/common.go index c2d6ad97f2..13c7603274 100644 --- a/giga/deps/testutil/processblock/common.go +++ b/giga/deps/testutil/processblock/common.go @@ -48,7 +48,7 @@ func NewTestApp(t *testing.T) *App { if err != nil { panic(err) } - _, err = a.InitChain(context.Background(), &types.RequestInitChain{ + _, err = a.InitChain(&types.RequestInitChain{ Time: time.Now(), ChainId: "tendermint_test", ConsensusParams: &cp, diff --git a/sei-cosmos/baseapp/abci.go b/sei-cosmos/baseapp/abci.go index 7ac14cb1d2..9c16d2c856 100644 --- a/sei-cosmos/baseapp/abci.go +++ b/sei-cosmos/baseapp/abci.go @@ -37,7 +37,7 @@ func (app *BaseApp) InitLastHeader(lastHeader *tmproto.Header) { // InitChain implements the ABCI interface. It runs the initialization logic // directly on the CommitMultiStore. -func (app *BaseApp) InitChain(ctx context.Context, req *abci.RequestInitChain) (*abci.ResponseInitChain, error) { +func (app *BaseApp) InitChain(req *abci.RequestInitChain) (*abci.ResponseInitChain, error) { // On a new chain, we consider the init chain block height as 0, even though // req.InitialHeight is 1 by default. initHeader := tmproto.Header{ChainID: req.ChainId, Time: req.Time} diff --git a/sei-cosmos/baseapp/abci_test.go b/sei-cosmos/baseapp/abci_test.go index 7741543a4f..a705dbe8ca 100644 --- a/sei-cosmos/baseapp/abci_test.go +++ b/sei-cosmos/baseapp/abci_test.go @@ -105,7 +105,7 @@ func TestGetBlockRentionHeight(t *testing.T) { tc := tc tc.bapp.SetParamStore(¶mStore{db: dbm.NewMemDB()}) - tc.bapp.InitChain(context.Background(), &abci.RequestInitChain{ + tc.bapp.InitChain(&abci.RequestInitChain{ ConsensusParams: &tmproto.ConsensusParams{ Evidence: &tmproto.EvidenceParams{ MaxAgeNumBlocks: tc.maxAgeBlocks, @@ -181,7 +181,7 @@ func TestCreateQueryContextUsesCheckStateBeforeFirstCommit(t *testing.T) { app.SetParamStore(¶mStore{db: dbm.NewMemDB()}) require.NoError(t, app.LoadLatestVersion()) - _, err := app.InitChain(context.Background(), &abci.RequestInitChain{ChainId: "sei"}) + _, err := app.InitChain(&abci.RequestInitChain{ChainId: "sei"}) require.NoError(t, err) require.Equal(t, int64(0), app.LastBlockHeight()) @@ -282,7 +282,7 @@ func TestProcessProposalCleanContextNotAffectedByHandler(t *testing.T) { err := app.LoadLatestVersion() require.NoError(t, err) - app.InitChain(context.Background(), &abci.RequestInitChain{ + app.InitChain(&abci.RequestInitChain{ AppStateBytes: []byte("{}"), ChainId: "test-chain", }) diff --git a/sei-cosmos/baseapp/deliver_tx_batch_test.go b/sei-cosmos/baseapp/deliver_tx_batch_test.go index 060f28d931..c0d7318c15 100644 --- a/sei-cosmos/baseapp/deliver_tx_batch_test.go +++ b/sei-cosmos/baseapp/deliver_tx_batch_test.go @@ -99,7 +99,7 @@ func TestDeliverTxBatch(t *testing.T) { } app := setupBaseApp(t, anteOpt, routerOpt) - app.InitChain(context.Background(), &abci.RequestInitChain{}) + app.InitChain(&abci.RequestInitChain{}) // Create same codec used in txDecoder codec := codec.NewLegacyAmino() @@ -158,7 +158,7 @@ func TestDeliverTxBatchEmpty(t *testing.T) { } app := setupBaseApp(t, anteOpt, routerOpt) - app.InitChain(context.Background(), &abci.RequestInitChain{}) + app.InitChain(&abci.RequestInitChain{}) // Create same codec used in txDecoder codec := codec.NewLegacyAmino() diff --git a/sei-cosmos/baseapp/deliver_tx_test.go b/sei-cosmos/baseapp/deliver_tx_test.go index a03a120426..936fc12ad1 100644 --- a/sei-cosmos/baseapp/deliver_tx_test.go +++ b/sei-cosmos/baseapp/deliver_tx_test.go @@ -198,7 +198,7 @@ func TestWithRouter(t *testing.T) { } app := setupBaseApp(t, anteOpt, routerOpt) - app.InitChain(context.Background(), &abci.RequestInitChain{}) + app.InitChain(&abci.RequestInitChain{}) // Create same codec used in txDecoder codec := codec.NewLegacyAmino() @@ -242,7 +242,7 @@ func TestBaseApp_EndBlock(t *testing.T) { codec := codec.NewLegacyAmino() app := NewBaseApp(name, db, testTxDecoder(codec), nil, &testutil.TestAppOpts{}) app.SetParamStore(¶mStore{db: dbm.NewMemDB()}) - app.InitChain(context.Background(), &abci.RequestInitChain{ + app.InitChain(&abci.RequestInitChain{ ConsensusParams: cp, }) @@ -284,7 +284,7 @@ func TestQuery(t *testing.T) { app := setupBaseApp(t, anteOpt, routerOpt) - app.InitChain(context.Background(), &abci.RequestInitChain{}) + app.InitChain(&abci.RequestInitChain{}) // NOTE: "/store/bank" tells us KVStore // and the final "/key" says to use the data as the @@ -333,7 +333,7 @@ func TestGRPCQuery(t *testing.T) { app := setupBaseApp(t, grpcQueryOpt) - app.InitChain(context.Background(), &abci.RequestInitChain{}) + app.InitChain(&abci.RequestInitChain{}) header := tmproto.Header{Height: app.LastBlockHeight() + 1} app.setDeliverState(header) app.SetDeliverStateToCommit() @@ -457,7 +457,7 @@ func TestSimulateTx(t *testing.T) { app := setupBaseApp(t, anteOpt, routerOpt) - app.InitChain(context.Background(), &abci.RequestInitChain{}) + app.InitChain(&abci.RequestInitChain{}) // Create same codec used in txDecoder cdc := codec.NewLegacyAmino() @@ -749,7 +749,7 @@ func TestTxGasLimits(t *testing.T) { // } // app := setupBaseApp(t, anteOpt, routerOpt) -// app.InitChain(context.Background(), &abci.RequestInitChain{ +// app.InitChain(&abci.RequestInitChain{ // ConsensusParams: &tmproto.ConsensusParams{ // Block: &tmproto.BlockParams{ // MaxGas: 100, @@ -881,7 +881,7 @@ func TestBaseAppAnteHandler(t *testing.T) { cdc := codec.NewLegacyAmino() app := setupBaseApp(t, anteOpt, routerOpt, preCommitHandlerOpt) - app.InitChain(context.Background(), &abci.RequestInitChain{}) + app.InitChain(&abci.RequestInitChain{}) registerTestCodec(cdc) header := tmproto.Header{Height: app.LastBlockHeight() + 1} @@ -969,7 +969,7 @@ func TestPrecommitHandlerPanic(t *testing.T) { cdc := codec.NewLegacyAmino() app := setupBaseApp(t, anteOpt, routerOpt, preCommitHandlerOpt) - app.InitChain(context.Background(), &abci.RequestInitChain{}) + app.InitChain(&abci.RequestInitChain{}) registerTestCodec(cdc) header := tmproto.Header{Height: app.LastBlockHeight() + 1} @@ -1078,7 +1078,7 @@ func TestGasConsumptionBadTx(t *testing.T) { registerTestCodec(cdc) app := setupBaseApp(t, anteOpt, routerOpt) - app.InitChain(context.Background(), &abci.RequestInitChain{ + app.InitChain(&abci.RequestInitChain{ ConsensusParams: &tmproto.ConsensusParams{ Block: &tmproto.BlockParams{ MaxGas: 9, @@ -1086,7 +1086,7 @@ func TestGasConsumptionBadTx(t *testing.T) { }, }) - app.InitChain(context.Background(), &abci.RequestInitChain{}) + app.InitChain(&abci.RequestInitChain{}) header := tmproto.Header{Height: app.LastBlockHeight() + 1} app.setDeliverState(header) @@ -1128,7 +1128,7 @@ func TestInitChainer(t *testing.T) { } // initChainer is nil - nothing happens - app.InitChain(context.Background(), &abci.RequestInitChain{}) + app.InitChain(&abci.RequestInitChain{}) res, _ := app.Query(context.Background(), &query) require.Equal(t, 0, len(res.Value)) @@ -1140,7 +1140,7 @@ func TestInitChainer(t *testing.T) { require.Nil(t, err) require.Equal(t, int64(0), app.LastBlockHeight()) - initChainRes, _ := app.InitChain(context.Background(), &abci.RequestInitChain{AppStateBytes: []byte("{}"), ChainId: "test-chain-id"}) // must have valid JSON genesis file, even if empty + initChainRes, _ := app.InitChain(&abci.RequestInitChain{AppStateBytes: []byte("{}"), ChainId: "test-chain-id"}) // must have valid JSON genesis file, even if empty // The AppHash returned by a new chain is the sha256 hash of "". // $ echo -n '' | sha256sum @@ -1194,10 +1194,9 @@ func TestInitChain_WithInitialHeight(t *testing.T) { cc := codec.NewLegacyAmino() app := NewBaseApp(name, db, testTxDecoder(cc), nil, &testutil.TestAppOpts{}) - app.InitChain( - context.Background(), &abci.RequestInitChain{ - InitialHeight: 3, - }, + app.InitChain(&abci.RequestInitChain{ + InitialHeight: 3, + }, ) app.Commit(context.Background()) @@ -1445,7 +1444,7 @@ func TestDeliverTx(t *testing.T) { } app := setupBaseApp(t, anteOpt, routerOpt) - app.InitChain(context.Background(), &abci.RequestInitChain{}) + app.InitChain(&abci.RequestInitChain{}) // Create same codec used in txDecoder codec := codec.NewLegacyAmino() @@ -1508,7 +1507,7 @@ func TestDeliverTxHooks(t *testing.T) { } app := setupBaseApp(t, anteOpt, routerOpt) - app.InitChain(context.Background(), &abci.RequestInitChain{}) + app.InitChain(&abci.RequestInitChain{}) // Create same codec used in txDecoder codec := codec.NewLegacyAmino() @@ -1712,7 +1711,7 @@ func setupBaseAppWithSnapshots(t *testing.T, blocks uint, blockTxs int, options SetPruning(sdk.PruningOptions{KeepEvery: 1}), routerOpt)...) - app.InitChain(context.Background(), &abci.RequestInitChain{}) + app.InitChain(&abci.RequestInitChain{}) r := rand.New(rand.NewSource(3920758213583)) keyCounter := 0 diff --git a/sei-cosmos/server/mock/app_test.go b/sei-cosmos/server/mock/app_test.go index b0f68f9f98..e737b21610 100644 --- a/sei-cosmos/server/mock/app_test.go +++ b/sei-cosmos/server/mock/app_test.go @@ -23,7 +23,7 @@ func TestInitApp(t *testing.T) { req := abci.RequestInitChain{ AppStateBytes: appState, } - app.InitChain(context.Background(), &req) + app.InitChain(&req) app.Commit(context.Background()) // make sure we can query these values @@ -54,7 +54,7 @@ func TestDeliverTx(t *testing.T) { req := abci.RequestInitChain{ AppStateBytes: appState, } - app.InitChain(goCtx, &req) + app.InitChain(&req) header := tmproto.Header{Height: 1} app.FinalizeBlock(goCtx, &abci.RequestFinalizeBlock{ Hash: []byte("apphash"), diff --git a/sei-cosmos/server/rollback_test.go b/sei-cosmos/server/rollback_test.go index ff4c4394bb..ffe8976ca2 100644 --- a/sei-cosmos/server/rollback_test.go +++ b/sei-cosmos/server/rollback_test.go @@ -64,7 +64,7 @@ func (m *mockApplication) Info(ctx context.Context, req *abci.RequestInfo) (*abc return &abci.ResponseInfo{}, nil } -func (m *mockApplication) InitChain(ctx context.Context, req *abci.RequestInitChain) (*abci.ResponseInitChain, error) { +func (m *mockApplication) InitChain(req *abci.RequestInitChain) (*abci.ResponseInitChain, error) { return &abci.ResponseInitChain{}, nil } diff --git a/sei-cosmos/x/auth/module_test.go b/sei-cosmos/x/auth/module_test.go index 8b87dae880..f2cf552474 100644 --- a/sei-cosmos/x/auth/module_test.go +++ b/sei-cosmos/x/auth/module_test.go @@ -1,7 +1,6 @@ package auth_test import ( - "context" "testing" "github.com/sei-protocol/sei-chain/app" @@ -16,11 +15,10 @@ func TestItCreatesModuleAccountOnInitBlock(t *testing.T) { app := app.Setup(t, false, false, false) ctx := app.BaseApp.NewContext(false, tmproto.Header{}) - app.InitChain( - context.Background(), &abcitypes.RequestInitChain{ - AppStateBytes: []byte("{}"), - ChainId: "test-chain-id", - }, + app.InitChain(&abcitypes.RequestInitChain{ + AppStateBytes: []byte("{}"), + ChainId: "test-chain-id", + }, ) acc := app.AccountKeeper.GetAccount(ctx, types.NewModuleAddress(types.FeeCollectorName)) diff --git a/sei-cosmos/x/distribution/module_test.go b/sei-cosmos/x/distribution/module_test.go index 1d03215890..d4bd58e4d8 100644 --- a/sei-cosmos/x/distribution/module_test.go +++ b/sei-cosmos/x/distribution/module_test.go @@ -1,7 +1,6 @@ package distribution_test import ( - "context" "testing" abcitypes "github.com/sei-protocol/sei-chain/sei-tendermint/abci/types" @@ -17,11 +16,10 @@ func TestItCreatesModuleAccountOnInitBlock(t *testing.T) { app := seiapp.Setup(t, false, false, false) ctx := app.BaseApp.NewContext(false, tmproto.Header{}) - app.InitChain( - context.Background(), &abcitypes.RequestInitChain{ - AppStateBytes: []byte("{}"), - ChainId: "test-chain-id", - }, + app.InitChain(&abcitypes.RequestInitChain{ + AppStateBytes: []byte("{}"), + ChainId: "test-chain-id", + }, ) acc := app.AccountKeeper.GetAccount(ctx, authtypes.NewModuleAddress(types.ModuleName)) diff --git a/sei-cosmos/x/gov/genesis_test.go b/sei-cosmos/x/gov/genesis_test.go index c1d5646f8e..176735c39e 100644 --- a/sei-cosmos/x/gov/genesis_test.go +++ b/sei-cosmos/x/gov/genesis_test.go @@ -70,11 +70,10 @@ func TestImportExportQueues(t *testing.T) { db := dbm.NewMemDB() app2 := seiapp.SetupWithDB(t, db, false, false, false) - app2.InitChain( - context.Background(), &abci.RequestInitChain{ - ConsensusParams: seiapp.DefaultConsensusParams, - AppStateBytes: stateBytes, - }, + app2.InitChain(&abci.RequestInitChain{ + ConsensusParams: seiapp.DefaultConsensusParams, + AppStateBytes: stateBytes, + }, ) app2.Commit(context.Background()) diff --git a/sei-cosmos/x/gov/module_test.go b/sei-cosmos/x/gov/module_test.go index 2fc87fc2e4..44704805c6 100644 --- a/sei-cosmos/x/gov/module_test.go +++ b/sei-cosmos/x/gov/module_test.go @@ -1,7 +1,6 @@ package gov_test import ( - "context" "testing" abcitypes "github.com/sei-protocol/sei-chain/sei-tendermint/abci/types" @@ -17,11 +16,10 @@ func TestItCreatesModuleAccountOnInitBlock(t *testing.T) { app := seiapp.Setup(t, false, false, false) ctx := app.BaseApp.NewContext(false, tmproto.Header{}) - app.InitChain( - context.Background(), &abcitypes.RequestInitChain{ - AppStateBytes: []byte("{}"), - ChainId: "test-chain-id", - }, + app.InitChain(&abcitypes.RequestInitChain{ + AppStateBytes: []byte("{}"), + ChainId: "test-chain-id", + }, ) acc := app.AccountKeeper.GetAccount(ctx, authtypes.NewModuleAddress(types.ModuleName)) diff --git a/sei-cosmos/x/staking/module_test.go b/sei-cosmos/x/staking/module_test.go index 705c40bdb6..1331ff27cf 100644 --- a/sei-cosmos/x/staking/module_test.go +++ b/sei-cosmos/x/staking/module_test.go @@ -1,7 +1,6 @@ package staking_test import ( - "context" "testing" abcitypes "github.com/sei-protocol/sei-chain/sei-tendermint/abci/types" @@ -17,11 +16,10 @@ func TestItCreatesModuleAccountOnInitBlock(t *testing.T) { app := seiapp.Setup(t, false, false, false) ctx := app.BaseApp.NewContext(false, tmproto.Header{}) - app.InitChain( - context.Background(), &abcitypes.RequestInitChain{ - AppStateBytes: []byte("{}"), - ChainId: "test-chain-id", - }, + app.InitChain(&abcitypes.RequestInitChain{ + AppStateBytes: []byte("{}"), + ChainId: "test-chain-id", + }, ) acc := app.AccountKeeper.GetAccount(ctx, authtypes.NewModuleAddress(types.BondedPoolName)) diff --git a/sei-cosmos/x/upgrade/abci_test.go b/sei-cosmos/x/upgrade/abci_test.go index 40f2c581c6..252c123b89 100644 --- a/sei-cosmos/x/upgrade/abci_test.go +++ b/sei-cosmos/x/upgrade/abci_test.go @@ -40,7 +40,7 @@ func setupTest(t *testing.T, height int64) TestSuite { if err != nil { panic(err) } - app.InitChain(t.Context(), &abci.RequestInitChain{AppStateBytes: stateBytes}) + app.InitChain(&abci.RequestInitChain{AppStateBytes: stateBytes}) s.keeper = app.UpgradeKeeper s.ctx = app.BaseApp.NewContext(false, tmproto.Header{Height: height, Time: time.Now()}) diff --git a/sei-ibc-go/testing/simapp/test_helpers.go b/sei-ibc-go/testing/simapp/test_helpers.go index b0ffebf156..eb27442f60 100644 --- a/sei-ibc-go/testing/simapp/test_helpers.go +++ b/sei-ibc-go/testing/simapp/test_helpers.go @@ -106,12 +106,10 @@ func Setup(isCheckTx bool) *SimApp { } // Initialize the chain - _, err = app.InitChain( - context.Background(), - &abci.RequestInitChain{ - ConsensusParams: DefaultConsensusParams, - AppStateBytes: stateBytes, - }, + _, err = app.InitChain(&abci.RequestInitChain{ + ConsensusParams: DefaultConsensusParams, + AppStateBytes: stateBytes, + }, ) if err != nil { panic(err) @@ -186,13 +184,11 @@ func SetupWithGenesisValSet(t *testing.T, valSet *tmtypes.ValidatorSet, genAccs require.NoError(t, err) // init chain will set the validator set and initialize the genesis accounts - _, err = app.InitChain( - t.Context(), - &abci.RequestInitChain{ - ChainId: chainID, - ConsensusParams: DefaultConsensusParams, - AppStateBytes: stateBytes, - }, + _, err = app.InitChain(&abci.RequestInitChain{ + ChainId: chainID, + ConsensusParams: DefaultConsensusParams, + AppStateBytes: stateBytes, + }, ) require.NoError(t, err) diff --git a/sei-tendermint/abci/example/kvstore/kvstore.go b/sei-tendermint/abci/example/kvstore/kvstore.go index c9b142d55e..40107b5770 100644 --- a/sei-tendermint/abci/example/kvstore/kvstore.go +++ b/sei-tendermint/abci/example/kvstore/kvstore.go @@ -101,7 +101,7 @@ func NewProxy() *proxy.Proxy { return proxy.New(NewApplication()) } -func (app *Application) InitChain(_ context.Context, req *types.RequestInitChain) (*types.ResponseInitChain, error) { +func (app *Application) InitChain(req *types.RequestInitChain) (*types.ResponseInitChain, error) { return &types.ResponseInitChain{Validators: app.Validators()}, nil } diff --git a/sei-tendermint/abci/example/kvstore/kvstore_test.go b/sei-tendermint/abci/example/kvstore/kvstore_test.go index 6297bb1483..d33f8a53c5 100644 --- a/sei-tendermint/abci/example/kvstore/kvstore_test.go +++ b/sei-tendermint/abci/example/kvstore/kvstore_test.go @@ -95,7 +95,7 @@ func TestValUpdates(t *testing.T) { nInit := 5 vals := RandVals(total) // initialize with the first nInit - _, err := kvstore.InitChain(ctx, &types.RequestInitChain{}) + _, err := kvstore.InitChain(&types.RequestInitChain{}) if err != nil { t.Fatal(err) } diff --git a/sei-tendermint/abci/types/application.go b/sei-tendermint/abci/types/application.go index af0e021cbf..ca780e9601 100644 --- a/sei-tendermint/abci/types/application.go +++ b/sei-tendermint/abci/types/application.go @@ -30,7 +30,7 @@ type Application interface { EvmBalance(common.Address, []byte) uint256.Int // Consensus Connection - InitChain(context.Context, *RequestInitChain) (*ResponseInitChain, error) // Initialize blockchain w validators/other info from TendermintCore + InitChain(*RequestInitChain) (*ResponseInitChain, error) // Initialize blockchain w validators/other info from TendermintCore InitLastHeader(lastHeader *tmproto.Header) ProcessProposal(context.Context, *RequestProcessProposal) (*ResponseProcessProposal, error) // Commit the state and return the application Merkle root hash @@ -72,7 +72,7 @@ func (BaseApplication) Query(_ context.Context, req *RequestQuery) (*ResponseQue func (BaseApplication) InitLastHeader(lastHeader *tmproto.Header) {} -func (BaseApplication) InitChain(_ context.Context, req *RequestInitChain) (*ResponseInitChain, error) { +func (BaseApplication) InitChain(req *RequestInitChain) (*ResponseInitChain, error) { return &ResponseInitChain{}, nil } diff --git a/sei-tendermint/abci/types/mocks/application.go b/sei-tendermint/abci/types/mocks/application.go index 1152d7655b..53760fba8f 100644 --- a/sei-tendermint/abci/types/mocks/application.go +++ b/sei-tendermint/abci/types/mocks/application.go @@ -249,9 +249,9 @@ func (_m *Application) Info(_a0 context.Context, _a1 *types.RequestInfo) (*types return r0, r1 } -// InitChain provides a mock function with given fields: _a0, _a1 -func (_m *Application) InitChain(_a0 context.Context, _a1 *types.RequestInitChain) (*types.ResponseInitChain, error) { - ret := _m.Called(_a0, _a1) +// InitChain provides a mock function with given fields: _a0 +func (_m *Application) InitChain(_a0 *types.RequestInitChain) (*types.ResponseInitChain, error) { + ret := _m.Called(_a0) if len(ret) == 0 { panic("no return value specified for InitChain") @@ -259,19 +259,19 @@ func (_m *Application) InitChain(_a0 context.Context, _a1 *types.RequestInitChai var r0 *types.ResponseInitChain var r1 error - if rf, ok := ret.Get(0).(func(context.Context, *types.RequestInitChain) (*types.ResponseInitChain, error)); ok { - return rf(_a0, _a1) + if rf, ok := ret.Get(0).(func(*types.RequestInitChain) (*types.ResponseInitChain, error)); ok { + return rf(_a0) } - if rf, ok := ret.Get(0).(func(context.Context, *types.RequestInitChain) *types.ResponseInitChain); ok { - r0 = rf(_a0, _a1) + if rf, ok := ret.Get(0).(func(*types.RequestInitChain) *types.ResponseInitChain); ok { + r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*types.ResponseInitChain) } } - if rf, ok := ret.Get(1).(func(context.Context, *types.RequestInitChain) error); ok { - r1 = rf(_a0, _a1) + if rf, ok := ret.Get(1).(func(*types.RequestInitChain) error); ok { + r1 = rf(_a0) } else { r1 = ret.Error(1) } diff --git a/sei-tendermint/internal/consensus/byzantine_test.go b/sei-tendermint/internal/consensus/byzantine_test.go index fa0bd68866..8ea8427c7d 100644 --- a/sei-tendermint/internal/consensus/byzantine_test.go +++ b/sei-tendermint/internal/consensus/byzantine_test.go @@ -70,7 +70,7 @@ package consensus // ensureDir(t, path.Dir(thisConfig.Consensus.WalFile()), 0700) // dir for wal // app := kvstore.NewApplication() // vals := types.TM2PB.ValidatorUpdates(state.Validators) -// _, err = app.InitChain(ctx, &abci.RequestInitChain{Validators: vals}) +// _, err = app.InitChain(&abci.RequestInitChain{Validators: vals}) // require.NoError(t, err) // // blockDB := dbm.NewMemDB() diff --git a/sei-tendermint/internal/consensus/common_test.go b/sei-tendermint/internal/consensus/common_test.go index 39379d3932..32eb988029 100644 --- a/sei-tendermint/internal/consensus/common_test.go +++ b/sei-tendermint/internal/consensus/common_test.go @@ -988,7 +988,7 @@ func makeConsensusState( closeFuncs = append(closeFuncs, app.Close) vals := types.TM2PB.ValidatorUpdates(state.Validators) - _, err = app.InitChain(ctx, &abci.RequestInitChain{}) + _, err = app.InitChain(&abci.RequestInitChain{}) require.NoError(t, err) app.SetValidators(vals) @@ -1052,7 +1052,7 @@ func randConsensusNetWithPeers( app := kvstore.NewApplication() vals := types.TM2PB.ValidatorUpdates(state.Validators) state.Version.Consensus.App = kvstore.ProtocolVersion - _, err = app.InitChain(ctx, &abci.RequestInitChain{}) + _, err = app.InitChain(&abci.RequestInitChain{}) require.NoError(t, err) app.SetValidators(vals) // sm.SaveState(stateDB,state) //height 1's validatorsInfo already saved in LoadStateFromDBOrGenesisDoc above diff --git a/sei-tendermint/internal/consensus/reactor_test.go b/sei-tendermint/internal/consensus/reactor_test.go index ced7f627a2..124815e3f0 100644 --- a/sei-tendermint/internal/consensus/reactor_test.go +++ b/sei-tendermint/internal/consensus/reactor_test.go @@ -261,7 +261,7 @@ func TestReactorWithEvidence(t *testing.T) { app := kvstore.NewApplication() vals := types.TM2PB.ValidatorUpdates(state.Validators) app.SetValidators(vals) - _, err = app.InitChain(ctx, &abci.RequestInitChain{}) + _, err = app.InitChain(&abci.RequestInitChain{}) require.NoError(t, err) pv := privVals[i] diff --git a/sei-tendermint/internal/consensus/replay.go b/sei-tendermint/internal/consensus/replay.go index 175be9c573..634d82efe1 100644 --- a/sei-tendermint/internal/consensus/replay.go +++ b/sei-tendermint/internal/consensus/replay.go @@ -204,7 +204,7 @@ func (h *Handshaker) ReplayBlocks( // If appBlockHeight == 0 it means that we are at genesis and hence should send InitChain. if appBlockHeight == 0 { - res, err := app.InitChain(ctx, h.genDoc.ToRequestInitChain()) + res, err := app.InitChain(h.genDoc.ToRequestInitChain()) if err != nil { return nil, err } diff --git a/sei-tendermint/internal/consensus/replay_test.go b/sei-tendermint/internal/consensus/replay_test.go index e114a73d1a..172d9f5d42 100644 --- a/sei-tendermint/internal/consensus/replay_test.go +++ b/sei-tendermint/internal/consensus/replay_test.go @@ -770,7 +770,7 @@ func buildAppStateFromChain( proxyApp := proxy.New(appClient) mempool := newReplayTxMempool(proxyApp) state.Version.Consensus.App = kvstore.ProtocolVersion // simulate handshake, receive app version - _, err := appClient.InitChain(ctx, &abci.RequestInitChain{}) + _, err := appClient.InitChain(&abci.RequestInitChain{}) require.NoError(t, err) require.NoError(t, stateStore.Save(state)) // save height 1's validatorsInfo @@ -814,7 +814,7 @@ func buildTMStateFromChain( app := newApp(types.TM2PB.ValidatorUpdates(state.Validators)) proxyApp := proxy.New(app) state.Version.Consensus.App = kvstore.ProtocolVersion // simulate handshake, receive app version - _, err := app.InitChain(ctx, &abci.RequestInitChain{}) + _, err := app.InitChain(&abci.RequestInitChain{}) require.NoError(t, err) require.NoError(t, stateStore.Save(state)) @@ -1140,6 +1140,6 @@ type initChainApp struct { vals []abci.ValidatorUpdate } -func (ica *initChainApp) InitChain(_ context.Context, req *abci.RequestInitChain) (*abci.ResponseInitChain, error) { +func (ica *initChainApp) InitChain(req *abci.RequestInitChain) (*abci.ResponseInitChain, error) { return &abci.ResponseInitChain{Validators: ica.vals}, nil } diff --git a/sei-tendermint/internal/consensus/state_empty_valset_test.go b/sei-tendermint/internal/consensus/state_empty_valset_test.go index f13503e277..19d897c5dc 100644 --- a/sei-tendermint/internal/consensus/state_empty_valset_test.go +++ b/sei-tendermint/internal/consensus/state_empty_valset_test.go @@ -23,8 +23,6 @@ import ( // leaving the round state holding an empty validator set. func newStateFromEmptyGenesisValidators(t *testing.T) *State { t.Helper() - ctx := t.Context() - cfg := configSetup(t) genDoc := factory.GenesisDoc(cfg, time.Now(), nil, factory.ConsensusParams()) state, err := sm.MakeGenesisState(genDoc) @@ -37,7 +35,7 @@ func newStateFromEmptyGenesisValidators(t *testing.T) *State { app := kvstore.NewApplication() t.Cleanup(func() { _ = app.Close() }) - _, err = app.InitChain(ctx, &abci.RequestInitChain{}) + _, err = app.InitChain(&abci.RequestInitChain{}) require.NoError(t, err) pv := loadPrivValidator(thisConfig) diff --git a/sei-tendermint/internal/p2p/giga_router_common.go b/sei-tendermint/internal/p2p/giga_router_common.go index 50870b9f00..39bf6885a0 100644 --- a/sei-tendermint/internal/p2p/giga_router_common.go +++ b/sei-tendermint/internal/p2p/giga_router_common.go @@ -369,7 +369,7 @@ func (r *gigaRouterCommon) runExecute(ctx context.Context) error { // Re-entering on restart (crashed after InitChain, before first // Commit) is safe — nothing was committed, so it behaves as a // fresh init. - if _, err := app.InitChain(ctx, r.cfg.GenDoc.ToRequestInitChain()); err != nil { + if _, err := app.InitChain(r.cfg.GenDoc.ToRequestInitChain()); err != nil { return fmt.Errorf("App.InitChain(): %w", err) } var ok bool diff --git a/sei-tendermint/internal/p2p/giga_router_testhelper_test.go b/sei-tendermint/internal/p2p/giga_router_testhelper_test.go index f63de09719..28bf7853ba 100644 --- a/sei-tendermint/internal/p2p/giga_router_testhelper_test.go +++ b/sei-tendermint/internal/p2p/giga_router_testhelper_test.go @@ -102,7 +102,7 @@ func (a *testApp) CheckTx(context.Context, *abci.RequestCheckTxV2) *abci.Respons } } -func (a *testApp) InitChain(_ context.Context, req *abci.RequestInitChain) (*abci.ResponseInitChain, error) { +func (a *testApp) InitChain(req *abci.RequestInitChain) (*abci.ResponseInitChain, error) { for state, ctrl := range a.state.Lock() { if state.Init.IsPresent() { return nil, fmt.Errorf("chain already initialized") @@ -217,7 +217,7 @@ func TestInitChainCommitThenFinalize(t *testing.T) { appState := testAppStateJSON(rng) // InitChain - _, err := app.InitChain(ctx, &abci.RequestInitChain{ + _, err := app.InitChain(&abci.RequestInitChain{ InitialHeight: initialHeight, AppStateBytes: appState, }) diff --git a/sei-tendermint/internal/proxy/proxy.go b/sei-tendermint/internal/proxy/proxy.go index cd1f998abd..1dbc6e5b06 100644 --- a/sei-tendermint/internal/proxy/proxy.go +++ b/sei-tendermint/internal/proxy/proxy.go @@ -24,9 +24,9 @@ func New(app types.Application) *Proxy { return &Proxy{app: app} } -func (app *Proxy) InitChain(ctx context.Context, req *types.RequestInitChain) (*types.ResponseInitChain, error) { +func (app *Proxy) InitChain(req *types.RequestInitChain) (*types.ResponseInitChain, error) { defer addTimeSample(Global.MethodTimingAt("init_chain", "sync"))() - return app.app.InitChain(ctx, req) + return app.app.InitChain(req) } func (app *Proxy) ProcessProposal(ctx context.Context, req *types.RequestProcessProposal) (*types.ResponseProcessProposal, error) { diff --git a/sei-tendermint/test/e2e/app/app.go b/sei-tendermint/test/e2e/app/app.go index aa07ef3f6a..b8f984b31d 100644 --- a/sei-tendermint/test/e2e/app/app.go +++ b/sei-tendermint/test/e2e/app/app.go @@ -123,7 +123,7 @@ func (app *Application) Info(_ context.Context, req *abci.RequestInfo) (*abci.Re } // Info implements ABCI. -func (app *Application) InitChain(_ context.Context, req *abci.RequestInitChain) (*abci.ResponseInitChain, error) { +func (app *Application) InitChain(req *abci.RequestInitChain) (*abci.ResponseInitChain, error) { app.mu.Lock() defer app.mu.Unlock() diff --git a/sei-wasmd/app/app_test.go b/sei-wasmd/app/app_test.go index 772aa8879a..17afc01d1d 100644 --- a/sei-wasmd/app/app_test.go +++ b/sei-wasmd/app/app_test.go @@ -25,7 +25,7 @@ func TestWasmdExport(t *testing.T) { require.NoError(t, err) // Initialize the chain - gapp.InitChain(t.Context(), &abci.RequestInitChain{AppStateBytes: stateBytes}) + gapp.InitChain(&abci.RequestInitChain{AppStateBytes: stateBytes}) gapp.SetDeliverStateToCommit() gapp.Commit(context.Background()) diff --git a/sei-wasmd/app/test_helpers.go b/sei-wasmd/app/test_helpers.go index f65bd14fac..e8057d7b49 100644 --- a/sei-wasmd/app/test_helpers.go +++ b/sei-wasmd/app/test_helpers.go @@ -165,13 +165,11 @@ func SetupWithGenesisValSet(t *testing.T, chainID string, valSet *tmtypes.Valida require.NoError(t, err) // init chain will set the validator set and initialize the genesis accounts - _, err = app.InitChain( - context.Background(), - &abci.RequestInitChain{ - ConsensusParams: DefaultConsensusParams, - AppStateBytes: stateBytes, - ChainId: chainID, - }, + _, err = app.InitChain(&abci.RequestInitChain{ + ConsensusParams: DefaultConsensusParams, + AppStateBytes: stateBytes, + ChainId: chainID, + }, ) require.NoError(t, err) // This line is necessary due to the capability module which has a map as well as store usages, diff --git a/sei-wasmd/benchmarks/app_test.go b/sei-wasmd/benchmarks/app_test.go index 5ff9bcaf66..db7d47f4ac 100644 --- a/sei-wasmd/benchmarks/app_test.go +++ b/sei-wasmd/benchmarks/app_test.go @@ -53,12 +53,10 @@ func SetupWithGenesisAccounts(b testing.TB, db dbm.DB, genAccs []authtypes.Genes panic(err) } - wasmApp.InitChain( - context.Background(), - &abci.RequestInitChain{ - ConsensusParams: seiapp.DefaultConsensusParams, - AppStateBytes: stateBytes, - }, + wasmApp.InitChain(&abci.RequestInitChain{ + ConsensusParams: seiapp.DefaultConsensusParams, + AppStateBytes: stateBytes, + }, ) wasmApp.SetDeliverStateToCommit() diff --git a/testutil/processblock/common.go b/testutil/processblock/common.go index e767e43f92..2bf1943211 100644 --- a/testutil/processblock/common.go +++ b/testutil/processblock/common.go @@ -48,7 +48,7 @@ func NewTestApp(t *testing.T) *App { if err != nil { panic(err) } - _, err = a.InitChain(context.Background(), &types.RequestInitChain{ + _, err = a.InitChain(&types.RequestInitChain{ Time: time.Now(), ChainId: "tendermint_test", ConsensusParams: &cp, From 6575b1a6c3d64b4488f6449a28e8d1d293881048 Mon Sep 17 00:00:00 2001 From: Grzegorz Prusak Date: Tue, 14 Jul 2026 14:26:08 +0200 Subject: [PATCH 37/54] simplified Application API --- sei-cosmos/baseapp/abci.go | 4 ++-- sei-cosmos/baseapp/deliver_tx_test.go | 3 +-- sei-cosmos/server/rollback_test.go | 4 ++-- .../abci/example/kvstore/kvstore.go | 4 ++-- .../abci/example/kvstore/kvstore_test.go | 3 +-- sei-tendermint/abci/types/application.go | 6 ++--- .../abci/types/mocks/application.go | 22 +++++-------------- .../internal/consensus/mempool_test.go | 4 ++-- sei-tendermint/internal/consensus/replay.go | 9 ++------ .../internal/consensus/replay_test.go | 5 +---- .../internal/p2p/giga_router_common.go | 6 +---- .../p2p/giga_router_testhelper_test.go | 14 +++++------- sei-tendermint/internal/proxy/proxy.go | 4 ++-- sei-tendermint/internal/rpc/core/abci.go | 7 +----- sei-tendermint/internal/state/helpers_test.go | 4 ++-- .../internal/statesync/reactor_test.go | 3 +-- sei-tendermint/internal/statesync/syncer.go | 7 +----- .../internal/statesync/syncer_test.go | 2 +- .../syncer_verifyapp_default_test.go | 5 +---- .../internal/statesync/test_app_test.go | 8 ++++--- sei-tendermint/test/e2e/app/app.go | 4 ++-- 21 files changed, 45 insertions(+), 83 deletions(-) diff --git a/sei-cosmos/baseapp/abci.go b/sei-cosmos/baseapp/abci.go index 9c16d2c856..57a564738b 100644 --- a/sei-cosmos/baseapp/abci.go +++ b/sei-cosmos/baseapp/abci.go @@ -107,7 +107,7 @@ func (app *BaseApp) InitChain(req *abci.RequestInitChain) (*abci.ResponseInitCha } // Info implements the ABCI interface. -func (app *BaseApp) Info(ctx context.Context, req *abci.RequestInfo) (*abci.ResponseInfo, error) { +func (app *BaseApp) Info() *abci.ResponseInfo { lastCommitID := app.cms.LastCommitID() return &abci.ResponseInfo{ @@ -117,7 +117,7 @@ func (app *BaseApp) Info(ctx context.Context, req *abci.RequestInfo) (*abci.Resp LastBlockHeight: lastCommitID.Version, LastBlockAppHash: lastCommitID.Hash, MinimumGasPrices: app.minGasPrices.String(), - }, nil + } } func (app *BaseApp) MidBlock(ctx sdk.Context, height int64) (events []abci.Event) { diff --git a/sei-cosmos/baseapp/deliver_tx_test.go b/sei-cosmos/baseapp/deliver_tx_test.go index 936fc12ad1..bd3e711e90 100644 --- a/sei-cosmos/baseapp/deliver_tx_test.go +++ b/sei-cosmos/baseapp/deliver_tx_test.go @@ -1576,8 +1576,7 @@ func TestInfo(t *testing.T) { app := newBaseApp(t.Name()) // ----- test an empty response ------- - reqInfo := abci.RequestInfo{} - res, _ := app.Info(context.Background(), &reqInfo) + res := app.Info() // should be empty assert.Equal(t, "", res.Version) diff --git a/sei-cosmos/server/rollback_test.go b/sei-cosmos/server/rollback_test.go index ffe8976ca2..1f5f4bbf8d 100644 --- a/sei-cosmos/server/rollback_test.go +++ b/sei-cosmos/server/rollback_test.go @@ -60,8 +60,8 @@ func (m *mockApplication) Close() error { } // Implement other required methods with no-ops -func (m *mockApplication) Info(ctx context.Context, req *abci.RequestInfo) (*abci.ResponseInfo, error) { - return &abci.ResponseInfo{}, nil +func (m *mockApplication) Info() *abci.ResponseInfo { + return &abci.ResponseInfo{} } func (m *mockApplication) InitChain(req *abci.RequestInitChain) (*abci.ResponseInitChain, error) { diff --git a/sei-tendermint/abci/example/kvstore/kvstore.go b/sei-tendermint/abci/example/kvstore/kvstore.go index 40107b5770..988ee185a6 100644 --- a/sei-tendermint/abci/example/kvstore/kvstore.go +++ b/sei-tendermint/abci/example/kvstore/kvstore.go @@ -105,7 +105,7 @@ func (app *Application) InitChain(req *types.RequestInitChain) (*types.ResponseI return &types.ResponseInitChain{Validators: app.Validators()}, nil } -func (app *Application) Info(_ context.Context, req *types.RequestInfo) (*types.ResponseInfo, error) { +func (app *Application) Info() *types.ResponseInfo { app.mu.Lock() defer app.mu.Unlock() return &types.ResponseInfo{ @@ -114,7 +114,7 @@ func (app *Application) Info(_ context.Context, req *types.RequestInfo) (*types. AppVersion: ProtocolVersion, LastBlockHeight: app.state.Height, LastBlockAppHash: app.state.AppHash, - }, nil + } } func (app *Application) LastBlockHeight() int64 { diff --git a/sei-tendermint/abci/example/kvstore/kvstore_test.go b/sei-tendermint/abci/example/kvstore/kvstore_test.go index d33f8a53c5..55fc62a0ce 100644 --- a/sei-tendermint/abci/example/kvstore/kvstore_test.go +++ b/sei-tendermint/abci/example/kvstore/kvstore_test.go @@ -32,8 +32,7 @@ func testKVStore(ctx context.Context, t *testing.T, app types.Application, tx [] _, err = app.Commit(ctx) require.NoError(t, err) - info, err := app.Info(ctx, &types.RequestInfo{}) - require.NoError(t, err) + info := app.Info() require.NotZero(t, info.LastBlockHeight) // make sure query is fine diff --git a/sei-tendermint/abci/types/application.go b/sei-tendermint/abci/types/application.go index ca780e9601..f16dd68c02 100644 --- a/sei-tendermint/abci/types/application.go +++ b/sei-tendermint/abci/types/application.go @@ -15,7 +15,7 @@ import ( //go:generate ../../scripts/mockery_generate.sh Application type Application interface { // Info/Query Connection - Info(context.Context, *RequestInfo) (*ResponseInfo, error) // Return application info + Info() *ResponseInfo // Return application info Query(context.Context, *RequestQuery) (*ResponseQuery, error) // Query for state GetValidators() []ValidatorUpdate // LastBlockHeight returns the height of the most recently committed @@ -52,8 +52,8 @@ var _ Application = BaseApplication{} type BaseApplication struct{} -func (BaseApplication) Info(_ context.Context, req *RequestInfo) (*ResponseInfo, error) { - return &ResponseInfo{}, nil +func (BaseApplication) Info() *ResponseInfo { + return &ResponseInfo{} } func (BaseApplication) GetValidators() []ValidatorUpdate { return nil } func (BaseApplication) LastBlockHeight() int64 { return 0 } diff --git a/sei-tendermint/abci/types/mocks/application.go b/sei-tendermint/abci/types/mocks/application.go index 53760fba8f..e36908bfad 100644 --- a/sei-tendermint/abci/types/mocks/application.go +++ b/sei-tendermint/abci/types/mocks/application.go @@ -219,34 +219,24 @@ func (_m *Application) GetValidators() []types.ValidatorUpdate { return r0 } -// Info provides a mock function with given fields: _a0, _a1 -func (_m *Application) Info(_a0 context.Context, _a1 *types.RequestInfo) (*types.ResponseInfo, error) { - ret := _m.Called(_a0, _a1) +// Info provides a mock function with no fields +func (_m *Application) Info() *types.ResponseInfo { + ret := _m.Called() if len(ret) == 0 { panic("no return value specified for Info") } var r0 *types.ResponseInfo - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, *types.RequestInfo) (*types.ResponseInfo, error)); ok { - return rf(_a0, _a1) - } - if rf, ok := ret.Get(0).(func(context.Context, *types.RequestInfo) *types.ResponseInfo); ok { - r0 = rf(_a0, _a1) + if rf, ok := ret.Get(0).(func() *types.ResponseInfo); ok { + r0 = rf() } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*types.ResponseInfo) } } - if rf, ok := ret.Get(1).(func(context.Context, *types.RequestInfo) error); ok { - r1 = rf(_a0, _a1) - } else { - r1 = ret.Error(1) - } - - return r0, r1 + return r0 } // InitChain provides a mock function with given fields: _a0 diff --git a/sei-tendermint/internal/consensus/mempool_test.go b/sei-tendermint/internal/consensus/mempool_test.go index 3487c01a1a..caea073894 100644 --- a/sei-tendermint/internal/consensus/mempool_test.go +++ b/sei-tendermint/internal/consensus/mempool_test.go @@ -293,11 +293,11 @@ func NewCounterApplication() *CounterApplication { return &CounterApplication{} } -func (app *CounterApplication) Info(_ context.Context, req *abci.RequestInfo) (*abci.ResponseInfo, error) { +func (app *CounterApplication) Info() *abci.ResponseInfo { app.mu.Lock() defer app.mu.Unlock() - return &abci.ResponseInfo{Data: fmt.Sprintf("txs:%v", app.txCount)}, nil + return &abci.ResponseInfo{Data: fmt.Sprintf("txs:%v", app.txCount)} } func (app *CounterApplication) FinalizeBlock(_ context.Context, req *abci.RequestFinalizeBlock) (*abci.ResponseFinalizeBlock, error) { diff --git a/sei-tendermint/internal/consensus/replay.go b/sei-tendermint/internal/consensus/replay.go index 634d82efe1..6fc1b2b552 100644 --- a/sei-tendermint/internal/consensus/replay.go +++ b/sei-tendermint/internal/consensus/replay.go @@ -15,7 +15,6 @@ import ( "github.com/sei-protocol/sei-chain/sei-tendermint/internal/proxy" sm "github.com/sei-protocol/sei-chain/sei-tendermint/internal/state" "github.com/sei-protocol/sei-chain/sei-tendermint/types" - "github.com/sei-protocol/sei-chain/sei-tendermint/version" "github.com/sei-protocol/seilog" ) @@ -149,11 +148,7 @@ func (h *Handshaker) NBlocks() int { // TODO: retry the handshake/replay if it fails ? func (h *Handshaker) Handshake(ctx context.Context, app *proxy.Proxy) error { - res, err := app.Info(ctx, &version.RequestInfo) - if err != nil { - return fmt.Errorf("error calling Info: %w", err) - } - + res := app.Info() blockHeight := res.LastBlockHeight if blockHeight < 0 { return fmt.Errorf("got a negative last block height (%d) from the app", blockHeight) @@ -174,7 +169,7 @@ func (h *Handshaker) Handshake(ctx context.Context, app *proxy.Proxy) error { } // Replay blocks up to the latest in the blockstore. - _, err = h.ReplayBlocks(ctx, h.initialState, appHash, blockHeight, app) + _, err := h.ReplayBlocks(ctx, h.initialState, appHash, blockHeight, app) if err != nil { return fmt.Errorf("error on replay: %w", err) } diff --git a/sei-tendermint/internal/consensus/replay_test.go b/sei-tendermint/internal/consensus/replay_test.go index 172d9f5d42..aa55bc68e5 100644 --- a/sei-tendermint/internal/consensus/replay_test.go +++ b/sei-tendermint/internal/consensus/replay_test.go @@ -704,10 +704,7 @@ func testHandshakeReplay( require.NoError(t, err, "Error on abci handshake") // get the latest app hash from the app - res, err := proxyApp.Info(ctx, &abci.RequestInfo{Version: ""}) - if err != nil { - t.Fatal(err) - } + res := proxyApp.Info() // the app hash should be synced up if !bytes.Equal(latestAppHash, res.LastBlockAppHash) { diff --git a/sei-tendermint/internal/p2p/giga_router_common.go b/sei-tendermint/internal/p2p/giga_router_common.go index 39bf6885a0..8987d96102 100644 --- a/sei-tendermint/internal/p2p/giga_router_common.go +++ b/sei-tendermint/internal/p2p/giga_router_common.go @@ -25,7 +25,6 @@ import ( "github.com/sei-protocol/sei-chain/sei-tendermint/libs/utils/tcp" "github.com/sei-protocol/sei-chain/sei-tendermint/rpc/coretypes" "github.com/sei-protocol/sei-chain/sei-tendermint/types" - "github.com/sei-protocol/sei-chain/sei-tendermint/version" ) // maxInboundFullnodePeers caps GigaRouterCommonConfig.MaxInboundFullnodePeers. @@ -351,10 +350,7 @@ func (r *gigaRouterCommon) runExecute(ctx context.Context) error { app := r.app - info, err := app.Info(ctx, &version.RequestInfo) - if err != nil { - return fmt.Errorf("App.Info(): %w", err) - } + info := app.Info() last, ok := utils.SafeCast[atypes.GlobalBlockNumber](info.LastBlockHeight) if !ok { return fmt.Errorf("invalid info.LastBlockHeight = %v", info.LastBlockHeight) diff --git a/sei-tendermint/internal/p2p/giga_router_testhelper_test.go b/sei-tendermint/internal/p2p/giga_router_testhelper_test.go index 28bf7853ba..07715f534c 100644 --- a/sei-tendermint/internal/p2p/giga_router_testhelper_test.go +++ b/sei-tendermint/internal/p2p/giga_router_testhelper_test.go @@ -60,23 +60,23 @@ func (a *testApp) GetValidators() []abci.ValidatorUpdate { panic("unreachable") } -func (a *testApp) Info(_ context.Context, _ *abci.RequestInfo) (*abci.ResponseInfo, error) { +func (a *testApp) Info() *abci.ResponseInfo { for state := range a.state.Lock() { init, ok := state.Init.Get() if !ok { - return &abci.ResponseInfo{}, nil + return &abci.ResponseInfo{} } if len(state.Blocks) == 0 { // Match the real SDK: InitChain without Commit leaves LastBlockHeight=0. return &abci.ResponseInfo{ LastBlockHeight: 0, LastBlockAppHash: slices.Clone(state.AppHash[:]), - }, nil + } } return &abci.ResponseInfo{ LastBlockHeight: init.InitialHeight + int64(len(state.Blocks)) - 1, LastBlockAppHash: slices.Clone(state.AppHash[:]), - }, nil + } } panic("unreachable") } @@ -227,8 +227,7 @@ func TestInitChainCommitThenFinalize(t *testing.T) { // using the deliverState set up by InitChain. // Verify app reports correct height after InitChain (no blocks yet) - info, err := app.Info(ctx, &abci.RequestInfo{}) - require.NoError(t, err) + info := app.Info() require.Equal(t, int64(0), info.LastBlockHeight, "testApp should report 0 after InitChain with no committed blocks (matches real SDK)") @@ -247,8 +246,7 @@ func TestInitChainCommitThenFinalize(t *testing.T) { require.NoError(t, err) // Verify height advanced - info, err = app.Info(ctx, &abci.RequestInfo{}) - require.NoError(t, err) + info = app.Info() require.Equal(t, initialHeight, info.LastBlockHeight, "testApp should report InitialHeight after 1 block") } diff --git a/sei-tendermint/internal/proxy/proxy.go b/sei-tendermint/internal/proxy/proxy.go index 1dbc6e5b06..8f435fc26f 100644 --- a/sei-tendermint/internal/proxy/proxy.go +++ b/sei-tendermint/internal/proxy/proxy.go @@ -85,9 +85,9 @@ func (app *Proxy) InitLastHeader(lastHeader *tmproto.Header) { app.app.InitLastHeader(lastHeader) } -func (app *Proxy) Info(ctx context.Context, req *types.RequestInfo) (*types.ResponseInfo, error) { +func (app *Proxy) Info() *types.ResponseInfo { defer addTimeSample(Global.MethodTimingAt("info", "sync"))() - return app.app.Info(ctx, req) + return app.app.Info() } func (app *Proxy) Query(ctx context.Context, req *types.RequestQuery) (*types.ResponseQuery, error) { diff --git a/sei-tendermint/internal/rpc/core/abci.go b/sei-tendermint/internal/rpc/core/abci.go index a334b0e4c3..ffc068c3ce 100644 --- a/sei-tendermint/internal/rpc/core/abci.go +++ b/sei-tendermint/internal/rpc/core/abci.go @@ -5,7 +5,6 @@ import ( abci "github.com/sei-protocol/sei-chain/sei-tendermint/abci/types" "github.com/sei-protocol/sei-chain/sei-tendermint/rpc/coretypes" - "github.com/sei-protocol/sei-chain/sei-tendermint/version" ) // ABCIQuery queries the application for some information. @@ -27,10 +26,6 @@ func (env *Environment) ABCIQuery(ctx context.Context, req *coretypes.RequestABC // ABCIInfo gets some info about the application. // More: https://docs.tendermint.com/master/rpc/#/ABCI/abci_info func (env *Environment) ABCIInfo(ctx context.Context) (*coretypes.ResultABCIInfo, error) { - resInfo, err := env.App.Info(ctx, &version.RequestInfo) - if err != nil { - return nil, err - } - + resInfo := env.App.Info() return &coretypes.ResultABCIInfo{Response: *resInfo}, nil } diff --git a/sei-tendermint/internal/state/helpers_test.go b/sei-tendermint/internal/state/helpers_test.go index 033beac048..fbd6da90bd 100644 --- a/sei-tendermint/internal/state/helpers_test.go +++ b/sei-tendermint/internal/state/helpers_test.go @@ -288,8 +288,8 @@ type testApp struct { var _ abci.Application = (*testApp)(nil) -func (app *testApp) Info(_ context.Context, req *abci.RequestInfo) (*abci.ResponseInfo, error) { - return &abci.ResponseInfo{}, nil +func (app *testApp) Info() *abci.ResponseInfo { + return &abci.ResponseInfo{} } func (app *testApp) FinalizeBlock(_ context.Context, req *abci.RequestFinalizeBlock) (*abci.ResponseFinalizeBlock, error) { diff --git a/sei-tendermint/internal/statesync/reactor_test.go b/sei-tendermint/internal/statesync/reactor_test.go index c237ae2b9a..f4b0a29a97 100644 --- a/sei-tendermint/internal/statesync/reactor_test.go +++ b/sei-tendermint/internal/statesync/reactor_test.go @@ -27,7 +27,6 @@ import ( "github.com/sei-protocol/sei-chain/sei-tendermint/light/provider" pb "github.com/sei-protocol/sei-chain/sei-tendermint/proto/tendermint/statesync" "github.com/sei-protocol/sei-chain/sei-tendermint/types" - "github.com/sei-protocol/sei-chain/sei-tendermint/version" ) var m = Global @@ -165,7 +164,7 @@ func TestReactor_Sync(t *testing.T) { appConn.applySnapshotChunk.Set(func(context.Context, *abci.RequestApplySnapshotChunk) (*abci.ResponseApplySnapshotChunk, error) { return &abci.ResponseApplySnapshotChunk{Result: abci.ResponseApplySnapshotChunk_ACCEPT}, nil }) - appConn.info.Push(mkHandler(&version.RequestInfo, &abci.ResponseInfo{ + appConn.info.Push(mkHandler(struct{}{}, &abci.ResponseInfo{ AppVersion: testAppVersion, LastBlockHeight: snapshotHeight, LastBlockAppHash: chain[snapshotHeight+1].AppHash, diff --git a/sei-tendermint/internal/statesync/syncer.go b/sei-tendermint/internal/statesync/syncer.go index b67078b9de..fdda604075 100644 --- a/sei-tendermint/internal/statesync/syncer.go +++ b/sei-tendermint/internal/statesync/syncer.go @@ -15,7 +15,6 @@ import ( "github.com/sei-protocol/sei-chain/sei-tendermint/light" pb "github.com/sei-protocol/sei-chain/sei-tendermint/proto/tendermint/statesync" "github.com/sei-protocol/sei-chain/sei-tendermint/types" - "github.com/sei-protocol/sei-chain/sei-tendermint/version" ) const ( @@ -559,11 +558,7 @@ func (s *syncer) requestChunk(snapshot *snapshot, chunk uint32) { // verifyApp verifies the sync, checking the app hash, last block height and app version func (s *syncer) verifyApp(ctx context.Context, snapshot *snapshot, appVersion uint64) error { - resp, err := s.conn.Info(ctx, &version.RequestInfo) - if err != nil { - return fmt.Errorf("failed to query ABCI app for appHash: %w", err) - } - + resp := s.conn.Info() // sanity check that the app version in the block matches the application's own record // of its version if resp.AppVersion != appVersion { diff --git a/sei-tendermint/internal/statesync/syncer_test.go b/sei-tendermint/internal/statesync/syncer_test.go index ded95bb318..3268800639 100644 --- a/sei-tendermint/internal/statesync/syncer_test.go +++ b/sei-tendermint/internal/statesync/syncer_test.go @@ -206,7 +206,7 @@ func TestSyncer_SyncAny(t *testing.T) { &abci.RequestApplySnapshotChunk{Index: 2, Chunk: []byte{1, 1, 2}}, &abci.ResponseApplySnapshotChunk{Result: abci.ResponseApplySnapshotChunk_ACCEPT}, )) - app.info.Push(mkHandler(&version.RequestInfo, &abci.ResponseInfo{ + app.info.Push(mkHandler(struct{}{}, &abci.ResponseInfo{ AppVersion: testAppVersion, LastBlockHeight: 1, LastBlockAppHash: []byte("app_hash"), diff --git a/sei-tendermint/internal/statesync/syncer_verifyapp_default_test.go b/sei-tendermint/internal/statesync/syncer_verifyapp_default_test.go index 44f7384dbe..7d4e557408 100644 --- a/sei-tendermint/internal/statesync/syncer_verifyapp_default_test.go +++ b/sei-tendermint/internal/statesync/syncer_verifyapp_default_test.go @@ -10,9 +10,7 @@ import ( "testing" abci "github.com/sei-protocol/sei-chain/sei-tendermint/abci/types" - "github.com/sei-protocol/sei-chain/sei-tendermint/libs/utils" "github.com/sei-protocol/sei-chain/sei-tendermint/libs/utils/require" - "github.com/sei-protocol/sei-chain/sei-tendermint/version" ) func TestSyncer_verifyApp(t *testing.T) { @@ -56,8 +54,7 @@ func TestSyncer_verifyApp(t *testing.T) { rts := setup(t, nil, nil, true) app := rts.conn - app.info.Push(func(_ context.Context, req *abci.RequestInfo) (*abci.ResponseInfo, error) { - utils.OrPanic(utils.TestDiff(&version.RequestInfo, req)) + app.info.Push(func(_ context.Context, _ struct{}) (*abci.ResponseInfo, error) { return tc.response, tc.err }) err := rts.reactor.syncer.verifyApp(ctx, s, appVersion) diff --git a/sei-tendermint/internal/statesync/test_app_test.go b/sei-tendermint/internal/statesync/test_app_test.go index fd88d944ad..c57bad00e9 100644 --- a/sei-tendermint/internal/statesync/test_app_test.go +++ b/sei-tendermint/internal/statesync/test_app_test.go @@ -46,7 +46,7 @@ type testStatesyncApp struct { applySnapshotChunk Queue[*abci.RequestApplySnapshotChunk, *abci.ResponseApplySnapshotChunk] listSnapshots Queue[*abci.RequestListSnapshots, *abci.ResponseListSnapshots] loadSnapshotChunk Queue[*abci.RequestLoadSnapshotChunk, *abci.ResponseLoadSnapshotChunk] - info Queue[*abci.RequestInfo, *abci.ResponseInfo] + info Queue[struct{}, *abci.ResponseInfo] } func newTestStatesyncApp() *testStatesyncApp { @@ -82,11 +82,13 @@ func (app *testStatesyncApp) LoadSnapshotChunk(ctx context.Context, req *abci.Re return h(ctx, req) } -func (app *testStatesyncApp) Info(ctx context.Context, req *abci.RequestInfo) (*abci.ResponseInfo, error) { +func (app *testStatesyncApp) Info() *abci.ResponseInfo { app.mu.Lock() h := app.info.Pop() app.mu.Unlock() - return h(ctx, req) + res, err := h(context.Background(), struct{}{}) + utils.OrPanic(err) + return res } func (app *testStatesyncApp) AssertExpectations(t testing.TB) { diff --git a/sei-tendermint/test/e2e/app/app.go b/sei-tendermint/test/e2e/app/app.go index b8f984b31d..bdcc04e4db 100644 --- a/sei-tendermint/test/e2e/app/app.go +++ b/sei-tendermint/test/e2e/app/app.go @@ -110,7 +110,7 @@ func NewApplication(cfg *Config) (*Application, error) { } // Info implements ABCI. -func (app *Application) Info(_ context.Context, req *abci.RequestInfo) (*abci.ResponseInfo, error) { +func (app *Application) Info() *abci.ResponseInfo { app.mu.Lock() defer app.mu.Unlock() @@ -119,7 +119,7 @@ func (app *Application) Info(_ context.Context, req *abci.RequestInfo) (*abci.Re AppVersion: 1, LastBlockHeight: int64(app.state.Height), //nolint:gosec // Height is a non-negative block height LastBlockAppHash: app.state.Hash, - }, nil + } } // Info implements ABCI. From 470d8e52c4b14812974bc62806687e52cbb7d5d4 Mon Sep 17 00:00:00 2001 From: Grzegorz Prusak Date: Tue, 14 Jul 2026 15:17:40 +0200 Subject: [PATCH 38/54] snapshot --- app/ante/cosmos_checktx.go | 16 +++------------- evmrpc/genesis_latest_state_test.go | 6 ++++-- evmrpc/simulate.go | 9 ++++++--- sei-cosmos/baseapp/abci.go | 6 ------ sei-cosmos/baseapp/deliver_tx_test.go | 2 +- 5 files changed, 14 insertions(+), 25 deletions(-) diff --git a/app/ante/cosmos_checktx.go b/app/ante/cosmos_checktx.go index e9311860a8..eeefe62921 100644 --- a/app/ante/cosmos_checktx.go +++ b/app/ante/cosmos_checktx.go @@ -241,11 +241,6 @@ func CosmosStatelessChecks(tx sdk.Tx, height int64, consensusParams *tmproto.Con func SetGasMeter(ctx sdk.Context, gasLimit uint64, paramsKeeper paramskeeper.Keeper) sdk.Context { cosmosGasParams := paramsKeeper.GetCosmosGasParams(ctx) - - if ctx.BlockHeight() == 0 { - return ctx.WithGasMeter(storetypes.NewInfiniteMultiplierGasMeter(cosmosGasParams.CosmosGasMultiplierNumerator, cosmosGasParams.CosmosGasMultiplierDenominator)) - } - return ctx.WithGasMeter(storetypes.NewMultiplierGasMeter(gasLimit, cosmosGasParams.CosmosGasMultiplierNumerator, cosmosGasParams.CosmosGasMultiplierDenominator)) } @@ -450,15 +445,10 @@ func CheckSignatures(ctx sdk.Context, txConfig client.TxConfig, tx sdk.Tx, signe } // retrieve signer data - genesis := ctx.BlockHeight() == 0 chainID := ctx.ChainID() - var accNum uint64 - if !genesis { - accNum = signerAcc.GetAccountNumber() - } signerData := authsigning.SignerData{ ChainID: chainID, - AccountNumber: accNum, + AccountNumber: signerAcc.GetAccountNumber(), Sequence: signerAcc.GetSequence(), } @@ -468,9 +458,9 @@ func CheckSignatures(ctx sdk.Context, txConfig client.TxConfig, tx sdk.Tx, signe if authante.OnlyLegacyAminoSigners(sig.Data) { // If all signers are using SIGN_MODE_LEGACY_AMINO, we rely on VerifySignature to check account sequence number, // and therefore communicate sequence number as a potential cause of error. - errMsg = fmt.Sprintf("signature verification failed; please verify account number (%d), sequence (%d) and chain-id (%s)", accNum, signerAcc.GetSequence(), chainID) + errMsg = fmt.Sprintf("signature verification failed; please verify account number (%d), sequence (%d) and chain-id (%s)", signerAcc.GetAccountNumber(), signerAcc.GetSequence(), chainID) } else { - errMsg = fmt.Sprintf("signature verification failed; please verify account number (%d) and chain-id (%s)", accNum, chainID) + errMsg = fmt.Sprintf("signature verification failed; please verify account number (%d) and chain-id (%s)", signerAcc.GetAccountNumber(), chainID) } return nil, sdkerrors.Wrap(sdkerrors.ErrUnauthorized, errMsg) diff --git a/evmrpc/genesis_latest_state_test.go b/evmrpc/genesis_latest_state_test.go index 0585c1d964..ac89e7dabd 100644 --- a/evmrpc/genesis_latest_state_test.go +++ b/evmrpc/genesis_latest_state_test.go @@ -71,9 +71,10 @@ func latestLikeTags() []rpc.BlockNumber { } } -func TestStateAPILatestLikeTagsUseGenesisCheckStateBeforeFirstCommit(t *testing.T) { +func TestStateAPILatestLikeTagsUseGenesisCommittedStateBeforeFirstCommit(t *testing.T) { testApp := app.Setup(t, false, false, false) checkCtx := testApp.GetCheckCtx() + require.Equal(t, int64(0), checkCtx.BlockHeight()) _, address := testkeeper.MockAddressPair() key := common.BytesToHash([]byte("key")) value := common.BytesToHash([]byte("value")) @@ -116,9 +117,10 @@ func TestStateAPILatestLikeTagsUseGenesisCheckStateBeforeFirstCommit(t *testing. } } -func TestSimulationBackendLatestLikeTagsUseGenesisCheckStateBeforeFirstCommit(t *testing.T) { +func TestSimulationBackendLatestLikeTagsUseGenesisCommittedStateBeforeFirstCommit(t *testing.T) { testApp := app.Setup(t, false, false, false) checkCtx := testApp.GetCheckCtx() + require.Equal(t, int64(0), checkCtx.BlockHeight()) _, address := testkeeper.MockAddressPair() code := []byte{0xde, 0xad, 0xbe, 0xef} amount := sdk.NewInt(4321) diff --git a/evmrpc/simulate.go b/evmrpc/simulate.go index e53b53943a..eb35acf942 100644 --- a/evmrpc/simulate.go +++ b/evmrpc/simulate.go @@ -314,11 +314,11 @@ func (b *Backend) SetTraceContextProvider(provider TraceContextProvider) { } func (b *Backend) StateAndHeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (vm.StateDB, *ethtypes.Header, error) { - isWasmdCall, ok := ctx.Value(CtxIsWasmdPrecompileCallKey).(bool) sdkCtx, header, isLatestBlock, err := b.resolveStateAndHeaderByNumberOrHash(ctx, blockNrOrHash) if err != nil { return nil, nil, err } + isWasmdCall, ok := ctx.Value(CtxIsWasmdPrecompileCallKey).(bool) sdkCtx = sdkCtx.WithIsEVM(true).WithEVMEntryViaWasmdPrecompile(ok && isWasmdCall) if !isLatestBlock { // no need to check version for latest block @@ -628,7 +628,7 @@ func (b *Backend) StateAtBlock(ctx context.Context, block *ethtypes.Block, reexe func (b *Backend) initializeBlock(ctx context.Context, block *ethtypes.Block, ctxProvider TraceContextProvider) (sdk.Context, *coretypes.ResultBlock, tracers.StateReleaseFunc, error) { emptyRelease := func() {} // get the parent block using block.parentHash - prevBlockHeight := block.Number().Int64() - 1 + prevBlockHeight := max(block.Number().Int64() - 1, 0) blockNumber := block.Number().Int64() tmBlock, err := blockByNumberRespectingWatermarks(ctx, b.tmClient, b.watermarks, &blockNumber, 1) @@ -730,6 +730,9 @@ func (b *Backend) resolveStateAndHeaderByNumberOrHash(ctx context.Context, block if isLatestLikeBlockRef(blockNrOrHash) { sdkCtx := b.ctxProvider(LatestCtxHeight) h := sdkCtx.BlockHeight() + if h <= 0 { + return sdkCtx, b.syntheticHeaderFromCtx(sdkCtx), true, nil + } if wmHeight, err := b.watermarks.LatestHeight(ctx); err != nil { return sdkCtx, nil, false, fmt.Errorf("b.watermarks.LatestHeight(): %w", err) } else if wmHeight != h { @@ -737,7 +740,7 @@ func (b *Backend) resolveStateAndHeaderByNumberOrHash(ctx context.Context, block } tmBlock, err := b.tmClient.Block(ctx, &h) if err != nil { - return sdkCtx, nil, false, fmt.Errorf("b.tmClient.Block(): %w", err) + return sdkCtx, b.syntheticHeaderFromCtx(sdkCtx), true, nil } header := b.getHeader(tmBlock) header.BaseFee = b.keeper.GetNextBaseFeePerGas(sdkCtx).TruncateInt().BigInt() diff --git a/sei-cosmos/baseapp/abci.go b/sei-cosmos/baseapp/abci.go index 57a564738b..8818423d1f 100644 --- a/sei-cosmos/baseapp/abci.go +++ b/sei-cosmos/baseapp/abci.go @@ -82,12 +82,6 @@ func (app *BaseApp) InitChain(req *abci.RequestInitChain) (*abci.ResponseInitCha // checks and can reject valid genesis txs. app.initChainer(app.checkState.ctx.WithIsCheckTx(false), *req) - // After genesis initialization completes, CheckTx should use the first - // executable block height rather than height 0. - if initHeader.Height == 0 { - app.checkState.SetContext(app.checkState.ctx.WithBlockHeight(1)) - } - // In the case of a new chain, AppHash will be the hash of an empty string. // During an upgrade, it'll be the hash of the last committed block. var appHash []byte diff --git a/sei-cosmos/baseapp/deliver_tx_test.go b/sei-cosmos/baseapp/deliver_tx_test.go index bd3e711e90..992ac2490c 100644 --- a/sei-cosmos/baseapp/deliver_tx_test.go +++ b/sei-cosmos/baseapp/deliver_tx_test.go @@ -1157,7 +1157,7 @@ func TestInitChainer(t *testing.T) { chainID = app.checkState.ctx.ChainID() require.Equal(t, "test-chain-id", chainID, "ChainID in checkState not set correctly in InitChain") - require.Equal(t, int64(1), app.checkState.ctx.BlockHeight(), "checkState height should reflect the first executable block after InitChain") + require.Equal(t, int64(0), app.checkState.ctx.BlockHeight(), "checkState height should remain at the latest committed height after InitChain") app.Commit(context.Background()) res, _ = app.Query(context.Background(), &query) From e5812ea4c3d68981893b3a651be35cb35e7715a8 Mon Sep 17 00:00:00 2001 From: Grzegorz Prusak Date: Tue, 14 Jul 2026 21:27:56 +0200 Subject: [PATCH 39/54] improved --- evmrpc/simulate.go | 145 ++++++++---------- sei-tendermint/internal/rpc/core/blocks.go | 12 +- sei-tendermint/internal/rpc/core/consensus.go | 2 +- 3 files changed, 66 insertions(+), 93 deletions(-) diff --git a/evmrpc/simulate.go b/evmrpc/simulate.go index eb35acf942..98009d3a66 100644 --- a/evmrpc/simulate.go +++ b/evmrpc/simulate.go @@ -313,19 +313,50 @@ func (b *Backend) SetTraceContextProvider(provider TraceContextProvider) { } } +func (b *Backend) isLatest(ctx context.Context, x rpc.BlockNumberOrHash) (bool, error) { + if x.BlockHash != nil { + return false, nil + } + if x.BlockNumber == nil { + return true, nil + } + resolved, err := getBlockNumber(ctx, b.tmClient, *x.BlockNumber) + return resolved == nil, err +} + func (b *Backend) StateAndHeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (vm.StateDB, *ethtypes.Header, error) { - sdkCtx, header, isLatestBlock, err := b.resolveStateAndHeaderByNumberOrHash(ctx, blockNrOrHash) + isWasmdCall, ok := ctx.Value(CtxIsWasmdPrecompileCallKey).(bool) + isLatest, err := b.isLatest(ctx, blockNrOrHash) if err != nil { return nil, nil, err } - isWasmdCall, ok := ctx.Value(CtxIsWasmdPrecompileCallKey).(bool) - sdkCtx = sdkCtx.WithIsEVM(true).WithEVMEntryViaWasmdPrecompile(ok && isWasmdCall) - if !isLatestBlock { - // no need to check version for latest block - if err := CheckVersion(sdkCtx, b.keeper); err != nil { + if isLatest { + h, err := b.watermarks.LatestHeight(ctx) + if err != nil { return nil, nil, err } + if h == 0 { + sdkCtx := b.ctxProvider(h).WithIsEVM(true).WithEVMEntryViaWasmdPrecompile(ok && isWasmdCall) + header := b.fallbackToEthHeaderOnly(h) + header.BaseFee = b.keeper.GetNextBaseFeePerGas(b.ctxProvider(LatestCtxHeight)).TruncateInt().BigInt() + header.Time = toUint64(sdkCtx.BlockTime().Unix()) //nolint:gosec + if cp := sdkCtx.ConsensusParams(); cp != nil && cp.Block != nil { + header.GasLimit = uint64(cp.Block.MaxGas) //nolint:gosec + } + return state.NewDBImpl(sdkCtx, b.keeper, true), header, nil + } + } + tmBlock, _, err := b.getBlockByNumberOrHash(ctx, blockNrOrHash) + if err != nil { + return nil, nil, err + } + height := tmBlock.Block.Height + sdkCtx := b.ctxProvider(height).WithIsEVM(true).WithEVMEntryViaWasmdPrecompile(ok && isWasmdCall) + if err := CheckVersion(sdkCtx, b.keeper); err != nil { + return nil, nil, err } + header := b.getHeader(tmBlock) + header.BaseFee = b.keeper.GetNextBaseFeePerGas(b.ctxProvider(LatestCtxHeight)).TruncateInt().BigInt() return state.NewDBImpl(sdkCtx, b.keeper, true), header, nil } @@ -520,7 +551,7 @@ func (b *Backend) Engine() consensus.Engine { } func (b *Backend) HeaderByNumber(ctx context.Context, bn rpc.BlockNumber) (*ethtypes.Header, error) { - tmBlock, err := b.getBlockByNumberOrHash(ctx, rpc.BlockNumberOrHashWithNumber(bn)) + tmBlock, _, err := b.getBlockByNumberOrHash(ctx, rpc.BlockNumberOrHashWithNumber(bn)) if err != nil { return nil, err } @@ -628,7 +659,7 @@ func (b *Backend) StateAtBlock(ctx context.Context, block *ethtypes.Block, reexe func (b *Backend) initializeBlock(ctx context.Context, block *ethtypes.Block, ctxProvider TraceContextProvider) (sdk.Context, *coretypes.ResultBlock, tracers.StateReleaseFunc, error) { emptyRelease := func() {} // get the parent block using block.parentHash - prevBlockHeight := max(block.Number().Int64() - 1, 0) + prevBlockHeight := max(block.Number().Int64()-1, 0) blockNumber := block.Number().Int64() tmBlock, err := blockByNumberRespectingWatermarks(ctx, b.tmClient, b.watermarks, &blockNumber, 1) @@ -669,10 +700,15 @@ func (b *Backend) GetEVM(_ context.Context, msg *core.Message, stateDB vm.StateD } func (b *Backend) CurrentHeader() *ethtypes.Header { - _, header, _, err := b.resolveStateAndHeaderByNumberOrHash(context.Background(), rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber)) - if err != nil { - return b.syntheticHeaderFromCtx(b.ctxProvider(LatestCtxHeight)) + height := b.ctxProvider(LatestCtxHeight).BlockHeight() + ctx := context.Background() + var header *ethtypes.Header + if tmBlock, err := blockByNumberRespectingWatermarks(ctx, b.tmClient, b.watermarks, &height, 1); err == nil { + header = b.getHeader(tmBlock) + } else { + header = b.fallbackToEthHeaderOnly(height) } + header.BaseFee = b.keeper.GetNextBaseFeePerGas(b.ctxProvider(LatestCtxHeight)).TruncateInt().BigInt() return header } @@ -682,97 +718,38 @@ func (b *Backend) SuggestGasTipCap(context.Context) (*big.Int, error) { // getBlockByNumberOrHash resolves blockNrOrHash to a Tendermint ResultBlock in one RPC path // (by hash or by number, including latest). Callers pass the result to getHeader. -func (b *Backend) getBlockByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*coretypes.ResultBlock, error) { +func (b *Backend) getBlockByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*coretypes.ResultBlock, bool, error) { if blockNrOrHash.BlockHash != nil { block, err := blockByHashRespectingWatermarks(ctx, b.tmClient, b.watermarks, blockNrOrHash.BlockHash[:], 1) if err != nil { - return nil, err + return nil, false, err } - return block, nil + return block, false, nil } - var blockNumberPtr *int64 - var err error if blockNrOrHash.BlockNumber != nil { + var err error blockNumberPtr, err = getBlockNumber(ctx, b.tmClient, *blockNrOrHash.BlockNumber) if err != nil { - return nil, err + return nil, false, err } } block, err := blockByNumberRespectingWatermarks(ctx, b.tmClient, b.watermarks, blockNumberPtr, 1) if err != nil { - return nil, err + return nil, false, err } - return block, nil + return block, blockNumberPtr == nil, nil } -func isLatestLikeBlockRef(blockNrOrHash rpc.BlockNumberOrHash) bool { - if blockNrOrHash.BlockHash != nil { - return false - } - if blockNrOrHash.BlockNumber == nil { - return true - } - switch *blockNrOrHash.BlockNumber { - case rpc.SafeBlockNumber, rpc.FinalizedBlockNumber, rpc.LatestBlockNumber, rpc.PendingBlockNumber: - return true - default: - return false - } -} - -// resolveStateAndHeaderByNumberOrHash normalizes a block reference into the -// SDK context and Ethereum header that should be paired together. For latest-like -// requests before the first Commit, this intentionally prefers the initialized -// latest checkState context over the height-0 Tendermint block so callers receive -// a coherent (state, header) view. -func (b *Backend) resolveStateAndHeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (sdk.Context, *ethtypes.Header, bool, error) { - if isLatestLikeBlockRef(blockNrOrHash) { - sdkCtx := b.ctxProvider(LatestCtxHeight) - h := sdkCtx.BlockHeight() - if h <= 0 { - return sdkCtx, b.syntheticHeaderFromCtx(sdkCtx), true, nil - } - if wmHeight, err := b.watermarks.LatestHeight(ctx); err != nil { - return sdkCtx, nil, false, fmt.Errorf("b.watermarks.LatestHeight(): %w", err) - } else if wmHeight != h { - return sdkCtx, b.syntheticHeaderFromCtx(sdkCtx), true, nil - } - tmBlock, err := b.tmClient.Block(ctx, &h) - if err != nil { - return sdkCtx, b.syntheticHeaderFromCtx(sdkCtx), true, nil - } - header := b.getHeader(tmBlock) - header.BaseFee = b.keeper.GetNextBaseFeePerGas(sdkCtx).TruncateInt().BigInt() - return sdkCtx, header, true, nil - } - - tmBlock, err := b.getBlockByNumberOrHash(ctx, blockNrOrHash) - if err != nil { - return sdk.Context{}, nil, false, err - } - sdkCtx := b.ctxProvider(tmBlock.Block.Height) - return sdkCtx, b.getHeader(tmBlock), false, nil -} - -// syntheticHeaderFromCtx builds a minimal header directly from the selected SDK -// context when no coherent Tendermint block/header is available for that state -// (e.g. latest-like queries before the first Commit). -func (b *Backend) syntheticHeaderFromCtx(sdkCtx sdk.Context) *ethtypes.Header { +// fallbackToEthHeaderOnly builds a minimal header when the block cannot be loaded +// (e.g. CurrentHeader when Block RPC fails). BaseFee is overwritten by CurrentHeader afterward. +func (b *Backend) fallbackToEthHeaderOnly(height int64) *ethtypes.Header { zeroExcessBlobGas := uint64(0) - baseFee := b.keeper.GetNextBaseFeePerGas(sdkCtx).TruncateInt().BigInt() - var gasLimit uint64 - if cp := sdkCtx.ConsensusParams(); cp != nil && cp.Block != nil && cp.Block.MaxGas > 0 { - gasLimit = uint64(cp.Block.MaxGas) //nolint:gosec - } else { - gasLimit = keeper.DefaultBlockGasLimit - } return ðtypes.Header{ Difficulty: common.Big0, - Number: big.NewInt(sdkCtx.BlockHeight()), - BaseFee: baseFee, - GasLimit: gasLimit, - Time: toUint64(sdkCtx.BlockTime().Unix()), //nolint:gosec + Number: big.NewInt(height), + GasLimit: keeper.DefaultBlockGasLimit, + Time: toUint64(time.Now().Unix()), //nolint:gosec ExcessBlobGas: &zeroExcessBlobGas, } } @@ -786,7 +763,7 @@ func (b *Backend) getHeader(tmBlock *coretypes.ResultBlock) *ethtypes.Header { baseFee = nil } var gasLimit uint64 - if cp := sdkCtx.ConsensusParams(); cp != nil && cp.Block != nil && cp.Block.MaxGas > 0 { + if cp := sdkCtx.ConsensusParams(); cp != nil && cp.Block != nil { gasLimit = uint64(cp.Block.MaxGas) //nolint:gosec } else { gasLimit = keeper.DefaultBlockGasLimit diff --git a/sei-tendermint/internal/rpc/core/blocks.go b/sei-tendermint/internal/rpc/core/blocks.go index 902a9f6863..403fd10ffa 100644 --- a/sei-tendermint/internal/rpc/core/blocks.go +++ b/sei-tendermint/internal/rpc/core/blocks.go @@ -98,7 +98,7 @@ func filterMinMax(base, height, min, max, limit int64) (int64, int64, error) { // individually branching on consensus mode. func (env *Environment) Block(ctx context.Context, req *coretypes.RequestBlockInfo) (*coretypes.ResultBlock, error) { if giga, ok := env.gigaRouter().Get(); ok { - height, err := env.autobahnCheckAndGetHeight(ctx, (*int64)(req.Height)) + height, err := env.autobahnCheckAndGetHeight((*int64)(req.Height)) if err != nil { return nil, err } @@ -144,12 +144,8 @@ func (env *Environment) Block(ctx context.Context, req *coretypes.RequestBlockIn // natural source becomes available once sei-db/ledger_db/block.BlockDB is // wired into block execution: switch this and BlockByNumber to read from // BlockDB, and source `base` from BlockDB.GetLowestBlockHeight. -func (env *Environment) autobahnCheckAndGetHeight(ctx context.Context, heightPtr *int64) (int64, error) { - info, err := env.ABCIInfo(ctx) - if err != nil { - return 0, err - } - return env.getHeight(info.Response.LastBlockHeight, heightPtr) +func (env *Environment) autobahnCheckAndGetHeight(heightPtr *int64) (int64, error) { + return env.getHeight(env.App.Info().LastBlockHeight, heightPtr) } // BlockByHash gets block by hash. @@ -259,7 +255,7 @@ func (env *Environment) Commit(ctx context.Context, req *coretypes.RequestBlockI // populating these under Autobahn is a separate follow-up. func (env *Environment) BlockResults(ctx context.Context, req *coretypes.RequestBlockInfo) (*coretypes.ResultBlockResults, error) { if giga, ok := env.gigaRouter().Get(); ok { - height, err := env.autobahnCheckAndGetHeight(ctx, (*int64)(req.Height)) + height, err := env.autobahnCheckAndGetHeight((*int64)(req.Height)) if err != nil { return nil, err } diff --git a/sei-tendermint/internal/rpc/core/consensus.go b/sei-tendermint/internal/rpc/core/consensus.go index 7e1e5be7a2..45a0e5e416 100644 --- a/sei-tendermint/internal/rpc/core/consensus.go +++ b/sei-tendermint/internal/rpc/core/consensus.go @@ -24,7 +24,7 @@ import ( // mirror the CometBFT path so external tools see consistent responses. func (env *Environment) Validators(ctx context.Context, req *coretypes.RequestValidators) (*coretypes.ResultValidators, error) { if env.gigaRouter().IsPresent() { - height, err := env.autobahnCheckAndGetHeight(ctx, (*int64)(req.Height)) + height, err := env.autobahnCheckAndGetHeight((*int64)(req.Height)) if err != nil { return nil, err } From f6e7f2896b0d3d00faffe71645fccfaf8b166644 Mon Sep 17 00:00:00 2001 From: Grzegorz Prusak Date: Tue, 14 Jul 2026 22:57:48 +0200 Subject: [PATCH 40/54] removed special cases --- app/antedecorators/gas.go | 2 +- app/app.go | 14 +-------- evmrpc/simulate.go | 44 +++++++++++++++-------------- evmrpc/watermark_manager.go | 4 +-- sei-cosmos/x/auth/ante/setup.go | 4 +-- sei-cosmos/x/auth/ante/sigverify.go | 11 ++------ 6 files changed, 31 insertions(+), 48 deletions(-) diff --git a/app/antedecorators/gas.go b/app/antedecorators/gas.go index 8f45e47cc9..bd1bc413a0 100644 --- a/app/antedecorators/gas.go +++ b/app/antedecorators/gas.go @@ -11,7 +11,7 @@ func GetGasMeterSetter(pk paramskeeper.Keeper) func(bool, sdk.Context, uint64, s cosmosGasParams := pk.GetCosmosGasParams(ctx) // In simulation, still use multiplier but with infinite gas limit - if simulate || ctx.BlockHeight() == 0 { + if simulate { return ctx.WithGasMeter(types.NewInfiniteMultiplierGasMeter(cosmosGasParams.CosmosGasMultiplierNumerator, cosmosGasParams.CosmosGasMultiplierDenominator)) } diff --git a/app/app.go b/app/app.go index fc181b7015..bbccac8f6c 100644 --- a/app/app.go +++ b/app/app.go @@ -2560,19 +2560,7 @@ func (app *App) LegacyAmino() *codec.LegacyAmino { } func (app *App) GetValidators() []abci.ValidatorUpdate { - // AUTOBAHN: After InitChain but before the first Commit, the committed - // store is empty — staking params don't exist, so reading from committed - // store panics in MaxValidators. Use DeliverContext when available at - // height 0, since it has the uncommitted staking state from InitChain. - // CometBFT consensus never hits this because its handshaker commits - // after InitChain before any block processing begins. - if app.LastBlockHeight() == 0 { - if dctx := app.DeliverContext(); dctx != nil { - return app.StakingKeeper.GetBondedValidators(*dctx) - } - } - ctx := app.NewUncachedContext(false, tmproto.Header{Height: max(app.LastBlockHeight(), 1)}) - return app.StakingKeeper.GetBondedValidators(ctx) + return app.StakingKeeper.GetBondedValidators(app.GetCheckCtx()) } // AppCodec returns an app codec. diff --git a/evmrpc/simulate.go b/evmrpc/simulate.go index 98009d3a66..4ce24d6ba7 100644 --- a/evmrpc/simulate.go +++ b/evmrpc/simulate.go @@ -325,38 +325,40 @@ func (b *Backend) isLatest(ctx context.Context, x rpc.BlockNumberOrHash) (bool, } func (b *Backend) StateAndHeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (vm.StateDB, *ethtypes.Header, error) { - isWasmdCall, ok := ctx.Value(CtxIsWasmdPrecompileCallKey).(bool) + sdkCtx := b.ctxProvider(LatestCtxHeight) + zeroExcessBlobGas := uint64(0) + header := ðtypes.Header{ + Difficulty: common.Big0, + Number: big.NewInt(sdkCtx.BlockHeight()), + BaseFee: b.keeper.GetNextBaseFeePerGas(sdkCtx).TruncateInt().BigInt(), + GasLimit: keeper.DefaultBlockGasLimit, + Time: toUint64(sdkCtx.BlockTime().Unix()), //nolint:gosec + ExcessBlobGas: &zeroExcessBlobGas, + } isLatest, err := b.isLatest(ctx, blockNrOrHash) if err != nil { return nil, nil, err } - if isLatest { - h, err := b.watermarks.LatestHeight(ctx) + if !isLatest || sdkCtx.BlockHeight() > 0 { + tmBlock, isLatest, err := b.getBlockByNumberOrHash(ctx, blockNrOrHash) if err != nil { return nil, nil, err } - if h == 0 { - sdkCtx := b.ctxProvider(h).WithIsEVM(true).WithEVMEntryViaWasmdPrecompile(ok && isWasmdCall) - header := b.fallbackToEthHeaderOnly(h) - header.BaseFee = b.keeper.GetNextBaseFeePerGas(b.ctxProvider(LatestCtxHeight)).TruncateInt().BigInt() - header.Time = toUint64(sdkCtx.BlockTime().Unix()) //nolint:gosec - if cp := sdkCtx.ConsensusParams(); cp != nil && cp.Block != nil { - header.GasLimit = uint64(cp.Block.MaxGas) //nolint:gosec + header.Number = big.NewInt(tmBlock.Block.Height) + header.Time = toUint64(tmBlock.Block.Time.Unix()) + header.ParentHash = common.BytesToHash(tmBlock.BlockID.Hash) + sdkCtx = b.ctxProvider(tmBlock.Block.Height) + if !isLatest { + if err := CheckVersion(sdkCtx, b.keeper); err != nil { + return nil, nil, err } - return state.NewDBImpl(sdkCtx, b.keeper, true), header, nil } } - tmBlock, _, err := b.getBlockByNumberOrHash(ctx, blockNrOrHash) - if err != nil { - return nil, nil, err - } - height := tmBlock.Block.Height - sdkCtx := b.ctxProvider(height).WithIsEVM(true).WithEVMEntryViaWasmdPrecompile(ok && isWasmdCall) - if err := CheckVersion(sdkCtx, b.keeper); err != nil { - return nil, nil, err + isWasmdCall, ok := ctx.Value(CtxIsWasmdPrecompileCallKey).(bool) + sdkCtx = sdkCtx.WithIsEVM(true).WithEVMEntryViaWasmdPrecompile(ok && isWasmdCall) + if cp := sdkCtx.ConsensusParams(); cp != nil && cp.Block != nil { + header.GasLimit = uint64(cp.Block.MaxGas) //nolint:gosec } - header := b.getHeader(tmBlock) - header.BaseFee = b.keeper.GetNextBaseFeePerGas(b.ctxProvider(LatestCtxHeight)).TruncateInt().BigInt() return state.NewDBImpl(sdkCtx, b.keeper, true), header, nil } diff --git a/evmrpc/watermark_manager.go b/evmrpc/watermark_manager.go index 8dcd2ad5c2..89c26f885e 100644 --- a/evmrpc/watermark_manager.go +++ b/evmrpc/watermark_manager.go @@ -95,9 +95,7 @@ func (m *WatermarkManager) Watermarks(ctx context.Context) (int64, int64, int64, } if m.ctxProvider != nil { - if ctxHeight := m.ctxProvider(LatestCtxHeight).BlockHeight(); ctxHeight > 0 { - setLatest(ctxHeight) - } + setLatest(m.ctxProvider(LatestCtxHeight).BlockHeight()) } // State store heights (historical state DB) may lag behind block pruning. diff --git a/sei-cosmos/x/auth/ante/setup.go b/sei-cosmos/x/auth/ante/setup.go index ddd59c9c43..8dc483d9fd 100644 --- a/sei-cosmos/x/auth/ante/setup.go +++ b/sei-cosmos/x/auth/ante/setup.go @@ -83,10 +83,10 @@ func (sud SetUpContextDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate // SetGasMeter returns a new context with a gas meter set from a given context. func SetGasMeter(simulate bool, ctx sdk.Context, gasLimit uint64, _ sdk.Tx) sdk.Context { - // In various cases such as simulation and during the genesis block, we do not + // In various cases such as simulation, we do not // meter any gas utilization. - if simulate || ctx.BlockHeight() == 0 { + if simulate { return ctx.WithGasMeter(sdk.NewInfiniteGasMeterWithMultiplier(ctx)) } diff --git a/sei-cosmos/x/auth/ante/sigverify.go b/sei-cosmos/x/auth/ante/sigverify.go index ed619a6fb7..0efaca73f0 100644 --- a/sei-cosmos/x/auth/ante/sigverify.go +++ b/sei-cosmos/x/auth/ante/sigverify.go @@ -277,15 +277,10 @@ func (svd SigVerificationDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simul } // retrieve signer data - genesis := ctx.BlockHeight() == 0 chainID := ctx.ChainID() - var accNum uint64 - if !genesis { - accNum = acc.GetAccountNumber() - } signerData := authsigning.SignerData{ ChainID: chainID, - AccountNumber: accNum, + AccountNumber: acc.GetAccountNumber(), Sequence: acc.GetSequence(), } @@ -297,9 +292,9 @@ func (svd SigVerificationDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simul if OnlyLegacyAminoSigners(sig.Data) { // If all signers are using SIGN_MODE_LEGACY_AMINO, we rely on VerifySignature to check account sequence number, // and therefore communicate sequence number as a potential cause of error. - errMsg = fmt.Sprintf("signature verification failed; please verify account number (%d), sequence (%d) and chain-id (%s)", accNum, acc.GetSequence(), chainID) + errMsg = fmt.Sprintf("signature verification failed; please verify account number (%d), sequence (%d) and chain-id (%s)", acc.GetAccountNumber(), acc.GetSequence(), chainID) } else { - errMsg = fmt.Sprintf("signature verification failed; please verify account number (%d) and chain-id (%s)", accNum, chainID) + errMsg = fmt.Sprintf("signature verification failed; please verify account number (%d) and chain-id (%s)", acc.GetAccountNumber(), chainID) } return ctx, sdkerrors.Wrap(sdkerrors.ErrUnauthorized, errMsg) From f1554af8937d5de4abe68aecc4f79a474e210395 Mon Sep 17 00:00:00 2001 From: Grzegorz Prusak Date: Wed, 15 Jul 2026 09:40:56 +0200 Subject: [PATCH 41/54] explicit genesis processing --- app/antedecorators/gas.go | 4 ++-- sei-cosmos/baseapp/abci.go | 3 +++ sei-cosmos/types/context.go | 10 ++++++++++ sei-cosmos/types/context_test.go | 2 ++ sei-cosmos/x/auth/ante/setup.go | 4 ++-- sei-cosmos/x/auth/ante/sigverify.go | 11 ++++++++--- 6 files changed, 27 insertions(+), 7 deletions(-) diff --git a/app/antedecorators/gas.go b/app/antedecorators/gas.go index bd1bc413a0..98656a187a 100644 --- a/app/antedecorators/gas.go +++ b/app/antedecorators/gas.go @@ -10,8 +10,8 @@ func GetGasMeterSetter(pk paramskeeper.Keeper) func(bool, sdk.Context, uint64, s return func(simulate bool, ctx sdk.Context, gasLimit uint64, tx sdk.Tx) sdk.Context { cosmosGasParams := pk.GetCosmosGasParams(ctx) - // In simulation, still use multiplier but with infinite gas limit - if simulate { + // In simulation and genesis delivery, still use multiplier but with infinite gas limit. + if simulate || ctx.IsGenesis() { return ctx.WithGasMeter(types.NewInfiniteMultiplierGasMeter(cosmosGasParams.CosmosGasMultiplierNumerator, cosmosGasParams.CosmosGasMultiplierDenominator)) } diff --git a/sei-cosmos/baseapp/abci.go b/sei-cosmos/baseapp/abci.go index 8818423d1f..e01d0aceaa 100644 --- a/sei-cosmos/baseapp/abci.go +++ b/sei-cosmos/baseapp/abci.go @@ -58,6 +58,9 @@ func (app *BaseApp) InitChain(req *abci.RequestInitChain) (*abci.ResponseInitCha app.setDeliverState(initHeader) app.setCheckState(initHeader) app.setProcessProposalState(initHeader) + app.deliverState.SetContext(app.deliverState.ctx.WithIsGenesis(true)) + app.checkState.SetContext(app.checkState.ctx.WithIsGenesis(true)) + app.processProposalState.SetContext(app.processProposalState.ctx.WithIsGenesis(true)) // Store the consensus params in the BaseApp's paramstore. Note, this must be // done after the deliver state and context have been set as it's persisted diff --git a/sei-cosmos/types/context.go b/sei-cosmos/types/context.go index a8f8740a53..f21a87b4e8 100644 --- a/sei-cosmos/types/context.go +++ b/sei-cosmos/types/context.go @@ -41,6 +41,7 @@ type Context struct { blockGasMeter GasMeter checkTx bool recheckTx bool // if recheckTx == true, then checkTx must also be true + isGenesis bool minGasPrice DecCoins consParams *tmproto.ConsensusParams eventManager *EventManager @@ -132,6 +133,10 @@ func (c Context) IsReCheckTx() bool { return c.recheckTx } +func (c Context) IsGenesis() bool { + return c.isGenesis +} + func (c Context) IsOCCEnabled() bool { return c.occEnabled } @@ -379,6 +384,11 @@ func (c Context) WithIsReCheckTx(isRecheckTx bool) Context { return c } +func (c Context) WithIsGenesis(isGenesis bool) Context { + c.isGenesis = isGenesis + return c +} + // WithMinGasPrices returns a Context with an updated minimum gas price value func (c Context) WithMinGasPrices(gasPrices DecCoins) Context { c.minGasPrice = gasPrices diff --git a/sei-cosmos/types/context_test.go b/sei-cosmos/types/context_test.go index 1cbeaa5e89..d91f708326 100644 --- a/sei-cosmos/types/context_test.go +++ b/sei-cosmos/types/context_test.go @@ -94,6 +94,8 @@ func (s *contextTestSuite) TestContextWithCustom() { s.Require().Equal(minGasPrices, ctx.MinGasPrices()) s.Require().Equal(headerHash, ctx.HeaderHash().Bytes()) s.Require().False(ctx.WithIsCheckTx(false).IsCheckTx()) + s.Require().False(ctx.IsGenesis()) + s.Require().True(ctx.WithIsGenesis(true).IsGenesis()) // test IsReCheckTx s.Require().False(ctx.IsReCheckTx()) diff --git a/sei-cosmos/x/auth/ante/setup.go b/sei-cosmos/x/auth/ante/setup.go index 8dc483d9fd..05547fe807 100644 --- a/sei-cosmos/x/auth/ante/setup.go +++ b/sei-cosmos/x/auth/ante/setup.go @@ -83,10 +83,10 @@ func (sud SetUpContextDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate // SetGasMeter returns a new context with a gas meter set from a given context. func SetGasMeter(simulate bool, ctx sdk.Context, gasLimit uint64, _ sdk.Tx) sdk.Context { - // In various cases such as simulation, we do not + // In various cases such as simulation and genesis delivery, we do not // meter any gas utilization. - if simulate { + if simulate || ctx.IsGenesis() { return ctx.WithGasMeter(sdk.NewInfiniteGasMeterWithMultiplier(ctx)) } diff --git a/sei-cosmos/x/auth/ante/sigverify.go b/sei-cosmos/x/auth/ante/sigverify.go index 0efaca73f0..f9c7ab5730 100644 --- a/sei-cosmos/x/auth/ante/sigverify.go +++ b/sei-cosmos/x/auth/ante/sigverify.go @@ -277,10 +277,15 @@ func (svd SigVerificationDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simul } // retrieve signer data + // Genesis gentxs are delivered under InitChain before normal account-number semantics apply. chainID := ctx.ChainID() + var accNum uint64 + if !ctx.IsGenesis() { + accNum = acc.GetAccountNumber() + } signerData := authsigning.SignerData{ ChainID: chainID, - AccountNumber: acc.GetAccountNumber(), + AccountNumber: accNum, Sequence: acc.GetSequence(), } @@ -292,9 +297,9 @@ func (svd SigVerificationDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simul if OnlyLegacyAminoSigners(sig.Data) { // If all signers are using SIGN_MODE_LEGACY_AMINO, we rely on VerifySignature to check account sequence number, // and therefore communicate sequence number as a potential cause of error. - errMsg = fmt.Sprintf("signature verification failed; please verify account number (%d), sequence (%d) and chain-id (%s)", acc.GetAccountNumber(), acc.GetSequence(), chainID) + errMsg = fmt.Sprintf("signature verification failed; please verify account number (%d), sequence (%d) and chain-id (%s)", accNum, acc.GetSequence(), chainID) } else { - errMsg = fmt.Sprintf("signature verification failed; please verify account number (%d) and chain-id (%s)", acc.GetAccountNumber(), chainID) + errMsg = fmt.Sprintf("signature verification failed; please verify account number (%d) and chain-id (%s)", accNum, chainID) } return ctx, sdkerrors.Wrap(sdkerrors.ErrUnauthorized, errMsg) From 772b3cce62c84c8c9f272bff097a033e046c6320 Mon Sep 17 00:00:00 2001 From: Grzegorz Prusak Date: Wed, 15 Jul 2026 13:34:42 +0200 Subject: [PATCH 42/54] IsGenesis only for InitChainer --- evmrpc/watermark_manager.go | 15 +----- sei-cosmos/baseapp/abci.go | 12 ++--- sei-cosmos/baseapp/deliver_tx_test.go | 66 +++++++++++++++++++++++++++ sei-cosmos/x/auth/ante/ante_test.go | 4 +- x/evm/ante/sig_test.go | 45 ++++++++++++++++++ 5 files changed, 120 insertions(+), 22 deletions(-) diff --git a/evmrpc/watermark_manager.go b/evmrpc/watermark_manager.go index 89c26f885e..98bd75b046 100644 --- a/evmrpc/watermark_manager.go +++ b/evmrpc/watermark_manager.go @@ -46,6 +46,7 @@ func NewWatermarkManager( // Watermarks returns the earliest block height, earliest state height, and // latest height that are safe to serve. Earliest heights are inclusive. +// It is possible that block latest < block earliest, in case there are no blocks yet. func (m *WatermarkManager) Watermarks(ctx context.Context) (int64, int64, int64, error) { var ( latest int64 @@ -127,14 +128,6 @@ func (m *WatermarkManager) Watermarks(ctx context.Context) (int64, int64, int64, stateEarliest = blockEarliest } - if blockEarliest > latest { - return 0, 0, 0, fmt.Errorf("computed block earliest watermark %d is beyond latest %d", blockEarliest, latest) - } - - if stateEarliest > latest { - return 0, 0, 0, fmt.Errorf("computed state earliest watermark %d is beyond latest %d", stateEarliest, latest) - } - return blockEarliest, stateEarliest, latest, nil } @@ -338,12 +331,6 @@ func (m *WatermarkManager) fetchTendermintWatermarks(ctx context.Context) (int64 TraceTendermintIfApplicable(ctx, "Status", []string{}, status) latest := status.SyncInfo.LatestBlockHeight earliest := status.SyncInfo.EarliestBlockHeight - if latest == 0 { - // Before the first Commit, Tendermint can still report InitialHeight as - // the earliest block height even though the only readable "latest" state - // is the synthetic height-0 genesis/checkState branch. - earliest = 0 - } return latest, earliest, nil } diff --git a/sei-cosmos/baseapp/abci.go b/sei-cosmos/baseapp/abci.go index e01d0aceaa..411ef69421 100644 --- a/sei-cosmos/baseapp/abci.go +++ b/sei-cosmos/baseapp/abci.go @@ -58,9 +58,9 @@ func (app *BaseApp) InitChain(req *abci.RequestInitChain) (*abci.ResponseInitCha app.setDeliverState(initHeader) app.setCheckState(initHeader) app.setProcessProposalState(initHeader) - app.deliverState.SetContext(app.deliverState.ctx.WithIsGenesis(true)) - app.checkState.SetContext(app.checkState.ctx.WithIsGenesis(true)) - app.processProposalState.SetContext(app.processProposalState.ctx.WithIsGenesis(true)) + app.deliverState.SetContext(app.deliverState.ctx) + app.checkState.SetContext(app.checkState.ctx) + app.processProposalState.SetContext(app.processProposalState.ctx) // Store the consensus params in the BaseApp's paramstore. Note, this must be // done after the deliver state and context have been set as it's persisted @@ -78,12 +78,12 @@ func (app *BaseApp) InitChain(req *abci.RequestInitChain) (*abci.ResponseInitCha return nil, nil } - resp := app.initChainer(app.deliverState.ctx, *req) - app.initChainer(app.processProposalState.ctx, *req) + resp := app.initChainer(app.deliverState.ctx.WithIsGenesis(true), *req) + app.initChainer(app.processProposalState.ctx.WithIsGenesis(true), *req) // Genesis initialization needs deliver semantics even when populating the // check state branch. Running it in CheckTx mode enables mempool-only fee // checks and can reject valid genesis txs. - app.initChainer(app.checkState.ctx.WithIsCheckTx(false), *req) + app.initChainer(app.checkState.ctx.WithIsGenesis(true).WithIsCheckTx(false), *req) // In the case of a new chain, AppHash will be the hash of an empty string. // During an upgrade, it'll be the hash of the last committed block. diff --git a/sei-cosmos/baseapp/deliver_tx_test.go b/sei-cosmos/baseapp/deliver_tx_test.go index 992ac2490c..7e8fc811a3 100644 --- a/sei-cosmos/baseapp/deliver_tx_test.go +++ b/sei-cosmos/baseapp/deliver_tx_test.go @@ -508,6 +508,72 @@ func TestSimulateTx(t *testing.T) { } } +func TestInitChainGenesisFlagDoesNotLeakToCheckOrSimulateBeforeFirstCommit(t *testing.T) { + type anteCall struct { + simulate bool + isCheckTx bool + isSimulation bool + isGenesis bool + } + + var anteCalls []anteCall + anteOpt := func(bapp *BaseApp) { + bapp.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx, simulate bool) (sdk.Context, error) { + anteCalls = append(anteCalls, anteCall{ + simulate: simulate, + isCheckTx: ctx.IsCheckTx(), + isSimulation: ctx.IsSimulation(), + isGenesis: ctx.IsGenesis(), + }) + return ctx, nil + }) + } + + routerOpt := func(bapp *BaseApp) { + r := sdk.NewRoute(routeMsgCounter, func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) { + return &sdk.Result{}, nil + }) + bapp.Router().AddRoute(r) + } + + var initGenesisFlags []bool + initChainerOpt := func(bapp *BaseApp) { + bapp.SetInitChainer(func(ctx sdk.Context, _ abci.RequestInitChain) abci.ResponseInitChain { + initGenesisFlags = append(initGenesisFlags, ctx.IsGenesis()) + return abci.ResponseInitChain{} + }) + } + + app := newBaseApp(t.Name(), anteOpt, routerOpt, initChainerOpt) + app.MountStores(capKey1, capKey2) + app.SetParamStore(¶mStore{db: dbm.NewMemDB()}) + require.NoError(t, app.LoadLatestVersion()) + + _, err := app.InitChain(&abci.RequestInitChain{}) + require.NoError(t, err) + require.Equal(t, []bool{true, true, true}, initGenesisFlags) + require.Equal(t, int64(0), app.LastBlockHeight()) + + tx := newTxCounter(0, 0) + _, _, err = app.Check(aminoTxEncoder(), tx) + require.NoError(t, err) + + txBytes, err := aminoTxEncoder()(tx) + require.NoError(t, err) + _, _, err = app.Simulate(txBytes) + require.NoError(t, err) + + require.Len(t, anteCalls, 2) + require.False(t, anteCalls[0].simulate) + require.True(t, anteCalls[0].isCheckTx) + require.False(t, anteCalls[0].isSimulation) + require.False(t, anteCalls[0].isGenesis) + require.True(t, anteCalls[1].simulate) + require.True(t, anteCalls[1].isCheckTx) + require.True(t, anteCalls[1].isSimulation) + require.False(t, anteCalls[1].isGenesis) +} + func TestRunInvalidTransaction(t *testing.T) { anteOpt := func(bapp *BaseApp) { bapp.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, err error) { diff --git a/sei-cosmos/x/auth/ante/ante_test.go b/sei-cosmos/x/auth/ante/ante_test.go index 1fd8c973de..38dcaac7d3 100644 --- a/sei-cosmos/x/auth/ante/ante_test.go +++ b/sei-cosmos/x/auth/ante/ante_test.go @@ -243,9 +243,9 @@ func (suite *AnteTestSuite) TestAnteHandlerAccountNumbers() { } // Test logic around account number checking with many signers when BlockHeight is 0. -func (suite *AnteTestSuite) TestAnteHandlerAccountNumbersAtBlockHeightZero() { +func (suite *AnteTestSuite) TestAnteHandlerAccountNumbersAtGenesis() { suite.SetupTest(false) // setup - suite.ctx = suite.ctx.WithBlockHeight(0) + suite.ctx = suite.ctx.WithBlockHeight(0).WithIsGenesis(true) // Same data for every test cases accounts := suite.CreateTestAccounts(2) diff --git a/x/evm/ante/sig_test.go b/x/evm/ante/sig_test.go index adda549be0..06c8ff0e65 100644 --- a/x/evm/ante/sig_test.go +++ b/x/evm/ante/sig_test.go @@ -158,3 +158,48 @@ func TestSigVerifyPendingTransaction(t *testing.T) { }) require.NotNil(t, err) } + +func TestEVMSimulationBeforeFirstCommitIsNotGenesis(t *testing.T) { + k := &testkeeper.EVMTestApp.EvmKeeper + ctx := testkeeper.EVMTestApp.GetCheckCtx().WithBlockTime(time.Now()) + require.False(t, ctx.IsGenesis()) + + handler := ante.NewEVMSigVerifyDecorator(k, func() sdk.Context { return ctx }) + privKey := testkeeper.MockPrivateKey() + testPrivHex := hex.EncodeToString(privKey.Bytes()) + key, _ := crypto.HexToECDSA(testPrivHex) + to := new(common.Address) + copy(to[:], []byte("0x1234567890abcdef1234567890abcdef12345678")) + txData := ethtypes.LegacyTx{ + Nonce: 0, + GasPrice: big.NewInt(10), + Gas: 1000, + To: to, + Value: big.NewInt(1000), + Data: []byte("abc"), + } + chainID := k.ChainID(ctx) + chainCfg := types.DefaultChainConfig() + ethCfg := chainCfg.EthereumConfig(chainID) + blockNum := big.NewInt(ctx.BlockHeight()) + signer := ethtypes.MakeSigner(ethCfg, blockNum, uint64(ctx.BlockTime().Unix())) + tx, err := ethtypes.SignTx(ethtypes.NewTx(&txData), signer, key) + require.NoError(t, err) + typedTx, err := ethtx.NewLegacyTx(tx) + require.NoError(t, err) + msg, err := types.NewMsgEVMTransaction(typedTx) + require.NoError(t, err) + + preprocessor := ante.NewEVMPreprocessDecorator(k, k.AccountKeeper()) + ctx, err = preprocessor.AnteHandle(ctx, mockTx{msgs: []sdk.Msg{msg}}, true, func(ctx sdk.Context, _ sdk.Tx, _ bool) (sdk.Context, error) { + require.False(t, ctx.IsGenesis()) + return ctx, nil + }) + require.NoError(t, err) + + _, err = handler.AnteHandle(ctx, mockTx{msgs: []sdk.Msg{msg}}, true, func(ctx sdk.Context, _ sdk.Tx, _ bool) (sdk.Context, error) { + require.False(t, ctx.IsGenesis()) + return ctx, nil + }) + require.NoError(t, err) +} From 9920a5a580e88c8eca9a0aabdb8d19bd0233e12f Mon Sep 17 00:00:00 2001 From: Grzegorz Prusak Date: Wed, 15 Jul 2026 14:03:11 +0200 Subject: [PATCH 43/54] test fixes --- evmrpc/block.go | 4 +-- evmrpc/height_availability_test.go | 2 +- evmrpc/watermark_manager.go | 26 +++++--------- evmrpc/watermark_manager_test.go | 12 +++---- .../internal/statesync/reactor_test.go | 2 +- .../internal/statesync/syncer_test.go | 2 +- .../syncer_verifyapp_default_test.go | 16 +++------ .../internal/statesync/test_app_test.go | 34 +++++++++---------- 8 files changed, 41 insertions(+), 57 deletions(-) diff --git a/evmrpc/block.go b/evmrpc/block.go index b6007edcfc..4a4321aa8b 100644 --- a/evmrpc/block.go +++ b/evmrpc/block.go @@ -185,7 +185,7 @@ func (a *BlockAPI) GetBlockTransactionCountByNumber(ctx context.Context, number if block == nil { return nil, nil } - if err = a.watermarks.EnsureReceiptHeightAvailable(ctx, block.Block.Height); err != nil { + if err = a.watermarks.EnsureReceiptHeightAvailable(block.Block.Height); err != nil { return nil, err } return a.getEvmTxCount(block), nil @@ -207,7 +207,7 @@ func (a *BlockAPI) GetBlockTransactionCountByHash(ctx context.Context, blockHash if block == nil { return nil, nil } - if err = a.watermarks.EnsureReceiptHeightAvailable(ctx, block.Block.Height); err != nil { + if err = a.watermarks.EnsureReceiptHeightAvailable(block.Block.Height); err != nil { return nil, err } return a.getEvmTxCount(block), nil diff --git a/evmrpc/height_availability_test.go b/evmrpc/height_availability_test.go index 931e4cc652..163dcfac9c 100644 --- a/evmrpc/height_availability_test.go +++ b/evmrpc/height_availability_test.go @@ -110,7 +110,7 @@ func mustDecodeHex(h string) []byte { func testTxConfigProvider(int64) client.TxConfig { return nil } -func testCtxProvider(int64) sdk.Context { return sdk.Context{} } +func testCtxProvider(h int64) sdk.Context { return sdk.Context{}.WithBlockHeight(h) } // GetBlockByHash for a block whose height sits above safe latest must return // JSON null per the Ethereum JSON-RPC spec (the block doesn't exist from the diff --git a/evmrpc/watermark_manager.go b/evmrpc/watermark_manager.go index 98bd75b046..756392b49f 100644 --- a/evmrpc/watermark_manager.go +++ b/evmrpc/watermark_manager.go @@ -101,29 +101,19 @@ func (m *WatermarkManager) Watermarks(ctx context.Context) (int64, int64, int64, // State store heights (historical state DB) may lag behind block pruning. if ssLatest, ssEarliest, ok := m.fetchStateStoreWatermarks(); ok { - if ssLatest > 0 { - setLatest(ssLatest) - } - if ssEarliest > 0 { - setStateEarliest(ssEarliest) - } + setLatest(ssLatest) + setStateEarliest(ssEarliest) } // Receipt store version participates only in the latest watermark. if m.receiptStore != nil { - if latest := m.receiptStore.LatestVersion(); latest > 0 { - setLatest(latest) - } + setLatest(m.receiptStore.LatestVersion()) } if !latestSet { return 0, 0, 0, errNoHeightSource } - if !blockEarliestSet { - blockEarliest = 0 - } - if !stateEarliestSet { stateEarliest = blockEarliest } @@ -169,7 +159,7 @@ func (m *WatermarkManager) ResolveHeight(ctx context.Context, blockNrOrHash rpc. return 0, err } height := res.Block.Height - if err := m.ensureWithinWatermarks(height, stateEarliest, latest); err != nil { + if err := ensureWithinWatermarks(height, stateEarliest, latest); err != nil { return 0, err } return height, nil @@ -194,7 +184,7 @@ func (m *WatermarkManager) ResolveHeight(ctx context.Context, blockNrOrHash rpc. if heightPtr == nil { return latest, nil } - if err := m.ensureWithinWatermarks(*heightPtr, stateEarliest, latest); err != nil { + if err := ensureWithinWatermarks(*heightPtr, stateEarliest, latest); err != nil { return 0, err } return *heightPtr, nil @@ -207,14 +197,14 @@ func (m *WatermarkManager) EnsureBlockHeightAvailable(ctx context.Context, heigh if err != nil { return err } - return m.ensureWithinWatermarks(height, blockEarliest, latest) + return ensureWithinWatermarks(height, blockEarliest, latest) } // EnsureReceiptHeightAvailable verifies that receipts for the given block height // have not been pruned from the receipt store. This is a separate check from // EnsureBlockHeightAvailable because the receipt store can be configured with a // smaller KeepRecent than the block or state stores. -func (m *WatermarkManager) EnsureReceiptHeightAvailable(_ context.Context, height int64) error { +func (m *WatermarkManager) EnsureReceiptHeightAvailable(height int64) error { if m.receiptStore == nil { return nil } @@ -225,7 +215,7 @@ func (m *WatermarkManager) EnsureReceiptHeightAvailable(_ context.Context, heigh return nil } -func (m *WatermarkManager) ensureWithinWatermarks(height, earliest, latest int64) error { +func ensureWithinWatermarks(height, earliest, latest int64) error { if height > latest { return fmt.Errorf("requested height %d is not yet available; safe latest is %d: %w", height, latest, ErrBlockHeightNotYetAvailable) } diff --git a/evmrpc/watermark_manager_test.go b/evmrpc/watermark_manager_test.go index 0d6c3b6cf1..d2a87ba5f3 100644 --- a/evmrpc/watermark_manager_test.go +++ b/evmrpc/watermark_manager_test.go @@ -114,27 +114,27 @@ func TestEnsureReceiptHeightAvailable(t *testing.T) { t.Run("no receipt store allows any height", func(t *testing.T) { wm := NewWatermarkManager(tmClient, nil, nil, nil) - require.NoError(t, wm.EnsureReceiptHeightAvailable(context.Background(), 5)) + require.NoError(t, wm.EnsureReceiptHeightAvailable(5)) }) t.Run("receipt store with no pruning allows any height", func(t *testing.T) { rs := &fakeReceiptStore{latest: 200, earliest: 0} wm := NewWatermarkManager(tmClient, nil, nil, rs) - require.NoError(t, wm.EnsureReceiptHeightAvailable(context.Background(), 5)) + require.NoError(t, wm.EnsureReceiptHeightAvailable(5)) }) t.Run("pruned receipt height returns error", func(t *testing.T) { rs := &fakeReceiptStore{latest: 200, earliest: 150} wm := NewWatermarkManager(tmClient, nil, nil, rs) - require.ErrorContains(t, wm.EnsureReceiptHeightAvailable(context.Background(), 100), "receipts have been pruned") - require.ErrorContains(t, wm.EnsureReceiptHeightAvailable(context.Background(), 149), "receipts have been pruned") + require.ErrorContains(t, wm.EnsureReceiptHeightAvailable(100), "receipts have been pruned") + require.ErrorContains(t, wm.EnsureReceiptHeightAvailable(149), "receipts have been pruned") }) t.Run("height within receipt retention succeeds", func(t *testing.T) { rs := &fakeReceiptStore{latest: 200, earliest: 150} wm := NewWatermarkManager(tmClient, nil, nil, rs) - require.NoError(t, wm.EnsureReceiptHeightAvailable(context.Background(), 150)) - require.NoError(t, wm.EnsureReceiptHeightAvailable(context.Background(), 175)) + require.NoError(t, wm.EnsureReceiptHeightAvailable(150)) + require.NoError(t, wm.EnsureReceiptHeightAvailable(175)) }) } diff --git a/sei-tendermint/internal/statesync/reactor_test.go b/sei-tendermint/internal/statesync/reactor_test.go index f4b0a29a97..9a3e8d08fc 100644 --- a/sei-tendermint/internal/statesync/reactor_test.go +++ b/sei-tendermint/internal/statesync/reactor_test.go @@ -164,7 +164,7 @@ func TestReactor_Sync(t *testing.T) { appConn.applySnapshotChunk.Set(func(context.Context, *abci.RequestApplySnapshotChunk) (*abci.ResponseApplySnapshotChunk, error) { return &abci.ResponseApplySnapshotChunk{Result: abci.ResponseApplySnapshotChunk_ACCEPT}, nil }) - appConn.info.Push(mkHandler(struct{}{}, &abci.ResponseInfo{ + appConn.info.Push(mkConst(&abci.ResponseInfo{ AppVersion: testAppVersion, LastBlockHeight: snapshotHeight, LastBlockAppHash: chain[snapshotHeight+1].AppHash, diff --git a/sei-tendermint/internal/statesync/syncer_test.go b/sei-tendermint/internal/statesync/syncer_test.go index 3268800639..c9b488db09 100644 --- a/sei-tendermint/internal/statesync/syncer_test.go +++ b/sei-tendermint/internal/statesync/syncer_test.go @@ -206,7 +206,7 @@ func TestSyncer_SyncAny(t *testing.T) { &abci.RequestApplySnapshotChunk{Index: 2, Chunk: []byte{1, 1, 2}}, &abci.ResponseApplySnapshotChunk{Result: abci.ResponseApplySnapshotChunk_ACCEPT}, )) - app.info.Push(mkHandler(struct{}{}, &abci.ResponseInfo{ + app.info.Push(mkConst(&abci.ResponseInfo{ AppVersion: testAppVersion, LastBlockHeight: 1, LastBlockAppHash: []byte("app_hash"), diff --git a/sei-tendermint/internal/statesync/syncer_verifyapp_default_test.go b/sei-tendermint/internal/statesync/syncer_verifyapp_default_test.go index 7d4e557408..024b957c99 100644 --- a/sei-tendermint/internal/statesync/syncer_verifyapp_default_test.go +++ b/sei-tendermint/internal/statesync/syncer_verifyapp_default_test.go @@ -5,7 +5,6 @@ package statesync import ( - "context" "errors" "testing" @@ -14,37 +13,34 @@ import ( ) func TestSyncer_verifyApp(t *testing.T) { - boom := errors.New("boom") const appVersion = 9 appVersionMismatchErr := errors.New("app version mismatch. Expected: 9, got: 2") s := &snapshot{Height: 3, Format: 1, Chunks: 5, Hash: []byte{1, 2, 3}, trustedAppHash: []byte("app_hash")} testcases := map[string]struct { response *abci.ResponseInfo - err error expectErr error }{ "verified": {&abci.ResponseInfo{ LastBlockHeight: 3, LastBlockAppHash: []byte("app_hash"), AppVersion: appVersion, - }, nil, nil}, + }, nil}, "invalid app version": {&abci.ResponseInfo{ LastBlockHeight: 3, LastBlockAppHash: []byte("app_hash"), AppVersion: 2, - }, nil, appVersionMismatchErr}, + }, appVersionMismatchErr}, "invalid height": {&abci.ResponseInfo{ LastBlockHeight: 5, LastBlockAppHash: []byte("app_hash"), AppVersion: appVersion, - }, nil, errVerifyFailed}, + }, errVerifyFailed}, "invalid hash": {&abci.ResponseInfo{ LastBlockHeight: 3, LastBlockAppHash: []byte("xxx"), AppVersion: appVersion, - }, nil, errVerifyFailed}, - "error": {nil, boom, boom}, + }, errVerifyFailed}, } for name, tc := range testcases { @@ -54,9 +50,7 @@ func TestSyncer_verifyApp(t *testing.T) { rts := setup(t, nil, nil, true) app := rts.conn - app.info.Push(func(_ context.Context, _ struct{}) (*abci.ResponseInfo, error) { - return tc.response, tc.err - }) + app.info.Push(mkConst(tc.response)) err := rts.reactor.syncer.verifyApp(ctx, s, appVersion) unwrapped := errors.Unwrap(err) if unwrapped != nil { diff --git a/sei-tendermint/internal/statesync/test_app_test.go b/sei-tendermint/internal/statesync/test_app_test.go index c57bad00e9..05af71d006 100644 --- a/sei-tendermint/internal/statesync/test_app_test.go +++ b/sei-tendermint/internal/statesync/test_app_test.go @@ -2,7 +2,6 @@ package statesync import ( "context" - "log" "sync" "testing" @@ -14,6 +13,10 @@ import ( type Handler[V, R any] = func(context.Context, V) (R, error) +func mkConst[R any](r R) func() R { + return func() R { return r } +} + func mkHandler[V, R any](v V, r R) Handler[V, R] { return func(_ context.Context, got V) (R, error) { utils.OrPanic(utils.TestDiff(v, got)) @@ -21,15 +24,15 @@ func mkHandler[V, R any](v V, r R) Handler[V, R] { } } -type Queue[V, R any] struct { - Handlers []Handler[V, R] - Fallback Handler[V, R] +type Queue[T any] struct { + Handlers []T + Fallback T } -func (q *Queue[V, R]) Len() int { return len(q.Handlers) } -func (q *Queue[V, R]) Set(v Handler[V, R]) { q.Fallback = v } -func (q *Queue[V, R]) Push(v Handler[V, R]) { q.Handlers = append(q.Handlers, v) } -func (q *Queue[V, R]) Pop() Handler[V, R] { +func (q *Queue[T]) Len() int { return len(q.Handlers) } +func (q *Queue[T]) Set(v T) { q.Fallback = v } +func (q *Queue[T]) Push(v T) { q.Handlers = append(q.Handlers, v) } +func (q *Queue[T]) Pop() T { if len(q.Handlers) > 0 { res := q.Handlers[0] q.Handlers = q.Handlers[1:] @@ -42,11 +45,11 @@ type testStatesyncApp struct { *abci.BaseApplication mu sync.Mutex - offerSnapshot Queue[*abci.RequestOfferSnapshot, *abci.ResponseOfferSnapshot] - applySnapshotChunk Queue[*abci.RequestApplySnapshotChunk, *abci.ResponseApplySnapshotChunk] - listSnapshots Queue[*abci.RequestListSnapshots, *abci.ResponseListSnapshots] - loadSnapshotChunk Queue[*abci.RequestLoadSnapshotChunk, *abci.ResponseLoadSnapshotChunk] - info Queue[struct{}, *abci.ResponseInfo] + offerSnapshot Queue[Handler[*abci.RequestOfferSnapshot, *abci.ResponseOfferSnapshot]] + applySnapshotChunk Queue[Handler[*abci.RequestApplySnapshotChunk, *abci.ResponseApplySnapshotChunk]] + listSnapshots Queue[Handler[*abci.RequestListSnapshots, *abci.ResponseListSnapshots]] + loadSnapshotChunk Queue[Handler[*abci.RequestLoadSnapshotChunk, *abci.ResponseLoadSnapshotChunk]] + info Queue[func() *abci.ResponseInfo] } func newTestStatesyncApp() *testStatesyncApp { @@ -68,7 +71,6 @@ func (app *testStatesyncApp) ApplySnapshotChunk(ctx context.Context, req *abci.R } func (app *testStatesyncApp) ListSnapshots(ctx context.Context, req *abci.RequestListSnapshots) (*abci.ResponseListSnapshots, error) { - log.Printf("QQQQQQ ListSnapshots()\n") app.mu.Lock() h := app.listSnapshots.Pop() app.mu.Unlock() @@ -86,9 +88,7 @@ func (app *testStatesyncApp) Info() *abci.ResponseInfo { app.mu.Lock() h := app.info.Pop() app.mu.Unlock() - res, err := h(context.Background(), struct{}{}) - utils.OrPanic(err) - return res + return h() } func (app *testStatesyncApp) AssertExpectations(t testing.TB) { From ca5a7a89f2dd4e0500f70eac7d62b6bb3a88715a Mon Sep 17 00:00:00 2001 From: Grzegorz Prusak Date: Wed, 15 Jul 2026 14:31:05 +0200 Subject: [PATCH 44/54] snapshot: --- evmrpc/block.go | 38 +++++++++++++----------------- evmrpc/block_test.go | 14 +++++------ evmrpc/height_availability_test.go | 15 ++++++++---- 3 files changed, 35 insertions(+), 32 deletions(-) diff --git a/evmrpc/block.go b/evmrpc/block.go index 4a4321aa8b..18986c5f08 100644 --- a/evmrpc/block.go +++ b/evmrpc/block.go @@ -150,17 +150,13 @@ func NewSei2BlockAPI( return blockAPI } -func (a *SeiBlockAPI) GetBlockByNumberExcludeTraceFail(ctx context.Context, number rpc.BlockNumber, fullTx bool) (result map[string]interface{}, returnErr error) { - // Match eth_getBlockByNumber("0x0"): synthetic genesis, not the Tendermint block at height 0. - if number == 0 { - return encodeGenesisBlock(), nil - } +func (a *SeiBlockAPI) GetBlockByNumberExcludeTraceFail(ctx context.Context, number rpc.BlockNumber, fullTx bool) (result map[string]any, returnErr error) { // Exclude synthetic txs (filterTransactions drops them) and ante-failure // stub receipts (EncodeTmBlock drops them via excludeUntraceable). return a.getBlockByNumber(ctx, number, fullTx, false, true) } -func (a *SeiBlockAPI) GetBlockByHashExcludeTraceFail(ctx context.Context, blockHash common.Hash, fullTx bool) (result map[string]interface{}, returnErr error) { +func (a *SeiBlockAPI) GetBlockByHashExcludeTraceFail(ctx context.Context, blockHash common.Hash, fullTx bool) (result map[string]any, returnErr error) { // See note on GetBlockByNumberExcludeTraceFail. return a.getBlockByHash(ctx, blockHash, fullTx, false, true) } @@ -213,12 +209,12 @@ func (a *BlockAPI) GetBlockTransactionCountByHash(ctx context.Context, blockHash return a.getEvmTxCount(block), nil } -func (a *BlockAPI) GetBlockByHash(ctx context.Context, blockHash common.Hash, fullTx bool) (result map[string]interface{}, returnErr error) { +func (a *BlockAPI) GetBlockByHash(ctx context.Context, blockHash common.Hash, fullTx bool) (result map[string]any, returnErr error) { // used for both: eth_ and sei_ namespaces return a.getBlockByHash(ctx, blockHash, fullTx, a.includeShellReceipts, false) } -func (a *BlockAPI) getBlockByHash(ctx context.Context, blockHash common.Hash, fullTx bool, includeSyntheticTxs bool, excludeUntraceable bool) (result map[string]interface{}, returnErr error) { +func (a *BlockAPI) getBlockByHash(ctx context.Context, blockHash common.Hash, fullTx bool, includeSyntheticTxs bool, excludeUntraceable bool) (result map[string]any, returnErr error) { startTime := time.Now() defer func() { recordMetricsWithError(ctx, fmt.Sprintf("%s_getBlockByHash", a.namespace), a.connectionType, startTime, returnErr, recover()) @@ -250,15 +246,11 @@ func (a *BlockAPI) getBlockByHash(ctx context.Context, blockHash common.Hash, fu return EncodeTmBlock(a.ctxProvider, a.txConfigProvider, block, a.keeper, fullTx, a.includeBankTransfers, includeSyntheticTxs, excludeUntraceable, a.globalBlockCache, a.cacheCreationMutex) } -func (a *BlockAPI) GetBlockByNumber(ctx context.Context, number rpc.BlockNumber, fullTx bool) (result map[string]interface{}, returnErr error) { +func (a *BlockAPI) GetBlockByNumber(ctx context.Context, number rpc.BlockNumber, fullTx bool) (result map[string]any, returnErr error) { startTime := time.Now() defer func() { recordMetricsWithError(ctx, fmt.Sprintf("%s_getBlockByNumber", a.namespace), a.connectionType, startTime, returnErr, recover()) - }() - if number == 0 { - // for compatibility with the graph, always return genesis block - return encodeGenesisBlock(), nil - } + }() return a.getBlockByNumber(ctx, number, fullTx, a.includeShellReceipts, false) } @@ -268,11 +260,15 @@ func (a *BlockAPI) getBlockByNumber( fullTx bool, includeSyntheticTxs bool, excludeUntraceable bool, -) (result map[string]interface{}, returnErr error) { +) (result map[string]any, returnErr error) { numberPtr, err := getBlockNumber(ctx, a.tmClient, number) if err != nil { return nil, err } + // synthetic genesis block, not the Tendermint block at height 0. + if number == 0 || (numberPtr == nil && a.ctxProvider(LatestCtxHeight).BlockHeight() == 0) { + return encodeGenesisBlock(), nil + } // Validate EVM block height for pacific-1 chain if numberPtr != nil { @@ -293,7 +289,7 @@ func (a *BlockAPI) getBlockByNumber( return EncodeTmBlock(a.ctxProvider, a.txConfigProvider, block, a.keeper, fullTx, a.includeBankTransfers, includeSyntheticTxs, excludeUntraceable, a.globalBlockCache, a.cacheCreationMutex) } -func (a *BlockAPI) GetBlockReceipts(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (result []map[string]interface{}, returnErr error) { +func (a *BlockAPI) GetBlockReceipts(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (result []map[string]any, returnErr error) { startTime := time.Now() defer func() { recordMetricsWithError(ctx, fmt.Sprintf("%s_getBlockReceipts", a.namespace), a.connectionType, startTime, returnErr, recover()) @@ -344,7 +340,7 @@ func (a *BlockAPI) GetBlockReceipts(ctx context.Context, blockNrOrHash rpc.Block // cannot spawn an unbounded number of goroutines. errgroup.SetLimit blocks // Go() until a slot frees, bounding the number of live goroutines rather // than just the number doing concurrent work. - allReceipts := make([]map[string]interface{}, len(txHashes)) + allReceipts := make([]map[string]any, len(txHashes)) g, ctx := errgroup.WithContext(ctx) g.SetLimit(maxBlockReceiptsConcurrency) for i, hash := range txHashes { @@ -375,7 +371,7 @@ func (a *BlockAPI) GetBlockReceipts(ctx context.Context, blockNrOrHash rpc.Block if err := g.Wait(); err != nil { return nil, err } - compactReceipts := make([]map[string]interface{}, 0) + compactReceipts := make([]map[string]any, 0) for _, r := range allReceipts { if len(r) > 0 { compactReceipts = append(compactReceipts, r) @@ -409,7 +405,7 @@ func EncodeTmBlock( excludeUntraceable bool, globalBlockCache BlockCache, cacheCreationMutex *sync.Mutex, -) (map[string]interface{}, error) { +) (map[string]any, error) { number := big.NewInt(block.Block.Height) blockhash := common.HexToHash(block.BlockID.Hash.String()) blockTime := block.Block.Time @@ -427,7 +423,7 @@ func EncodeTmBlock( } var blockGasUsed int64 chainConfig := types.DefaultChainConfig().EthereumConfig(k.ChainID(ctx)) - transactions := []interface{}{} + transactions := []any{} latestCtx := ctxProvider(LatestCtxHeight) msgs := filterTransactions(k, ctxProvider, txConfigProvider, block, includeSyntheticTxs, includeBankTransfers, cacheCreationMutex, globalBlockCache) @@ -535,7 +531,7 @@ func EncodeTmBlock( if cp := ctx.ConsensusParams(); cp != nil && cp.Block != nil { gasLimit = cp.Block.MaxGas } - result := map[string]interface{}{ + result := map[string]any{ "number": (*hexutil.Big)(number), "hash": blockhash, "parentHash": lastHash, diff --git a/evmrpc/block_test.go b/evmrpc/block_test.go index 6c83626e5c..a52c8201d5 100644 --- a/evmrpc/block_test.go +++ b/evmrpc/block_test.go @@ -75,7 +75,7 @@ func TestEncodeBankMsg(t *testing.T) { } res, err := evmrpc.EncodeTmBlock(func(i int64) sdk.Context { return ctx }, func(i int64) client.TxConfig { return TxConfig }, &resBlock, k, true, false, false, false, evmrpc.NewBlockCache(3000), &sync.Mutex{}) require.Nil(t, err) - txs := res["transactions"].([]interface{}) + txs := res["transactions"].([]any) require.Equal(t, 0, len(txs)) } @@ -115,7 +115,7 @@ func TestEncodeWasmExecuteMsg(t *testing.T) { } res, err := evmrpc.EncodeTmBlock(func(i int64) sdk.Context { return ctx }, func(i int64) client.TxConfig { return TxConfig }, &resBlock, k, true, false, true, false, evmrpc.NewBlockCache(3000), &sync.Mutex{}) require.Nil(t, err) - txs := res["transactions"].([]interface{}) + txs := res["transactions"].([]any) require.Equal(t, 1, len(txs)) ti := uint64(0) bh := common.HexToHash(MockBlockID.Hash.String()) @@ -163,7 +163,7 @@ func TestEncodeBankTransferMsg(t *testing.T) { } res, err := evmrpc.EncodeTmBlock(func(i int64) sdk.Context { return ctx }, func(i int64) client.TxConfig { return TxConfig }, &resBlock, k, true, true, false, false, evmrpc.NewBlockCache(3000), &sync.Mutex{}) require.Nil(t, err) - txs := res["transactions"].([]interface{}) + txs := res["transactions"].([]any) require.Equal(t, 1, len(txs)) bh := common.HexToHash(MockBlockID.Hash.String()) to := common.Address(toSeiAddr) @@ -218,7 +218,7 @@ func TestEncodeWasmExecuteMsg_GasUsedFromReceipt(t *testing.T) { res, err := evmrpc.EncodeTmBlock(func(i int64) sdk.Context { return ctx }, func(i int64) client.TxConfig { return TxConfig }, &resBlock, k, true, false, true, false, evmrpc.NewBlockCache(3000), &sync.Mutex{}) require.Nil(t, err) require.Equal(t, hexutil.Uint64(54321), res["gasUsed"]) - txs := res["transactions"].([]interface{}) + txs := res["transactions"].([]any) require.Equal(t, 1, len(txs)) } @@ -255,7 +255,7 @@ func TestEncodeBankTransferMsg_NoReceiptGasUsedZero(t *testing.T) { res, err := evmrpc.EncodeTmBlock(func(i int64) sdk.Context { return ctx }, func(i int64) client.TxConfig { return TxConfig }, &resBlock, k, true, true, false, false, evmrpc.NewBlockCache(3000), &sync.Mutex{}) require.Nil(t, err) require.Equal(t, hexutil.Uint64(0), res["gasUsed"]) - txs := res["transactions"].([]interface{}) + txs := res["transactions"].([]any) require.Equal(t, 1, len(txs)) } @@ -332,7 +332,7 @@ func TestEncodeTmBlock_ExcludeUntraceable(t *testing.T) { false /*fullTx*/, false /*includeBankTransfers*/, false /*includeSyntheticTxs*/, true, /*excludeUntraceable*/ evmrpc.NewBlockCache(3000), &sync.Mutex{}) require.NoError(t, err) - txs := res["transactions"].([]interface{}) + txs := res["transactions"].([]any) require.Len(t, txs, 1, "expected only the revert to survive, got %v", txs) require.Equal(t, strings.ToLower(TestNonPanicTxHash), strings.ToLower(txs[0].(string))) @@ -342,7 +342,7 @@ func TestEncodeTmBlock_ExcludeUntraceable(t *testing.T) { false /*fullTx*/, false /*includeBankTransfers*/, false /*includeSyntheticTxs*/, false, /*excludeUntraceable*/ evmrpc.NewBlockCache(3000), &sync.Mutex{}) require.NoError(t, err) - txs = res["transactions"].([]interface{}) + txs = res["transactions"].([]any) require.Len(t, txs, 2, "expected revert + ante stub to flow through, got %v", txs) } diff --git a/evmrpc/height_availability_test.go b/evmrpc/height_availability_test.go index 163dcfac9c..bf1f24f891 100644 --- a/evmrpc/height_availability_test.go +++ b/evmrpc/height_availability_test.go @@ -14,7 +14,6 @@ import ( sdk "github.com/sei-protocol/sei-chain/sei-cosmos/types" "github.com/sei-protocol/sei-chain/sei-tendermint/libs/bytes" "github.com/sei-protocol/sei-chain/sei-tendermint/libs/utils" - "github.com/sei-protocol/sei-chain/sei-tendermint/rpc/client/mock" "github.com/sei-protocol/sei-chain/sei-tendermint/rpc/coretypes" tmtypes "github.com/sei-protocol/sei-chain/sei-tendermint/types" evmtypes "github.com/sei-protocol/sei-chain/x/evm/types" @@ -24,7 +23,7 @@ import ( const highBlockHashHex = "0xfeedfeedfeedfeedfeedfeedfeedfeedfeedfeedfeedfeedfeedfeedfeedfeed" type heightTestClient struct { - mock.Client + client.Client highHash bytes.HexBytes highBlock *coretypes.ResultBlock earliest int64 @@ -45,7 +44,6 @@ func (*heightTestClient) EvmProxy(common.Address) utils.Option[*url.URL] { func newHeightTestClient(highHeight, earliest, latest int64) *heightTestClient { return &heightTestClient{ - Client: mock.Client{}, highHash: bytes.HexBytes(mustDecodeHex(highBlockHashHex[2:])), highBlock: &coretypes.ResultBlock{ Block: &tmtypes.Block{ @@ -87,6 +85,14 @@ func (c *heightTestClient) Status(context.Context) (*coretypes.ResultStatus, err }, nil } +func (c *heightTestClient) Genesis(context.Context) (*coretypes.ResultGenesis, error) { + return &coretypes.ResultGenesis{ + Genesis: &tmtypes.GenesisDoc{ + InitialHeight: c.earliest, + }, + }, nil +} + // blockNotFoundTestClient returns ResultBlock{Block: nil} for a specific hash to simulate Tendermint "block not found". type blockNotFoundTestClient struct { *heightTestClient @@ -269,7 +275,8 @@ func TestGetBlockReceiptsGenesisByNumber(t *testing.T) { func TestGetBlockByNumberExcludeTraceFailGenesis(t *testing.T) { t.Parallel() - api := NewSeiBlockAPI(nil, nil, testCtxProvider, testTxConfigProvider, ConnectionTypeHTTP, nil, nil, nil) + client := newHeightTestClient(1, 1, 1) + api := NewSeiBlockAPI(client, nil, testCtxProvider, testTxConfigProvider, ConnectionTypeHTTP, nil, nil, nil) block, err := api.GetBlockByNumberExcludeTraceFail(context.Background(), 0, false) require.NoError(t, err) require.NotNil(t, block) From 8e76c4df463677f7f16bccd9ae9429c5036abc01 Mon Sep 17 00:00:00 2001 From: Grzegorz Prusak Date: Wed, 15 Jul 2026 14:41:34 +0200 Subject: [PATCH 45/54] mock is gone --- evmrpc/block_txcount_parity_test.go | 3 +- evmrpc/genesis_latest_state_test.go | 3 +- evmrpc/setup_test.go | 3 +- evmrpc/simulate_test.go | 3 +- evmrpc/tests/mock_client.go | 4 +- evmrpc/tx_test.go | 3 +- evmrpc/watermark_manager_test.go | 4 +- sei-cosmos/client/broadcast_test.go | 3 +- sei-cosmos/testutil/cli/tm_mocks.go | 5 +- sei-cosmos/x/gov/client/cli/tx_test.go | 11 +- sei-cosmos/x/gov/client/utils/query_test.go | 3 +- sei-tendermint/rpc/client/helpers_test.go | 81 --------- sei-tendermint/rpc/client/mock/client.go | 171 ------------------ sei-tendermint/rpc/client/mock/status.go | 72 -------- sei-tendermint/rpc/client/mock/status_test.go | 73 -------- 15 files changed, 18 insertions(+), 424 deletions(-) delete mode 100644 sei-tendermint/rpc/client/helpers_test.go delete mode 100644 sei-tendermint/rpc/client/mock/client.go delete mode 100644 sei-tendermint/rpc/client/mock/status.go delete mode 100644 sei-tendermint/rpc/client/mock/status_test.go diff --git a/evmrpc/block_txcount_parity_test.go b/evmrpc/block_txcount_parity_test.go index c09ef88ad6..6f669135dd 100644 --- a/evmrpc/block_txcount_parity_test.go +++ b/evmrpc/block_txcount_parity_test.go @@ -18,7 +18,6 @@ import ( sdk "github.com/sei-protocol/sei-chain/sei-cosmos/types" tmbytes "github.com/sei-protocol/sei-chain/sei-tendermint/libs/bytes" "github.com/sei-protocol/sei-chain/sei-tendermint/libs/utils" - tmmock "github.com/sei-protocol/sei-chain/sei-tendermint/rpc/client/mock" "github.com/sei-protocol/sei-chain/sei-tendermint/rpc/coretypes" tmtypes "github.com/sei-protocol/sei-chain/sei-tendermint/types" testkeeper "github.com/sei-protocol/sei-chain/testutil/keeper" @@ -29,7 +28,7 @@ const parityTestHeight int64 = 771 // Tendermint client stub for Block / BlockByHash / Status (count by number and by hash). type parityTxCountTMClient struct { - tmmock.Client + client.Client block *coretypes.ResultBlock } diff --git a/evmrpc/genesis_latest_state_test.go b/evmrpc/genesis_latest_state_test.go index ac89e7dabd..9147bd031e 100644 --- a/evmrpc/genesis_latest_state_test.go +++ b/evmrpc/genesis_latest_state_test.go @@ -16,7 +16,6 @@ import ( sdk "github.com/sei-protocol/sei-chain/sei-cosmos/types" "github.com/sei-protocol/sei-chain/sei-tendermint/libs/bytes" "github.com/sei-protocol/sei-chain/sei-tendermint/libs/utils" - "github.com/sei-protocol/sei-chain/sei-tendermint/rpc/client/mock" "github.com/sei-protocol/sei-chain/sei-tendermint/rpc/coretypes" tmtypes "github.com/sei-protocol/sei-chain/sei-tendermint/types" testkeeper "github.com/sei-protocol/sei-chain/testutil/keeper" @@ -26,7 +25,7 @@ import ( ) type freshChainClient struct { - mock.Client + client.Client } func (*freshChainClient) EvmNextPendingNonce(common.Address) uint64 { diff --git a/evmrpc/setup_test.go b/evmrpc/setup_test.go index 1f2c093a17..a442ed66c8 100644 --- a/evmrpc/setup_test.go +++ b/evmrpc/setup_test.go @@ -33,7 +33,6 @@ import ( "github.com/sei-protocol/sei-chain/sei-tendermint/libs/bytes" tmutils "github.com/sei-protocol/sei-chain/sei-tendermint/libs/utils" types2 "github.com/sei-protocol/sei-chain/sei-tendermint/proto/tendermint/types" - "github.com/sei-protocol/sei-chain/sei-tendermint/rpc/client/mock" "github.com/sei-protocol/sei-chain/sei-tendermint/rpc/coretypes" tmtypes "github.com/sei-protocol/sei-chain/sei-tendermint/types" "github.com/sei-protocol/sei-chain/sei-tendermint/version" @@ -139,7 +138,7 @@ var NewHeadsCalled = make(chan struct{}, 1) var NotifierForTest = evmrpc.NewBlockHeaderNotifier(16) type MockClient struct { - mock.Client + client.Client latestOverride int64 } diff --git a/evmrpc/simulate_test.go b/evmrpc/simulate_test.go index 45def61493..46b9e725ab 100644 --- a/evmrpc/simulate_test.go +++ b/evmrpc/simulate_test.go @@ -32,7 +32,6 @@ import ( "github.com/sei-protocol/sei-chain/sei-tendermint/libs/bytes" "github.com/sei-protocol/sei-chain/sei-tendermint/libs/utils" tenderminttypes "github.com/sei-protocol/sei-chain/sei-tendermint/proto/tendermint/types" - "github.com/sei-protocol/sei-chain/sei-tendermint/rpc/client/mock" "github.com/sei-protocol/sei-chain/sei-tendermint/rpc/coretypes" tmtypes "github.com/sei-protocol/sei-chain/sei-tendermint/types" testkeeper "github.com/sei-protocol/sei-chain/testutil/keeper" @@ -1000,7 +999,7 @@ func TestSimulationAPIRequestLimiter(t *testing.T) { // pin a specific Block.Header / BlockID combination without dragging in the // rest of the mock infrastructure. type fixedBlockClient struct { - mock.Client + client.Client block *coretypes.ResultBlock } diff --git a/evmrpc/tests/mock_client.go b/evmrpc/tests/mock_client.go index 91a1a67b6c..4fb2b29414 100644 --- a/evmrpc/tests/mock_client.go +++ b/evmrpc/tests/mock_client.go @@ -18,14 +18,14 @@ import ( tmbytes "github.com/sei-protocol/sei-chain/sei-tendermint/libs/bytes" "github.com/sei-protocol/sei-chain/sei-tendermint/libs/utils" tmproto "github.com/sei-protocol/sei-chain/sei-tendermint/proto/tendermint/types" - "github.com/sei-protocol/sei-chain/sei-tendermint/rpc/client/mock" + rpcclient "github.com/sei-protocol/sei-chain/sei-tendermint/rpc/client" "github.com/sei-protocol/sei-chain/sei-tendermint/rpc/coretypes" tmtypes "github.com/sei-protocol/sei-chain/sei-tendermint/types" seiutils "github.com/sei-protocol/sei-chain/utils" ) type MockClient struct { - mock.Client + rpcclient.Client blocks [][][]byte txResults [][]*abci.ExecTxResult consParamUpdates []*tmproto.ConsensusParams diff --git a/evmrpc/tx_test.go b/evmrpc/tx_test.go index 5670edaa0d..1e1800e12a 100644 --- a/evmrpc/tx_test.go +++ b/evmrpc/tx_test.go @@ -28,7 +28,6 @@ import ( "github.com/sei-protocol/sei-chain/sei-cosmos/crypto/keyring" sdk "github.com/sei-protocol/sei-chain/sei-cosmos/types" "github.com/sei-protocol/sei-chain/sei-tendermint/libs/utils" - "github.com/sei-protocol/sei-chain/sei-tendermint/rpc/client/mock" "github.com/sei-protocol/sei-chain/sei-tendermint/rpc/coretypes" tmtypes "github.com/sei-protocol/sei-chain/sei-tendermint/types" testkeeper "github.com/sei-protocol/sei-chain/testutil/keeper" @@ -297,7 +296,7 @@ func TestGetTransactionReceiptExcludeTraceFailLateReceipt(t *testing.T) { // lowLatestTMClient reports a fixed LatestBlockHeight via Status, regardless // of what blocks the receipt store contains. type lowLatestTMClient struct { - mock.Client + client.Client latest int64 } diff --git a/evmrpc/watermark_manager_test.go b/evmrpc/watermark_manager_test.go index d2a87ba5f3..0541cd780c 100644 --- a/evmrpc/watermark_manager_test.go +++ b/evmrpc/watermark_manager_test.go @@ -13,6 +13,7 @@ import ( "github.com/ethereum/go-ethereum/rpc" "github.com/stretchr/testify/require" + "github.com/sei-protocol/sei-chain/sei-cosmos/client" storetypes "github.com/sei-protocol/sei-chain/sei-cosmos/store/types" sdk "github.com/sei-protocol/sei-chain/sei-cosmos/types" "github.com/sei-protocol/sei-chain/sei-db/db_engine/types" @@ -21,7 +22,6 @@ import ( abci "github.com/sei-protocol/sei-chain/sei-tendermint/abci/types" "github.com/sei-protocol/sei-chain/sei-tendermint/libs/bytes" "github.com/sei-protocol/sei-chain/sei-tendermint/libs/utils" - "github.com/sei-protocol/sei-chain/sei-tendermint/rpc/client/mock" "github.com/sei-protocol/sei-chain/sei-tendermint/rpc/coretypes" tmtypes "github.com/sei-protocol/sei-chain/sei-tendermint/types" evmtypes "github.com/sei-protocol/sei-chain/x/evm/types" @@ -187,7 +187,7 @@ func TestStateWatermarksCanLagBlocks(t *testing.T) { } type fakeTMClient struct { - mock.Client + client.Client status *coretypes.ResultStatus statusErr error blockByHash *coretypes.ResultBlock diff --git a/sei-cosmos/client/broadcast_test.go b/sei-cosmos/client/broadcast_test.go index 917c5b882d..c0bc028c7d 100644 --- a/sei-cosmos/client/broadcast_test.go +++ b/sei-cosmos/client/broadcast_test.go @@ -6,7 +6,6 @@ import ( "fmt" "testing" - "github.com/sei-protocol/sei-chain/sei-tendermint/rpc/client/mock" ctypes "github.com/sei-protocol/sei-chain/sei-tendermint/rpc/coretypes" tmtypes "github.com/sei-protocol/sei-chain/sei-tendermint/types" "github.com/stretchr/testify/require" @@ -16,7 +15,7 @@ import ( ) type MockClient struct { - mock.Client + Client err error } diff --git a/sei-cosmos/testutil/cli/tm_mocks.go b/sei-cosmos/testutil/cli/tm_mocks.go index e3fcf2de43..d428ee20fc 100644 --- a/sei-cosmos/testutil/cli/tm_mocks.go +++ b/sei-cosmos/testutil/cli/tm_mocks.go @@ -6,7 +6,6 @@ import ( abci "github.com/sei-protocol/sei-chain/sei-tendermint/abci/types" tmbytes "github.com/sei-protocol/sei-chain/sei-tendermint/libs/bytes" rpcclient "github.com/sei-protocol/sei-chain/sei-tendermint/rpc/client" - rpcclientmock "github.com/sei-protocol/sei-chain/sei-tendermint/rpc/client/mock" "github.com/sei-protocol/sei-chain/sei-tendermint/rpc/coretypes" tmtypes "github.com/sei-protocol/sei-chain/sei-tendermint/types" @@ -16,14 +15,14 @@ import ( var _ client.TendermintRPC = (*MockTendermintRPC)(nil) type MockTendermintRPC struct { - rpcclientmock.Client + rpcclient.Client responseQuery abci.ResponseQuery } // NewMockTendermintRPC returns a mock TendermintRPC implementation. // It is used for CLI testing. -func NewMockTendermintRPC(respQuery abci.ResponseQuery, client rpcclientmock.Client) MockTendermintRPC { +func NewMockTendermintRPC(respQuery abci.ResponseQuery, client rpcclient.Client) MockTendermintRPC { return MockTendermintRPC{ Client: client, responseQuery: respQuery, diff --git a/sei-cosmos/x/gov/client/cli/tx_test.go b/sei-cosmos/x/gov/client/cli/tx_test.go index b6957e65cb..63a0beed3f 100644 --- a/sei-cosmos/x/gov/client/cli/tx_test.go +++ b/sei-cosmos/x/gov/client/cli/tx_test.go @@ -3,12 +3,12 @@ package cli_test import ( "bytes" "fmt" - "github.com/gogo/protobuf/proto" - abci "github.com/sei-protocol/sei-chain/sei-tendermint/abci/types" - rpcclientmock "github.com/sei-protocol/sei-chain/sei-tendermint/rpc/client/mock" "io" "testing" + "github.com/gogo/protobuf/proto" + abci "github.com/sei-protocol/sei-chain/sei-tendermint/abci/types" + "github.com/sei-protocol/sei-chain/sei-cosmos/client" "github.com/sei-protocol/sei-chain/sei-cosmos/client/flags" "github.com/sei-protocol/sei-chain/sei-cosmos/crypto/keyring" @@ -39,12 +39,11 @@ func TestCLITestSuite(t *testing.T) { func (s *CLITestSuite) SetupSuite() { s.encCfg = testutilmod.MakeTestEncodingConfig(gov.AppModuleBasic{}) s.kr = keyring.NewInMemory() - mockRPC := rpcclientmock.New() s.baseCtx = client.Context{}. WithKeyring(s.kr). WithTxConfig(s.encCfg.TxConfig). WithCodec(s.encCfg.Codec). - WithClient(clitestutil.MockTendermintRPC{Client: mockRPC}). + WithClient(clitestutil.MockTendermintRPC{}). WithAccountRetriever(client.MockAccountRetriever{}). WithOutput(io.Discard). WithChainID("test-chain") @@ -54,7 +53,7 @@ func (s *CLITestSuite) SetupSuite() { bz, _ := s.encCfg.Codec.Marshal(&sdk.TxResponse{}) c := clitestutil.NewMockTendermintRPC(abci.ResponseQuery{ Value: bz, - }, mockRPC) + }, nil) return s.baseCtx.WithClient(c) } s.clientCtx = ctxGen().WithOutput(&outBuf) diff --git a/sei-cosmos/x/gov/client/utils/query_test.go b/sei-cosmos/x/gov/client/utils/query_test.go index e72e23457b..765abce3e4 100644 --- a/sei-cosmos/x/gov/client/utils/query_test.go +++ b/sei-cosmos/x/gov/client/utils/query_test.go @@ -5,7 +5,6 @@ import ( "regexp" "testing" - "github.com/sei-protocol/sei-chain/sei-tendermint/rpc/client/mock" ctypes "github.com/sei-protocol/sei-chain/sei-tendermint/rpc/coretypes" tmtypes "github.com/sei-protocol/sei-chain/sei-tendermint/types" "github.com/stretchr/testify/require" @@ -20,7 +19,7 @@ import ( type TxSearchMock struct { txConfig client.TxConfig - mock.Client + client.Client txs []tmtypes.Tx } diff --git a/sei-tendermint/rpc/client/helpers_test.go b/sei-tendermint/rpc/client/helpers_test.go deleted file mode 100644 index f77753d5a8..0000000000 --- a/sei-tendermint/rpc/client/helpers_test.go +++ /dev/null @@ -1,81 +0,0 @@ -package client_test - -import ( - "errors" - "strings" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/sei-protocol/sei-chain/sei-tendermint/rpc/client" - "github.com/sei-protocol/sei-chain/sei-tendermint/rpc/client/mock" - "github.com/sei-protocol/sei-chain/sei-tendermint/rpc/coretypes" -) - -func TestWaitForHeight(t *testing.T) { - ctx := t.Context() - - // test with error result - immediate failure - m := &mock.StatusMock{ - Call: mock.Call{ - Error: errors.New("bye"), - }, - } - r := mock.NewStatusRecorder(m) - - // connection failure always leads to error - err := client.WaitForHeight(ctx, r, 8, nil) - require.Error(t, err) - require.Equal(t, "bye", err.Error()) - - // we called status once to check - require.Equal(t, 1, len(r.Calls)) - - // now set current block height to 10 - m.Call = mock.Call{ - Response: &coretypes.ResultStatus{SyncInfo: coretypes.SyncInfo{LatestBlockHeight: 10}}, - } - - // we will not wait for more than 10 blocks - err = client.WaitForHeight(ctx, r, 40, nil) - require.Error(t, err) - require.True(t, strings.Contains(err.Error(), "aborting")) - - // we called status once more to check - require.Equal(t, 2, len(r.Calls)) - - // waiting for the past returns immediately - err = client.WaitForHeight(ctx, r, 5, nil) - require.NoError(t, err) - - // we called status once more to check - require.Equal(t, 3, len(r.Calls)) - - // since we can't update in a background goroutine (test --race) - // we use the callback to update the status height - myWaiter := func(delta int64) error { - // update the height for the next call - m.Call.Response = &coretypes.ResultStatus{SyncInfo: coretypes.SyncInfo{LatestBlockHeight: 15}} - return client.DefaultWaitStrategy(delta) - } - - // we wait for a few blocks - err = client.WaitForHeight(ctx, r, 12, myWaiter) - require.NoError(t, err) - - // we called status once to check - require.Equal(t, 5, len(r.Calls)) - - pre := r.Calls[3] - require.Nil(t, pre.Error) - prer, ok := pre.Response.(*coretypes.ResultStatus) - require.True(t, ok) - assert.Equal(t, int64(10), prer.SyncInfo.LatestBlockHeight) - - post := r.Calls[4] - require.Nil(t, post.Error) - postr, ok := post.Response.(*coretypes.ResultStatus) - require.True(t, ok) - assert.Equal(t, int64(15), postr.SyncInfo.LatestBlockHeight) -} diff --git a/sei-tendermint/rpc/client/mock/client.go b/sei-tendermint/rpc/client/mock/client.go deleted file mode 100644 index 1f6f4249ac..0000000000 --- a/sei-tendermint/rpc/client/mock/client.go +++ /dev/null @@ -1,171 +0,0 @@ -package mock - -/* -package mock returns a Client implementation that -accepts various (mock) implementations of the various methods. - -This implementation is useful for using in tests, when you don't -need a real server, but want a high-level of control about -the server response you want to mock (eg. error handling), -or if you just want to record the calls to verify in your tests. - -For real clients, you probably want the "http" package. If you -want to directly call a tendermint node in process, you can use the -"local" package. -*/ - -import ( - "context" - "reflect" - - "github.com/sei-protocol/sei-chain/sei-tendermint/internal/rpc/core" - "github.com/sei-protocol/sei-chain/sei-tendermint/libs/bytes" - "github.com/sei-protocol/sei-chain/sei-tendermint/rpc/client" - "github.com/sei-protocol/sei-chain/sei-tendermint/rpc/coretypes" - "github.com/sei-protocol/sei-chain/sei-tendermint/types" -) - -// Client wraps arbitrary implementations of the various interfaces. -type Client struct { - client.Client - env *core.Environment -} - -func New() Client { - return Client{ - env: &core.Environment{}, - } -} - -var _ client.Client = Client{} - -// Call is used by recorders to save a call and response. -// It can also be used to configure mock responses. -type Call struct { - Name string - Args interface{} - Response interface{} - Error error -} - -// GetResponse will generate the apporiate response for us, when -// using the Call struct to configure a Mock handler. -// -// When configuring a response, if only one of Response or Error is -// set then that will always be returned. If both are set, then -// we return Response if the Args match the set args, Error otherwise. -func (c Call) GetResponse(args interface{}) (interface{}, error) { - // handle the case with no response - if c.Response == nil { - if c.Error == nil { - panic("Misconfigured call, you must set either Response or Error") - } - return nil, c.Error - } - // response without error - if c.Error == nil { - return c.Response, nil - } - // have both, we must check args.... - if reflect.DeepEqual(args, c.Args) { - return c.Response, nil - } - return nil, c.Error -} - -func (c Client) Status(ctx context.Context) (*coretypes.ResultStatus, error) { - return c.env.Status(ctx) -} - -func (c Client) LagStatus(ctx context.Context) (*coretypes.ResultLagStatus, error) { - return c.env.LagStatus(ctx) -} - -func (c Client) ABCIInfo(ctx context.Context) (*coretypes.ResultABCIInfo, error) { - return c.env.ABCIInfo(ctx) -} - -func (c Client) ABCIQuery(ctx context.Context, path string, data bytes.HexBytes) (*coretypes.ResultABCIQuery, error) { - return c.ABCIQueryWithOptions(ctx, path, data, client.DefaultABCIQueryOptions) -} - -func (c Client) ABCIQueryWithOptions( - ctx context.Context, - path string, - data bytes.HexBytes, - opts client.ABCIQueryOptions) (*coretypes.ResultABCIQuery, error) { - return c.env.ABCIQuery(ctx, &coretypes.RequestABCIQuery{ - Path: path, Data: data, Height: coretypes.Int64(opts.Height), Prove: opts.Prove, - }) -} - -func (c Client) BroadcastTxCommit(ctx context.Context, tx types.Tx) (*coretypes.ResultBroadcastTxCommit, error) { - return c.env.BroadcastTxCommit(ctx, &coretypes.RequestBroadcastTx{Tx: tx}) -} - -func (c Client) BroadcastTxAsync(ctx context.Context, tx types.Tx) (*coretypes.ResultBroadcastTx, error) { - return c.env.BroadcastTxAsync(ctx, &coretypes.RequestBroadcastTx{Tx: tx}) -} - -func (c Client) BroadcastTxSync(ctx context.Context, tx types.Tx) (*coretypes.ResultBroadcastTx, error) { - return c.env.BroadcastTxSync(ctx, &coretypes.RequestBroadcastTx{Tx: tx}) -} - -func (c Client) CheckTx(ctx context.Context, tx types.Tx) (*coretypes.ResultCheckTx, error) { - return c.env.CheckTx(ctx, &coretypes.RequestCheckTx{Tx: tx}) -} - -func (c Client) NetInfo(ctx context.Context) (*coretypes.ResultNetInfo, error) { - return c.env.NetInfo(ctx) -} - -func (c Client) ConsensusState(ctx context.Context) (*coretypes.ResultConsensusState, error) { - return c.env.GetConsensusState(ctx) -} - -func (c Client) DumpConsensusState(ctx context.Context) (*coretypes.ResultDumpConsensusState, error) { - return c.env.DumpConsensusState(ctx) -} - -func (c Client) ConsensusParams(ctx context.Context, height *int64) (*coretypes.ResultConsensusParams, error) { - return c.env.ConsensusParams(ctx, &coretypes.RequestConsensusParams{Height: (*coretypes.Int64)(height)}) -} - -func (c Client) Health(ctx context.Context) (*coretypes.ResultHealth, error) { - return c.env.Health(ctx) -} - -func (c Client) BlockchainInfo(ctx context.Context, minHeight, maxHeight int64) (*coretypes.ResultBlockchainInfo, error) { - return c.env.BlockchainInfo(ctx, &coretypes.RequestBlockchainInfo{ - MinHeight: coretypes.Int64(minHeight), - MaxHeight: coretypes.Int64(maxHeight), - }) -} - -func (c Client) Genesis(ctx context.Context) (*coretypes.ResultGenesis, error) { - return c.env.Genesis(ctx) -} - -func (c Client) Block(ctx context.Context, height *int64) (*coretypes.ResultBlock, error) { - return c.env.Block(ctx, &coretypes.RequestBlockInfo{Height: (*coretypes.Int64)(height)}) -} - -func (c Client) BlockByHash(ctx context.Context, hash bytes.HexBytes) (*coretypes.ResultBlock, error) { - return c.env.BlockByHash(ctx, &coretypes.RequestBlockByHash{Hash: hash}) -} - -func (c Client) Commit(ctx context.Context, height *int64) (*coretypes.ResultCommit, error) { - return c.env.Commit(ctx, &coretypes.RequestBlockInfo{Height: (*coretypes.Int64)(height)}) -} - -func (c Client) Validators(ctx context.Context, height *int64, page, perPage *int) (*coretypes.ResultValidators, error) { - return c.env.Validators(ctx, &coretypes.RequestValidators{ - Height: (*coretypes.Int64)(height), - Page: coretypes.Int64Ptr(page), - PerPage: coretypes.Int64Ptr(perPage), - }) -} - -func (c Client) BroadcastEvidence(ctx context.Context, ev types.Evidence) (*coretypes.ResultBroadcastEvidence, error) { - return c.env.BroadcastEvidence(ctx, &coretypes.RequestBroadcastEvidence{Evidence: ev}) -} diff --git a/sei-tendermint/rpc/client/mock/status.go b/sei-tendermint/rpc/client/mock/status.go deleted file mode 100644 index dbf468803f..0000000000 --- a/sei-tendermint/rpc/client/mock/status.go +++ /dev/null @@ -1,72 +0,0 @@ -package mock - -import ( - "context" - - "github.com/sei-protocol/sei-chain/sei-tendermint/rpc/client" - "github.com/sei-protocol/sei-chain/sei-tendermint/rpc/coretypes" -) - -// StatusMock returns the result specified by the Call -type StatusMock struct { - Call -} - -var ( - _ client.StatusClient = (*StatusMock)(nil) - _ client.StatusClient = (*StatusRecorder)(nil) -) - -func (m *StatusMock) Status(ctx context.Context) (*coretypes.ResultStatus, error) { - res, err := m.GetResponse(nil) - if err != nil { - return nil, err - } - return res.(*coretypes.ResultStatus), nil -} - -func (m *StatusMock) LagStatus(ctx context.Context) (*coretypes.ResultLagStatus, error) { - res, err := m.GetResponse(nil) - if err != nil { - return nil, err - } - return res.(*coretypes.ResultLagStatus), nil -} - -// StatusRecorder can wrap another type (StatusMock, full client) -// and record the status calls -type StatusRecorder struct { - Client client.StatusClient - Calls []Call -} - -func NewStatusRecorder(client client.StatusClient) *StatusRecorder { - return &StatusRecorder{ - Client: client, - Calls: []Call{}, - } -} - -func (r *StatusRecorder) addCall(call Call) { - r.Calls = append(r.Calls, call) -} - -func (r *StatusRecorder) Status(ctx context.Context) (*coretypes.ResultStatus, error) { - res, err := r.Client.Status(ctx) - r.addCall(Call{ - Name: "status", - Response: res, - Error: err, - }) - return res, err -} - -func (r *StatusRecorder) LagStatus(ctx context.Context) (*coretypes.ResultLagStatus, error) { - res, err := r.Client.LagStatus(ctx) - r.addCall(Call{ - Name: "lag_status", - Response: res, - Error: err, - }) - return res, err -} diff --git a/sei-tendermint/rpc/client/mock/status_test.go b/sei-tendermint/rpc/client/mock/status_test.go deleted file mode 100644 index 077bc2c7bf..0000000000 --- a/sei-tendermint/rpc/client/mock/status_test.go +++ /dev/null @@ -1,73 +0,0 @@ -package mock_test - -import ( - "testing" - "time" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/sei-protocol/sei-chain/sei-tendermint/libs/bytes" - "github.com/sei-protocol/sei-chain/sei-tendermint/rpc/client/mock" - "github.com/sei-protocol/sei-chain/sei-tendermint/rpc/coretypes" -) - -func TestStatus(t *testing.T) { - ctx := t.Context() - - m := &mock.StatusMock{ - Call: mock.Call{ - Response: &coretypes.ResultStatus{ - SyncInfo: coretypes.SyncInfo{ - LatestBlockHash: bytes.HexBytes("block"), - LatestAppHash: bytes.HexBytes("app"), - LatestBlockHeight: 10, - MaxPeerBlockHeight: 20, - TotalSyncedTime: time.Second, - RemainingTime: time.Minute, - TotalSnapshots: 10, - ChunkProcessAvgTime: time.Duration(10), - SnapshotHeight: 10, - SnapshotChunksCount: 9, - SnapshotChunksTotal: 10, - BackFilledBlocks: 9, - BackFillBlocksTotal: 10, - }, - }}, - } - - r := mock.NewStatusRecorder(m) - require.Equal(t, 0, len(r.Calls)) - - // make sure response works proper - status, err := r.Status(ctx) - require.NoError(t, err) - assert.EqualValues(t, "block", status.SyncInfo.LatestBlockHash) - assert.EqualValues(t, 10, status.SyncInfo.LatestBlockHeight) - assert.EqualValues(t, 20, status.SyncInfo.MaxPeerBlockHeight) - assert.EqualValues(t, time.Second, status.SyncInfo.TotalSyncedTime) - assert.EqualValues(t, time.Minute, status.SyncInfo.RemainingTime) - - // make sure recorder works properly - require.Equal(t, 1, len(r.Calls)) - rs := r.Calls[0] - assert.Equal(t, "status", rs.Name) - assert.Nil(t, rs.Args) - assert.Nil(t, rs.Error) - require.NotNil(t, rs.Response) - st, ok := rs.Response.(*coretypes.ResultStatus) - require.True(t, ok) - assert.EqualValues(t, "block", st.SyncInfo.LatestBlockHash) - assert.EqualValues(t, 10, st.SyncInfo.LatestBlockHeight) - assert.EqualValues(t, 20, st.SyncInfo.MaxPeerBlockHeight) - assert.EqualValues(t, time.Second, status.SyncInfo.TotalSyncedTime) - assert.EqualValues(t, time.Minute, status.SyncInfo.RemainingTime) - - assert.EqualValues(t, 10, st.SyncInfo.TotalSnapshots) - assert.EqualValues(t, time.Duration(10), st.SyncInfo.ChunkProcessAvgTime) - assert.EqualValues(t, 10, st.SyncInfo.SnapshotHeight) - assert.EqualValues(t, 9, status.SyncInfo.SnapshotChunksCount) - assert.EqualValues(t, 10, status.SyncInfo.SnapshotChunksTotal) - assert.EqualValues(t, 9, status.SyncInfo.BackFilledBlocks) - assert.EqualValues(t, 10, status.SyncInfo.BackFillBlocksTotal) -} From 3a948887dc8f2e71c0fb284f2aac7a5017e4abea Mon Sep 17 00:00:00 2001 From: Grzegorz Prusak Date: Wed, 15 Jul 2026 14:58:05 +0200 Subject: [PATCH 46/54] snapshot --- evmrpc/simulate_test.go | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/evmrpc/simulate_test.go b/evmrpc/simulate_test.go index 46b9e725ab..bb79ff9455 100644 --- a/evmrpc/simulate_test.go +++ b/evmrpc/simulate_test.go @@ -569,8 +569,6 @@ func TestSimulateBackendBlockResolutionCoverage(t *testing.T) { require.NotNil(t, h) require.Equal(t, int64(1), h.Number.Int64()) require.Equal(t, common.BytesToHash(MockBlockID.Hash), h.ParentHash) - expectedBaseFee := testApp.EvmKeeper.GetNextBaseFeePerGas(ctxProvider(evmrpc.LatestCtxHeight)).TruncateInt().BigInt() - require.Equal(t, 0, h.BaseFee.Cmp(expectedBaseFee)) }) t.Run("CurrentHeader_fallback_gas_limit_when_block_unavailable", func(t *testing.T) { @@ -582,9 +580,7 @@ func TestSimulateBackendBlockResolutionCoverage(t *testing.T) { h := b2.CurrentHeader() require.NotNil(t, h) require.Equal(t, int64(1), h.Number.Int64()) - require.Equal(t, uint64(baseCtx.ConsensusParams().Block.MaxGas), h.GasLimit) - expectedBaseFee := testApp.EvmKeeper.GetNextBaseFeePerGas(ctxProvider(evmrpc.LatestCtxHeight)).TruncateInt().BigInt() - require.Equal(t, 0, h.BaseFee.Cmp(expectedBaseFee)) + require.Equal(t, uint64(10_000_000), h.GasLimit) require.Equal(t, common.Hash{}, h.ParentHash) }) } @@ -666,7 +662,7 @@ func TestSimulationAPIRequestLimiter(t *testing.T) { t.Run("TestEthCallRateLimiting", func(t *testing.T) { tEnv := newTestEnv(t) // Test eth_call rate limiting with concurrent requests - const numRequests = 10 // Much more than the limit of 2 + numRequests := 10 // Much more than the limit of 2 runBurst := func() []error { results := make(chan error, numRequests) start := make(chan struct{}) @@ -692,6 +688,7 @@ func TestSimulationAPIRequestLimiter(t *testing.T) { return errors } + // Count successful vs rejected requests successCount := 0 rejectedCount := 0 for _, err := range runBurst() { @@ -704,9 +701,10 @@ func TestSimulationAPIRequestLimiter(t *testing.T) { } } - require.Equal(t, numRequests, successCount+rejectedCount, "All requests should be accounted for") - require.Greaterf(t, rejectedCount, 0, "Should have rejected requests due to rate limiting (burst: %d successful, %d rejected)", successCount, rejectedCount) + // With only 2 concurrent slots and 10 requests, we should have rejections + require.Greater(t, rejectedCount, 0, "Should have rejected requests due to rate limiting") require.Greater(t, successCount, 0, "Should have some successful requests") + require.Equal(t, numRequests, successCount+rejectedCount, "All requests should be accounted for") t.Logf("eth_call rate limiting: %d successful, %d rejected out of %d total", successCount, rejectedCount, numRequests) }) From a24c628d935179a928763d654157cd4c480694c5 Mon Sep 17 00:00:00 2001 From: Grzegorz Prusak Date: Wed, 15 Jul 2026 15:06:05 +0200 Subject: [PATCH 47/54] test fixes --- evmrpc/block.go | 4 ++-- evmrpc/simulate_test.go | 12 +++++------- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/evmrpc/block.go b/evmrpc/block.go index 18986c5f08..54cb2f2e78 100644 --- a/evmrpc/block.go +++ b/evmrpc/block.go @@ -150,7 +150,7 @@ func NewSei2BlockAPI( return blockAPI } -func (a *SeiBlockAPI) GetBlockByNumberExcludeTraceFail(ctx context.Context, number rpc.BlockNumber, fullTx bool) (result map[string]any, returnErr error) { +func (a *SeiBlockAPI) GetBlockByNumberExcludeTraceFail(ctx context.Context, number rpc.BlockNumber, fullTx bool) (result map[string]any, returnErr error) { // Exclude synthetic txs (filterTransactions drops them) and ante-failure // stub receipts (EncodeTmBlock drops them via excludeUntraceable). return a.getBlockByNumber(ctx, number, fullTx, false, true) @@ -250,7 +250,7 @@ func (a *BlockAPI) GetBlockByNumber(ctx context.Context, number rpc.BlockNumber, startTime := time.Now() defer func() { recordMetricsWithError(ctx, fmt.Sprintf("%s_getBlockByNumber", a.namespace), a.connectionType, startTime, returnErr, recover()) - }() + }() return a.getBlockByNumber(ctx, number, fullTx, a.includeShellReceipts, false) } diff --git a/evmrpc/simulate_test.go b/evmrpc/simulate_test.go index bb79ff9455..1d64eb507f 100644 --- a/evmrpc/simulate_test.go +++ b/evmrpc/simulate_test.go @@ -44,15 +44,8 @@ import ( func primeReceiptStore(t *testing.T, store receipt.ReceiptStore, latest int64) { t.Helper() - if store == nil { - return - } - if latest <= 0 { - latest = 1 - } require.NoError(t, store.SetLatestVersion(latest)) require.NoError(t, store.SetEarliestVersion(1)) - require.Equal(t, int64(1), store.EarliestVersion()) } // bcAlwaysFailClient fails every Block call (header resolution uses a single block fetch). @@ -433,6 +426,7 @@ func TestPreV620UpgradeUsesBaseFeeNil(t *testing.T) { EVMTimeout: time.Second * 30, } + primeReceiptStore(t, testApp.EvmKeeper.ReceiptStore(), 3000) tmClient := NewMockClientWithLatest(3000) watermarks := evmrpc.NewWatermarkManager(tmClient, ctxProvider, nil, testApp.EvmKeeper.ReceiptStore()) backend := evmrpc.NewBackend( @@ -504,6 +498,7 @@ func TestGasLimitUsesConsensusOrConfig(t *testing.T) { ctxProvider := func(h int64) sdk.Context { return baseCtx.WithBlockHeight(h) } cfg := &evmrpc.SimulateConfig{GasCap: 10_000_000, EVMTimeout: time.Second} + primeReceiptStore(t, testApp.EvmKeeper.ReceiptStore(), 1) tmClient := &MockClient{} watermarks := evmrpc.NewWatermarkManager(tmClient, ctxProvider, nil, testApp.EvmKeeper.ReceiptStore()) backend := evmrpc.NewBackend(ctxProvider, &testApp.EvmKeeper, @@ -528,6 +523,7 @@ func TestGasLimitFallbackToDefault(t *testing.T) { // Case 1: ConsensusParams is nil → DefaultBlockGasLimit. nilParamsCtx := testApp.GetContextForDeliverTx([]byte{}).WithBlockHeight(1).WithConsensusParams(nil) ctxProvider1 := func(h int64) sdk.Context { return nilParamsCtx.WithBlockHeight(h) } + primeReceiptStore(t, testApp.EvmKeeper.ReceiptStore(), 1) tmClient1 := &MockClient{} watermarks1 := evmrpc.NewWatermarkManager(tmClient1, ctxProvider1, nil, testApp.EvmKeeper.ReceiptStore()) backend1 := evmrpc.NewBackend(ctxProvider1, &testApp.EvmKeeper, legacyabci.BeginBlockKeepers{}, func(int64) client.TxConfig { return TxConfig }, tmClient1, cfg, testApp.BaseApp, testApp.TracerAnteHandler, evmrpc.NewBlockCache(3000), &sync.Mutex{}, watermarks1) @@ -1046,6 +1042,7 @@ func TestTraceBlockByNumberUsesCompatDecoderForHistoricalCosmosTx(t *testing.T) testApp := app.Setup(t, false, false, false) ctx := testApp.GetContextForDeliverTx([]byte{}).WithBlockHeight(v65Height).WithClosestUpgradeName("v6.5") testApp.UpgradeKeeper.SetDone(ctx, "v6.5") + primeReceiptStore(t, testApp.EvmKeeper.ReceiptStore(), v65Height) ctxProvider := func(height int64) sdk.Context { if height == evmrpc.LatestCtxHeight { return ctx @@ -1184,6 +1181,7 @@ func TestBlockByNumberNonTracedTxPassesTxBytes(t *testing.T) { return ctx.WithBlockHeight(height) } + primeReceiptStore(t, testApp.EvmKeeper.ReceiptStore(), blockHeight) tmClient := &fixedBlockClient{block: tmBlock} watermarks := evmrpc.NewWatermarkManager(tmClient, ctxProvider, nil, testApp.EvmKeeper.ReceiptStore()) backend := evmrpc.NewBackend( From 453ca4918c8c94395552636c91851829830ac404 Mon Sep 17 00:00:00 2001 From: Grzegorz Prusak Date: Wed, 15 Jul 2026 15:08:38 +0200 Subject: [PATCH 48/54] removed noop code --- sei-cosmos/baseapp/abci.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/sei-cosmos/baseapp/abci.go b/sei-cosmos/baseapp/abci.go index 411ef69421..1ab8896750 100644 --- a/sei-cosmos/baseapp/abci.go +++ b/sei-cosmos/baseapp/abci.go @@ -58,9 +58,6 @@ func (app *BaseApp) InitChain(req *abci.RequestInitChain) (*abci.ResponseInitCha app.setDeliverState(initHeader) app.setCheckState(initHeader) app.setProcessProposalState(initHeader) - app.deliverState.SetContext(app.deliverState.ctx) - app.checkState.SetContext(app.checkState.ctx) - app.processProposalState.SetContext(app.processProposalState.ctx) // Store the consensus params in the BaseApp's paramstore. Note, this must be // done after the deliver state and context have been set as it's persisted From 1f2688874caed009ade540a1d8772b0cd5e49ab2 Mon Sep 17 00:00:00 2001 From: Grzegorz Prusak Date: Wed, 15 Jul 2026 15:39:23 +0200 Subject: [PATCH 49/54] test fix --- sei-cosmos/x/genutil/gentx_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sei-cosmos/x/genutil/gentx_test.go b/sei-cosmos/x/genutil/gentx_test.go index 993c17b424..3dd0dc8308 100644 --- a/sei-cosmos/x/genutil/gentx_test.go +++ b/sei-cosmos/x/genutil/gentx_test.go @@ -260,7 +260,7 @@ func (suite *GenTxTestSuite) TestDeliverGenTxs() { tc.malleate() - ctx := suite.app.GetContextForDeliverTx([]byte{}) + ctx := suite.app.GetContextForDeliverTx([]byte{}).WithIsGenesis(true) if tc.expPass { suite.Require().NotPanics(func() { genutil.DeliverGenTxs( From 809f5a703411a3151a328c3d521175507d0a55c2 Mon Sep 17 00:00:00 2001 From: Grzegorz Prusak Date: Wed, 15 Jul 2026 17:14:35 +0200 Subject: [PATCH 50/54] better precision --- assets/SeiLogo.png | Bin 22464 -> 0 bytes .../rpc_tests/utils/gasPriceUtils.ts | 28 +++++++++--------- 2 files changed, 14 insertions(+), 14 deletions(-) delete mode 100644 assets/SeiLogo.png diff --git a/assets/SeiLogo.png b/assets/SeiLogo.png deleted file mode 100644 index 601706f264dff85d602d7b72076dc0f47fdb2b4e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 22464 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 P00000NkvXXu0mjfyzBl; diff --git a/integration_test/rpc_tests/utils/gasPriceUtils.ts b/integration_test/rpc_tests/utils/gasPriceUtils.ts index 8ae0039c93..f7beeeab39 100644 --- a/integration_test/rpc_tests/utils/gasPriceUtils.ts +++ b/integration_test/rpc_tests/utils/gasPriceUtils.ts @@ -39,23 +39,23 @@ export interface PriorityFeeSample { export async function maxPriorityFeePerGasAtStableBlock( provider: ethers.JsonRpcProvider, ): Promise { - for (let i = 0; i < 20; i++) { + for (let i = 0; i < 50; i++) { const b1 = await provider.getBlockNumber(); const tip = await maxPriorityFeePerGas(provider); - const info = await blockGasInfo(provider, 'latest'); - const evmGasUsed = await evmGasUsedForBlock(provider, info.number); const b2 = await provider.getBlockNumber(); - if (b1 === b2 && info.number === b1) { - return { - tip, - block: info.number, - gasUsed: info.gasUsed, - evmGasUsed, - gasLimit: info.gasLimit, - ratio: Number(info.gasUsed) / Number(info.gasLimit), - evmRatio: Number(evmGasUsed) / Number(info.gasLimit), - }; - } + if (b1 !== b2) continue; + + const info = await blockGasInfo(provider, b1); + const evmGasUsed = await evmGasUsedForBlock(provider, b1); + return { + tip, + block: info.number, + gasUsed: info.gasUsed, + evmGasUsed, + gasLimit: info.gasLimit, + ratio: Number(info.gasUsed) / Number(info.gasLimit), + evmRatio: Number(evmGasUsed) / Number(info.gasLimit), + }; } throw new Error('maxPriorityFeePerGasAtStableBlock: block kept advancing across the sample'); } From caabca47c6f93f5b4f2000a53607dcb468a18b42 Mon Sep 17 00:00:00 2001 From: Grzegorz Prusak Date: Thu, 16 Jul 2026 11:25:14 +0200 Subject: [PATCH 51/54] reverted logo --- assets/SeiLogo.png | Bin 0 -> 22464 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 assets/SeiLogo.png diff --git a/assets/SeiLogo.png b/assets/SeiLogo.png new file mode 100644 index 0000000000000000000000000000000000000000..601706f264dff85d602d7b72076dc0f47fdb2b4e GIT binary patch literal 22464 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 P00000NkvXXu0mjfyzBl; literal 0 HcmV?d00001 From 1993f432e70278adfa45f7da4d8d6fbc20541da1 Mon Sep 17 00:00:00 2001 From: Grzegorz Prusak Date: Thu, 16 Jul 2026 16:09:08 +0200 Subject: [PATCH 52/54] WIP --- evmrpc/watermark_manager.go | 52 ++++++++----------------------------- 1 file changed, 11 insertions(+), 41 deletions(-) diff --git a/evmrpc/watermark_manager.go b/evmrpc/watermark_manager.go index 756392b49f..fe58fd78d7 100644 --- a/evmrpc/watermark_manager.go +++ b/evmrpc/watermark_manager.go @@ -26,7 +26,7 @@ var ErrBlockHeightNotYetAvailable = errors.New("block height not yet available") type WatermarkManager struct { tmClient client.LocalClient ctxProvider func(int64) sdk.Context - stateStore types.StateStore + stateStore types.StateStore // nil if SS is disabled. receiptStore receipt.ReceiptStore } @@ -50,30 +50,14 @@ func NewWatermarkManager( func (m *WatermarkManager) Watermarks(ctx context.Context) (int64, int64, int64, error) { var ( latest int64 - latestSet bool blockEarliest int64 - blockEarliestSet bool stateEarliest int64 stateEarliestSet bool ) setLatest := func(candidate int64) { - if candidate < 0 { - return - } - if !latestSet || candidate < latest { - latest = candidate - latestSet = true - } - } - - setBlockEarliest := func(candidate int64) { - if candidate < 0 { - return - } - if !blockEarliestSet || candidate > blockEarliest { - blockEarliest = candidate - blockEarliestSet = true + if candidate >= 0 { + latest = min(latest,candidate) } } @@ -88,30 +72,26 @@ func (m *WatermarkManager) Watermarks(ctx context.Context) (int64, int64, int64, } // Tendermint heights govern both block availability and the latest safe height. - if tmLatest, tmEarliest, err := m.fetchTendermintWatermarks(ctx); err == nil { - setLatest(tmLatest) - setBlockEarliest(tmEarliest) - } else if !errors.Is(err, errNoHeightSource) { + tmLatest, tmEarliest, err := m.fetchTendermintWatermarks(ctx) + if err!=nil { return 0, 0, 0, err } + latest = tmLatest + blockEarliest = tmEarliest if m.ctxProvider != nil { setLatest(m.ctxProvider(LatestCtxHeight).BlockHeight()) } - // State store heights (historical state DB) may lag behind block pruning. - if ssLatest, ssEarliest, ok := m.fetchStateStoreWatermarks(); ok { - setLatest(ssLatest) - setStateEarliest(ssEarliest) - } - // Receipt store version participates only in the latest watermark. if m.receiptStore != nil { setLatest(m.receiptStore.LatestVersion()) } - if !latestSet { - return 0, 0, 0, errNoHeightSource + // State store heights (historical state DB) may lag behind block pruning. + if m.stateStore != nil { + setLatest(m.stateStore.GetLatestVersion()) + setStateEarliest(m.stateStore.GetEarliestVersion()) } if !stateEarliestSet { @@ -311,9 +291,6 @@ func blockByHashOrNullForJSONRPC( } func (m *WatermarkManager) fetchTendermintWatermarks(ctx context.Context) (int64, int64, error) { - if m.tmClient == nil { - return 0, 0, errNoHeightSource - } status, err := m.tmClient.Status(ctx) if err != nil { return 0, 0, err @@ -323,10 +300,3 @@ func (m *WatermarkManager) fetchTendermintWatermarks(ctx context.Context) (int64 earliest := status.SyncInfo.EarliestBlockHeight return latest, earliest, nil } - -func (m *WatermarkManager) fetchStateStoreWatermarks() (int64, int64, bool) { - if m.stateStore == nil { - return 0, 0, false - } - return m.stateStore.GetLatestVersion(), m.stateStore.GetEarliestVersion(), true -} From 3324da2badd863717d8648ec903c2ed819cf9dd7 Mon Sep 17 00:00:00 2001 From: Grzegorz Prusak Date: Thu, 16 Jul 2026 17:39:22 +0200 Subject: [PATCH 53/54] reduced watermarks logic --- evmrpc/block_txcount_parity_test.go | 2 +- evmrpc/height_availability_test.go | 23 ++++++--- evmrpc/historical_debug_trace_test.go | 2 +- evmrpc/simulate_test.go | 27 +++++++++-- evmrpc/tracers_semaphore_test.go | 2 +- evmrpc/tx_test.go | 2 +- evmrpc/watermark_manager.go | 55 ++++------------------ evmrpc/watermark_manager_test.go | 68 +++++++++++++++------------ 8 files changed, 93 insertions(+), 88 deletions(-) diff --git a/evmrpc/block_txcount_parity_test.go b/evmrpc/block_txcount_parity_test.go index 6f669135dd..53270ed83c 100644 --- a/evmrpc/block_txcount_parity_test.go +++ b/evmrpc/block_txcount_parity_test.go @@ -128,7 +128,7 @@ func TestBlockTransactionCountMatchesGetBlockByNumber(t *testing.T) { list := encoded["transactions"].([]interface{}) tm := &parityTxCountTMClient{block: block} - wm := evmrpc.NewWatermarkManager(tm, ctxProvider, nil, nil) + wm := evmrpc.NewWatermarkManager(tm, ctxProvider, nil, k.ReceiptStore()) api := evmrpc.NewBlockAPI(tm, k, ctxProvider, txConfigProvider, evmrpc.ConnectionTypeHTTP, wm, cache, mu) rpcCount, err := api.GetBlockTransactionCountByNumber(context.Background(), rpc.BlockNumber(parityTestHeight)) require.NoError(t, err) diff --git a/evmrpc/height_availability_test.go b/evmrpc/height_availability_test.go index bf1f24f891..eef63fa9b5 100644 --- a/evmrpc/height_availability_test.go +++ b/evmrpc/height_availability_test.go @@ -116,7 +116,16 @@ func mustDecodeHex(h string) []byte { func testTxConfigProvider(int64) client.TxConfig { return nil } -func testCtxProvider(h int64) sdk.Context { return sdk.Context{}.WithBlockHeight(h) } +func testCtxProvider(h int64) sdk.Context { + if h == LatestCtxHeight { + return sdk.Context{}.WithBlockHeight(1 << 60) + } + return sdk.Context{}.WithBlockHeight(h) +} + +func newHeightTestWatermarks(client client.LocalClient, latest int64) *WatermarkManager { + return NewWatermarkManager(client, testCtxProvider, nil, &fakeReceiptStore{latest: latest}) +} // GetBlockByHash for a block whose height sits above safe latest must return // JSON null per the Ethereum JSON-RPC spec (the block doesn't exist from the @@ -129,7 +138,7 @@ func TestBlockAPIAboveWatermarkReturnsNull(t *testing.T) { latest := int64(100) highHeight := latest + 5 client := newHeightTestClient(highHeight, earliest, latest) - watermarks := NewWatermarkManager(client, testCtxProvider, nil, nil) + watermarks := newHeightTestWatermarks(client, latest) api := NewBlockAPI(client, nil, testCtxProvider, testTxConfigProvider, ConnectionTypeHTTP, watermarks, nil, nil) result, err := api.GetBlockByHash(context.Background(), common.HexToHash(highBlockHashHex), false) @@ -150,7 +159,7 @@ func TestGetBlockByHashNotFoundReturnsNull(t *testing.T) { heightTestClient: base, notFoundHash: bytes.HexBytes(mustDecodeHex(notFoundHashHex[2:])), } - watermarks := NewWatermarkManager(client, testCtxProvider, nil, nil) + watermarks := newHeightTestWatermarks(client, latest) api := NewBlockAPI(client, nil, testCtxProvider, testTxConfigProvider, ConnectionTypeHTTP, watermarks, nil, nil) ctx := context.Background() @@ -178,7 +187,7 @@ func TestGetBlockReceiptsNotFoundReturnsNull(t *testing.T) { heightTestClient: base, notFoundHash: bytes.HexBytes(mustDecodeHex(notFoundHashHex[2:])), } - watermarks := NewWatermarkManager(client, testCtxProvider, nil, nil) + watermarks := newHeightTestWatermarks(client, latest) api := NewBlockAPI(client, nil, testCtxProvider, testTxConfigProvider, ConnectionTypeHTTP, watermarks, nil, nil) ctx := context.Background() @@ -208,7 +217,7 @@ func TestBlockAPILatestTagResolves(t *testing.T) { earliest := int64(1) latest := int64(100) client := newHeightTestClient(latest+5, earliest, latest) - watermarks := NewWatermarkManager(client, testCtxProvider, nil, nil) + watermarks := newHeightTestWatermarks(client, latest) api := NewBlockAPI(client, nil, testCtxProvider, testTxConfigProvider, ConnectionTypeHTTP, watermarks, nil, nil) ctx := context.Background() @@ -304,7 +313,7 @@ func TestLogFetcherSkipsUnavailableCachedBlock(t *testing.T) { latest := int64(90) highHeight := latest + 3 client := newHeightTestClient(highHeight, earliest, latest) - watermarks := NewWatermarkManager(client, testCtxProvider, nil, nil) + watermarks := newHeightTestWatermarks(client, latest) cache := NewBlockCache(2) cache.Add(highHeight, &BlockCacheEntry{ Block: client.highBlock, @@ -373,7 +382,7 @@ func TestStateAPIGetProofUnavailableHeight(t *testing.T) { latest := int64(80) highHeight := latest + 4 client := newHeightTestClient(highHeight, earliest, latest) - watermarks := NewWatermarkManager(client, testCtxProvider, nil, nil) + watermarks := newHeightTestWatermarks(client, latest) api := NewStateAPI(client, nil, testCtxProvider, ConnectionTypeHTTP, watermarks) blockParam := rpc.BlockNumberOrHashWithHash(common.HexToHash(highBlockHashHex), true) diff --git a/evmrpc/historical_debug_trace_test.go b/evmrpc/historical_debug_trace_test.go index 894b4e4660..a9a97d8271 100644 --- a/evmrpc/historical_debug_trace_test.go +++ b/evmrpc/historical_debug_trace_test.go @@ -103,7 +103,7 @@ func TestGuardHistoricalDebugTraceByHashUsesTendermintHeight(t *testing.T) { connectionType: ConnectionTypeHTTP, maxBlockLookback: 1, backend: &Backend{ - watermarks: NewWatermarkManager(tmClient, func(int64) sdk.Context { return latestCtx }, nil, nil), + watermarks: NewWatermarkManager(tmClient, func(int64) sdk.Context { return latestCtx }, nil, &fakeReceiptStore{latest: latestHeight}), }, } diff --git a/evmrpc/simulate_test.go b/evmrpc/simulate_test.go index 1d64eb507f..5c8440c3ff 100644 --- a/evmrpc/simulate_test.go +++ b/evmrpc/simulate_test.go @@ -418,6 +418,9 @@ func TestPreV620UpgradeUsesBaseFeeNil(t *testing.T) { // Create a backend with our test context provider ctxProvider := func(height int64) sdk.Context { + if height == evmrpc.LatestCtxHeight { + height = 3000 + } return testCtx.WithBlockHeight(height) } @@ -462,6 +465,9 @@ func TestPreV620UpgradeUsesBaseFeeNil(t *testing.T) { // Test with a different chain ID (not pacific-1) testCtxDifferentChain := testCtx.WithChainID("test-chain") ctxProviderDifferentChain := func(height int64) sdk.Context { + if height == evmrpc.LatestCtxHeight { + height = 3000 + } return testCtxDifferentChain.WithBlockHeight(height) } @@ -495,7 +501,12 @@ func TestGasLimitUsesConsensusOrConfig(t *testing.T) { baseCtx := testApp.GetContextForDeliverTx([]byte{}).WithBlockHeight(1). WithConsensusParams(&tenderminttypes.ConsensusParams{Block: &tenderminttypes.BlockParams{MaxGas: 200_000_000}}) - ctxProvider := func(h int64) sdk.Context { return baseCtx.WithBlockHeight(h) } + ctxProvider := func(h int64) sdk.Context { + if h == evmrpc.LatestCtxHeight { + h = 1 + } + return baseCtx.WithBlockHeight(h) + } cfg := &evmrpc.SimulateConfig{GasCap: 10_000_000, EVMTimeout: time.Second} primeReceiptStore(t, testApp.EvmKeeper.ReceiptStore(), 1) @@ -522,7 +533,12 @@ func TestGasLimitFallbackToDefault(t *testing.T) { // Case 1: ConsensusParams is nil → DefaultBlockGasLimit. nilParamsCtx := testApp.GetContextForDeliverTx([]byte{}).WithBlockHeight(1).WithConsensusParams(nil) - ctxProvider1 := func(h int64) sdk.Context { return nilParamsCtx.WithBlockHeight(h) } + ctxProvider1 := func(h int64) sdk.Context { + if h == evmrpc.LatestCtxHeight { + h = 1 + } + return nilParamsCtx.WithBlockHeight(h) + } primeReceiptStore(t, testApp.EvmKeeper.ReceiptStore(), 1) tmClient1 := &MockClient{} watermarks1 := evmrpc.NewWatermarkManager(tmClient1, ctxProvider1, nil, testApp.EvmKeeper.ReceiptStore()) @@ -533,7 +549,12 @@ func TestGasLimitFallbackToDefault(t *testing.T) { // Case 2: Block fails — resolution errors out entirely. baseCtx := testApp.GetContextForDeliverTx([]byte{}).WithBlockHeight(1) - ctxProvider2 := func(h int64) sdk.Context { return baseCtx.WithBlockHeight(h) } + ctxProvider2 := func(h int64) sdk.Context { + if h == evmrpc.LatestCtxHeight { + h = 1 + } + return baseCtx.WithBlockHeight(h) + } bcClient := &bcAlwaysFailClient{MockClient: &MockClient{}} watermarks2 := evmrpc.NewWatermarkManager(bcClient, ctxProvider2, nil, testApp.EvmKeeper.ReceiptStore()) backend2 := evmrpc.NewBackend(ctxProvider2, &testApp.EvmKeeper, legacyabci.BeginBlockKeepers{}, func(int64) client.TxConfig { return TxConfig }, bcClient, cfg, testApp.BaseApp, testApp.TracerAnteHandler, evmrpc.NewBlockCache(3000), &sync.Mutex{}, watermarks2) diff --git a/evmrpc/tracers_semaphore_test.go b/evmrpc/tracers_semaphore_test.go index 37bf079cd3..efd2db45fe 100644 --- a/evmrpc/tracers_semaphore_test.go +++ b/evmrpc/tracers_semaphore_test.go @@ -103,7 +103,7 @@ func TestHashBasedTraceEndpointsAcquireSemaphoreBeforeHashLookup(t *testing.T) { tmClient := &panicHashLookupClient{ heightTestClient: newHeightTestClient(8, 1, latestHeight), } - watermarks := NewWatermarkManager(tmClient, func(int64) sdk.Context { return latestCtx }, nil, nil) + watermarks := NewWatermarkManager(tmClient, func(int64) sdk.Context { return latestCtx }, nil, &fakeReceiptStore{latest: latestHeight}) api := &DebugAPI{ tmClient: tmClient, ctxProvider: func(int64) sdk.Context { return latestCtx }, diff --git a/evmrpc/tx_test.go b/evmrpc/tx_test.go index 1e1800e12a..b35105c2f5 100644 --- a/evmrpc/tx_test.go +++ b/evmrpc/tx_test.go @@ -334,7 +334,7 @@ func TestGetTransactionReceiptReturnsNullAboveWatermark(t *testing.T) { tmClient := &lowLatestTMClient{latest: MockHeight8} ctxProvider := func(int64) sdk.Context { return Ctx.WithBlockHeight(MockHeight8) } - watermarks := evmrpc.NewWatermarkManager(tmClient, ctxProvider, nil, nil) + watermarks := evmrpc.NewWatermarkManager(tmClient, ctxProvider, nil, EVMKeeper.ReceiptStore()) txAPI := evmrpc.NewTransactionAPI(tmClient, EVMKeeper, ctxProvider, nil, t.TempDir(), evmrpc.ConnectionTypeHTTP, utils.None[time.Duration](), watermarks, evmrpc.NewBlockCache(8), &sync.Mutex{}) result, err := txAPI.GetTransactionReceipt(context.Background(), hash) diff --git a/evmrpc/watermark_manager.go b/evmrpc/watermark_manager.go index fe58fd78d7..1a0c678259 100644 --- a/evmrpc/watermark_manager.go +++ b/evmrpc/watermark_manager.go @@ -48,56 +48,24 @@ func NewWatermarkManager( // latest height that are safe to serve. Earliest heights are inclusive. // It is possible that block latest < block earliest, in case there are no blocks yet. func (m *WatermarkManager) Watermarks(ctx context.Context) (int64, int64, int64, error) { - var ( - latest int64 - blockEarliest int64 - stateEarliest int64 - stateEarliestSet bool - ) - - setLatest := func(candidate int64) { - if candidate >= 0 { - latest = min(latest,candidate) - } - } - - setStateEarliest := func(candidate int64) { - if candidate < 0 { - return - } - if !stateEarliestSet || candidate > stateEarliest { - stateEarliest = candidate - stateEarliestSet = true - } - } - // Tendermint heights govern both block availability and the latest safe height. tmLatest, tmEarliest, err := m.fetchTendermintWatermarks(ctx) - if err!=nil { + if err != nil { return 0, 0, 0, err } - latest = tmLatest - blockEarliest = tmEarliest - - if m.ctxProvider != nil { - setLatest(m.ctxProvider(LatestCtxHeight).BlockHeight()) - } - - // Receipt store version participates only in the latest watermark. - if m.receiptStore != nil { - setLatest(m.receiptStore.LatestVersion()) - } + blockEarliest := tmEarliest + latest := min( + tmLatest, + m.ctxProvider(LatestCtxHeight).BlockHeight(), + m.receiptStore.LatestVersion(), + ) // State store heights (historical state DB) may lag behind block pruning. + stateEarliest := latest // no historical storage => just the current state. if m.stateStore != nil { - setLatest(m.stateStore.GetLatestVersion()) - setStateEarliest(m.stateStore.GetEarliestVersion()) + latest = min(latest, m.stateStore.GetLatestVersion()) + stateEarliest = m.stateStore.GetEarliestVersion() } - - if !stateEarliestSet { - stateEarliest = blockEarliest - } - return blockEarliest, stateEarliest, latest, nil } @@ -185,9 +153,6 @@ func (m *WatermarkManager) EnsureBlockHeightAvailable(ctx context.Context, heigh // EnsureBlockHeightAvailable because the receipt store can be configured with a // smaller KeepRecent than the block or state stores. func (m *WatermarkManager) EnsureReceiptHeightAvailable(height int64) error { - if m.receiptStore == nil { - return nil - } earliest := m.receiptStore.EarliestVersion() if height < earliest { return fmt.Errorf("requested height %d receipts have been pruned; earliest available is %d", height, earliest) diff --git a/evmrpc/watermark_manager_test.go b/evmrpc/watermark_manager_test.go index 0541cd780c..697761f7f9 100644 --- a/evmrpc/watermark_manager_test.go +++ b/evmrpc/watermark_manager_test.go @@ -33,8 +33,7 @@ func TestWatermarksAggregatesSources(t *testing.T) { status: &coretypes.ResultStatus{SyncInfo: coretypes.SyncInfo{LatestBlockHeight: 10, EarliestBlockHeight: 2}}, } stateStore := &fakeStateStore{latest: 8, earliest: 3} - receiptStore := &fakeReceiptStore{latest: 9} - wm := NewWatermarkManager(tmClient, nil, stateStore, receiptStore) + wm := newTestWatermarkManager(tmClient, 12, stateStore, 9) blockEarliest, stateEarliest, latest, err := wm.Watermarks(context.Background()) require.NoError(t, err) @@ -51,17 +50,17 @@ func TestWatermarksIncludesCtxProviderHeight(t *testing.T) { tmClient := &fakeTMClient{ status: &coretypes.ResultStatus{SyncInfo: coretypes.SyncInfo{LatestBlockHeight: 15, EarliestBlockHeight: 5}}, } - wm := NewWatermarkManager(tmClient, func(int64) sdk.Context { return ctx }, nil, nil) + wm := NewWatermarkManager(tmClient, func(int64) sdk.Context { return ctx }, nil, &fakeReceiptStore{latest: 14}) blockEarliest, stateEarliest, latest, err := wm.Watermarks(context.Background()) require.NoError(t, err) require.Equal(t, int64(5), blockEarliest) - require.Equal(t, int64(5), stateEarliest) + require.Equal(t, int64(12), stateEarliest) require.Equal(t, int64(12), latest) } -func TestWatermarksNoSources(t *testing.T) { - wm := NewWatermarkManager(nil, nil, nil, nil) +func TestWatermarksPropagatesHeightSourceError(t *testing.T) { + wm := NewWatermarkManager(&fakeTMClient{statusErr: errNoHeightSource}, watermarkTestCtxProvider(0), nil, &fakeReceiptStore{}) _, _, _, err := wm.Watermarks(context.Background()) require.ErrorIs(t, err, errNoHeightSource) } @@ -70,7 +69,8 @@ func TestResolveHeightGating(t *testing.T) { tmClient := &fakeTMClient{ status: &coretypes.ResultStatus{SyncInfo: coretypes.SyncInfo{LatestBlockHeight: 5, EarliestBlockHeight: 2}}, } - wm := NewWatermarkManager(tmClient, nil, nil, nil) + stateStore := &fakeStateStore{latest: 5, earliest: 2} + wm := newTestWatermarkManager(tmClient, 5, stateStore, 5) tooHigh := rpc.BlockNumber(6) _, err := wm.ResolveHeight(context.Background(), rpc.BlockNumberOrHash{BlockNumber: &tooHigh}) @@ -88,7 +88,8 @@ func TestResolveHeightByHash(t *testing.T) { status: &coretypes.ResultStatus{SyncInfo: coretypes.SyncInfo{LatestBlockHeight: 5, EarliestBlockHeight: 1}}, blockByHash: makeBlockResult(4), } - wm := NewWatermarkManager(tmClient, nil, nil, nil) + stateStore := &fakeStateStore{latest: 5, earliest: 1} + wm := newTestWatermarkManager(tmClient, 5, stateStore, 5) h := common.HexToHash("0x1") blockHeight, err := wm.ResolveHeight(context.Background(), rpc.BlockNumberOrHash{BlockHash: &h}) require.NoError(t, err) @@ -99,7 +100,7 @@ func TestEnsureBlockHeightAvailableBounds(t *testing.T) { tmClient := &fakeTMClient{ status: &coretypes.ResultStatus{SyncInfo: coretypes.SyncInfo{LatestBlockHeight: 6, EarliestBlockHeight: 3}}, } - wm := NewWatermarkManager(tmClient, nil, nil, nil) + wm := newTestWatermarkManager(tmClient, 6, nil, 6) require.NoError(t, wm.EnsureBlockHeightAvailable(context.Background(), 5)) @@ -112,27 +113,22 @@ func TestEnsureReceiptHeightAvailable(t *testing.T) { status: &coretypes.ResultStatus{SyncInfo: coretypes.SyncInfo{LatestBlockHeight: 200, EarliestBlockHeight: 1}}, } - t.Run("no receipt store allows any height", func(t *testing.T) { - wm := NewWatermarkManager(tmClient, nil, nil, nil) - require.NoError(t, wm.EnsureReceiptHeightAvailable(5)) - }) - t.Run("receipt store with no pruning allows any height", func(t *testing.T) { rs := &fakeReceiptStore{latest: 200, earliest: 0} - wm := NewWatermarkManager(tmClient, nil, nil, rs) + wm := NewWatermarkManager(tmClient, watermarkTestCtxProvider(200), nil, rs) require.NoError(t, wm.EnsureReceiptHeightAvailable(5)) }) t.Run("pruned receipt height returns error", func(t *testing.T) { rs := &fakeReceiptStore{latest: 200, earliest: 150} - wm := NewWatermarkManager(tmClient, nil, nil, rs) + wm := NewWatermarkManager(tmClient, watermarkTestCtxProvider(200), nil, rs) require.ErrorContains(t, wm.EnsureReceiptHeightAvailable(100), "receipts have been pruned") require.ErrorContains(t, wm.EnsureReceiptHeightAvailable(149), "receipts have been pruned") }) t.Run("height within receipt retention succeeds", func(t *testing.T) { rs := &fakeReceiptStore{latest: 200, earliest: 150} - wm := NewWatermarkManager(tmClient, nil, nil, rs) + wm := NewWatermarkManager(tmClient, watermarkTestCtxProvider(200), nil, rs) require.NoError(t, wm.EnsureReceiptHeightAvailable(150)) require.NoError(t, wm.EnsureReceiptHeightAvailable(175)) }) @@ -142,7 +138,8 @@ func TestLatestAndEarliestHeightHelpers(t *testing.T) { tmClient := &fakeTMClient{ status: &coretypes.ResultStatus{SyncInfo: coretypes.SyncInfo{LatestBlockHeight: 22, EarliestBlockHeight: 11}}, } - wm := NewWatermarkManager(tmClient, nil, nil, nil) + stateStore := &fakeStateStore{latest: 22, earliest: 11} + wm := newTestWatermarkManager(tmClient, 22, stateStore, 22) earliest, err := wm.EarliestHeight(context.Background()) require.NoError(t, err) require.Equal(t, int64(11), earliest) @@ -159,7 +156,7 @@ func TestResolveHeightUsesStateEarliest(t *testing.T) { status: &coretypes.ResultStatus{SyncInfo: coretypes.SyncInfo{LatestBlockHeight: 20, EarliestBlockHeight: 5}}, } stateStore := &fakeStateStore{latest: 18, earliest: 10} - wm := NewWatermarkManager(tmClient, nil, stateStore, nil) + wm := newTestWatermarkManager(tmClient, 20, stateStore, 20) belowState := rpc.BlockNumber(9) _, err := wm.ResolveHeight(context.Background(), rpc.BlockNumberOrHash{BlockNumber: &belowState}) @@ -177,7 +174,7 @@ func TestStateWatermarksCanLagBlocks(t *testing.T) { status: &coretypes.ResultStatus{SyncInfo: coretypes.SyncInfo{LatestBlockHeight: 30, EarliestBlockHeight: 12}}, } stateStore := &fakeStateStore{latest: 28, earliest: 15} - wm := NewWatermarkManager(tmClient, nil, stateStore, nil) + wm := newTestWatermarkManager(tmClient, 30, stateStore, 29) blockEarliest, stateEarliest, latest, err := wm.Watermarks(context.Background()) require.NoError(t, err) @@ -186,6 +183,18 @@ func TestStateWatermarksCanLagBlocks(t *testing.T) { require.Equal(t, int64(28), latest) } +func newTestWatermarkManager(tmClient client.LocalClient, ctxHeight int64, stateStore types.StateStore, receiptLatest int64) *WatermarkManager { + return NewWatermarkManager(tmClient, watermarkTestCtxProvider(ctxHeight), stateStore, &fakeReceiptStore{latest: receiptLatest}) +} + +func watermarkTestCtxProvider(height int64) func(int64) sdk.Context { + return func(int64) sdk.Context { + return sdk.Context{}. + WithBlockHeight(height). + WithMultiStore(&fakeMultiStore{latest: height}) + } +} + type fakeTMClient struct { client.Client status *coretypes.ResultStatus @@ -371,7 +380,7 @@ func TestBlockByNumberOrNullForJSONRPC(t *testing.T) { t.Run("above watermark returns (nil, nil)", func(t *testing.T) { c := &fakeTMClient{status: stat, blocksByHeight: map[int64]*coretypes.ResultBlock{150: makeBlockResult(150)}} - wm := NewWatermarkManager(c, nil, nil, nil) + wm := newTestWatermarkManager(c, 100, nil, 100) h := int64(150) // above latest=100 block, err := blockByNumberOrNullForJSONRPC(context.Background(), c, wm, &h, 0) require.NoError(t, err) @@ -380,7 +389,7 @@ func TestBlockByNumberOrNullForJSONRPC(t *testing.T) { t.Run("in-range height returns block", func(t *testing.T) { c := &fakeTMClient{status: stat, blocksByHeight: map[int64]*coretypes.ResultBlock{50: makeBlockResult(50)}} - wm := NewWatermarkManager(c, nil, nil, nil) + wm := newTestWatermarkManager(c, 100, nil, 100) h := int64(50) block, err := blockByNumberOrNullForJSONRPC(context.Background(), c, wm, &h, 0) require.NoError(t, err) @@ -389,11 +398,12 @@ func TestBlockByNumberOrNullForJSONRPC(t *testing.T) { }) t.Run("non-watermark error propagates", func(t *testing.T) { - // Watermark itself fails (no sources) — error is errNoHeightSource, + // Watermark itself fails (TM status error) — error is errNoHeightSource, // which must NOT be silently converted to null. - wm := NewWatermarkManager(nil, nil, nil, nil) + c := &fakeTMClient{statusErr: errNoHeightSource} + wm := newTestWatermarkManager(c, 100, nil, 100) h := int64(50) - _, err := blockByNumberOrNullForJSONRPC(context.Background(), nil, wm, &h, 0) + _, err := blockByNumberOrNullForJSONRPC(context.Background(), c, wm, &h, 0) require.Error(t, err) require.False(t, errors.Is(err, ErrBlockHeightNotYetAvailable)) }) @@ -408,7 +418,7 @@ func TestBlockByHashOrNullForJSONRPC(t *testing.T) { t.Run("above watermark returns (nil, nil)", func(t *testing.T) { c := &fakeTMClient{status: stat, blockByHash: makeBlockResult(150)} // height above latest=100 - wm := NewWatermarkManager(c, nil, nil, nil) + wm := newTestWatermarkManager(c, 100, nil, 100) block, err := blockByHashOrNullForJSONRPC(context.Background(), c, wm, []byte{0xaa}, 0) require.NoError(t, err) require.Nil(t, block) @@ -418,7 +428,7 @@ func TestBlockByHashOrNullForJSONRPC(t *testing.T) { // blockByHashWithRetry wraps Block:nil as ErrBlockNotFoundByHash; // the helper must catch that sentinel too. c := &fakeTMClient{status: stat, blockByHash: &coretypes.ResultBlock{Block: nil}} - wm := NewWatermarkManager(c, nil, nil, nil) + wm := newTestWatermarkManager(c, 100, nil, 100) block, err := blockByHashOrNullForJSONRPC(context.Background(), c, wm, []byte{0xbb}, 0) require.NoError(t, err) require.Nil(t, block) @@ -426,7 +436,7 @@ func TestBlockByHashOrNullForJSONRPC(t *testing.T) { t.Run("in-range hash returns block", func(t *testing.T) { c := &fakeTMClient{status: stat, blockByHash: makeBlockResult(50)} - wm := NewWatermarkManager(c, nil, nil, nil) + wm := newTestWatermarkManager(c, 100, nil, 100) block, err := blockByHashOrNullForJSONRPC(context.Background(), c, wm, []byte{0xcc}, 0) require.NoError(t, err) require.NotNil(t, block) @@ -437,7 +447,7 @@ func TestBlockByHashOrNullForJSONRPC(t *testing.T) { // A non-sentinel error from the TM client (e.g. RPC transport // failure) must NOT be silently swallowed into null. c := &fakeTMClient{status: stat, blockByHashErr: io.ErrUnexpectedEOF} - wm := NewWatermarkManager(c, nil, nil, nil) + wm := newTestWatermarkManager(c, 100, nil, 100) _, err := blockByHashOrNullForJSONRPC(context.Background(), c, wm, []byte{0xdd}, 0) require.ErrorIs(t, err, io.ErrUnexpectedEOF) }) From cdcacc2b0f39e0297ef02000b89f70ac25af60ec Mon Sep 17 00:00:00 2001 From: Grzegorz Prusak Date: Thu, 16 Jul 2026 18:39:50 +0200 Subject: [PATCH 54/54] test fix --- evmrpc/tests/utils.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/evmrpc/tests/utils.go b/evmrpc/tests/utils.go index 530f62114a..0b2bb478f8 100644 --- a/evmrpc/tests/utils.go +++ b/evmrpc/tests/utils.go @@ -161,11 +161,19 @@ func setupTestServer( ctxProvider, func(int64) client.TxConfig { return a.GetTxConfig() }, "", - nil, + a.GetStateStore(), ) if err != nil { panic(err) } + if stateStore := a.GetStateStore(); stateStore != nil { + latest := ctxProvider(evmrpc.LatestCtxHeight).BlockHeight() + if stateStore.GetLatestVersion() < latest { + if err := stateStore.SetLatestVersion(latest); err != nil { + panic(err) + } + } + } if store := a.EvmKeeper.ReceiptStore(); store != nil { latest := int64(math.MaxInt64) if err := store.SetLatestVersion(latest); err != nil {