-
Notifications
You must be signed in to change notification settings - Fork 886
fix(sei-tendermint): publish Autobahn block events (CON-352) #3998
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -244,13 +244,62 @@ | |
| if err != nil { | ||
| return nil, fmt.Errorf("app.Commit(): %w", err) | ||
| } | ||
| // Indexer-backed RPCs (broadcast_tx_commit, /tx) wait on these events. | ||
| r.publishExecutedBlockEvents(b, proposerAddress, resp) | ||
| if err := r.data.PushAppHash(ctx, b.GlobalNumber, resp.AppHash); err != nil { | ||
| return nil, fmt.Errorf("r.data.PushAppHash(%v): %w", b.GlobalNumber, err) | ||
| } | ||
| r.data.PushGasUsed(finalizeBlockGasUsed(resp)) | ||
| return commitResp, nil | ||
| } | ||
|
|
||
| // publishExecutedBlockEvents publishes NewBlock, NewBlockHeader, and per-tx | ||
| // events for a committed Autobahn block. | ||
| func (r *gigaRouterCommon) publishExecutedBlockEvents( | ||
| b *atypes.GlobalBlock, | ||
| proposer types.Address, | ||
| resp *abci.ResponseFinalizeBlock, | ||
| ) { | ||
| translated := r.translateGlobalBlock(b) | ||
| translated.Block.Header.ProposerAddress = proposer | ||
| block := translated.Block | ||
| blockID := translated.BlockID | ||
| if len(resp.TxResults) != len(block.Txs) { | ||
| panic(fmt.Sprintf("number of TXs (%d) and ABCI TX responses (%d) do not match", | ||
| len(block.Txs), len(resp.TxResults))) | ||
| } | ||
| eventBus := r.cfg.EventBus | ||
|
|
||
| if err := eventBus.PublishEventNewBlock(types.EventDataNewBlock{ | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [suggestion] The translated block's header has no |
||
| Block: block, | ||
| BlockID: blockID, | ||
| ResultFinalizeBlock: *resp, | ||
| }); err != nil { | ||
| logger.Error("failed publishing new block", "err", err) | ||
| } | ||
|
|
||
| if err := eventBus.PublishEventNewBlockHeader(types.EventDataNewBlockHeader{ | ||
| Header: block.Header, | ||
| NumTxs: int64(len(block.Txs)), | ||
| ResultFinalizeBlock: *resp, | ||
| }); err != nil { | ||
| logger.Error("failed publishing new block header", "err", err) | ||
| } | ||
|
|
||
| for i, tx := range block.Txs { | ||
| if err := eventBus.PublishEventTx(types.EventDataTx{ | ||
| TxResultV2: abci.TxResultV2{ | ||
| Height: block.Height, | ||
| Index: uint32(i), //nolint:gosec // i is bounded by block.Txs length which fits in uint32 | ||
| Tx: tx, | ||
| Result: *(resp.TxResults[i]), | ||
| }, | ||
| }); err != nil { | ||
| logger.Error("failed publishing event TX", "err", err) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // manages lifecycle of evmrpc connections to validators. | ||
| func (r *gigaRouterCommon) runEvmProxies(ctx context.Context) error { | ||
| return scope.Run(ctx, func(ctx context.Context, s scope.Scope) error { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,164 @@ | ||
| package p2p | ||
|
|
||
| import ( | ||
| "testing" | ||
| "time" | ||
|
|
||
| abci "github.com/sei-protocol/sei-chain/sei-tendermint/abci/types" | ||
| atypes "github.com/sei-protocol/sei-chain/sei-tendermint/autobahn/types" | ||
| "github.com/sei-protocol/sei-chain/sei-tendermint/internal/eventbus" | ||
| tmpubsub "github.com/sei-protocol/sei-chain/sei-tendermint/internal/pubsub" | ||
| tmquery "github.com/sei-protocol/sei-chain/sei-tendermint/internal/pubsub/query" | ||
| "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/types" | ||
| ) | ||
|
|
||
| func testGlobalBlock(t *testing.T, rng utils.Rng, height atypes.GlobalBlockNumber, txs [][]byte) *atypes.GlobalBlock { | ||
| t.Helper() | ||
| payload, err := atypes.PayloadBuilder{ | ||
| CreatedAt: time.Now(), | ||
| Txs: txs, | ||
| }.Build() | ||
| require.NoError(t, err) | ||
| block := atypes.NewBlock(atypes.GenLaneID(rng), 1, atypes.GenBlockHeaderHash(rng), payload) | ||
| return &atypes.GlobalBlock{ | ||
| Header: block.Header(), | ||
| Timestamp: time.Now(), | ||
| GlobalNumber: height, | ||
| Payload: payload, | ||
| } | ||
| } | ||
|
|
||
| func subscribe(t *testing.T, bus *eventbus.EventBus, clientID string, query *tmquery.Query, limit int) eventbus.Subscription { | ||
| t.Helper() | ||
| sub, err := bus.SubscribeWithArgs(t.Context(), tmpubsub.SubscribeArgs{ | ||
| ClientID: clientID, | ||
| Query: query, | ||
| Limit: limit, | ||
| }) | ||
| require.NoError(t, err) | ||
| return sub | ||
| } | ||
|
|
||
| func waitForN(t *testing.T, sub eventbus.Subscription, n int) { | ||
| t.Helper() | ||
| for range n { | ||
| _, err := sub.Next(t.Context()) | ||
| require.NoError(t, err) | ||
| } | ||
| } | ||
|
|
||
| func TestPublishExecutedBlockEventsIndexesTxs(t *testing.T) { | ||
| rng := utils.TestRng() | ||
|
|
||
| // Setup: EventBus, KV indexer, and subscriptions for header + tx events. | ||
| genDoc := &types.GenesisDoc{ChainID: "giga-index-test", InitialHeight: 1} | ||
| require.NoError(t, genDoc.ValidateAndComplete()) | ||
|
|
||
| bus := startedEventBus(t) | ||
| sink := startedTxIndexer(t, bus) | ||
| txSub := subscribe(t, bus, "txs", types.EventQueryTx, 8) | ||
| headerSub := subscribe(t, bus, "headers", types.EventQueryNewBlockHeader, 8) | ||
|
|
||
| // Setup: committed Autobahn block with two txs and matching FinalizeBlock results. | ||
| txs := [][]byte{[]byte("tx-a"), []byte("tx-b")} | ||
| height := atypes.GlobalBlockNumber(7) | ||
| gb := testGlobalBlock(t, rng, height, txs) | ||
| resp := &abci.ResponseFinalizeBlock{ | ||
| TxResults: []*abci.ExecTxResult{ | ||
| {Code: abci.CodeTypeOK, GasUsed: 11}, | ||
| {Code: abci.CodeTypeOK, GasUsed: 22}, | ||
| }, | ||
| } | ||
|
|
||
| // Test: publish NewBlock / NewBlockHeader / per-tx events as executeBlock does. | ||
| r := &gigaRouterCommon{cfg: &GigaRouterCommonConfig{GenDoc: genDoc, EventBus: bus}} | ||
| r.publishExecutedBlockEvents(gb, nil, resp) | ||
|
|
||
| // Validate: indexer has the block and each tx by hash, with height/index/gas. | ||
| waitForN(t, headerSub, 1) | ||
| waitForN(t, txSub, len(txs)) | ||
|
|
||
| ok, err := sink.HasBlock(int64(height)) | ||
| require.NoError(t, err) | ||
| require.True(t, ok) | ||
|
|
||
| for i, tx := range txs { | ||
| got, err := sink.GetTxByHash(types.Tx(tx).Hash().Bytes()) | ||
| require.NoError(t, err) | ||
| require.NotNil(t, got) | ||
| require.Equal(t, int64(height), got.Height) | ||
| require.Equal(t, uint32(i), got.Index) | ||
| require.Equal(t, tx, []byte(got.Tx)) | ||
| require.Equal(t, resp.TxResults[i].GasUsed, got.Result.GasUsed) | ||
| } | ||
| } | ||
|
|
||
| func TestPublishExecutedBlockEventsIndexesEmptyBlock(t *testing.T) { | ||
| rng := utils.TestRng() | ||
|
|
||
| // Setup: EventBus, KV indexer, and a header subscription. | ||
| genDoc := &types.GenesisDoc{ChainID: "giga-index-empty", InitialHeight: 1} | ||
| require.NoError(t, genDoc.ValidateAndComplete()) | ||
|
|
||
| bus := startedEventBus(t) | ||
| sink := startedTxIndexer(t, bus) | ||
| headerSub := subscribe(t, bus, "headers", types.EventQueryNewBlockHeader, 4) | ||
|
|
||
| // Test: publish events for a committed block with no txs. | ||
| height := atypes.GlobalBlockNumber(3) | ||
| gb := testGlobalBlock(t, rng, height, nil) | ||
| r := &gigaRouterCommon{cfg: &GigaRouterCommonConfig{GenDoc: genDoc, EventBus: bus}} | ||
| r.publishExecutedBlockEvents(gb, nil, &abci.ResponseFinalizeBlock{}) | ||
|
|
||
| // Validate: indexer recorded the empty block. | ||
| waitForN(t, headerSub, 1) | ||
|
|
||
| ok, err := sink.HasBlock(int64(height)) | ||
| require.NoError(t, err) | ||
| require.True(t, ok) | ||
| } | ||
|
|
||
| func TestPublishExecutedBlockEventsPanicsBeforeDispatchOnTxCountMismatch(t *testing.T) { | ||
| rng := utils.TestRng() | ||
|
|
||
| // Setup: counting EventBus and a one-tx block whose TxResults length does not match. | ||
| genDoc := &types.GenesisDoc{ChainID: "giga-index-mismatch", InitialHeight: 1} | ||
| require.NoError(t, genDoc.ValidateAndComplete()) | ||
|
|
||
| bus := &countingBlockEvents{} | ||
| gb := testGlobalBlock(t, rng, 1, [][]byte{[]byte("tx-a")}) | ||
| r := &gigaRouterCommon{cfg: &GigaRouterCommonConfig{GenDoc: genDoc, EventBus: bus}} | ||
|
|
||
| // Test: publish with empty TxResults. | ||
| require.Panics(t, func() { | ||
| r.publishExecutedBlockEvents(gb, nil, &abci.ResponseFinalizeBlock{}) | ||
| }) | ||
|
|
||
| // Validate: no EventBus methods ran before the panic. | ||
| require.Equal(t, 0, bus.n) | ||
| } | ||
|
|
||
| type countingBlockEvents struct{ n int } | ||
|
|
||
| func (c *countingBlockEvents) PublishEventNewBlock(types.EventDataNewBlock) error { | ||
| c.n++ | ||
| return nil | ||
| } | ||
| func (c *countingBlockEvents) PublishEventNewBlockHeader(types.EventDataNewBlockHeader) error { | ||
| c.n++ | ||
| return nil | ||
| } | ||
| func (c *countingBlockEvents) PublishEventNewEvidence(types.EventDataNewEvidence) error { | ||
| c.n++ | ||
| return nil | ||
| } | ||
| func (c *countingBlockEvents) PublishEventTx(types.EventDataTx) error { | ||
| c.n++ | ||
| return nil | ||
| } | ||
| func (c *countingBlockEvents) PublishEventValidatorSetUpdates(types.EventDataValidatorSetUpdates) error { | ||
| c.n++ | ||
| return nil | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing Autobahn event reindex path
Medium Severity
Autobahn now publishes indexer events only inside
executeBlockafterapp.Commit, but restart recovery advances from the app tip without re-publishing the last committed height. A crash after commit and before those events are indexed permanently skips that block for/txandbroadcast_tx_commit. CometBFT covers this with handshakereplayEvents; Autobahn skips the handshaker.Additional Locations (1)
sei-tendermint/internal/p2p/giga_router_common.go#L450-L485Reviewed by Cursor Bugbot for commit 1c31e68. Configure here.