cosmos-sdk/server/v2/cometbft/abci_test.go
cool-developer 064c9ba638
feat(store/v2): build the migration manager in the root store factory (#22336)
Co-authored-by: marbar3778 <marbar3778@yahoo.com>
Co-authored-by: Marko <marko@baricevic.me>
Co-authored-by: Alex | Interchain Labs <alex@skip.money>
2025-01-13 10:56:48 +00:00

992 lines
28 KiB
Go

package cometbft
import (
"context"
"crypto/sha256"
"encoding/json"
"errors"
"io"
"reflect"
"strings"
"sync"
"testing"
"time"
abci "github.com/cometbft/cometbft/abci/types"
abciproto "github.com/cometbft/cometbft/api/cometbft/abci/v1"
v1 "github.com/cometbft/cometbft/api/cometbft/types/v1"
gogoproto "github.com/cosmos/gogoproto/proto"
gogotypes "github.com/cosmos/gogoproto/types"
"github.com/stretchr/testify/require"
appmodulev2 "cosmossdk.io/core/appmodule/v2"
"cosmossdk.io/core/server"
"cosmossdk.io/core/store"
"cosmossdk.io/core/transaction"
"cosmossdk.io/log"
"cosmossdk.io/server/v2/appmanager"
"cosmossdk.io/server/v2/cometbft/handlers"
cometmock "cosmossdk.io/server/v2/cometbft/internal/mock"
"cosmossdk.io/server/v2/cometbft/mempool"
"cosmossdk.io/server/v2/cometbft/oe"
"cosmossdk.io/server/v2/cometbft/types"
"cosmossdk.io/server/v2/stf"
"cosmossdk.io/server/v2/stf/branch"
"cosmossdk.io/server/v2/stf/mock"
consensustypes "cosmossdk.io/x/consensus/types"
"github.com/cosmos/cosmos-sdk/testutil/testdata"
sdk "github.com/cosmos/cosmos-sdk/types"
)
var (
sum = sha256.Sum256([]byte("test-hash"))
emptyHash = sha256.Sum256([]byte(""))
DefaulConsensusParams = &v1.ConsensusParams{
Block: &v1.BlockParams{
MaxGas: 5000000,
},
}
mockTx = mock.Tx{
Sender: []byte("sender"),
Msg: &gogotypes.BoolValue{Value: true},
GasLimit: 100_000,
}
invalidMockTx = mock.Tx{
Sender: []byte("sender"),
Msg: &gogotypes.BoolValue{Value: true},
GasLimit: 0,
}
actorName = []byte("cookies")
testAcc = sdk.AccAddress([]byte("addr1_______________"))
versionStr = "0.0.0"
)
func getQueryRouterBuilder[T any, PT interface {
*T
gogoproto.Message
},
U any, UT interface {
*U
gogoproto.Message
}](
t *testing.T,
handler func(ctx context.Context, msg PT) (UT, error),
) *stf.MsgRouterBuilder {
t.Helper()
queryRouterBuilder := stf.NewMsgRouterBuilder()
err := queryRouterBuilder.RegisterHandler(
gogoproto.MessageName(PT(new(T))),
func(ctx context.Context, msg transaction.Msg) (msgResp transaction.Msg, err error) {
typedReq := msg.(PT)
typedResp, err := handler(ctx, typedReq)
if err != nil {
return nil, err
}
return typedResp, nil
},
)
require.NoError(t, err)
return queryRouterBuilder
}
func getMsgRouterBuilder[T any, PT interface {
*T
transaction.Msg
},
U any, UT interface {
*U
transaction.Msg
}](
t *testing.T,
handler func(ctx context.Context, msg PT) (UT, error),
) *stf.MsgRouterBuilder {
t.Helper()
msgRouterBuilder := stf.NewMsgRouterBuilder()
err := msgRouterBuilder.RegisterHandler(
gogoproto.MessageName(PT(new(T))),
func(ctx context.Context, msg transaction.Msg) (msgResp transaction.Msg, err error) {
typedReq := msg.(PT)
typedResp, err := handler(ctx, typedReq)
if err != nil {
return nil, err
}
return typedResp, nil
},
)
require.NoError(t, err)
return msgRouterBuilder
}
func TestConsensus_InitChain_Without_UpdateParam(t *testing.T) {
c := setUpConsensus(t, 100_000, mempool.NoOpMempool[mock.Tx]{})
mockStore := c.store
_, err := c.InitChain(context.Background(), &abciproto.InitChainRequest{
Time: time.Now(),
ChainId: "test",
InitialHeight: 1,
})
require.NoError(t, err)
assertStoreLatestVersion(t, mockStore, 0)
_, err = c.FinalizeBlock(context.Background(), &abciproto.FinalizeBlockRequest{
Time: time.Now(),
Height: 1,
Hash: emptyHash[:],
})
require.NoError(t, err)
assertStoreLatestVersion(t, mockStore, 1)
}
func TestConsensus_InitChain_With_UpdateParam(t *testing.T) {
c := setUpConsensus(t, 100_000, mempool.NoOpMempool[mock.Tx]{})
mockStore := c.store
_, err := c.InitChain(context.Background(), &abciproto.InitChainRequest{
Time: time.Now(),
ChainId: "test",
ConsensusParams: DefaulConsensusParams,
InitialHeight: 1,
})
require.NoError(t, err)
assertStoreLatestVersion(t, mockStore, 0)
_, err = c.FinalizeBlock(context.Background(), &abciproto.FinalizeBlockRequest{
Time: time.Now(),
Height: 1,
Hash: emptyHash[:],
})
require.NoError(t, err)
assertStoreLatestVersion(t, mockStore, 1)
}
func TestConsensus_InitChain_Invalid_Height(t *testing.T) {
c := setUpConsensus(t, 100_000, mempool.NoOpMempool[mock.Tx]{})
mockStore := c.store
_, err := c.InitChain(context.Background(), &abciproto.InitChainRequest{
Time: time.Now(),
ChainId: "test",
InitialHeight: 2,
})
require.NoError(t, err)
assertStoreLatestVersion(t, mockStore, 1)
// Shouldn't be able to commit genesis block 3
_, err = c.FinalizeBlock(context.Background(), &abciproto.FinalizeBlockRequest{
Time: time.Now(),
Height: 3,
Hash: emptyHash[:],
})
require.Error(t, err)
require.True(t, strings.Contains(err.Error(), "invalid height"))
}
func TestConsensus_FinalizeBlock_Invalid_Height(t *testing.T) {
c := setUpConsensus(t, 100_000, mempool.NoOpMempool[mock.Tx]{})
_, err := c.InitChain(context.Background(), &abciproto.InitChainRequest{
Time: time.Now(),
ChainId: "test",
InitialHeight: 1,
})
require.NoError(t, err)
_, err = c.FinalizeBlock(context.Background(), &abciproto.FinalizeBlockRequest{
Time: time.Now(),
Height: 1,
Hash: emptyHash[:],
})
require.NoError(t, err)
_, err = c.FinalizeBlock(context.Background(), &abciproto.FinalizeBlockRequest{
Time: time.Now(),
Height: 3,
Hash: emptyHash[:],
})
require.Error(t, err)
}
func TestConsensus_FinalizeBlock_NoTxs(t *testing.T) {
c := setUpConsensus(t, 100_000, mempool.NoOpMempool[mock.Tx]{})
mockStore := c.store
_, err := c.InitChain(context.Background(), &abciproto.InitChainRequest{
Time: time.Now(),
ChainId: "test",
InitialHeight: 1,
})
require.NoError(t, err)
_, err = c.FinalizeBlock(context.Background(), &abciproto.FinalizeBlockRequest{
Time: time.Now(),
Height: 1,
Hash: emptyHash[:],
})
require.NoError(t, err)
endBlock := 10
for i := 2; i <= endBlock; i++ {
_, err = c.FinalizeBlock(context.Background(), &abciproto.FinalizeBlockRequest{
Time: time.Now(),
Height: int64(i),
Hash: sum[:],
})
require.NoError(t, err)
assertStoreLatestVersion(t, mockStore, uint64(i))
}
require.Equal(t, int64(endBlock), c.lastCommittedHeight.Load())
}
func TestConsensus_FinalizeBlock_MultiTxs_OutOfGas(t *testing.T) {
c := setUpConsensus(t, 100_000, mempool.NoOpMempool[mock.Tx]{})
_, err := c.InitChain(context.Background(), &abciproto.InitChainRequest{
Time: time.Now(),
ChainId: "test",
InitialHeight: 1,
})
require.NoError(t, err)
_, err = c.FinalizeBlock(context.Background(), &abciproto.FinalizeBlockRequest{
Time: time.Now(),
Height: 1,
Hash: emptyHash[:],
})
require.NoError(t, err)
endBlock := 10
for i := 2; i <= endBlock; i++ {
res, err := c.FinalizeBlock(context.Background(), &abciproto.FinalizeBlockRequest{
Time: time.Now(),
Height: int64(i),
Hash: sum[:],
Txs: [][]byte{invalidMockTx.Bytes(), mockTx.Bytes()},
})
require.NoError(t, err)
require.NotEqual(t, res.TxResults[0].Code, 0)
}
require.Equal(t, int64(endBlock), c.lastCommittedHeight.Load())
}
func TestConsensus_FinalizeBlock_MultiTxs(t *testing.T) {
c := setUpConsensus(t, 100_000, mempool.NoOpMempool[mock.Tx]{})
mockStore := c.store
_, err := c.InitChain(context.Background(), &abciproto.InitChainRequest{
Time: time.Now(),
ChainId: "test",
InitialHeight: 1,
})
require.NoError(t, err)
_, err = c.FinalizeBlock(context.Background(), &abciproto.FinalizeBlockRequest{
Time: time.Now(),
Height: 1,
Hash: emptyHash[:],
})
require.NoError(t, err)
endBlock := 10
for i := 2; i <= endBlock; i++ {
_, err = c.FinalizeBlock(context.Background(), &abciproto.FinalizeBlockRequest{
Time: time.Now(),
Height: int64(i),
Hash: sum[:],
Txs: [][]byte{mockTx.Bytes(), mockTx.Bytes()},
})
require.NoError(t, err)
assertStoreLatestVersion(t, mockStore, uint64(i))
}
require.Equal(t, int64(endBlock), c.lastCommittedHeight.Load())
}
func TestConsensus_CheckTx(t *testing.T) {
c := setUpConsensus(t, 0, mempool.NoOpMempool[mock.Tx]{})
_, err := c.InitChain(context.Background(), &abciproto.InitChainRequest{
Time: time.Now(),
ChainId: "test",
InitialHeight: 1,
})
require.NoError(t, err)
// empty byte
_, err = c.CheckTx(context.Background(), &abciproto.CheckTxRequest{
Tx: []byte{},
})
require.Error(t, err)
// out of gas
res, err := c.CheckTx(context.Background(), &abciproto.CheckTxRequest{
Tx: mock.Tx{
Sender: []byte("sender"),
Msg: &gogotypes.BoolValue{Value: true},
GasLimit: 100_000,
}.Bytes(),
})
require.NoError(t, err)
require.NotEqual(t, res.Code, 0)
c = setUpConsensus(t, 100_000, mempool.NoOpMempool[mock.Tx]{})
res, err = c.CheckTx(context.Background(), &abciproto.CheckTxRequest{
Tx: mock.Tx{
Sender: []byte("sender"),
Msg: &gogotypes.BoolValue{Value: true},
GasLimit: 100_000,
}.Bytes(),
})
require.NoError(t, err)
require.NotEqual(t, res.GasUsed, 0)
}
func TestConsensus_ExtendVote(t *testing.T) {
c := setUpConsensus(t, 100_000, mempool.NoOpMempool[mock.Tx]{})
_, err := c.InitChain(context.Background(), &abciproto.InitChainRequest{
Time: time.Now(),
ChainId: "test",
InitialHeight: 1,
ConsensusParams: &v1.ConsensusParams{
Block: &v1.BlockParams{
MaxGas: 5000000,
},
Feature: &v1.FeatureParams{
VoteExtensionsEnableHeight: &gogotypes.Int64Value{Value: 2},
},
},
})
require.NoError(t, err)
// Votes not enabled yet
_, err = c.ExtendVote(context.Background(), &abciproto.ExtendVoteRequest{
Height: 1,
})
require.ErrorContains(t, err, "vote extensions are not enabled")
// Empty extendVote handler
_, err = c.ExtendVote(context.Background(), &abciproto.ExtendVoteRequest{
Height: 2,
})
require.ErrorContains(t, err, "no extend function was set")
// Use NoOp handler
c.extendVote = DefaultServerOptions[mock.Tx]().ExtendVoteHandler
res, err := c.ExtendVote(context.Background(), &abciproto.ExtendVoteRequest{
Height: 2,
})
require.NoError(t, err)
require.Equal(t, len(res.VoteExtension), 0)
}
func TestConsensus_VerifyVoteExtension(t *testing.T) {
c := setUpConsensus(t, 100_000, mempool.NoOpMempool[mock.Tx]{})
_, err := c.InitChain(context.Background(), &abciproto.InitChainRequest{
Time: time.Now(),
ChainId: "test",
InitialHeight: 1,
ConsensusParams: &v1.ConsensusParams{
Block: &v1.BlockParams{
MaxGas: 5000000,
},
Feature: &v1.FeatureParams{
VoteExtensionsEnableHeight: &gogotypes.Int64Value{Value: 2},
},
},
})
require.NoError(t, err)
// Votes not enabled yet
_, err = c.VerifyVoteExtension(context.Background(), &abciproto.VerifyVoteExtensionRequest{
Height: 1,
})
require.ErrorContains(t, err, "vote extensions are not enabled")
// Empty verifyVote handler
_, err = c.VerifyVoteExtension(context.Background(), &abciproto.VerifyVoteExtensionRequest{
Height: 2,
})
require.ErrorContains(t, err, "no verify function was set")
// Use NoOp handler
c.verifyVoteExt = DefaultServerOptions[mock.Tx]().VerifyVoteExtensionHandler
res, err := c.VerifyVoteExtension(context.Background(), &abciproto.VerifyVoteExtensionRequest{
Height: 2,
Hash: []byte("test"),
})
require.NoError(t, err)
require.Equal(t, res.Status, abciproto.VERIFY_VOTE_EXTENSION_STATUS_ACCEPT)
}
func TestConsensus_PrepareProposal(t *testing.T) {
c := setUpConsensus(t, 100_000, mempool.NoOpMempool[mock.Tx]{})
// Invalid height
_, err := c.PrepareProposal(context.Background(), &abciproto.PrepareProposalRequest{
Height: 0,
})
require.Error(t, err)
// empty handler
_, err = c.PrepareProposal(context.Background(), &abciproto.PrepareProposalRequest{
Height: 1,
})
require.Error(t, err)
// NoOp handler
c.prepareProposalHandler = DefaultServerOptions[mock.Tx]().PrepareProposalHandler
_, err = c.PrepareProposal(context.Background(), &abciproto.PrepareProposalRequest{
Height: 1,
Txs: [][]byte{mockTx.Bytes()},
})
require.NoError(t, err)
}
func TestConsensus_PrepareProposal_With_Handler_NoOpMempool(t *testing.T) {
c := setUpConsensus(t, 100_000, mempool.NoOpMempool[mock.Tx]{})
c.prepareProposalHandler = handlers.NewDefaultProposalHandler(c.mempool).PrepareHandler()
// zero MaxTxBytes
res, err := c.PrepareProposal(context.Background(), &abciproto.PrepareProposalRequest{
Height: 1,
MaxTxBytes: 0,
Txs: [][]byte{mockTx.Bytes()},
})
require.NoError(t, err)
require.Equal(t, len(res.Txs), 0)
// have tx exceed MaxTxBytes
// each mock tx has 128 bytes, should select 2 txs
res, err = c.PrepareProposal(context.Background(), &abciproto.PrepareProposalRequest{
Height: 1,
MaxTxBytes: 300,
Txs: [][]byte{mockTx.Bytes(), mockTx.Bytes(), mockTx.Bytes()},
})
require.NoError(t, err)
require.Equal(t, len(res.Txs), 2)
// reach MaxTxBytes
res, err = c.PrepareProposal(context.Background(), &abciproto.PrepareProposalRequest{
Height: 1,
MaxTxBytes: 256,
Txs: [][]byte{mockTx.Bytes(), mockTx.Bytes()},
})
require.NoError(t, err)
require.Equal(t, len(res.Txs), 2)
// Over gas, under MaxTxBytes
// 300_000 gas limit, should only take 3 txs
res, err = c.PrepareProposal(context.Background(), &abciproto.PrepareProposalRequest{
Height: 1,
MaxTxBytes: 1000,
Txs: [][]byte{mockTx.Bytes(), mockTx.Bytes(), mockTx.Bytes(), mockTx.Bytes()},
})
require.NoError(t, err)
require.Equal(t, len(res.Txs), 3)
// Reach max gas
res, err = c.PrepareProposal(context.Background(), &abciproto.PrepareProposalRequest{
Height: 1,
MaxTxBytes: 1000,
Txs: [][]byte{mockTx.Bytes(), mockTx.Bytes(), mockTx.Bytes()},
})
require.NoError(t, err)
require.Equal(t, len(res.Txs), 3)
// have a bad encoding tx
res, err = c.PrepareProposal(context.Background(), &abciproto.PrepareProposalRequest{
Height: 1,
MaxTxBytes: 1000,
Txs: [][]byte{mockTx.Bytes(), append(mockTx.Bytes(), []byte("bad")...), mockTx.Bytes()},
})
require.NoError(t, err)
require.Equal(t, len(res.Txs), 2)
}
func TestConsensus_ProcessProposal(t *testing.T) {
c := setUpConsensus(t, 100_000, mempool.NoOpMempool[mock.Tx]{})
// Invalid height
_, err := c.ProcessProposal(context.Background(), &abciproto.ProcessProposalRequest{
Height: 0,
})
require.Error(t, err)
// empty handler
_, err = c.ProcessProposal(context.Background(), &abciproto.ProcessProposalRequest{
Height: 1,
})
require.Error(t, err)
// NoOp handler
// dummy optimistic execution
optimisticMockFunc := func(context.Context, *abci.FinalizeBlockRequest) (*server.BlockResponse, store.WriterMap, []mock.Tx, error) {
return nil, nil, nil, errors.New("test error")
}
c.optimisticExec = oe.NewOptimisticExecution[mock.Tx](log.NewNopLogger(), optimisticMockFunc)
c.processProposalHandler = DefaultServerOptions[mock.Tx]().ProcessProposalHandler
_, err = c.ProcessProposal(context.Background(), &abciproto.ProcessProposalRequest{
Height: 1,
Txs: [][]byte{mockTx.Bytes()},
})
require.NoError(t, err)
}
func TestConsensus_ProcessProposal_With_Handler(t *testing.T) {
c := setUpConsensus(t, 100_000, cometmock.MockMempool[mock.Tx]{})
c.processProposalHandler = handlers.NewDefaultProposalHandler(c.mempool).ProcessHandler()
// exceed max gas
res, err := c.ProcessProposal(context.Background(), &abciproto.ProcessProposalRequest{
Height: 1,
Txs: [][]byte{mockTx.Bytes(), mockTx.Bytes(), mockTx.Bytes(), mockTx.Bytes()},
})
require.NoError(t, err)
require.Equal(t, res.Status, abciproto.PROCESS_PROPOSAL_STATUS_REJECT)
// have bad encode tx
// should reject
res, err = c.ProcessProposal(context.Background(), &abciproto.ProcessProposalRequest{
Height: 1,
Txs: [][]byte{mockTx.Bytes(), append(mockTx.Bytes(), []byte("bad")...), mockTx.Bytes(), mockTx.Bytes()},
})
require.NoError(t, err)
require.Equal(t, res.Status, abciproto.PROCESS_PROPOSAL_STATUS_REJECT)
}
func TestConsensus_Info(t *testing.T) {
c := setUpConsensus(t, 100_000, cometmock.MockMempool[mock.Tx]{})
// Version 0
res, err := c.Info(context.Background(), &abciproto.InfoRequest{})
require.NoError(t, err)
require.Equal(t, res.LastBlockHeight, int64(0))
// Commit store to version 1
_, err = c.InitChain(context.Background(), &abciproto.InitChainRequest{
Time: time.Now(),
ChainId: "test",
InitialHeight: 1,
})
require.NoError(t, err)
_, err = c.FinalizeBlock(context.Background(), &abciproto.FinalizeBlockRequest{
Time: time.Now(),
Height: 1,
Hash: emptyHash[:],
})
require.NoError(t, err)
res, err = c.Info(context.Background(), &abciproto.InfoRequest{})
require.NoError(t, err)
require.Equal(t, res.LastBlockHeight, int64(1))
}
func TestConsensus_QueryStore(t *testing.T) {
c := setUpConsensus(t, 100_000, cometmock.MockMempool[mock.Tx]{})
// Write data to state storage
err := c.store.GetStateCommitment().WriteChangeset(&store.Changeset{
Version: 1,
Changes: []store.StateChanges{
{
Actor: actorName,
StateChanges: []store.KVPair{
{
Key: []byte("key"),
Value: []byte("value"),
Remove: false,
},
},
},
},
})
require.NoError(t, err)
_, err = c.InitChain(context.Background(), &abciproto.InitChainRequest{
Time: time.Now(),
ChainId: "test",
InitialHeight: 1,
})
require.NoError(t, err)
_, err = c.FinalizeBlock(context.Background(), &abciproto.FinalizeBlockRequest{
Time: time.Now(),
Height: 1,
Txs: [][]byte{mockTx.Bytes()},
Hash: emptyHash[:],
})
require.NoError(t, err)
// empty request
res, err := c.Query(context.Background(), &abciproto.QueryRequest{})
require.NoError(t, err)
require.Equal(t, res.Code, uint32(1))
require.Contains(t, res.Log, "no query path provided")
// Query store
res, err = c.Query(context.Background(), &abciproto.QueryRequest{
Path: "store/cookies/",
Data: []byte("key"),
Height: 1,
})
require.NoError(t, err)
require.Equal(t, string(res.Value), "value")
// Query store with no value
res, err = c.Query(context.Background(), &abciproto.QueryRequest{
Path: "store/cookies/",
Data: []byte("exec"),
Height: 1,
})
require.NoError(t, err)
require.Equal(t, res.Value, []byte(nil))
}
func TestConsensus_GRPCQuery(t *testing.T) {
c := setUpConsensus(t, 100_000, cometmock.MockMempool[mock.Tx]{})
_, err := c.InitChain(context.Background(), &abciproto.InitChainRequest{
Time: time.Now(),
ChainId: "test",
InitialHeight: 1,
})
require.NoError(t, err)
_, err = c.FinalizeBlock(context.Background(), &abciproto.FinalizeBlockRequest{
Time: time.Now(),
Height: 1,
Txs: [][]byte{mockTx.Bytes()},
Hash: emptyHash[:],
})
require.NoError(t, err)
// empty request
res, err := c.Query(context.Background(), &abciproto.QueryRequest{})
require.NoError(t, err)
require.Equal(t, res.Code, uint32(1))
require.Contains(t, res.Log, "no query path provided")
// query request not exist in handler map
invalidReq := testdata.EchoRequest{
Message: "echo",
}
invalidReqBz, err := invalidReq.Marshal()
require.NoError(t, err)
invalidQuery := abci.QueryRequest{
Data: invalidReqBz,
Path: "testpb.EchoRequest",
}
invalidRes, err := c.Query(context.TODO(), &invalidQuery)
require.Error(t, err)
require.Nil(t, invalidRes)
require.Contains(t, err.Error(), "no query handler found")
// Valid query
req := testdata.SayHelloRequest{Name: "foo"}
reqBz, err := req.Marshal()
require.NoError(t, err)
reqQuery := abci.QueryRequest{
Data: reqBz,
Path: "testpb.SayHelloRequest",
}
resQuery, err := c.Query(context.TODO(), &reqQuery)
require.NoError(t, err)
require.Equal(t, abci.CodeTypeOK, resQuery.Code, resQuery)
var response testdata.SayHelloResponse
require.NoError(t, response.Unmarshal(resQuery.Value))
require.Equal(t, "Hello foo!", response.Greeting)
}
func TestConsensus_P2PQuery(t *testing.T) {
c := setUpConsensus(t, 100_000, cometmock.MockMempool[mock.Tx]{})
_, err := c.InitChain(context.Background(), &abciproto.InitChainRequest{
Time: time.Now(),
ChainId: "test",
InitialHeight: 1,
})
require.NoError(t, err)
_, err = c.FinalizeBlock(context.Background(), &abciproto.FinalizeBlockRequest{
Time: time.Now(),
Height: 1,
Txs: [][]byte{mockTx.Bytes()},
Hash: emptyHash[:],
})
require.NoError(t, err)
// empty request
res, err := c.Query(context.Background(), &abciproto.QueryRequest{})
require.NoError(t, err)
require.Equal(t, res.Code, uint32(1))
require.Contains(t, res.Log, "no query path provided")
addrQuery := abci.QueryRequest{
Path: "/p2p/filter/addr/1.1.1.1:8000",
}
res, err = c.Query(context.TODO(), &addrQuery)
require.NoError(t, err)
require.Equal(t, uint32(3), res.Code)
idQuery := abci.QueryRequest{
Path: "/p2p/filter/id/testid",
}
res, err = c.Query(context.TODO(), &idQuery)
require.NoError(t, err)
require.Equal(t, uint32(4), res.Code)
}
func TestConsensus_AppQuery(t *testing.T) {
c := setUpConsensus(t, 100_000, cometmock.MockMempool[mock.Tx]{})
_, err := c.InitChain(context.Background(), &abciproto.InitChainRequest{
Time: time.Now(),
ChainId: "test",
InitialHeight: 1,
})
require.NoError(t, err)
_, err = c.FinalizeBlock(context.Background(), &abciproto.FinalizeBlockRequest{
Time: time.Now(),
Height: 1,
Txs: [][]byte{mockTx.Bytes()},
Hash: emptyHash[:],
})
require.NoError(t, err)
tx := mock.Tx{
Sender: testAcc,
Msg: &gogotypes.BoolValue{Value: true},
GasLimit: 1000,
}
txBytes := tx.Bytes()
// simulate by calling Query with encoded tx
query := abci.QueryRequest{
Path: "/app/simulate",
Data: txBytes,
}
queryResult, err := c.Query(context.TODO(), &query)
require.NoError(t, err)
require.True(t, queryResult.IsOK(), queryResult.Log)
// Query app version
res, err := c.Query(context.TODO(), &abci.QueryRequest{Path: "app/version"})
require.NoError(t, err)
require.True(t, res.IsOK())
require.Equal(t, versionStr, string(res.Value))
}
func setUpConsensus(t *testing.T, gasLimit uint64, mempool mempool.Mempool[mock.Tx]) *consensus[mock.Tx] {
t.Helper()
queryHandler := make(map[string]appmodulev2.Handler)
msgRouterBuilder := getMsgRouterBuilder(t, func(ctx context.Context, msg *gogotypes.BoolValue) (*gogotypes.BoolValue, error) {
return msg, nil
})
queryRouterBuilder := getQueryRouterBuilder(t, func(ctx context.Context, q *consensustypes.QueryParamsRequest) (*consensustypes.QueryParamsResponse, error) {
cParams := &v1.ConsensusParams{
Block: &v1.BlockParams{
MaxGas: 300000,
},
Feature: &v1.FeatureParams{
VoteExtensionsEnableHeight: &gogotypes.Int64Value{Value: 2},
},
}
return &consensustypes.QueryParamsResponse{
Params: cParams,
}, nil
})
helloFooHandler := func(ctx context.Context, msg transaction.Msg) (msgResp transaction.Msg, err error) {
typedReq := msg.(*testdata.SayHelloRequest)
handler := testdata.QueryImpl{}
typedResp, err := handler.SayHello(ctx, typedReq)
if err != nil {
return nil, err
}
return typedResp, nil
}
_ = queryRouterBuilder.RegisterHandler(
gogoproto.MessageName(&testdata.SayHelloRequest{}),
helloFooHandler,
)
queryHandler[gogoproto.MessageName(&testdata.SayHelloRequest{})] = appmodulev2.Handler{
Func: helloFooHandler,
MakeMsg: func() transaction.Msg {
return reflect.New(gogoproto.MessageType(gogoproto.MessageName(&testdata.SayHelloRequest{})).Elem()).Interface().(transaction.Msg)
},
MakeMsgResp: func() transaction.Msg {
return reflect.New(gogoproto.MessageType(gogoproto.MessageName(&testdata.SayHelloResponse{})).Elem()).Interface().(transaction.Msg)
},
}
s, err := stf.New(
log.NewNopLogger().With("module", "stf"),
msgRouterBuilder,
queryRouterBuilder,
func(ctx context.Context, txs []mock.Tx) error { return nil },
func(ctx context.Context) error {
return nil
},
func(ctx context.Context) error {
return nil
},
func(ctx context.Context, tx mock.Tx) error {
return nil
},
func(ctx context.Context) ([]appmodulev2.ValidatorUpdate, error) { return nil, nil },
func(ctx context.Context, tx mock.Tx, success bool) error {
return nil
},
branch.DefaultNewWriterMap,
)
require.NoError(t, err)
sc := cometmock.NewMockCommiter(log.NewNopLogger(), string(actorName), "stf")
mockStore := cometmock.NewMockStore(sc)
am := appmanager.New(appmanager.Config{
ValidateTxGasLimit: gasLimit,
QueryGasLimit: gasLimit,
SimulationGasLimit: gasLimit,
},
mockStore,
s,
func(ctx context.Context, src io.Reader, txHandler func(json.RawMessage) error) (store.WriterMap, []appmodulev2.ValidatorUpdate, error) {
_, st, err := mockStore.StateLatest()
require.NoError(t, err)
return branch.DefaultNewWriterMap(st), nil, nil
},
nil,
)
addrPeerFilter := func(info string) (*abci.QueryResponse, error) {
require.Equal(t, "1.1.1.1:8000", info)
return &abci.QueryResponse{Code: uint32(3)}, nil
}
idPeerFilter := func(id string) (*abci.QueryResponse, error) {
require.Equal(t, "testid", id)
return &abci.QueryResponse{Code: uint32(4)}, nil
}
return &consensus[mock.Tx]{
logger: log.NewNopLogger(),
appName: "testing-app",
app: am,
mempool: mempool,
store: mockStore,
cfg: Config{AppTomlConfig: DefaultAppTomlConfig()},
appCodecs: AppCodecs[mock.Tx]{
TxCodec: mock.TxCodec{},
},
chainID: "test",
getProtoRegistry: sync.OnceValues(gogoproto.MergedRegistry),
queryHandlersMap: queryHandler,
addrPeerFilter: addrPeerFilter,
idPeerFilter: idPeerFilter,
version: versionStr,
}
}
// Check target version same with store's latest version
// And should have commit info of target version
func assertStoreLatestVersion(t *testing.T, store types.Store, target uint64) {
t.Helper()
version, err := store.GetLatestVersion()
require.NoError(t, err)
require.Equal(t, target, version)
commitInfo, err := store.GetStateCommitment().GetCommitInfo(version)
require.NoError(t, err)
require.Equal(t, target, uint64(commitInfo.Version))
}
func TestOptimisticExecution(t *testing.T) {
c := setUpConsensus(t, 100_000, mempool.NoOpMempool[mock.Tx]{})
// Set up handlers
c.processProposalHandler = DefaultServerOptions[mock.Tx]().ProcessProposalHandler
// mock optimistic execution
calledTimes := 0
optimisticMockFunc := func(context.Context, *abci.FinalizeBlockRequest) (*server.BlockResponse, store.WriterMap, []mock.Tx, error) {
calledTimes++
return nil, nil, nil, errors.New("test error")
}
c.optimisticExec = oe.NewOptimisticExecution[mock.Tx](log.NewNopLogger(), optimisticMockFunc)
_, err := c.InitChain(context.Background(), &abciproto.InitChainRequest{
Time: time.Now(),
ChainId: "test",
InitialHeight: 1,
})
require.NoError(t, err)
_, err = c.FinalizeBlock(context.Background(), &abciproto.FinalizeBlockRequest{
Time: time.Now(),
Height: 1,
Txs: [][]byte{mockTx.Bytes()},
Hash: emptyHash[:],
})
require.NoError(t, err)
theHash := sha256.Sum256([]byte("test"))
ppReq := &abciproto.ProcessProposalRequest{
Height: 2,
Hash: theHash[:],
Time: time.Now(),
Txs: [][]byte{mockTx.Bytes()},
}
// Start optimistic execution
resp, err := c.ProcessProposal(context.Background(), ppReq)
require.NoError(t, err)
require.Equal(t, resp.Status, abciproto.PROCESS_PROPOSAL_STATUS_ACCEPT)
// Initialize FinalizeBlock with correct hash - should use optimistic result
theHash2 := sha256.Sum256([]byte("test"))
fbReq := &abciproto.FinalizeBlockRequest{
Height: 2,
Hash: theHash2[:],
Time: ppReq.Time,
Txs: ppReq.Txs,
}
fbResp, err := c.FinalizeBlock(context.Background(), fbReq)
require.Nil(t, fbResp)
require.Error(t, err)
require.ErrorContains(t, err, "test error") // from optimisticMockFunc
require.Equal(t, 1, calledTimes)
resp, err = c.ProcessProposal(context.Background(), ppReq)
require.NoError(t, err)
require.Equal(t, resp.Status, abciproto.PROCESS_PROPOSAL_STATUS_ACCEPT)
theWrongHash := sha256.Sum256([]byte("wrong_hash"))
fbReq.Hash = theWrongHash[:]
// Initialize FinalizeBlock with wrong hash - should abort optimistic execution
// Because is aborted, the result comes from the normal execution
fbResp, err = c.FinalizeBlock(context.Background(), fbReq)
require.NotNil(t, fbResp)
require.NoError(t, err)
require.Equal(t, 2, calledTimes)
// Verify optimistic execution was reset
require.False(t, c.optimisticExec.Initialized())
}