package state_test import ( "fmt" "testing" "time" dbm "github.com/cometbft/cometbft-db" cmtcrypto "github.com/cometbft/cometbft/proto/tendermint/crypto" cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" sm "github.com/cometbft/cometbft/state" abci "github.com/cometbft/cometbft/abci/types" cmtstate "github.com/cometbft/cometbft/proto/tendermint/state" "github.com/stretchr/testify/require" ) // Compatibility test across different state proto versions func calcABCIResponsesKey(height int64) []byte { return []byte(fmt.Sprintf("abciResponsesKey:%v", height)) } var lastABCIResponseKey = []byte("lastABCIResponseKey") var ( _ sm.Store = (*MultiStore)(nil) _ LegacyStore = (*MultiStore)(nil) ) // MultiStore represents a state store that implements the Store interface // and contains additional store and database options. type MultiStore struct { sm.Store db dbm.DB sm.StoreOptions } // NewMultiStore returns a new MultiStore. // It sets the store, db, and StoreOptions fields of the MultiStore struct. func NewMultiStore(db dbm.DB, options sm.StoreOptions, store sm.Store) *MultiStore { return &MultiStore{ Store: store, db: db, StoreOptions: options, } } // LegacyStore represents a legacy data store. type LegacyStore interface { SaveABCIResponses(height int64, abciResponses *cmtstate.LegacyABCIResponses) error } // SaveABCIResponses saves the ABCIResponses for a given height in the MultiStore. // It strips out any nil values from the DeliverTxs field, and saves the ABCIResponses to // disk if the DiscardABCIResponses flag is set to false. It also saves the last ABCI response // for crash recovery, overwriting the previously saved response. func (multi MultiStore) SaveABCIResponses(height int64, abciResponses *cmtstate.LegacyABCIResponses) error { var dtxs []*abci.ExecTxResult // strip nil values, for _, tx := range abciResponses.DeliverTxs { if tx != nil { dtxs = append(dtxs, tx) } } abciResponses.DeliverTxs = dtxs // If the flag is false then we save the ABCIResponse. This can be used for the /block_results // query or to reindex an event using the command line. if !multi.DiscardABCIResponses { bz, err := abciResponses.Marshal() if err != nil { return err } if err := multi.db.Set(calcABCIResponsesKey(height), bz); err != nil { return err } } // We always save the last ABCI response for crash recovery. // This overwrites the previous saved ABCI Response. response := &cmtstate.ABCIResponsesInfo{ LegacyAbciResponses: abciResponses, Height: height, } bz, err := response.Marshal() if err != nil { return err } return multi.db.SetSync(lastABCIResponseKey, bz) } // TestLegacySaveAndLoadFinalizeBlock tests saving and loading of ABCIResponses // using the multiStore. It verifies that the loaded ABCIResponses match the // original ones and that missing fields are correctly handled. // This test is important for the LoadFinalizeBlockResponse method in the state store. func TestLegacySaveAndLoadFinalizeBlock(t *testing.T) { tearDown, stateDB, _, store := setupTestCaseWithStore(t) defer tearDown(t) options := sm.StoreOptions{ DiscardABCIResponses: false, } height := int64(1) multiStore := NewMultiStore(stateDB, options, store) // try with a complete ABCI Response legacyABCIResponses := newLegacyABCIResponses() err := multiStore.SaveABCIResponses(height, &legacyABCIResponses) require.NoError(t, err) require.Equal(t, 1, len(legacyABCIResponses.DeliverTxs)) require.Equal(t, 1, len(legacyABCIResponses.BeginBlock.Events)) require.Equal(t, 1, len(legacyABCIResponses.EndBlock.Events)) responseFinalizeBlock, err := multiStore.LoadFinalizeBlockResponse(height) require.NoError(t, err) // Test for not nil require.NotNil(t, responseFinalizeBlock.TxResults) require.NotNil(t, responseFinalizeBlock.Events) require.NotNil(t, responseFinalizeBlock.ValidatorUpdates) require.NotNil(t, responseFinalizeBlock.ConsensusParamUpdates) require.Nil(t, responseFinalizeBlock.AppHash) // Test for equality require.Equal(t, 1, len(responseFinalizeBlock.TxResults)) require.Equal(t, len(legacyABCIResponses.DeliverTxs), len(responseFinalizeBlock.TxResults)) require.Equal(t, legacyABCIResponses.DeliverTxs[0].Code, responseFinalizeBlock.TxResults[0].Code) require.Equal(t, legacyABCIResponses.DeliverTxs[0].Data, responseFinalizeBlock.TxResults[0].Data) require.Equal(t, legacyABCIResponses.DeliverTxs[0].Log, responseFinalizeBlock.TxResults[0].Log) require.Equal(t, legacyABCIResponses.DeliverTxs[0].GasWanted, responseFinalizeBlock.TxResults[0].GasWanted) require.Equal(t, legacyABCIResponses.DeliverTxs[0].GasUsed, responseFinalizeBlock.TxResults[0].GasUsed) require.Equal(t, len(legacyABCIResponses.DeliverTxs[0].Events), len(responseFinalizeBlock.TxResults[0].Events)) require.Equal(t, legacyABCIResponses.DeliverTxs[0].Events[0].Type, responseFinalizeBlock.TxResults[0].Events[0].Type) require.Equal(t, len(legacyABCIResponses.DeliverTxs[0].Events[0].Attributes), len(responseFinalizeBlock.TxResults[0].Events[0].Attributes)) require.Equal(t, legacyABCIResponses.DeliverTxs[0].Events[0].Attributes[0].Key, responseFinalizeBlock.TxResults[0].Events[0].Attributes[0].Key) require.Equal(t, legacyABCIResponses.DeliverTxs[0].Events[0].Attributes[0].Value, responseFinalizeBlock.TxResults[0].Events[0].Attributes[0].Value) require.Equal(t, legacyABCIResponses.DeliverTxs[0].Codespace, responseFinalizeBlock.TxResults[0].Codespace) require.Equal(t, 2, len(responseFinalizeBlock.Events)) require.Equal(t, len(legacyABCIResponses.BeginBlock.Events)+len(legacyABCIResponses.EndBlock.Events), len(responseFinalizeBlock.Events)) require.Equal(t, legacyABCIResponses.BeginBlock.Events[0].Type, responseFinalizeBlock.Events[0].Type) require.Equal(t, len(legacyABCIResponses.BeginBlock.Events[0].Attributes)+1, len(responseFinalizeBlock.Events[0].Attributes)) // +1 for inject 'mode' attribute require.Equal(t, legacyABCIResponses.BeginBlock.Events[0].Attributes[0].Key, responseFinalizeBlock.Events[0].Attributes[0].Key) require.Equal(t, legacyABCIResponses.BeginBlock.Events[0].Attributes[0].Value, responseFinalizeBlock.Events[0].Attributes[0].Value) require.Equal(t, legacyABCIResponses.EndBlock.ConsensusParamUpdates.Block.MaxBytes, responseFinalizeBlock.ConsensusParamUpdates.Block.MaxBytes) require.Equal(t, legacyABCIResponses.EndBlock.ConsensusParamUpdates.Block.MaxGas, responseFinalizeBlock.ConsensusParamUpdates.Block.MaxGas) require.Equal(t, legacyABCIResponses.EndBlock.ConsensusParamUpdates.Evidence.MaxAgeNumBlocks, responseFinalizeBlock.ConsensusParamUpdates.Evidence.MaxAgeNumBlocks) require.Equal(t, legacyABCIResponses.EndBlock.ConsensusParamUpdates.Evidence.MaxAgeDuration, responseFinalizeBlock.ConsensusParamUpdates.Evidence.MaxAgeDuration) require.Equal(t, legacyABCIResponses.EndBlock.ConsensusParamUpdates.Evidence.MaxBytes, responseFinalizeBlock.ConsensusParamUpdates.Evidence.MaxBytes) require.Equal(t, legacyABCIResponses.EndBlock.ConsensusParamUpdates.Validator.PubKeyTypes, responseFinalizeBlock.ConsensusParamUpdates.Validator.PubKeyTypes) require.Equal(t, legacyABCIResponses.EndBlock.ConsensusParamUpdates.Version.App, responseFinalizeBlock.ConsensusParamUpdates.Version.App) require.Nil(t, responseFinalizeBlock.ConsensusParamUpdates.Abci) require.Nil(t, responseFinalizeBlock.AppHash) require.Equal(t, len(legacyABCIResponses.EndBlock.ValidatorUpdates), len(responseFinalizeBlock.ValidatorUpdates)) require.Equal(t, legacyABCIResponses.EndBlock.ValidatorUpdates[0].Power, responseFinalizeBlock.ValidatorUpdates[0].Power) // skip until an equivalency test is possible require.Equal(t, legacyABCIResponses.EndBlock.ValidatorUpdates[0].PubKey.GetEd25519(), responseFinalizeBlock.ValidatorUpdates[0].PubKey.GetEd25519()) // try with an ABCI Response missing fields height = int64(2) legacyABCIResponses = newLegacyABCIResponsesWithNullFields() require.Equal(t, 1, len(legacyABCIResponses.DeliverTxs)) require.Equal(t, 1, len(legacyABCIResponses.BeginBlock.Events)) require.Nil(t, legacyABCIResponses.EndBlock) err = multiStore.SaveABCIResponses(height, &legacyABCIResponses) require.NoError(t, err) responseFinalizeBlock, err = multiStore.LoadFinalizeBlockResponse(height) require.NoError(t, err) require.Equal(t, len(legacyABCIResponses.DeliverTxs), len(responseFinalizeBlock.TxResults)) require.Equal(t, legacyABCIResponses.DeliverTxs[0].String(), responseFinalizeBlock.TxResults[0].String()) require.Equal(t, len(legacyABCIResponses.BeginBlock.Events), len(responseFinalizeBlock.Events)) } // Generate a Legacy ABCIResponses with data for all fields. func newLegacyABCIResponses() cmtstate.LegacyABCIResponses { eventAttr := abci.EventAttribute{ Key: "key", Value: "value", } deliverTxEvent := abci.Event{ Type: "deliver_tx_event", Attributes: []abci.EventAttribute{eventAttr}, } endBlockEvent := abci.Event{ Type: "end_block_event", Attributes: []abci.EventAttribute{eventAttr}, } beginBlockEvent := abci.Event{ Type: "begin_block_event", Attributes: []abci.EventAttribute{eventAttr}, } responseDeliverTx := abci.ExecTxResult{ Code: abci.CodeTypeOK, Events: []abci.Event{deliverTxEvent}, } validatorUpdates := []abci.ValidatorUpdate{{ PubKey: cmtcrypto.PublicKey{Sum: &cmtcrypto.PublicKey_Ed25519{Ed25519: make([]byte, 1)}}, Power: int64(10), }} consensusParams := &cmtproto.ConsensusParams{ Block: &cmtproto.BlockParams{ MaxBytes: int64(100000), MaxGas: int64(10000), }, Evidence: &cmtproto.EvidenceParams{ MaxAgeNumBlocks: int64(10), MaxAgeDuration: time.Duration(1000), MaxBytes: int64(10000), }, Validator: &cmtproto.ValidatorParams{ PubKeyTypes: []string{"ed25519"}, }, Version: &cmtproto.VersionParams{ App: uint64(10), }, } // Legacy ABCI Responses legacyABCIResponses := cmtstate.LegacyABCIResponses{ DeliverTxs: []*abci.ExecTxResult{ &responseDeliverTx, }, EndBlock: &cmtstate.ResponseEndBlock{ Events: []abci.Event{endBlockEvent}, ConsensusParamUpdates: consensusParams, ValidatorUpdates: validatorUpdates, }, BeginBlock: &cmtstate.ResponseBeginBlock{ Events: []abci.Event{beginBlockEvent}, }, } return legacyABCIResponses } // Generate a Legacy ABCIResponses with null data for some fields. func newLegacyABCIResponsesWithNullFields() cmtstate.LegacyABCIResponses { eventAttr := abci.EventAttribute{ Key: "key", Value: "value", } deliverTxEvent := abci.Event{ Type: "deliver_tx_event", Attributes: []abci.EventAttribute{eventAttr}, } beginBlockEvent := abci.Event{ Type: "begin_block_event", Attributes: []abci.EventAttribute{eventAttr}, } responseDeliverTx := abci.ExecTxResult{ Code: abci.CodeTypeOK, Events: []abci.Event{deliverTxEvent}, } // Legacy ABCI Responses legacyABCIResponses := cmtstate.LegacyABCIResponses{ DeliverTxs: []*abci.ExecTxResult{ &responseDeliverTx, }, BeginBlock: &cmtstate.ResponseBeginBlock{ Events: []abci.Event{beginBlockEvent}, }, } return legacyABCIResponses }