Skip to content
Merged
11 changes: 11 additions & 0 deletions app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -2188,6 +2188,17 @@ 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)
}
Expand Down
9 changes: 9 additions & 0 deletions sei-cosmos/baseapp/test_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,15 @@ func (app *BaseApp) NewUncachedContext(isCheckTx bool, header tmproto.Header) sd
return sdk.NewContext(app.cms, header, isCheckTx)
}

// DeliverContext returns the current deliverState context, or nil if not in a deliver block.
// Useful for reading uncommitted state (e.g. after InitChain before Commit).
func (app *BaseApp) DeliverContext() *sdk.Context {
if app.deliverState == nil {
return nil
}
return &app.deliverState.ctx
}

func (app *BaseApp) GetContextForDeliverTx(txBytes []byte) sdk.Context {
return app.getContextForTx(runTxModeDeliver, txBytes)
}
3 changes: 3 additions & 0 deletions sei-tendermint/internal/mempool/testonly.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,8 @@ func TestConfig() *Config {
cfg := DefaultConfig()
cfg.CacheSize = 1000
cfg.DropUtilisationThreshold = 0.0
// Disable TTL purging in tests.
cfg.TTLNumBlocks = 0
cfg.TTLDuration = 0
return cfg
}
19 changes: 13 additions & 6 deletions sei-tendermint/internal/p2p/giga_router.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,12 +171,19 @@ func (r *GigaRouter) runExecute(ctx context.Context) error {
}
next := last + 1
if last == 0 {
if _, err := app.InitChain(ctx, r.cfg.GenDoc.ToRequestInitChain()); err != nil {
return fmt.Errorf("app.InitChain(): %w", err)
}
if _, err := app.Commit(ctx); err != nil {
return fmt.Errorf("app.Commit(): %w", err)
}
// The CometBFT handshaker already called InitChain when it saw
// appHeight==0 (see consensus/replay.go). We must NOT call it again —
// a second InitChain re-runs initChainer and corrupts state.
// Just set next to InitialHeight so the first FinalizeBlock uses the
// deliverState that the handshaker's InitChain set up.
//
// WARNING: This assumes the handshaker ran before GigaRouter.Run().
// State sync (--state-sync) skips the handshaker (node.go:358:
// shouldHandshake = !stateSync), so autobahn + state sync would
// reach here without InitChain ever being called, causing the first
// FinalizeBlock to fail on nil deliverState. If state sync support
// is added, runExecute must detect whether InitChain is needed
// (e.g. check DeliverContext != nil) and call it when missing.
var ok bool
next, ok = utils.SafeCast[atypes.GlobalBlockNumber](r.cfg.GenDoc.InitialHeight)
if !ok {
Expand Down
75 changes: 70 additions & 5 deletions sei-tendermint/internal/p2p/giga_router_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,11 @@ type testAppState struct {
Blocks []*abci.RequestFinalizeBlock
Txs map[shaHash]bool
AppHash shaHash
Committed bool
Comment thread
pompon0 marked this conversation as resolved.
// Committed tracks whether FinalizeBlock is allowed.
// Set to true by InitChain (so FinalizeBlock can follow without Commit,
// matching the CometBFT handshaker flow) and by Commit.
// Cleared by FinalizeBlock.
Committed bool
}

func testAppStateJSON(rng utils.Rng) json.RawMessage {
Expand Down Expand Up @@ -70,6 +74,13 @@ func (a *testApp) Info(_ context.Context, _ *abci.RequestInfo) (*abci.ResponseIn
if !ok {
return &abci.ResponseInfo{}, nil
}
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[:]),
Expand Down Expand Up @@ -102,7 +113,7 @@ func (a *testApp) InitChain(_ context.Context, req *abci.RequestInitChain) (*abc
state.Init = utils.Some(req)
state.AppHash = sha256.Sum256(req.AppStateBytes)
state.Validators = utils.Slice(val)
state.Committed = false
state.Committed = true
ctrl.Updated()
return &abci.ResponseInitChain{
AppHash: slices.Clone(state.AppHash[:]),
Expand All @@ -115,7 +126,7 @@ func (a *testApp) InitChain(_ context.Context, req *abci.RequestInitChain) (*abc
func (a *testApp) FinalizeBlock(_ context.Context, req *abci.RequestFinalizeBlock) (*abci.ResponseFinalizeBlock, error) {
for state, ctrl := range a.state.Lock() {
if !state.Committed {
return nil, fmt.Errorf("not committed")
return nil, fmt.Errorf("FinalizeBlock before Commit")
}
init, ok := state.Init.Get()
if !ok {
Expand Down Expand Up @@ -165,9 +176,8 @@ func (a *testApp) WaitForTx(ctx context.Context, tx []byte) error {
func (a *testApp) Snapshot() testAppState {
for state := range a.state.Lock() {
s := *state
// Txs is derived and the only mutable field.
// Txs is derived and Committed is not deterministic.
s.Txs = nil
// "Committed" field is not guaranteed to be consistent.
s.Committed = false
return s
}
Expand All @@ -187,6 +197,56 @@ func (c *testNodeCfg) GigaNodeAddr() GigaNodeAddr {
}
}

// TestInitChainCommitThenFinalize is a contract test for testApp: it verifies
// that testApp supports the autobahn block execution flow where the CometBFT
// handshaker calls InitChain (no Commit), then GigaRouter.runExecute() calls
// FinalizeBlock at InitialHeight using the deliverState set up by InitChain,
// followed by Commit.
func TestInitChainCommitThenFinalize(t *testing.T) {
rng := utils.TestRng()
app := newTestApp()
ctx := t.Context()

initialHeight := rng.Int63n(100000) + 1
appState := testAppStateJSON(rng)

// InitChain
_, err := app.InitChain(ctx, &abci.RequestInitChain{
InitialHeight: initialHeight,
AppStateBytes: appState,
})
require.NoError(t, err)

// No Commit after InitChain — the SDK expects FinalizeBlock at InitialHeight
// 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)
require.Equal(t, int64(0), info.LastBlockHeight,
"testApp should report 0 after InitChain with no committed blocks (matches real SDK)")

// FinalizeBlock should succeed — deliverState was set up by InitChain
blockHash := sha256.Sum256([]byte("test-block"))
_, err = app.FinalizeBlock(ctx, &abci.RequestFinalizeBlock{
Hash: blockHash[:],
Header: (&types.Header{
Height: initialHeight,
}).ToProto(),
})
require.NoError(t, err)

// Second Commit should succeed
_, err = app.Commit(ctx)
require.NoError(t, err)

// Verify height advanced
info, err = app.Info(ctx, &abci.RequestInfo{})
require.NoError(t, err)
require.Equal(t, initialHeight, info.LastBlockHeight,
"testApp should report InitialHeight after 1 block")
}

func TestGigaRouter_FinalizeBlocks(t *testing.T) {
const maxTxsPerBlock = 20
const blocksPerLane = 5
Expand Down Expand Up @@ -223,6 +283,11 @@ func TestGigaRouter_FinalizeBlocks(t *testing.T) {
nodeInfo.Network = genDoc.ChainID
e := Endpoint{AddrPort: cfg.addr}
app := newTestApp()
// Simulate CometBFT handshaker calling InitChain (see consensus/replay.go).
// In production, the handshaker always runs before GigaRouter.Run().
if _, err := app.InitChain(ctx, genDoc.ToRequestInitChain()); err != nil {
return fmt.Errorf("app.InitChain(): %w", err)
}
txMempool := mempool.NewTxMempool(mempool.TestConfig(), app, mempool.NopMetrics(), mempool.NopTxConstraintsFetcher)
router, err := NewRouter(
NopMetrics(),
Expand Down
Loading