Co-authored-by: Marko <marko@baricevic.me> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2760 lines
86 KiB
Go
2760 lines
86 KiB
Go
package baseapp_test
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"crypto/sha256"
|
|
"encoding/binary"
|
|
"encoding/hex"
|
|
"errors"
|
|
"fmt"
|
|
"math/rand"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
abci "github.com/cometbft/cometbft/api/cometbft/abci/v1"
|
|
cmtprotocrypto "github.com/cometbft/cometbft/api/cometbft/crypto/v1"
|
|
cmtproto "github.com/cometbft/cometbft/api/cometbft/types/v1"
|
|
"github.com/cometbft/cometbft/crypto/secp256k1"
|
|
cmttypes "github.com/cometbft/cometbft/types"
|
|
dbm "github.com/cosmos/cosmos-db"
|
|
protoio "github.com/cosmos/gogoproto/io"
|
|
"github.com/cosmos/gogoproto/jsonpb"
|
|
"github.com/cosmos/gogoproto/proto"
|
|
gogotypes "github.com/cosmos/gogoproto/types"
|
|
any "github.com/cosmos/gogoproto/types/any"
|
|
"github.com/golang/mock/gomock"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
errorsmod "cosmossdk.io/errors"
|
|
"cosmossdk.io/log"
|
|
pruningtypes "cosmossdk.io/store/pruning/types"
|
|
"cosmossdk.io/store/snapshots"
|
|
snapshottypes "cosmossdk.io/store/snapshots/types"
|
|
storetypes "cosmossdk.io/store/types"
|
|
"cosmossdk.io/x/auth/signing"
|
|
|
|
"github.com/cosmos/cosmos-sdk/baseapp"
|
|
baseapptestutil "github.com/cosmos/cosmos-sdk/baseapp/testutil"
|
|
"github.com/cosmos/cosmos-sdk/baseapp/testutil/mock"
|
|
"github.com/cosmos/cosmos-sdk/codec"
|
|
cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec"
|
|
"github.com/cosmos/cosmos-sdk/testutil"
|
|
"github.com/cosmos/cosmos-sdk/testutil/testdata"
|
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
|
"github.com/cosmos/cosmos-sdk/types/mempool"
|
|
)
|
|
|
|
const (
|
|
failStr = "&failOnAnte=false"
|
|
fooStr = "foo"
|
|
counterStr = "counter="
|
|
)
|
|
|
|
func TestABCI_Info(t *testing.T) {
|
|
suite := NewBaseAppSuite(t)
|
|
ctx := suite.baseApp.NewContext(true)
|
|
err := suite.baseApp.StoreConsensusParams(ctx, cmttypes.DefaultConsensusParams().ToProto())
|
|
require.NoError(t, err)
|
|
|
|
reqInfo := abci.InfoRequest{}
|
|
res, err := suite.baseApp.Info(&reqInfo)
|
|
require.NoError(t, err)
|
|
|
|
emptyHash := sha256.Sum256([]byte{})
|
|
appHash := emptyHash[:]
|
|
require.Equal(t, "", res.Version)
|
|
require.Equal(t, t.Name(), res.GetData())
|
|
require.Equal(t, int64(0), res.LastBlockHeight)
|
|
require.Equal(t, appHash, res.LastBlockAppHash)
|
|
appVersion, err := suite.baseApp.AppVersion(ctx)
|
|
require.NoError(t, err)
|
|
require.Equal(t, appVersion, res.AppVersion)
|
|
|
|
_, err = suite.baseApp.FinalizeBlock(&abci.FinalizeBlockRequest{Height: 1})
|
|
require.NoError(t, err)
|
|
_, err = suite.baseApp.Commit()
|
|
require.NoError(t, err)
|
|
require.NoError(t, suite.baseApp.SetAppVersion(ctx, 1))
|
|
res, err = suite.baseApp.Info(&reqInfo)
|
|
require.NoError(t, err)
|
|
require.Equal(t, uint64(1), res.AppVersion)
|
|
}
|
|
|
|
func TestABCI_First_block_Height(t *testing.T) {
|
|
suite := NewBaseAppSuite(t, baseapp.SetChainID("test-chain-id"))
|
|
app := suite.baseApp
|
|
|
|
_, err := app.InitChain(&abci.InitChainRequest{
|
|
ChainId: "test-chain-id",
|
|
ConsensusParams: &cmtproto.ConsensusParams{Block: &cmtproto.BlockParams{MaxGas: 5000000}},
|
|
InitialHeight: 1,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
_, err = app.Commit()
|
|
require.NoError(t, err)
|
|
|
|
ctx := app.GetContextForCheckTx(nil)
|
|
require.Equal(t, int64(1), ctx.BlockHeight())
|
|
}
|
|
|
|
func TestABCI_InitChain(t *testing.T) {
|
|
name := t.Name()
|
|
db := dbm.NewMemDB()
|
|
logger := log.NewTestLogger(t)
|
|
app := baseapp.NewBaseApp(name, logger, db, nil, baseapp.SetChainID("test-chain-id"))
|
|
|
|
capKey := storetypes.NewKVStoreKey("main")
|
|
capKey2 := storetypes.NewKVStoreKey("key2")
|
|
app.MountStores(capKey, capKey2)
|
|
|
|
// set a value in the store on init chain
|
|
key, value := []byte("hello"), []byte("goodbye")
|
|
var initChainer sdk.InitChainer = func(ctx sdk.Context, req *abci.InitChainRequest) (*abci.InitChainResponse, error) {
|
|
store := ctx.KVStore(capKey)
|
|
store.Set(key, value)
|
|
return &abci.InitChainResponse{}, nil
|
|
}
|
|
|
|
query := abci.QueryRequest{
|
|
Path: "/store/main/key",
|
|
Data: key,
|
|
}
|
|
|
|
// initChain is nil and chain ID is wrong - errors
|
|
_, err := app.InitChain(&abci.InitChainRequest{ChainId: "wrong-chain-id"})
|
|
require.Error(t, err)
|
|
|
|
// initChain is nil - nothing happens
|
|
_, err = app.InitChain(&abci.InitChainRequest{ChainId: "test-chain-id"})
|
|
require.NoError(t, err)
|
|
resQ, err := app.Query(context.TODO(), &query)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 0, len(resQ.Value))
|
|
|
|
// set initChainer and try again - should see the value
|
|
app.SetInitChainer(initChainer)
|
|
|
|
// stores are mounted and private members are set - sealing baseapp
|
|
err = app.LoadLatestVersion() // needed to make stores non-nil
|
|
require.Nil(t, err)
|
|
require.Equal(t, int64(0), app.LastBlockHeight())
|
|
|
|
initChainRes, err := app.InitChain(&abci.InitChainRequest{AppStateBytes: []byte("{}"), ChainId: "test-chain-id"}) // must have valid JSON genesis file, even if empty
|
|
require.NoError(t, err)
|
|
|
|
// The AppHash returned by a new chain is the sha256 hash of "".
|
|
// $ echo -n '' | sha256sum
|
|
// e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
|
|
apphash, err := hex.DecodeString("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855")
|
|
require.NoError(t, err)
|
|
emptyHash := sha256.Sum256([]byte{})
|
|
require.Equal(t, emptyHash[:], apphash)
|
|
|
|
require.Equal(t, apphash, initChainRes.AppHash)
|
|
|
|
// assert that chainID is set correctly in InitChain
|
|
chainID := getFinalizeBlockStateCtx(app).ChainID()
|
|
require.Equal(t, "test-chain-id", chainID, "ChainID in deliverState not set correctly in InitChain")
|
|
|
|
chainID = getCheckStateCtx(app).ChainID()
|
|
require.Equal(t, "test-chain-id", chainID, "ChainID in checkState not set correctly in InitChain")
|
|
|
|
_, err = app.FinalizeBlock(&abci.FinalizeBlockRequest{
|
|
Hash: initChainRes.AppHash,
|
|
Height: 1,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
_, err = app.Commit()
|
|
require.NoError(t, err)
|
|
|
|
resQ, err = app.Query(context.TODO(), &query)
|
|
require.NoError(t, err)
|
|
require.Equal(t, int64(1), app.LastBlockHeight())
|
|
require.Equal(t, value, resQ.Value)
|
|
|
|
// reload app
|
|
app = baseapp.NewBaseApp(name, logger, db, nil)
|
|
app.SetInitChainer(initChainer)
|
|
app.MountStores(capKey, capKey2)
|
|
err = app.LoadLatestVersion() // needed to make stores non-nil
|
|
require.Nil(t, err)
|
|
require.Equal(t, int64(1), app.LastBlockHeight())
|
|
|
|
// ensure we can still query after reloading
|
|
resQ, err = app.Query(context.TODO(), &query)
|
|
require.NoError(t, err)
|
|
require.Equal(t, value, resQ.Value)
|
|
|
|
// commit and ensure we can still query
|
|
_, err = app.FinalizeBlock(&abci.FinalizeBlockRequest{Height: app.LastBlockHeight() + 1})
|
|
require.NoError(t, err)
|
|
_, err = app.Commit()
|
|
require.NoError(t, err)
|
|
|
|
resQ, err = app.Query(context.TODO(), &query)
|
|
require.NoError(t, err)
|
|
require.Equal(t, value, resQ.Value)
|
|
}
|
|
|
|
func TestABCI_InitChain_WithInitialHeight(t *testing.T) {
|
|
name := t.Name()
|
|
db := dbm.NewMemDB()
|
|
app := baseapp.NewBaseApp(name, log.NewTestLogger(t), db, nil)
|
|
|
|
_, err := app.InitChain(
|
|
&abci.InitChainRequest{
|
|
InitialHeight: 3,
|
|
},
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
_, err = app.Commit()
|
|
require.NoError(t, err)
|
|
require.Equal(t, int64(3), app.LastBlockHeight())
|
|
}
|
|
|
|
func TestABCI_FinalizeBlock_WithInitialHeight(t *testing.T) {
|
|
name := t.Name()
|
|
db := dbm.NewMemDB()
|
|
app := baseapp.NewBaseApp(name, log.NewTestLogger(t), db, nil)
|
|
|
|
_, err := app.InitChain(
|
|
&abci.InitChainRequest{
|
|
InitialHeight: 3,
|
|
},
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
_, err = app.FinalizeBlock(&abci.FinalizeBlockRequest{Height: 4})
|
|
require.Error(t, err, "invalid height: 4; expected: 3")
|
|
|
|
_, err = app.FinalizeBlock(&abci.FinalizeBlockRequest{Height: 3})
|
|
require.NoError(t, err)
|
|
_, err = app.Commit()
|
|
require.NoError(t, err)
|
|
require.Equal(t, int64(3), app.LastBlockHeight())
|
|
}
|
|
|
|
func TestABCI_FinalizeBlock_WithBeginAndEndBlocker(t *testing.T) {
|
|
name := t.Name()
|
|
db := dbm.NewMemDB()
|
|
app := baseapp.NewBaseApp(name, log.NewTestLogger(t), db, nil)
|
|
|
|
app.SetBeginBlocker(func(ctx sdk.Context) (sdk.BeginBlock, error) {
|
|
return sdk.BeginBlock{
|
|
Events: []abci.Event{
|
|
{
|
|
Type: "sometype",
|
|
Attributes: []abci.EventAttribute{
|
|
{
|
|
Key: fooStr,
|
|
Value: "bar",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}, nil
|
|
})
|
|
|
|
app.SetEndBlocker(func(ctx sdk.Context) (sdk.EndBlock, error) {
|
|
return sdk.EndBlock{
|
|
Events: []abci.Event{
|
|
{
|
|
Type: "anothertype",
|
|
Attributes: []abci.EventAttribute{
|
|
{
|
|
Key: fooStr,
|
|
Value: "bar",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}, nil
|
|
})
|
|
|
|
_, err := app.InitChain(
|
|
&abci.InitChainRequest{
|
|
InitialHeight: 1,
|
|
},
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
res, err := app.FinalizeBlock(&abci.FinalizeBlockRequest{Height: 1})
|
|
require.NoError(t, err)
|
|
|
|
require.Len(t, res.Events, 2)
|
|
|
|
require.Equal(t, "sometype", res.Events[0].Type)
|
|
require.Equal(t, fooStr, res.Events[0].Attributes[0].Key)
|
|
require.Equal(t, "bar", res.Events[0].Attributes[0].Value)
|
|
require.Equal(t, "mode", res.Events[0].Attributes[1].Key)
|
|
require.Equal(t, "BeginBlock", res.Events[0].Attributes[1].Value)
|
|
|
|
require.Equal(t, "anothertype", res.Events[1].Type)
|
|
require.Equal(t, fooStr, res.Events[1].Attributes[0].Key)
|
|
require.Equal(t, "bar", res.Events[1].Attributes[0].Value)
|
|
require.Equal(t, "mode", res.Events[1].Attributes[1].Key)
|
|
require.Equal(t, "EndBlock", res.Events[1].Attributes[1].Value)
|
|
|
|
_, err = app.Commit()
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, int64(1), app.LastBlockHeight())
|
|
}
|
|
|
|
func TestABCI_ExtendVote(t *testing.T) {
|
|
name := t.Name()
|
|
db := dbm.NewMemDB()
|
|
app := baseapp.NewBaseApp(name, log.NewTestLogger(t), db, nil)
|
|
|
|
app.SetExtendVoteHandler(func(ctx sdk.Context, req *abci.ExtendVoteRequest) (*abci.ExtendVoteResponse, error) {
|
|
voteExt := fooStr + hex.EncodeToString(req.Hash) + strconv.FormatInt(req.Height, 10)
|
|
return &abci.ExtendVoteResponse{VoteExtension: []byte(voteExt)}, nil
|
|
})
|
|
|
|
app.SetVerifyVoteExtensionHandler(func(ctx sdk.Context, req *abci.VerifyVoteExtensionRequest) (*abci.VerifyVoteExtensionResponse, error) {
|
|
// do some kind of verification here
|
|
expectedVoteExt := fooStr + hex.EncodeToString(req.Hash) + strconv.FormatInt(req.Height, 10)
|
|
if !bytes.Equal(req.VoteExtension, []byte(expectedVoteExt)) {
|
|
return &abci.VerifyVoteExtensionResponse{Status: abci.VERIFY_VOTE_EXTENSION_STATUS_REJECT}, nil
|
|
}
|
|
|
|
return &abci.VerifyVoteExtensionResponse{Status: abci.VERIFY_VOTE_EXTENSION_STATUS_ACCEPT}, nil
|
|
})
|
|
|
|
app.SetParamStore(¶mStore{db: dbm.NewMemDB()})
|
|
_, err := app.InitChain(
|
|
&abci.InitChainRequest{
|
|
InitialHeight: 1,
|
|
ConsensusParams: &cmtproto.ConsensusParams{
|
|
Feature: &cmtproto.FeatureParams{
|
|
VoteExtensionsEnableHeight: &gogotypes.Int64Value{Value: 200},
|
|
},
|
|
},
|
|
},
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
// Votes not enabled yet
|
|
_, err = app.ExtendVote(context.Background(), &abci.ExtendVoteRequest{Height: 123, Hash: []byte("thehash")})
|
|
require.ErrorContains(t, err, "vote extensions are not enabled")
|
|
|
|
// First vote on the first enabled height
|
|
res, err := app.ExtendVote(context.Background(), &abci.ExtendVoteRequest{Height: 200, Hash: []byte("thehash")})
|
|
require.NoError(t, err)
|
|
require.Len(t, res.VoteExtension, 20)
|
|
|
|
res, err = app.ExtendVote(context.Background(), &abci.ExtendVoteRequest{Height: 1000, Hash: []byte("thehash")})
|
|
require.NoError(t, err)
|
|
require.Len(t, res.VoteExtension, 21)
|
|
|
|
// Error during vote extension should return an empty vote extension and no error
|
|
app.SetExtendVoteHandler(func(ctx sdk.Context, req *abci.ExtendVoteRequest) (*abci.ExtendVoteResponse, error) {
|
|
return nil, errors.New("some error")
|
|
})
|
|
res, err = app.ExtendVote(context.Background(), &abci.ExtendVoteRequest{Height: 1000, Hash: []byte("thehash")})
|
|
require.NoError(t, err)
|
|
require.Len(t, res.VoteExtension, 0)
|
|
|
|
// Verify Vote Extensions
|
|
_, err = app.VerifyVoteExtension(&abci.VerifyVoteExtensionRequest{Height: 123, VoteExtension: []byte("1234567")})
|
|
require.ErrorContains(t, err, "vote extensions are not enabled")
|
|
|
|
// First vote on the first enabled height
|
|
vres, err := app.VerifyVoteExtension(&abci.VerifyVoteExtensionRequest{Height: 200, Hash: []byte("thehash"), VoteExtension: []byte("foo74686568617368200")})
|
|
require.NoError(t, err)
|
|
require.Equal(t, abci.VERIFY_VOTE_EXTENSION_STATUS_ACCEPT, vres.Status)
|
|
|
|
vres, err = app.VerifyVoteExtension(&abci.VerifyVoteExtensionRequest{Height: 1000, Hash: []byte("thehash"), VoteExtension: []byte("foo746865686173681000")})
|
|
require.NoError(t, err)
|
|
require.Equal(t, abci.VERIFY_VOTE_EXTENSION_STATUS_ACCEPT, vres.Status)
|
|
|
|
// Reject because it's just some random bytes
|
|
vres, err = app.VerifyVoteExtension(&abci.VerifyVoteExtensionRequest{Height: 201, Hash: []byte("thehash"), VoteExtension: []byte("12345678")})
|
|
require.NoError(t, err)
|
|
require.Equal(t, abci.VERIFY_VOTE_EXTENSION_STATUS_REJECT, vres.Status)
|
|
|
|
// Reject because the verification failed (no error)
|
|
app.SetVerifyVoteExtensionHandler(func(ctx sdk.Context, req *abci.VerifyVoteExtensionRequest) (*abci.VerifyVoteExtensionResponse, error) {
|
|
return nil, errors.New("some error")
|
|
})
|
|
vres, err = app.VerifyVoteExtension(&abci.VerifyVoteExtensionRequest{Height: 201, Hash: []byte("thehash"), VoteExtension: []byte("12345678")})
|
|
require.NoError(t, err)
|
|
require.Equal(t, abci.VERIFY_VOTE_EXTENSION_STATUS_REJECT, vres.Status)
|
|
}
|
|
|
|
// TestABCI_OnlyVerifyVoteExtension makes sure we can call VerifyVoteExtension
|
|
// without having called ExtendVote before.
|
|
func TestABCI_OnlyVerifyVoteExtension(t *testing.T) {
|
|
name := t.Name()
|
|
db := dbm.NewMemDB()
|
|
app := baseapp.NewBaseApp(name, log.NewTestLogger(t), db, nil)
|
|
|
|
app.SetVerifyVoteExtensionHandler(func(ctx sdk.Context, req *abci.VerifyVoteExtensionRequest) (*abci.VerifyVoteExtensionResponse, error) {
|
|
// do some kind of verification here
|
|
expectedVoteExt := fooStr + hex.EncodeToString(req.Hash) + strconv.FormatInt(req.Height, 10)
|
|
if !bytes.Equal(req.VoteExtension, []byte(expectedVoteExt)) {
|
|
return &abci.VerifyVoteExtensionResponse{Status: abci.VERIFY_VOTE_EXTENSION_STATUS_REJECT}, nil
|
|
}
|
|
|
|
return &abci.VerifyVoteExtensionResponse{Status: abci.VERIFY_VOTE_EXTENSION_STATUS_ACCEPT}, nil
|
|
})
|
|
|
|
app.SetParamStore(¶mStore{db: dbm.NewMemDB()})
|
|
_, err := app.InitChain(
|
|
&abci.InitChainRequest{
|
|
InitialHeight: 1,
|
|
ConsensusParams: &cmtproto.ConsensusParams{
|
|
Feature: &cmtproto.FeatureParams{
|
|
VoteExtensionsEnableHeight: &gogotypes.Int64Value{Value: 200},
|
|
},
|
|
},
|
|
},
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
// Verify Vote Extensions
|
|
_, err = app.VerifyVoteExtension(&abci.VerifyVoteExtensionRequest{Height: 123, VoteExtension: []byte("1234567")})
|
|
require.ErrorContains(t, err, "vote extensions are not enabled")
|
|
|
|
// First vote on the first enabled height
|
|
vres, err := app.VerifyVoteExtension(&abci.VerifyVoteExtensionRequest{Height: 200, Hash: []byte("thehash"), VoteExtension: []byte("foo74686568617368200")})
|
|
require.NoError(t, err)
|
|
require.Equal(t, abci.VERIFY_VOTE_EXTENSION_STATUS_ACCEPT, vres.Status)
|
|
|
|
vres, err = app.VerifyVoteExtension(&abci.VerifyVoteExtensionRequest{Height: 1000, Hash: []byte("thehash"), VoteExtension: []byte("foo746865686173681000")})
|
|
require.NoError(t, err)
|
|
require.Equal(t, abci.VERIFY_VOTE_EXTENSION_STATUS_ACCEPT, vres.Status)
|
|
|
|
// Reject because it's just some random bytes
|
|
vres, err = app.VerifyVoteExtension(&abci.VerifyVoteExtensionRequest{Height: 201, Hash: []byte("thehash"), VoteExtension: []byte("12345678")})
|
|
require.NoError(t, err)
|
|
require.Equal(t, abci.VERIFY_VOTE_EXTENSION_STATUS_REJECT, vres.Status)
|
|
|
|
// Reject because the verification failed (no error)
|
|
app.SetVerifyVoteExtensionHandler(func(ctx sdk.Context, req *abci.VerifyVoteExtensionRequest) (*abci.VerifyVoteExtensionResponse, error) {
|
|
return nil, errors.New("some error")
|
|
})
|
|
vres, err = app.VerifyVoteExtension(&abci.VerifyVoteExtensionRequest{Height: 201, Hash: []byte("thehash"), VoteExtension: []byte("12345678")})
|
|
require.NoError(t, err)
|
|
require.Equal(t, abci.VERIFY_VOTE_EXTENSION_STATUS_REJECT, vres.Status)
|
|
}
|
|
|
|
func TestABCI_GRPCQuery(t *testing.T) {
|
|
grpcQueryOpt := func(bapp *baseapp.BaseApp) {
|
|
testdata.RegisterQueryServer(
|
|
bapp.GRPCQueryRouter(),
|
|
testdata.QueryImpl{},
|
|
)
|
|
}
|
|
|
|
suite := NewBaseAppSuite(t, grpcQueryOpt)
|
|
|
|
_, err := suite.baseApp.InitChain(&abci.InitChainRequest{
|
|
ConsensusParams: &cmtproto.ConsensusParams{},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
req := testdata.SayHelloRequest{Name: fooStr}
|
|
reqBz, err := req.Marshal()
|
|
require.NoError(t, err)
|
|
|
|
resQuery, err := suite.baseApp.Query(context.TODO(), &abci.QueryRequest{
|
|
Data: reqBz,
|
|
Path: "/testpb.Query/SayHello",
|
|
})
|
|
require.NoError(t, err)
|
|
require.Equal(t, sdkerrors.ErrInvalidHeight.ABCICode(), resQuery.Code, resQuery)
|
|
require.Contains(t, resQuery.Log, "TestABCI_GRPCQuery is not ready; please wait for first block")
|
|
|
|
_, err = suite.baseApp.FinalizeBlock(&abci.FinalizeBlockRequest{Height: suite.baseApp.LastBlockHeight() + 1})
|
|
require.NoError(t, err)
|
|
|
|
_, err = suite.baseApp.Commit()
|
|
require.NoError(t, err)
|
|
|
|
reqQuery := abci.QueryRequest{
|
|
Data: reqBz,
|
|
Path: "/testpb.Query/SayHello",
|
|
}
|
|
|
|
resQuery, err = suite.baseApp.Query(context.TODO(), &reqQuery)
|
|
require.NoError(t, err)
|
|
require.Equal(t, abci.CodeTypeOK, resQuery.Code, resQuery)
|
|
|
|
var res testdata.SayHelloResponse
|
|
require.NoError(t, res.Unmarshal(resQuery.Value))
|
|
require.Equal(t, "Hello foo!", res.Greeting)
|
|
}
|
|
|
|
func TestABCI_P2PQuery(t *testing.T) {
|
|
addrPeerFilterOpt := func(bapp *baseapp.BaseApp) {
|
|
bapp.SetAddrPeerFilter(func(addrport string) *abci.QueryResponse {
|
|
require.Equal(t, "1.1.1.1:8000", addrport)
|
|
return &abci.QueryResponse{Code: uint32(3)}
|
|
})
|
|
}
|
|
|
|
idPeerFilterOpt := func(bapp *baseapp.BaseApp) {
|
|
bapp.SetIDPeerFilter(func(id string) *abci.QueryResponse {
|
|
require.Equal(t, "testid", id)
|
|
return &abci.QueryResponse{Code: uint32(4)}
|
|
})
|
|
}
|
|
|
|
suite := NewBaseAppSuite(t, addrPeerFilterOpt, idPeerFilterOpt)
|
|
|
|
addrQuery := abci.QueryRequest{
|
|
Path: "/p2p/filter/addr/1.1.1.1:8000",
|
|
}
|
|
res, err := suite.baseApp.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 = suite.baseApp.Query(context.TODO(), &idQuery)
|
|
require.NoError(t, err)
|
|
require.Equal(t, uint32(4), res.Code)
|
|
}
|
|
|
|
func TestBaseApp_PrepareCheckState(t *testing.T) {
|
|
db := dbm.NewMemDB()
|
|
name := t.Name()
|
|
logger := log.NewTestLogger(t)
|
|
|
|
cp := &cmtproto.ConsensusParams{
|
|
Block: &cmtproto.BlockParams{
|
|
MaxGas: 5000000,
|
|
},
|
|
}
|
|
|
|
app := baseapp.NewBaseApp(name, logger, db, nil)
|
|
app.SetParamStore(¶mStore{db: dbm.NewMemDB()})
|
|
_, err := app.InitChain(&abci.InitChainRequest{
|
|
ConsensusParams: cp,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
wasPrepareCheckStateCalled := false
|
|
app.SetPrepareCheckStater(func(ctx sdk.Context) {
|
|
wasPrepareCheckStateCalled = true
|
|
})
|
|
app.Seal()
|
|
|
|
_, err = app.Commit()
|
|
require.NoError(t, err)
|
|
require.Equal(t, true, wasPrepareCheckStateCalled)
|
|
}
|
|
|
|
func TestBaseApp_Precommit(t *testing.T) {
|
|
db := dbm.NewMemDB()
|
|
name := t.Name()
|
|
logger := log.NewTestLogger(t)
|
|
|
|
cp := &cmtproto.ConsensusParams{
|
|
Block: &cmtproto.BlockParams{
|
|
MaxGas: 5000000,
|
|
},
|
|
}
|
|
|
|
app := baseapp.NewBaseApp(name, logger, db, nil)
|
|
app.SetParamStore(¶mStore{db: dbm.NewMemDB()})
|
|
_, err := app.InitChain(&abci.InitChainRequest{
|
|
ConsensusParams: cp,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
wasPrecommiterCalled := false
|
|
app.SetPrecommiter(func(ctx sdk.Context) {
|
|
wasPrecommiterCalled = true
|
|
})
|
|
app.Seal()
|
|
|
|
_, err = app.Commit()
|
|
require.NoError(t, err)
|
|
require.Equal(t, true, wasPrecommiterCalled)
|
|
}
|
|
|
|
func TestABCI_CheckTx(t *testing.T) {
|
|
// This ante handler reads the key and checks that the value matches the
|
|
// current counter. This ensures changes to the KVStore persist across
|
|
// successive CheckTx runs.
|
|
counterKey := []byte("counter-key")
|
|
anteOpt := func(bapp *baseapp.BaseApp) { bapp.SetAnteHandler(anteHandlerTxTest(t, capKey1, counterKey)) }
|
|
suite := NewBaseAppSuite(t, anteOpt)
|
|
|
|
baseapptestutil.RegisterCounterServer(suite.baseApp.MsgServiceRouter(), CounterServerImpl{t, capKey1, counterKey})
|
|
|
|
nTxs := int64(5)
|
|
_, err := suite.baseApp.InitChain(&abci.InitChainRequest{
|
|
ConsensusParams: &cmtproto.ConsensusParams{},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
for i := int64(0); i < nTxs; i++ {
|
|
tx := newTxCounter(t, suite.txConfig, i, 0) // no messages
|
|
txBytes, err := suite.txConfig.TxEncoder()(tx)
|
|
require.NoError(t, err)
|
|
|
|
r, err := suite.baseApp.CheckTx(&abci.CheckTxRequest{Tx: txBytes, Type: abci.CHECK_TX_TYPE_CHECK})
|
|
require.NoError(t, err)
|
|
require.True(t, r.IsOK(), fmt.Sprintf("%v", r))
|
|
require.Empty(t, r.GetEvents())
|
|
}
|
|
|
|
checkStateStore := getCheckStateCtx(suite.baseApp).KVStore(capKey1)
|
|
storedCounter := getIntFromStore(t, checkStateStore, counterKey)
|
|
|
|
// ensure AnteHandler ran
|
|
require.Equal(t, nTxs, storedCounter)
|
|
|
|
// if a block is committed, CheckTx state should be reset
|
|
_, err = suite.baseApp.FinalizeBlock(&abci.FinalizeBlockRequest{
|
|
Height: 1,
|
|
Hash: []byte("hash"),
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
require.NotNil(t, getCheckStateCtx(suite.baseApp).BlockGasMeter(), "block gas meter should have been set to checkState")
|
|
require.NotEmpty(t, getCheckStateCtx(suite.baseApp).HeaderHash())
|
|
|
|
_, err = suite.baseApp.Commit()
|
|
require.NoError(t, err)
|
|
|
|
checkStateStore = getCheckStateCtx(suite.baseApp).KVStore(capKey1)
|
|
storedBytes := checkStateStore.Get(counterKey)
|
|
require.Nil(t, storedBytes)
|
|
}
|
|
|
|
func TestABCI_FinalizeBlock_DeliverTx(t *testing.T) {
|
|
anteKey := []byte("ante-key")
|
|
anteOpt := func(bapp *baseapp.BaseApp) { bapp.SetAnteHandler(anteHandlerTxTest(t, capKey1, anteKey)) }
|
|
suite := NewBaseAppSuite(t, anteOpt)
|
|
|
|
_, err := suite.baseApp.InitChain(&abci.InitChainRequest{
|
|
ConsensusParams: &cmtproto.ConsensusParams{},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
deliverKey := []byte("deliver-key")
|
|
baseapptestutil.RegisterCounterServer(suite.baseApp.MsgServiceRouter(), CounterServerImpl{t, capKey1, deliverKey})
|
|
|
|
nBlocks := 3
|
|
txPerHeight := 5
|
|
|
|
for blockN := 0; blockN < nBlocks; blockN++ {
|
|
|
|
txs := [][]byte{}
|
|
for i := 0; i < txPerHeight; i++ {
|
|
counter := int64(blockN*txPerHeight + i)
|
|
tx := newTxCounter(t, suite.txConfig, counter, counter)
|
|
|
|
txBytes, err := suite.txConfig.TxEncoder()(tx)
|
|
require.NoError(t, err)
|
|
|
|
txs = append(txs, txBytes)
|
|
}
|
|
|
|
res, err := suite.baseApp.FinalizeBlock(&abci.FinalizeBlockRequest{
|
|
Height: int64(blockN) + 1,
|
|
Txs: txs,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
for i := 0; i < txPerHeight; i++ {
|
|
counter := int64(blockN*txPerHeight + i)
|
|
require.True(t, res.TxResults[i].IsOK(), fmt.Sprintf("%v", res))
|
|
|
|
events := res.TxResults[i].GetEvents()
|
|
require.Len(t, events, 3, "should contain ante handler, message type and counter events respectively")
|
|
require.Equal(t, sdk.MarkEventsToIndex(counterEvent("ante_handler", counter).ToABCIEvents(), map[string]struct{}{})[0], events[0], "ante handler event")
|
|
require.Equal(t, sdk.MarkEventsToIndex(counterEvent(sdk.EventTypeMessage, counter).ToABCIEvents(), map[string]struct{}{})[0].Attributes[0], events[2].Attributes[0], "msg handler update counter event")
|
|
}
|
|
|
|
_, err = suite.baseApp.Commit()
|
|
require.NoError(t, err)
|
|
|
|
}
|
|
}
|
|
|
|
func TestABCI_FinalizeBlock_MultiMsg(t *testing.T) {
|
|
anteKey := []byte("ante-key")
|
|
anteOpt := func(bapp *baseapp.BaseApp) { bapp.SetAnteHandler(anteHandlerTxTest(t, capKey1, anteKey)) }
|
|
suite := NewBaseAppSuite(t, anteOpt)
|
|
|
|
_, err := suite.baseApp.InitChain(&abci.InitChainRequest{
|
|
ConsensusParams: &cmtproto.ConsensusParams{},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
deliverKey := []byte("deliver-key")
|
|
baseapptestutil.RegisterCounterServer(suite.baseApp.MsgServiceRouter(), CounterServerImpl{t, capKey1, deliverKey})
|
|
|
|
deliverKey2 := []byte("deliver-key2")
|
|
baseapptestutil.RegisterCounter2Server(suite.baseApp.MsgServiceRouter(), Counter2ServerImpl{t, capKey1, deliverKey2})
|
|
|
|
// run a multi-msg tx
|
|
// with all msgs the same route
|
|
tx := newTxCounter(t, suite.txConfig, 0, 0, 1, 2)
|
|
txBytes, err := suite.txConfig.TxEncoder()(tx)
|
|
require.NoError(t, err)
|
|
|
|
_, err = suite.baseApp.FinalizeBlock(&abci.FinalizeBlockRequest{
|
|
Height: 1,
|
|
Txs: [][]byte{txBytes},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
store := getFinalizeBlockStateCtx(suite.baseApp).KVStore(capKey1)
|
|
|
|
// tx counter only incremented once
|
|
txCounter := getIntFromStore(t, store, anteKey)
|
|
require.Equal(t, int64(1), txCounter)
|
|
|
|
// msg counter incremented three times
|
|
msgCounter := getIntFromStore(t, store, deliverKey)
|
|
require.Equal(t, int64(3), msgCounter)
|
|
|
|
// replace the second message with a Counter2
|
|
tx = newTxCounter(t, suite.txConfig, 1, 3)
|
|
|
|
builder := suite.txConfig.NewTxBuilder()
|
|
msgs := tx.GetMsgs()
|
|
_, _, addr := testdata.KeyTestPubAddr()
|
|
msgs = append(msgs, &baseapptestutil.MsgCounter2{Counter: 0, Signer: addr.String()})
|
|
msgs = append(msgs, &baseapptestutil.MsgCounter2{Counter: 1, Signer: addr.String()})
|
|
|
|
err = builder.SetMsgs(msgs...)
|
|
require.NoError(t, err)
|
|
|
|
builder.SetMemo(tx.GetMemo())
|
|
setTxSignature(t, builder, 0)
|
|
|
|
txBytes, err = suite.txConfig.TxEncoder()(builder.GetTx())
|
|
require.NoError(t, err)
|
|
|
|
_, err = suite.baseApp.FinalizeBlock(&abci.FinalizeBlockRequest{
|
|
Height: 1,
|
|
Txs: [][]byte{txBytes},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
store = getFinalizeBlockStateCtx(suite.baseApp).KVStore(capKey1)
|
|
|
|
// tx counter only incremented once
|
|
txCounter = getIntFromStore(t, store, anteKey)
|
|
require.Equal(t, int64(2), txCounter)
|
|
|
|
// original counter increments by one
|
|
// new counter increments by two
|
|
msgCounter = getIntFromStore(t, store, deliverKey)
|
|
require.Equal(t, int64(4), msgCounter)
|
|
|
|
msgCounter2 := getIntFromStore(t, store, deliverKey2)
|
|
require.Equal(t, int64(2), msgCounter2)
|
|
}
|
|
|
|
func anyMessage(t *testing.T, cdc codec.Codec, msg *baseapptestutil.MsgSend) *any.Any {
|
|
t.Helper()
|
|
b, err := cdc.Marshal(msg)
|
|
require.NoError(t, err)
|
|
return &any.Any{
|
|
TypeUrl: sdk.MsgTypeURL(msg),
|
|
Value: b,
|
|
}
|
|
}
|
|
|
|
func TestABCI_Query_SimulateNestedMessagesTx(t *testing.T) {
|
|
anteOpt := func(bapp *baseapp.BaseApp) {
|
|
bapp.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, err error) {
|
|
newCtx = ctx.WithGasMeter(storetypes.NewGasMeter(uint64(15)))
|
|
return
|
|
})
|
|
}
|
|
suite := NewBaseAppSuite(t, anteOpt)
|
|
|
|
_, err := suite.baseApp.InitChain(&abci.InitChainRequest{
|
|
ConsensusParams: &cmtproto.ConsensusParams{},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
baseapptestutil.RegisterNestedMessagesServer(suite.baseApp.MsgServiceRouter(), NestedMessgesServerImpl{})
|
|
baseapptestutil.RegisterSendServer(suite.baseApp.MsgServiceRouter(), SendServerImpl{})
|
|
|
|
_, _, addr := testdata.KeyTestPubAddr()
|
|
_, _, toAddr := testdata.KeyTestPubAddr()
|
|
tests := []struct {
|
|
name string
|
|
message sdk.Msg
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "ok nested message",
|
|
message: &baseapptestutil.MsgSend{
|
|
From: addr.String(),
|
|
To: toAddr.String(),
|
|
Amount: "10000stake",
|
|
},
|
|
},
|
|
{
|
|
name: "different signers",
|
|
message: &baseapptestutil.MsgSend{
|
|
From: toAddr.String(),
|
|
To: addr.String(),
|
|
Amount: "10000stake",
|
|
},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "empty from",
|
|
message: &baseapptestutil.MsgSend{
|
|
From: "",
|
|
To: toAddr.String(),
|
|
Amount: "10000stake",
|
|
},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "empty to",
|
|
message: &baseapptestutil.MsgSend{
|
|
From: addr.String(),
|
|
To: "",
|
|
Amount: "10000stake",
|
|
},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "negative amount",
|
|
message: &baseapptestutil.MsgSend{
|
|
From: addr.String(),
|
|
To: toAddr.String(),
|
|
Amount: "-10000stake",
|
|
},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "with nested messages",
|
|
message: &baseapptestutil.MsgNestedMessages{
|
|
Signer: addr.String(),
|
|
Messages: []*any.Any{
|
|
anyMessage(t, suite.cdc, &baseapptestutil.MsgSend{
|
|
From: addr.String(),
|
|
To: toAddr.String(),
|
|
Amount: "10000stake",
|
|
}),
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "with invalid nested messages",
|
|
message: &baseapptestutil.MsgNestedMessages{
|
|
Signer: addr.String(),
|
|
Messages: []*any.Any{
|
|
anyMessage(t, suite.cdc, &baseapptestutil.MsgSend{
|
|
From: "",
|
|
To: toAddr.String(),
|
|
Amount: "10000stake",
|
|
}),
|
|
},
|
|
},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "with different signer ",
|
|
message: &baseapptestutil.MsgNestedMessages{
|
|
Signer: addr.String(),
|
|
Messages: []*any.Any{
|
|
anyMessage(t, suite.cdc, &baseapptestutil.MsgSend{
|
|
From: toAddr.String(),
|
|
To: addr.String(),
|
|
Amount: "10000stake",
|
|
}),
|
|
},
|
|
},
|
|
wantErr: true,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
nestedMessages := make([]*any.Any, 1)
|
|
b, err := suite.cdc.Marshal(tt.message)
|
|
require.NoError(t, err)
|
|
nestedMessages[0] = &any.Any{
|
|
TypeUrl: sdk.MsgTypeURL(tt.message),
|
|
Value: b,
|
|
}
|
|
|
|
msg := &baseapptestutil.MsgNestedMessages{
|
|
Messages: nestedMessages,
|
|
Signer: addr.String(),
|
|
}
|
|
|
|
builder := suite.txConfig.NewTxBuilder()
|
|
err = builder.SetMsgs(msg)
|
|
require.NoError(t, err)
|
|
setTxSignature(t, builder, 0)
|
|
tx := builder.GetTx()
|
|
|
|
txBytes, err := suite.txConfig.TxEncoder()(tx)
|
|
require.Nil(t, err)
|
|
|
|
_, result, err := suite.baseApp.Simulate(txBytes)
|
|
if tt.wantErr {
|
|
require.Error(t, err)
|
|
require.Nil(t, result)
|
|
} else {
|
|
require.NoError(t, err)
|
|
require.NotNil(t, result)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestABCI_Query_SimulateNestedMessagesGas(t *testing.T) {
|
|
anteOpt := func(bapp *baseapp.BaseApp) {
|
|
bapp.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, err error) {
|
|
newCtx = ctx.WithGasMeter(storetypes.NewGasMeter(uint64(10)))
|
|
return
|
|
})
|
|
}
|
|
|
|
_, _, addr := testdata.KeyTestPubAddr()
|
|
_, _, toAddr := testdata.KeyTestPubAddr()
|
|
|
|
tests := []struct {
|
|
name string
|
|
suite *BaseAppSuite
|
|
message sdk.Msg
|
|
consumedGas uint64
|
|
}{
|
|
{
|
|
name: "don't add gas",
|
|
suite: NewBaseAppSuite(t, anteOpt),
|
|
message: &baseapptestutil.MsgSend{
|
|
From: addr.String(),
|
|
To: toAddr.String(),
|
|
Amount: "10000stake",
|
|
},
|
|
consumedGas: 5,
|
|
},
|
|
{
|
|
name: "add gas",
|
|
suite: NewBaseAppSuite(t, anteOpt, baseapp.SetIncludeNestedMsgsGas([]sdk.Msg{&baseapptestutil.MsgNestedMessages{}})),
|
|
message: &baseapptestutil.MsgSend{
|
|
From: addr.String(),
|
|
To: toAddr.String(),
|
|
Amount: "10000stake",
|
|
},
|
|
consumedGas: 10,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
_, err := tt.suite.baseApp.InitChain(&abci.InitChainRequest{
|
|
ConsensusParams: &cmtproto.ConsensusParams{},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
baseapptestutil.RegisterNestedMessagesServer(tt.suite.baseApp.MsgServiceRouter(), NestedMessgesServerImpl{})
|
|
baseapptestutil.RegisterSendServer(tt.suite.baseApp.MsgServiceRouter(), SendServerImpl{})
|
|
|
|
nestedMessages := make([]*any.Any, 1)
|
|
b, err := tt.suite.cdc.Marshal(tt.message)
|
|
require.NoError(t, err)
|
|
nestedMessages[0] = &any.Any{
|
|
TypeUrl: sdk.MsgTypeURL(tt.message),
|
|
Value: b,
|
|
}
|
|
|
|
msg := &baseapptestutil.MsgNestedMessages{
|
|
Messages: nestedMessages,
|
|
Signer: addr.String(),
|
|
}
|
|
|
|
builder := tt.suite.txConfig.NewTxBuilder()
|
|
err = builder.SetMsgs(msg)
|
|
require.NoError(t, err)
|
|
setTxSignature(t, builder, 0)
|
|
tx := builder.GetTx()
|
|
|
|
txBytes, err := tt.suite.txConfig.TxEncoder()(tx)
|
|
require.Nil(t, err)
|
|
|
|
gas, result, err := tt.suite.baseApp.Simulate(txBytes)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, result)
|
|
require.True(t, gas.GasUsed == tt.consumedGas)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestABCI_Query_SimulateTx(t *testing.T) {
|
|
gasConsumed := uint64(5)
|
|
anteOpt := func(bapp *baseapp.BaseApp) {
|
|
bapp.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, err error) {
|
|
newCtx = ctx.WithGasMeter(storetypes.NewGasMeter(gasConsumed))
|
|
return
|
|
})
|
|
}
|
|
suite := NewBaseAppSuite(t, anteOpt)
|
|
|
|
_, err := suite.baseApp.InitChain(&abci.InitChainRequest{
|
|
ConsensusParams: &cmtproto.ConsensusParams{},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
baseapptestutil.RegisterCounterServer(suite.baseApp.MsgServiceRouter(), CounterServerImplGasMeterOnly{gasConsumed})
|
|
|
|
nBlocks := 3
|
|
for blockN := 0; blockN < nBlocks; blockN++ {
|
|
count := int64(blockN + 1)
|
|
|
|
tx := newTxCounter(t, suite.txConfig, count, count)
|
|
|
|
txBytes, err := suite.txConfig.TxEncoder()(tx)
|
|
require.Nil(t, err)
|
|
|
|
// simulate a message, check gas reported
|
|
gInfo, result, err := suite.baseApp.Simulate(txBytes)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, result)
|
|
require.Equal(t, gasConsumed, gInfo.GasUsed)
|
|
|
|
// simulate again, same result
|
|
gInfo, result, err = suite.baseApp.Simulate(txBytes)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, result)
|
|
require.Equal(t, gasConsumed, gInfo.GasUsed)
|
|
|
|
// simulate by calling Query with encoded tx
|
|
query := abci.QueryRequest{
|
|
Path: "/app/simulate",
|
|
Data: txBytes,
|
|
}
|
|
queryResult, err := suite.baseApp.Query(context.TODO(), &query)
|
|
require.NoError(t, err)
|
|
require.True(t, queryResult.IsOK(), queryResult.Log)
|
|
|
|
var simRes sdk.SimulationResponse
|
|
require.NoError(t, jsonpb.Unmarshal(strings.NewReader(string(queryResult.Value)), &simRes))
|
|
|
|
require.Equal(t, gInfo, simRes.GasInfo)
|
|
require.Equal(t, result.Log, simRes.Result.Log)
|
|
require.Equal(t, result.Events, simRes.Result.Events)
|
|
require.True(t, bytes.Equal(result.Data, simRes.Result.Data))
|
|
|
|
_, err = suite.baseApp.FinalizeBlock(&abci.FinalizeBlockRequest{Height: count})
|
|
require.NoError(t, err)
|
|
_, err = suite.baseApp.Commit()
|
|
require.NoError(t, err)
|
|
}
|
|
}
|
|
|
|
func TestABCI_InvalidTransaction(t *testing.T) {
|
|
anteOpt := func(bapp *baseapp.BaseApp) {
|
|
bapp.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, err error) {
|
|
return
|
|
})
|
|
}
|
|
|
|
suite := NewBaseAppSuite(t, anteOpt)
|
|
baseapptestutil.RegisterCounterServer(suite.baseApp.MsgServiceRouter(), CounterServerImplGasMeterOnly{})
|
|
|
|
_, err := suite.baseApp.InitChain(&abci.InitChainRequest{
|
|
ConsensusParams: &cmtproto.ConsensusParams{},
|
|
})
|
|
require.NoError(t, err)
|
|
_, err = suite.baseApp.FinalizeBlock(&abci.FinalizeBlockRequest{
|
|
Height: 1,
|
|
})
|
|
require.NoError(t, err)
|
|
// malformed transaction bytes
|
|
{
|
|
bz := []byte("example vote extension")
|
|
result, err := suite.baseApp.FinalizeBlock(&abci.FinalizeBlockRequest{
|
|
Height: 1,
|
|
Txs: [][]byte{bz},
|
|
})
|
|
|
|
require.EqualValues(t, sdkerrors.ErrTxDecode.Codespace(), result.TxResults[0].Codespace, err)
|
|
require.EqualValues(t, sdkerrors.ErrTxDecode.ABCICode(), result.TxResults[0].Code, err)
|
|
require.EqualValues(t, 0, result.TxResults[0].GasUsed, err)
|
|
require.EqualValues(t, 0, result.TxResults[0].GasWanted, err)
|
|
}
|
|
// transaction with no messages
|
|
{
|
|
emptyTx := suite.txConfig.NewTxBuilder().GetTx()
|
|
bz, err := suite.txConfig.TxEncoder()(emptyTx)
|
|
require.NoError(t, err)
|
|
result, err := suite.baseApp.FinalizeBlock(&abci.FinalizeBlockRequest{
|
|
Height: 1,
|
|
Txs: [][]byte{bz},
|
|
})
|
|
require.EqualValues(t, sdkerrors.ErrInvalidRequest.Codespace(), result.TxResults[0].Codespace, err)
|
|
require.EqualValues(t, sdkerrors.ErrInvalidRequest.ABCICode(), result.TxResults[0].Code, err)
|
|
}
|
|
|
|
// transaction where ValidateBasic fails
|
|
{
|
|
testCases := []struct {
|
|
tx signing.Tx
|
|
fail bool
|
|
}{
|
|
{newTxCounter(t, suite.txConfig, 0, 0), false},
|
|
{newTxCounter(t, suite.txConfig, -1, 0), false},
|
|
{newTxCounter(t, suite.txConfig, 100, 100), false},
|
|
{newTxCounter(t, suite.txConfig, 100, 5, 4, 3, 2, 1), false},
|
|
|
|
{newTxCounter(t, suite.txConfig, 0, -1), true},
|
|
{newTxCounter(t, suite.txConfig, 0, 1, -2), true},
|
|
{newTxCounter(t, suite.txConfig, 0, 1, 2, -10, 5), true},
|
|
}
|
|
|
|
for _, testCase := range testCases {
|
|
tx := testCase.tx
|
|
_, result, err := suite.baseApp.SimDeliver(suite.txConfig.TxEncoder(), tx)
|
|
|
|
if testCase.fail {
|
|
require.Error(t, err)
|
|
|
|
space, code, _ := errorsmod.ABCIInfo(err, false)
|
|
require.EqualValues(t, sdkerrors.ErrInvalidSequence.Codespace(), space, err)
|
|
require.EqualValues(t, sdkerrors.ErrInvalidSequence.ABCICode(), code, err)
|
|
} else {
|
|
require.NotNil(t, result)
|
|
}
|
|
}
|
|
}
|
|
|
|
// transaction with no known route
|
|
{
|
|
txBuilder := suite.txConfig.NewTxBuilder()
|
|
_, _, addr := testdata.KeyTestPubAddr()
|
|
err = txBuilder.SetMsgs(&baseapptestutil.MsgCounter2{Signer: addr.String()})
|
|
require.NoError(t, err)
|
|
setTxSignature(t, txBuilder, 0)
|
|
unknownRouteTx := txBuilder.GetTx()
|
|
|
|
_, result, err := suite.baseApp.SimDeliver(suite.txConfig.TxEncoder(), unknownRouteTx)
|
|
require.Error(t, err)
|
|
require.Nil(t, result)
|
|
|
|
space, code, _ := errorsmod.ABCIInfo(err, false)
|
|
require.EqualValues(t, sdkerrors.ErrUnknownRequest.Codespace(), space, err)
|
|
require.EqualValues(t, sdkerrors.ErrUnknownRequest.ABCICode(), code, err)
|
|
|
|
txBuilder = suite.txConfig.NewTxBuilder()
|
|
err = txBuilder.SetMsgs(
|
|
&baseapptestutil.MsgCounter{Signer: addr.String()},
|
|
&baseapptestutil.MsgCounter2{Signer: addr.String()},
|
|
)
|
|
require.NoError(t, err)
|
|
setTxSignature(t, txBuilder, 0)
|
|
unknownRouteTx = txBuilder.GetTx()
|
|
|
|
_, result, err = suite.baseApp.SimDeliver(suite.txConfig.TxEncoder(), unknownRouteTx)
|
|
require.Error(t, err)
|
|
require.Nil(t, result)
|
|
|
|
space, code, _ = errorsmod.ABCIInfo(err, false)
|
|
require.EqualValues(t, sdkerrors.ErrUnknownRequest.Codespace(), space, err)
|
|
require.EqualValues(t, sdkerrors.ErrUnknownRequest.ABCICode(), code, err)
|
|
}
|
|
}
|
|
|
|
func TestABCI_TxGasLimits(t *testing.T) {
|
|
gasGranted := uint64(10)
|
|
anteOpt := func(bapp *baseapp.BaseApp) {
|
|
bapp.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, err error) {
|
|
newCtx = ctx.WithGasMeter(storetypes.NewGasMeter(gasGranted))
|
|
|
|
// AnteHandlers must have their own defer/recover in order for the BaseApp
|
|
// to know how much gas was used! This is because the GasMeter is created in
|
|
// the AnteHandler, but if it panics the context won't be set properly in
|
|
// runTx's recover call.
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
switch rType := r.(type) {
|
|
case storetypes.ErrorOutOfGas:
|
|
err = errorsmod.Wrapf(sdkerrors.ErrOutOfGas, "out of gas in location: %v", rType.Descriptor)
|
|
default:
|
|
panic(r)
|
|
}
|
|
}
|
|
}()
|
|
|
|
count, _ := parseTxMemo(t, tx)
|
|
newCtx.GasMeter().ConsumeGas(uint64(count), "counter-ante")
|
|
|
|
return newCtx, nil
|
|
})
|
|
}
|
|
|
|
suite := NewBaseAppSuite(t, anteOpt)
|
|
baseapptestutil.RegisterCounterServer(suite.baseApp.MsgServiceRouter(), CounterServerImplGasMeterOnly{})
|
|
|
|
_, err := suite.baseApp.InitChain(&abci.InitChainRequest{
|
|
ConsensusParams: &cmtproto.ConsensusParams{},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
_, err = suite.baseApp.FinalizeBlock(&abci.FinalizeBlockRequest{
|
|
Height: 1,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
_, err = suite.baseApp.Commit()
|
|
require.NoError(t, err)
|
|
|
|
testCases := []struct {
|
|
tx signing.Tx
|
|
gasUsed int64
|
|
fail bool
|
|
}{
|
|
{newTxCounter(t, suite.txConfig, 0, 0), 0, false},
|
|
{newTxCounter(t, suite.txConfig, 1, 1), 2, false},
|
|
{newTxCounter(t, suite.txConfig, 9, 1), 10, false},
|
|
{newTxCounter(t, suite.txConfig, 1, 9), 10, false},
|
|
{newTxCounter(t, suite.txConfig, 10, 0), 10, false},
|
|
|
|
{newTxCounter(t, suite.txConfig, 9, 2), 11, true},
|
|
{newTxCounter(t, suite.txConfig, 2, 9), 11, true},
|
|
// {newTxCounter(t, suite.txConfig, 9, 1, 1), 11, true},
|
|
// {newTxCounter(t, suite.txConfig, 1, 8, 1, 1), 11, true},
|
|
// {newTxCounter(t, suite.txConfig, 11, 0), 11, true},
|
|
// {newTxCounter(t, suite.txConfig, 0, 11), 11, true},
|
|
// {newTxCounter(t, suite.txConfig, 0, 5, 11), 16, true},
|
|
}
|
|
|
|
txs := [][]byte{}
|
|
for _, tc := range testCases {
|
|
tx := tc.tx
|
|
bz, err := suite.txConfig.TxEncoder()(tx)
|
|
require.NoError(t, err)
|
|
txs = append(txs, bz)
|
|
}
|
|
|
|
// Deliver the txs
|
|
res, err := suite.baseApp.FinalizeBlock(&abci.FinalizeBlockRequest{
|
|
Height: 2,
|
|
Txs: txs,
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
|
|
for i, tc := range testCases {
|
|
|
|
result := res.TxResults[i]
|
|
|
|
require.Equal(t, tc.gasUsed, result.GasUsed, fmt.Sprintf("tc #%d; gas: %v, result: %v, err: %s", i, result.GasUsed, result, err))
|
|
|
|
// check for out of gas
|
|
if !tc.fail {
|
|
require.NotNil(t, result, fmt.Sprintf("%d: %v, %v", i, tc, err))
|
|
} else {
|
|
require.EqualValues(t, sdkerrors.ErrOutOfGas.Codespace(), result.Codespace, err)
|
|
require.EqualValues(t, sdkerrors.ErrOutOfGas.ABCICode(), result.Code, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestABCI_MaxBlockGasLimits(t *testing.T) {
|
|
gasGranted := uint64(10)
|
|
anteOpt := func(bapp *baseapp.BaseApp) {
|
|
bapp.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, err error) {
|
|
newCtx = ctx.WithGasMeter(storetypes.NewGasMeter(gasGranted))
|
|
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
switch rType := r.(type) {
|
|
case storetypes.ErrorOutOfGas:
|
|
err = errorsmod.Wrapf(sdkerrors.ErrOutOfGas, "out of gas in location: %v", rType.Descriptor)
|
|
default:
|
|
panic(r)
|
|
}
|
|
}
|
|
}()
|
|
|
|
count, _ := parseTxMemo(t, tx)
|
|
newCtx.GasMeter().ConsumeGas(uint64(count), "counter-ante")
|
|
|
|
return
|
|
})
|
|
}
|
|
|
|
suite := NewBaseAppSuite(t, anteOpt)
|
|
baseapptestutil.RegisterCounterServer(suite.baseApp.MsgServiceRouter(), CounterServerImplGasMeterOnly{})
|
|
|
|
_, err := suite.baseApp.InitChain(&abci.InitChainRequest{
|
|
ConsensusParams: &cmtproto.ConsensusParams{
|
|
Block: &cmtproto.BlockParams{
|
|
MaxGas: 100,
|
|
},
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
_, err = suite.baseApp.FinalizeBlock(&abci.FinalizeBlockRequest{Height: 1})
|
|
require.NoError(t, err)
|
|
|
|
testCases := []struct {
|
|
tx signing.Tx
|
|
numDelivers int
|
|
gasUsedPerDeliver uint64
|
|
fail bool
|
|
failAfterDeliver int
|
|
}{
|
|
{newTxCounter(t, suite.txConfig, 0, 0), 0, 0, false, 0},
|
|
{newTxCounter(t, suite.txConfig, 9, 1), 2, 10, false, 0},
|
|
{newTxCounter(t, suite.txConfig, 10, 0), 3, 10, false, 0},
|
|
{newTxCounter(t, suite.txConfig, 10, 0), 10, 10, false, 0},
|
|
{newTxCounter(t, suite.txConfig, 2, 7), 11, 9, false, 0},
|
|
// {newTxCounter(t, suite.txConfig, 10, 0), 10, 10, false, 0}, // hit the limit but pass
|
|
|
|
// {newTxCounter(t, suite.txConfig, 10, 0), 11, 10, true, 10},
|
|
// {newTxCounter(t, suite.txConfig, 10, 0), 15, 10, true, 10},
|
|
// {newTxCounter(t, suite.txConfig, 9, 0), 12, 9, true, 11}, // fly past the limit
|
|
}
|
|
|
|
for i, tc := range testCases {
|
|
tx := tc.tx
|
|
|
|
// reset block gas
|
|
_, err := suite.baseApp.FinalizeBlock(&abci.FinalizeBlockRequest{Height: suite.baseApp.LastBlockHeight() + 1})
|
|
require.NoError(t, err)
|
|
|
|
// execute the transaction multiple times
|
|
for j := 0; j < tc.numDelivers; j++ {
|
|
|
|
_, result, err := suite.baseApp.SimDeliver(suite.txConfig.TxEncoder(), tx)
|
|
|
|
ctx := getFinalizeBlockStateCtx(suite.baseApp)
|
|
|
|
// check for failed transactions
|
|
if tc.fail && (j+1) > tc.failAfterDeliver {
|
|
require.Error(t, err, fmt.Sprintf("tc #%d; result: %v, err: %s", i, result, err))
|
|
require.Nil(t, tx, fmt.Sprintf("tc #%d; result: %v, err: %s", i, result, err))
|
|
|
|
space, code, _ := errorsmod.ABCIInfo(err, false)
|
|
require.EqualValues(t, sdkerrors.ErrOutOfGas.Codespace(), space, err)
|
|
require.EqualValues(t, sdkerrors.ErrOutOfGas.ABCICode(), code, err)
|
|
require.True(t, ctx.BlockGasMeter().IsOutOfGas())
|
|
} else {
|
|
// check gas used and wanted
|
|
blockGasUsed := ctx.BlockGasMeter().GasConsumed()
|
|
expBlockGasUsed := tc.gasUsedPerDeliver * uint64(j+1)
|
|
require.Equal(
|
|
t, expBlockGasUsed, blockGasUsed,
|
|
fmt.Sprintf("%d,%d: %v, %v, %v, %v", i, j, tc, expBlockGasUsed, blockGasUsed, result),
|
|
)
|
|
|
|
require.NotNil(t, tx, fmt.Sprintf("tc #%d; currDeliver: %d, result: %v, err: %s", i, j, result, err))
|
|
require.False(t, ctx.BlockGasMeter().IsPastLimit())
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestABCI_GasConsumptionBadTx(t *testing.T) {
|
|
gasWanted := uint64(5)
|
|
anteOpt := func(bapp *baseapp.BaseApp) {
|
|
bapp.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, err error) {
|
|
newCtx = ctx.WithGasMeter(storetypes.NewGasMeter(gasWanted))
|
|
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
switch rType := r.(type) {
|
|
case storetypes.ErrorOutOfGas:
|
|
log := fmt.Sprintf("out of gas in location: %v", rType.Descriptor)
|
|
err = errorsmod.Wrap(sdkerrors.ErrOutOfGas, log)
|
|
default:
|
|
panic(r)
|
|
}
|
|
}
|
|
}()
|
|
|
|
counter, failOnAnte := parseTxMemo(t, tx)
|
|
newCtx.GasMeter().ConsumeGas(uint64(counter), "counter-ante")
|
|
if failOnAnte {
|
|
return newCtx, errorsmod.Wrap(sdkerrors.ErrUnauthorized, "ante handler failure")
|
|
}
|
|
|
|
return
|
|
})
|
|
}
|
|
|
|
suite := NewBaseAppSuite(t, anteOpt)
|
|
baseapptestutil.RegisterCounterServer(suite.baseApp.MsgServiceRouter(), CounterServerImplGasMeterOnly{})
|
|
|
|
_, err := suite.baseApp.InitChain(&abci.InitChainRequest{
|
|
ConsensusParams: &cmtproto.ConsensusParams{
|
|
Block: &cmtproto.BlockParams{
|
|
MaxGas: 9,
|
|
},
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
tx := newTxCounter(t, suite.txConfig, 5, 0)
|
|
tx = setFailOnAnte(t, suite.txConfig, tx, true)
|
|
txBytes, err := suite.txConfig.TxEncoder()(tx)
|
|
require.NoError(t, err)
|
|
|
|
// require next tx to fail due to black gas limit
|
|
tx = newTxCounter(t, suite.txConfig, 5, 0)
|
|
txBytes2, err := suite.txConfig.TxEncoder()(tx)
|
|
require.NoError(t, err)
|
|
|
|
_, err = suite.baseApp.FinalizeBlock(&abci.FinalizeBlockRequest{
|
|
Height: suite.baseApp.LastBlockHeight() + 1,
|
|
Txs: [][]byte{txBytes, txBytes2},
|
|
})
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func TestABCI_Query(t *testing.T) {
|
|
key, value := []byte("hello"), []byte("goodbye")
|
|
anteOpt := func(bapp *baseapp.BaseApp) {
|
|
bapp.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, err error) {
|
|
store := ctx.KVStore(capKey1)
|
|
store.Set(key, value)
|
|
return
|
|
})
|
|
}
|
|
|
|
suite := NewBaseAppSuite(t, anteOpt)
|
|
baseapptestutil.RegisterCounterServer(suite.baseApp.MsgServiceRouter(), CounterServerImplGasMeterOnly{})
|
|
|
|
_, err := suite.baseApp.InitChain(&abci.InitChainRequest{
|
|
ConsensusParams: &cmtproto.ConsensusParams{},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// NOTE: "/store/key1" tells us KVStore
|
|
// and the final "/key" says to use the data as the
|
|
// key in the given KVStore ...
|
|
query := abci.QueryRequest{
|
|
Path: "/store/key1/key",
|
|
Data: key,
|
|
}
|
|
tx := newTxCounter(t, suite.txConfig, 0, 0)
|
|
|
|
// query is empty before we do anything
|
|
res, err := suite.baseApp.Query(context.TODO(), &query)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 0, len(res.Value))
|
|
|
|
// query is still empty after a CheckTx
|
|
_, resTx, err := suite.baseApp.SimCheck(suite.txConfig.TxEncoder(), tx)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, resTx)
|
|
|
|
res, err = suite.baseApp.Query(context.TODO(), &query)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 0, len(res.Value))
|
|
|
|
bz, err := suite.txConfig.TxEncoder()(tx)
|
|
require.NoError(t, err)
|
|
|
|
_, err = suite.baseApp.FinalizeBlock(&abci.FinalizeBlockRequest{
|
|
Height: 1,
|
|
Txs: [][]byte{bz},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
res, err = suite.baseApp.Query(context.TODO(), &query)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 0, len(res.Value))
|
|
|
|
// query returns correct value after Commit
|
|
_, err = suite.baseApp.Commit()
|
|
require.NoError(t, err)
|
|
|
|
res, err = suite.baseApp.Query(context.TODO(), &query)
|
|
require.NoError(t, err)
|
|
require.Equal(t, value, res.Value)
|
|
}
|
|
|
|
func TestABCI_GetBlockRetentionHeight(t *testing.T) {
|
|
logger := log.NewTestLogger(t)
|
|
db := dbm.NewMemDB()
|
|
name := t.Name()
|
|
|
|
snapshotStore, err := snapshots.NewStore(dbm.NewMemDB(), testutil.GetTempDir(t))
|
|
require.NoError(t, err)
|
|
|
|
testCases := map[string]struct {
|
|
bapp *baseapp.BaseApp
|
|
maxAgeBlocks int64
|
|
commitHeight int64
|
|
expected int64
|
|
}{
|
|
"defaults": {
|
|
bapp: baseapp.NewBaseApp(name, logger, db, nil),
|
|
maxAgeBlocks: 0,
|
|
commitHeight: 499000,
|
|
expected: 0,
|
|
},
|
|
"pruning unbonding time only": {
|
|
bapp: baseapp.NewBaseApp(name, logger, db, nil, baseapp.SetMinRetainBlocks(1)),
|
|
maxAgeBlocks: 362880,
|
|
commitHeight: 499000,
|
|
expected: 136120,
|
|
},
|
|
"pruning iavl snapshot only": {
|
|
bapp: baseapp.NewBaseApp(
|
|
name, logger, db, nil,
|
|
baseapp.SetPruning(pruningtypes.NewPruningOptions(pruningtypes.PruningNothing)),
|
|
baseapp.SetMinRetainBlocks(1),
|
|
baseapp.SetSnapshot(snapshotStore, snapshottypes.NewSnapshotOptions(10000, 1)),
|
|
),
|
|
maxAgeBlocks: 0,
|
|
commitHeight: 499000,
|
|
expected: 489000,
|
|
},
|
|
"pruning state sync snapshot only": {
|
|
bapp: baseapp.NewBaseApp(
|
|
name, logger, db, nil,
|
|
baseapp.SetSnapshot(snapshotStore, snapshottypes.NewSnapshotOptions(50000, 3)),
|
|
baseapp.SetMinRetainBlocks(1),
|
|
),
|
|
maxAgeBlocks: 0,
|
|
commitHeight: 499000,
|
|
expected: 349000,
|
|
},
|
|
"pruning min retention only": {
|
|
bapp: baseapp.NewBaseApp(
|
|
name, logger, db, nil,
|
|
baseapp.SetMinRetainBlocks(400000),
|
|
),
|
|
maxAgeBlocks: 0,
|
|
commitHeight: 499000,
|
|
expected: 99000,
|
|
},
|
|
"pruning all conditions": {
|
|
bapp: baseapp.NewBaseApp(
|
|
name, logger, db, nil,
|
|
baseapp.SetPruning(pruningtypes.NewCustomPruningOptions(0, 0)),
|
|
baseapp.SetMinRetainBlocks(400000),
|
|
baseapp.SetSnapshot(snapshotStore, snapshottypes.NewSnapshotOptions(50000, 3)),
|
|
),
|
|
maxAgeBlocks: 362880,
|
|
commitHeight: 499000,
|
|
expected: 99000,
|
|
},
|
|
"no pruning due to no persisted state": {
|
|
bapp: baseapp.NewBaseApp(
|
|
name, logger, db, nil,
|
|
baseapp.SetPruning(pruningtypes.NewCustomPruningOptions(0, 0)),
|
|
baseapp.SetMinRetainBlocks(400000),
|
|
baseapp.SetSnapshot(snapshotStore, snapshottypes.NewSnapshotOptions(50000, 3)),
|
|
),
|
|
maxAgeBlocks: 362880,
|
|
commitHeight: 10000,
|
|
expected: 0,
|
|
},
|
|
"disable pruning": {
|
|
bapp: baseapp.NewBaseApp(
|
|
name, logger, db, nil,
|
|
baseapp.SetPruning(pruningtypes.NewCustomPruningOptions(0, 0)),
|
|
baseapp.SetMinRetainBlocks(0),
|
|
baseapp.SetSnapshot(snapshotStore, snapshottypes.NewSnapshotOptions(50000, 3)),
|
|
),
|
|
maxAgeBlocks: 362880,
|
|
commitHeight: 499000,
|
|
expected: 0,
|
|
},
|
|
}
|
|
|
|
for name, tc := range testCases {
|
|
tc := tc
|
|
|
|
tc.bapp.SetParamStore(¶mStore{db: dbm.NewMemDB()})
|
|
_, err := tc.bapp.InitChain(&abci.InitChainRequest{
|
|
ConsensusParams: &cmtproto.ConsensusParams{
|
|
Evidence: &cmtproto.EvidenceParams{
|
|
MaxAgeNumBlocks: tc.maxAgeBlocks,
|
|
},
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
t.Run(name, func(t *testing.T) {
|
|
require.Equal(t, tc.expected, tc.bapp.GetBlockRetentionHeight(tc.commitHeight))
|
|
})
|
|
}
|
|
}
|
|
|
|
// Verifies that PrepareCheckState is called with the checkState.
|
|
func TestPrepareCheckStateCalledWithCheckState(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
logger := log.NewTestLogger(t)
|
|
db := dbm.NewMemDB()
|
|
name := t.Name()
|
|
app := baseapp.NewBaseApp(name, logger, db, nil)
|
|
|
|
wasPrepareCheckStateCalled := false
|
|
app.SetPrepareCheckStater(func(ctx sdk.Context) {
|
|
require.Equal(t, true, ctx.IsCheckTx())
|
|
wasPrepareCheckStateCalled = true
|
|
})
|
|
|
|
_, err := app.FinalizeBlock(&abci.FinalizeBlockRequest{Height: 1})
|
|
require.NoError(t, err)
|
|
_, err = app.Commit()
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, true, wasPrepareCheckStateCalled)
|
|
}
|
|
|
|
// Verifies that the Precommiter is called with the deliverState.
|
|
func TestPrecommiterCalledWithDeliverState(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
logger := log.NewTestLogger(t)
|
|
db := dbm.NewMemDB()
|
|
name := t.Name()
|
|
app := baseapp.NewBaseApp(name, logger, db, nil)
|
|
|
|
wasPrecommiterCalled := false
|
|
app.SetPrecommiter(func(ctx sdk.Context) {
|
|
require.Equal(t, false, ctx.IsCheckTx())
|
|
require.Equal(t, false, ctx.IsReCheckTx())
|
|
wasPrecommiterCalled = true
|
|
})
|
|
|
|
_, err := app.FinalizeBlock(&abci.FinalizeBlockRequest{Height: 1})
|
|
require.NoError(t, err)
|
|
_, err = app.Commit()
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, true, wasPrecommiterCalled)
|
|
}
|
|
|
|
func TestABCI_Proposal_HappyPath(t *testing.T) {
|
|
anteKey := []byte("ante-key")
|
|
pool := mempool.NewSenderNonceMempool(mempool.SenderNonceMaxTxOpt(5000))
|
|
anteOpt := func(bapp *baseapp.BaseApp) {
|
|
bapp.SetAnteHandler(anteHandlerTxTest(t, capKey1, anteKey))
|
|
}
|
|
|
|
suite := NewBaseAppSuite(t, anteOpt, baseapp.SetMempool(pool))
|
|
baseapptestutil.RegisterKeyValueServer(suite.baseApp.MsgServiceRouter(), MsgKeyValueImpl{})
|
|
baseapptestutil.RegisterCounterServer(suite.baseApp.MsgServiceRouter(), NoopCounterServerImpl{})
|
|
|
|
_, err := suite.baseApp.InitChain(&abci.InitChainRequest{
|
|
ConsensusParams: &cmtproto.ConsensusParams{},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
tx := newTxCounter(t, suite.txConfig, 0, 1)
|
|
txBytes, err := suite.txConfig.TxEncoder()(tx)
|
|
require.NoError(t, err)
|
|
|
|
reqCheckTx := abci.CheckTxRequest{
|
|
Tx: txBytes,
|
|
Type: abci.CHECK_TX_TYPE_CHECK,
|
|
}
|
|
_, err = suite.baseApp.CheckTx(&reqCheckTx)
|
|
require.NoError(t, err)
|
|
|
|
tx2 := newTxCounter(t, suite.txConfig, 1, 1)
|
|
|
|
tx2Bytes, err := suite.txConfig.TxEncoder()(tx2)
|
|
require.NoError(t, err)
|
|
|
|
err = pool.Insert(sdk.Context{}, tx2)
|
|
require.NoError(t, err)
|
|
|
|
reqPrepareProposal := abci.PrepareProposalRequest{
|
|
MaxTxBytes: 1000,
|
|
Height: 1,
|
|
}
|
|
resPrepareProposal, err := suite.baseApp.PrepareProposal(&reqPrepareProposal)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 2, len(resPrepareProposal.Txs))
|
|
|
|
reqProposalTxBytes := [2][]byte{
|
|
txBytes,
|
|
tx2Bytes,
|
|
}
|
|
reqProcessProposal := abci.ProcessProposalRequest{
|
|
Txs: reqProposalTxBytes[:],
|
|
Height: reqPrepareProposal.Height,
|
|
}
|
|
|
|
resProcessProposal, err := suite.baseApp.ProcessProposal(&reqProcessProposal)
|
|
require.NoError(t, err)
|
|
require.Equal(t, abci.PROCESS_PROPOSAL_STATUS_ACCEPT, resProcessProposal.Status)
|
|
|
|
// the same txs as in PrepareProposal
|
|
res, err := suite.baseApp.FinalizeBlock(&abci.FinalizeBlockRequest{
|
|
Height: suite.baseApp.LastBlockHeight() + 1,
|
|
Txs: reqProposalTxBytes[:],
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, 0, pool.CountTx())
|
|
|
|
require.NotEmpty(t, res.TxResults[0].Events)
|
|
require.True(t, res.TxResults[0].IsOK(), fmt.Sprintf("%v", res))
|
|
}
|
|
|
|
func TestABCI_Proposal_Read_State_PrepareProposal(t *testing.T) {
|
|
someKey := []byte("some-key")
|
|
|
|
setInitChainerOpt := func(bapp *baseapp.BaseApp) {
|
|
bapp.SetInitChainer(func(ctx sdk.Context, req *abci.InitChainRequest) (*abci.InitChainResponse, error) {
|
|
ctx.KVStore(capKey1).Set(someKey, []byte(fooStr))
|
|
return &abci.InitChainResponse{}, nil
|
|
})
|
|
}
|
|
|
|
prepareOpt := func(bapp *baseapp.BaseApp) {
|
|
bapp.SetPrepareProposal(func(ctx sdk.Context, req *abci.PrepareProposalRequest) (*abci.PrepareProposalResponse, error) {
|
|
value := ctx.KVStore(capKey1).Get(someKey)
|
|
// We should be able to access any state written in InitChain
|
|
require.Equal(t, fooStr, string(value))
|
|
return &abci.PrepareProposalResponse{Txs: req.Txs}, nil
|
|
})
|
|
}
|
|
|
|
suite := NewBaseAppSuite(t, setInitChainerOpt, prepareOpt)
|
|
|
|
_, err := suite.baseApp.InitChain(&abci.InitChainRequest{
|
|
InitialHeight: 1,
|
|
ConsensusParams: &cmtproto.ConsensusParams{},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
reqPrepareProposal := abci.PrepareProposalRequest{
|
|
MaxTxBytes: 1000,
|
|
Height: 1, // this value can't be 0
|
|
}
|
|
resPrepareProposal, err := suite.baseApp.PrepareProposal(&reqPrepareProposal)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 0, len(resPrepareProposal.Txs))
|
|
|
|
reqProposalTxBytes := [][]byte{}
|
|
reqProcessProposal := abci.ProcessProposalRequest{
|
|
Txs: reqProposalTxBytes,
|
|
Height: reqPrepareProposal.Height,
|
|
}
|
|
|
|
resProcessProposal, err := suite.baseApp.ProcessProposal(&reqProcessProposal)
|
|
require.NoError(t, err)
|
|
require.Equal(t, abci.PROCESS_PROPOSAL_STATUS_ACCEPT, resProcessProposal.Status)
|
|
}
|
|
|
|
func TestABCI_Proposals_WithVE(t *testing.T) {
|
|
someVoteExtension := []byte("some-vote-extension")
|
|
|
|
setInitChainerOpt := func(bapp *baseapp.BaseApp) {
|
|
bapp.SetInitChainer(func(ctx sdk.Context, req *abci.InitChainRequest) (*abci.InitChainResponse, error) {
|
|
return &abci.InitChainResponse{}, nil
|
|
})
|
|
}
|
|
|
|
prepareOpt := func(bapp *baseapp.BaseApp) {
|
|
bapp.SetPrepareProposal(func(ctx sdk.Context, req *abci.PrepareProposalRequest) (*abci.PrepareProposalResponse, error) {
|
|
// Inject the vote extension to the beginning of the proposal
|
|
txs := make([][]byte, len(req.Txs)+1)
|
|
txs[0] = someVoteExtension
|
|
copy(txs[1:], req.Txs)
|
|
|
|
return &abci.PrepareProposalResponse{Txs: txs}, nil
|
|
})
|
|
|
|
bapp.SetProcessProposal(func(ctx sdk.Context, req *abci.ProcessProposalRequest) (*abci.ProcessProposalResponse, error) {
|
|
// Check that the vote extension is still there
|
|
require.Equal(t, someVoteExtension, req.Txs[0])
|
|
return &abci.ProcessProposalResponse{Status: abci.PROCESS_PROPOSAL_STATUS_ACCEPT}, nil
|
|
})
|
|
}
|
|
|
|
suite := NewBaseAppSuite(t, setInitChainerOpt, prepareOpt)
|
|
|
|
_, err := suite.baseApp.InitChain(&abci.InitChainRequest{
|
|
InitialHeight: 1,
|
|
ConsensusParams: &cmtproto.ConsensusParams{},
|
|
})
|
|
require.NoError(t, err)
|
|
reqPrepareProposal := abci.PrepareProposalRequest{
|
|
MaxTxBytes: 100000,
|
|
Height: 1, // this value can't be 0
|
|
}
|
|
resPrepareProposal, err := suite.baseApp.PrepareProposal(&reqPrepareProposal)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 1, len(resPrepareProposal.Txs))
|
|
|
|
reqProcessProposal := abci.ProcessProposalRequest{
|
|
Txs: resPrepareProposal.Txs,
|
|
Height: reqPrepareProposal.Height,
|
|
}
|
|
resProcessProposal, err := suite.baseApp.ProcessProposal(&reqProcessProposal)
|
|
require.NoError(t, err)
|
|
require.Equal(t, abci.PROCESS_PROPOSAL_STATUS_ACCEPT, resProcessProposal.Status)
|
|
|
|
// Run finalize block and ensure that the vote extension is still there and that
|
|
// the proposal is accepted
|
|
result, err := suite.baseApp.FinalizeBlock(&abci.FinalizeBlockRequest{
|
|
Txs: resPrepareProposal.Txs,
|
|
Height: reqPrepareProposal.Height,
|
|
})
|
|
require.NoError(t, err)
|
|
require.Equal(t, 1, len(result.TxResults))
|
|
require.EqualValues(t, sdkerrors.ErrTxDecode.Codespace(), result.TxResults[0].Codespace, err)
|
|
require.EqualValues(t, sdkerrors.ErrTxDecode.ABCICode(), result.TxResults[0].Code, err)
|
|
require.EqualValues(t, 0, result.TxResults[0].GasUsed, err)
|
|
require.EqualValues(t, 0, result.TxResults[0].GasWanted, err)
|
|
}
|
|
|
|
func TestABCI_PrepareProposal_ReachedMaxBytes(t *testing.T) {
|
|
anteKey := []byte("ante-key")
|
|
pool := mempool.NewSenderNonceMempool(mempool.SenderNonceMaxTxOpt(5000))
|
|
anteOpt := func(bapp *baseapp.BaseApp) {
|
|
bapp.SetAnteHandler(anteHandlerTxTest(t, capKey1, anteKey))
|
|
}
|
|
|
|
suite := NewBaseAppSuite(t, anteOpt, baseapp.SetMempool(pool))
|
|
baseapptestutil.RegisterCounterServer(suite.baseApp.MsgServiceRouter(), NoopCounterServerImpl{})
|
|
|
|
_, err := suite.baseApp.InitChain(&abci.InitChainRequest{
|
|
ConsensusParams: &cmtproto.ConsensusParams{},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
expectedTxs := 8
|
|
var expectedTxBytes int64
|
|
|
|
for i := 0; i < 100; i++ {
|
|
tx2 := newTxCounter(t, suite.txConfig, int64(i), int64(i))
|
|
err := pool.Insert(sdk.Context{}, tx2)
|
|
require.NoError(t, err)
|
|
|
|
txBz, err := suite.txConfig.TxEncoder()(tx2)
|
|
require.NoError(t, err)
|
|
txDataSize := int(cmttypes.ComputeProtoSizeForTxs([]cmttypes.Tx{txBz}))
|
|
if i < expectedTxs {
|
|
expectedTxBytes += int64(txDataSize)
|
|
}
|
|
}
|
|
|
|
reqPrepareProposal := abci.PrepareProposalRequest{
|
|
MaxTxBytes: expectedTxBytes,
|
|
Height: 1,
|
|
}
|
|
resPrepareProposal, err := suite.baseApp.PrepareProposal(&reqPrepareProposal)
|
|
require.NoError(t, err)
|
|
require.Equal(t, expectedTxs, len(resPrepareProposal.Txs))
|
|
}
|
|
|
|
func TestABCI_PrepareProposal_BadEncoding(t *testing.T) {
|
|
anteKey := []byte("ante-key")
|
|
pool := mempool.NewSenderNonceMempool(mempool.SenderNonceMaxTxOpt(5000))
|
|
anteOpt := func(bapp *baseapp.BaseApp) {
|
|
bapp.SetAnteHandler(anteHandlerTxTest(t, capKey1, anteKey))
|
|
}
|
|
|
|
suite := NewBaseAppSuite(t, anteOpt, baseapp.SetMempool(pool))
|
|
baseapptestutil.RegisterCounterServer(suite.baseApp.MsgServiceRouter(), NoopCounterServerImpl{})
|
|
|
|
_, err := suite.baseApp.InitChain(&abci.InitChainRequest{
|
|
ConsensusParams: &cmtproto.ConsensusParams{},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
tx := newTxCounter(t, suite.txConfig, 0, 0)
|
|
err = pool.Insert(sdk.Context{}, tx)
|
|
require.NoError(t, err)
|
|
|
|
reqPrepareProposal := abci.PrepareProposalRequest{
|
|
MaxTxBytes: 1000,
|
|
Height: 1,
|
|
}
|
|
resPrepareProposal, err := suite.baseApp.PrepareProposal(&reqPrepareProposal)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 1, len(resPrepareProposal.Txs))
|
|
}
|
|
|
|
func TestABCI_PrepareProposal_OverGasUnderBytes(t *testing.T) {
|
|
pool := mempool.NewSenderNonceMempool(mempool.SenderNonceMaxTxOpt(5000))
|
|
suite := NewBaseAppSuite(t, baseapp.SetMempool(pool))
|
|
baseapptestutil.RegisterCounterServer(suite.baseApp.MsgServiceRouter(), NoopCounterServerImpl{})
|
|
|
|
// set max block gas limit to 99, this will allow 9 txs of 10 gas each.
|
|
_, err := suite.baseApp.InitChain(&abci.InitChainRequest{
|
|
ConsensusParams: &cmtproto.ConsensusParams{
|
|
Block: &cmtproto.BlockParams{MaxGas: 99},
|
|
},
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
// insert 100 txs, each with a gas limit of 10
|
|
_, _, addr := testdata.KeyTestPubAddr()
|
|
for i := int64(0); i < 100; i++ {
|
|
msg := &baseapptestutil.MsgCounter{Counter: i, FailOnHandler: false, Signer: addr.String()}
|
|
msgs := []sdk.Msg{msg}
|
|
|
|
builder := suite.txConfig.NewTxBuilder()
|
|
err = builder.SetMsgs(msgs...)
|
|
require.NoError(t, err)
|
|
builder.SetMemo(counterStr + strconv.FormatInt(i, 10) + failStr)
|
|
builder.SetGasLimit(10)
|
|
setTxSignature(t, builder, uint64(i))
|
|
|
|
err := pool.Insert(sdk.Context{}, builder.GetTx())
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
// ensure we only select transactions that fit within the block gas limit
|
|
res, err := suite.baseApp.PrepareProposal(&abci.PrepareProposalRequest{
|
|
MaxTxBytes: 1_000_000, // large enough to ignore restriction
|
|
Height: 1,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// Should include 9 transactions
|
|
require.Len(t, res.Txs, 9, "invalid number of transactions returned")
|
|
}
|
|
|
|
func TestABCI_PrepareProposal_MaxGas(t *testing.T) {
|
|
pool := mempool.NewSenderNonceMempool(mempool.SenderNonceMaxTxOpt(5000))
|
|
suite := NewBaseAppSuite(t, baseapp.SetMempool(pool))
|
|
baseapptestutil.RegisterCounterServer(suite.baseApp.MsgServiceRouter(), NoopCounterServerImpl{})
|
|
|
|
// set max block gas limit to 100
|
|
_, err := suite.baseApp.InitChain(&abci.InitChainRequest{
|
|
ConsensusParams: &cmtproto.ConsensusParams{
|
|
Block: &cmtproto.BlockParams{MaxGas: 100},
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
// insert 100 txs, each with a gas limit of 10
|
|
_, _, addr := testdata.KeyTestPubAddr()
|
|
for i := int64(0); i < 100; i++ {
|
|
msg := &baseapptestutil.MsgCounter{Counter: i, FailOnHandler: false, Signer: addr.String()}
|
|
msgs := []sdk.Msg{msg}
|
|
|
|
builder := suite.txConfig.NewTxBuilder()
|
|
err = builder.SetMsgs(msgs...)
|
|
require.NoError(t, err)
|
|
builder.SetMemo(counterStr + strconv.FormatInt(i, 10) + failStr)
|
|
builder.SetGasLimit(10)
|
|
setTxSignature(t, builder, uint64(i))
|
|
|
|
err := pool.Insert(sdk.Context{}, builder.GetTx())
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
// ensure we only select transactions that fit within the block gas limit
|
|
res, err := suite.baseApp.PrepareProposal(&abci.PrepareProposalRequest{
|
|
MaxTxBytes: 1_000_000, // large enough to ignore restriction
|
|
Height: 1,
|
|
})
|
|
require.NoError(t, err)
|
|
require.Len(t, res.Txs, 10, "invalid number of transactions returned")
|
|
}
|
|
|
|
func TestABCI_PrepareProposal_Failures(t *testing.T) {
|
|
anteKey := []byte("ante-key")
|
|
pool := mempool.NewSenderNonceMempool(mempool.SenderNonceMaxTxOpt(5000))
|
|
anteOpt := func(bapp *baseapp.BaseApp) {
|
|
bapp.SetAnteHandler(anteHandlerTxTest(t, capKey1, anteKey))
|
|
}
|
|
|
|
suite := NewBaseAppSuite(t, anteOpt, baseapp.SetMempool(pool))
|
|
baseapptestutil.RegisterCounterServer(suite.baseApp.MsgServiceRouter(), NoopCounterServerImpl{})
|
|
|
|
_, err := suite.baseApp.InitChain(&abci.InitChainRequest{
|
|
ConsensusParams: &cmtproto.ConsensusParams{},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
tx := newTxCounter(t, suite.txConfig, 0, 0)
|
|
txBytes, err := suite.txConfig.TxEncoder()(tx)
|
|
require.NoError(t, err)
|
|
|
|
reqCheckTx := abci.CheckTxRequest{
|
|
Tx: txBytes,
|
|
Type: abci.CHECK_TX_TYPE_CHECK,
|
|
}
|
|
checkTxRes, err := suite.baseApp.CheckTx(&reqCheckTx)
|
|
require.NoError(t, err)
|
|
require.True(t, checkTxRes.IsOK())
|
|
|
|
failTx := newTxCounter(t, suite.txConfig, 1, 1)
|
|
failTx = setFailOnAnte(t, suite.txConfig, failTx, true)
|
|
|
|
err = pool.Insert(sdk.Context{}, failTx)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 2, pool.CountTx())
|
|
|
|
req := abci.PrepareProposalRequest{
|
|
MaxTxBytes: 1000,
|
|
Height: 1,
|
|
}
|
|
res, err := suite.baseApp.PrepareProposal(&req)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 1, len(res.Txs))
|
|
}
|
|
|
|
func TestABCI_PrepareProposal_PanicRecovery(t *testing.T) {
|
|
prepareOpt := func(app *baseapp.BaseApp) {
|
|
app.SetPrepareProposal(func(ctx sdk.Context, rpp *abci.PrepareProposalRequest) (*abci.PrepareProposalResponse, error) {
|
|
panic(errors.New("test"))
|
|
})
|
|
}
|
|
suite := NewBaseAppSuite(t, prepareOpt)
|
|
|
|
_, err := suite.baseApp.InitChain(&abci.InitChainRequest{
|
|
ConsensusParams: &cmtproto.ConsensusParams{},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
req := abci.PrepareProposalRequest{
|
|
MaxTxBytes: 1000,
|
|
Height: 1,
|
|
}
|
|
|
|
require.NotPanics(t, func() {
|
|
res, err := suite.baseApp.PrepareProposal(&req)
|
|
require.NoError(t, err)
|
|
require.Equal(t, req.Txs, res.Txs)
|
|
})
|
|
}
|
|
|
|
func TestABCI_PrepareProposal_VoteExtensions(t *testing.T) {
|
|
// set up mocks
|
|
ctrl := gomock.NewController(t)
|
|
valStore := mock.NewMockValidatorStore(ctrl)
|
|
privkey := secp256k1.GenPrivKey()
|
|
pubkey := privkey.PubKey()
|
|
addr := sdk.AccAddress(pubkey.Address())
|
|
tmPk := cmtprotocrypto.PublicKey{
|
|
Sum: &cmtprotocrypto.PublicKey_Secp256K1{
|
|
Secp256K1: pubkey.Bytes(),
|
|
},
|
|
}
|
|
|
|
pk, err := cryptocodec.FromCmtProtoPublicKey(tmPk)
|
|
require.NoError(t, err)
|
|
|
|
consAddr := sdk.ConsAddress(addr.String())
|
|
valStore.EXPECT().GetPubKeyByConsAddr(gomock.Any(), consAddr.Bytes()).Return(pk, nil)
|
|
|
|
// set up baseapp
|
|
prepareOpt := func(bapp *baseapp.BaseApp) {
|
|
bapp.SetPrepareProposal(func(ctx sdk.Context, req *abci.PrepareProposalRequest) (*abci.PrepareProposalResponse, error) {
|
|
ctx = ctx.WithBlockHeight(req.Height).WithChainID(bapp.ChainID())
|
|
_, info := extendedCommitToLastCommit(req.LocalLastCommit)
|
|
ctx = ctx.WithCometInfo(info)
|
|
err := baseapp.ValidateVoteExtensions(ctx, valStore, req.LocalLastCommit)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
cp := ctx.ConsensusParams() // nolint:staticcheck // ignore linting error
|
|
extsEnabled := cp.Feature.VoteExtensionsEnableHeight != nil && req.Height >= cp.Feature.VoteExtensionsEnableHeight.Value && cp.Feature.VoteExtensionsEnableHeight.Value != 0
|
|
if !extsEnabled {
|
|
// check abci params
|
|
extsEnabled = cp.Abci != nil && req.Height >= cp.Abci.VoteExtensionsEnableHeight && cp.Abci.VoteExtensionsEnableHeight != 0
|
|
}
|
|
if extsEnabled {
|
|
req.Txs = append(req.Txs, []byte("some-tx-that-does-something-from-votes"))
|
|
}
|
|
return &abci.PrepareProposalResponse{Txs: req.Txs}, nil
|
|
})
|
|
}
|
|
|
|
suite := NewBaseAppSuite(t, prepareOpt)
|
|
|
|
_, err = suite.baseApp.InitChain(&abci.InitChainRequest{
|
|
InitialHeight: 1,
|
|
ConsensusParams: &cmtproto.ConsensusParams{
|
|
Feature: &cmtproto.FeatureParams{
|
|
VoteExtensionsEnableHeight: &gogotypes.Int64Value{Value: 2},
|
|
},
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// first test without vote extensions, no new txs should be added
|
|
reqPrepareProposal := abci.PrepareProposalRequest{
|
|
MaxTxBytes: 1000,
|
|
Height: 1, // this value can't be 0
|
|
}
|
|
resPrepareProposal, err := suite.baseApp.PrepareProposal(&reqPrepareProposal)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 0, len(resPrepareProposal.Txs))
|
|
|
|
// now we try with vote extensions, a new tx should show up
|
|
marshalDelimitedFn := func(msg proto.Message) ([]byte, error) {
|
|
var buf bytes.Buffer
|
|
if err := protoio.NewDelimitedWriter(&buf).WriteMsg(msg); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return buf.Bytes(), nil
|
|
}
|
|
|
|
ext := []byte("something")
|
|
cve := cmtproto.CanonicalVoteExtension{
|
|
Extension: ext,
|
|
Height: 2, // the vote extension was signed in the previous height
|
|
Round: int64(0),
|
|
ChainId: suite.baseApp.ChainID(),
|
|
}
|
|
|
|
bz, err := marshalDelimitedFn(&cve)
|
|
require.NoError(t, err)
|
|
|
|
extSig, err := privkey.Sign(bz)
|
|
require.NoError(t, err)
|
|
|
|
reqPrepareProposal = abci.PrepareProposalRequest{
|
|
MaxTxBytes: 1000,
|
|
Height: 3, // this value can't be 0
|
|
LocalLastCommit: abci.ExtendedCommitInfo{
|
|
Round: 0,
|
|
Votes: []abci.ExtendedVoteInfo{
|
|
{
|
|
Validator: abci.Validator{
|
|
Address: consAddr.Bytes(),
|
|
Power: 666,
|
|
},
|
|
VoteExtension: ext,
|
|
ExtensionSignature: extSig,
|
|
BlockIdFlag: cmtproto.BlockIDFlagCommit,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
resPrepareProposal, err = suite.baseApp.PrepareProposal(&reqPrepareProposal)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 1, len(resPrepareProposal.Txs))
|
|
|
|
// now vote extensions but our sole voter doesn't reach majority
|
|
reqPrepareProposal = abci.PrepareProposalRequest{
|
|
MaxTxBytes: 1000,
|
|
Height: 3, // this value can't be 0
|
|
LocalLastCommit: abci.ExtendedCommitInfo{
|
|
Round: 0,
|
|
Votes: []abci.ExtendedVoteInfo{
|
|
{
|
|
Validator: abci.Validator{
|
|
Address: consAddr.Bytes(),
|
|
Power: 666,
|
|
},
|
|
VoteExtension: ext,
|
|
ExtensionSignature: extSig,
|
|
BlockIdFlag: cmtproto.BlockIDFlagNil, // This will ignore the vote extension
|
|
},
|
|
},
|
|
},
|
|
}
|
|
resPrepareProposal, err = suite.baseApp.PrepareProposal(&reqPrepareProposal)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 0, len(resPrepareProposal.Txs))
|
|
}
|
|
|
|
func TestABCI_ProcessProposal_PanicRecovery(t *testing.T) {
|
|
processOpt := func(app *baseapp.BaseApp) {
|
|
app.SetProcessProposal(func(ctx sdk.Context, rpp *abci.ProcessProposalRequest) (*abci.ProcessProposalResponse, error) {
|
|
panic(errors.New("test"))
|
|
})
|
|
}
|
|
suite := NewBaseAppSuite(t, processOpt)
|
|
|
|
_, err := suite.baseApp.InitChain(&abci.InitChainRequest{
|
|
ConsensusParams: &cmtproto.ConsensusParams{},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
require.NotPanics(t, func() {
|
|
res, err := suite.baseApp.ProcessProposal(&abci.ProcessProposalRequest{Height: 1})
|
|
require.NoError(t, err)
|
|
require.Equal(t, res.Status, abci.PROCESS_PROPOSAL_STATUS_REJECT)
|
|
})
|
|
}
|
|
|
|
// TestABCI_Proposal_Reset_State ensures that state is reset between runs of
|
|
// PrepareProposal and ProcessProposal in case they are called multiple times.
|
|
// This is only valid for heights > 1, given that on height 1 we always set the
|
|
// state to be deliverState.
|
|
func TestABCI_Proposal_Reset_State_Between_Calls(t *testing.T) {
|
|
someKey := []byte("some-key")
|
|
|
|
prepareOpt := func(bapp *baseapp.BaseApp) {
|
|
bapp.SetPrepareProposal(func(ctx sdk.Context, req *abci.PrepareProposalRequest) (*abci.PrepareProposalResponse, error) {
|
|
// This key should not exist given that we reset the state on every call.
|
|
require.False(t, ctx.KVStore(capKey1).Has(someKey))
|
|
ctx.KVStore(capKey1).Set(someKey, someKey)
|
|
return &abci.PrepareProposalResponse{Txs: req.Txs}, nil
|
|
})
|
|
}
|
|
|
|
processOpt := func(bapp *baseapp.BaseApp) {
|
|
bapp.SetProcessProposal(func(ctx sdk.Context, req *abci.ProcessProposalRequest) (*abci.ProcessProposalResponse, error) {
|
|
// This key should not exist given that we reset the state on every call.
|
|
require.False(t, ctx.KVStore(capKey1).Has(someKey))
|
|
ctx.KVStore(capKey1).Set(someKey, someKey)
|
|
return &abci.ProcessProposalResponse{Status: abci.PROCESS_PROPOSAL_STATUS_ACCEPT}, nil
|
|
})
|
|
}
|
|
|
|
suite := NewBaseAppSuite(t, prepareOpt, processOpt)
|
|
|
|
_, err := suite.baseApp.InitChain(&abci.InitChainRequest{
|
|
ConsensusParams: &cmtproto.ConsensusParams{},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
reqPrepareProposal := abci.PrepareProposalRequest{
|
|
MaxTxBytes: 1000,
|
|
Height: 2, // this value can't be 0
|
|
}
|
|
|
|
// Let's pretend something happened and PrepareProposal gets called many
|
|
// times, this must be safe to do.
|
|
for i := 0; i < 5; i++ {
|
|
resPrepareProposal, err := suite.baseApp.PrepareProposal(&reqPrepareProposal)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 0, len(resPrepareProposal.Txs))
|
|
}
|
|
|
|
reqProposalTxBytes := [][]byte{}
|
|
reqProcessProposal := abci.ProcessProposalRequest{
|
|
Txs: reqProposalTxBytes,
|
|
Height: 2,
|
|
}
|
|
|
|
// Let's pretend something happened and ProcessProposal gets called many
|
|
// times, this must be safe to do.
|
|
for i := 0; i < 5; i++ {
|
|
resProcessProposal, err := suite.baseApp.ProcessProposal(&reqProcessProposal)
|
|
require.NoError(t, err)
|
|
require.Equal(t, abci.PROCESS_PROPOSAL_STATUS_ACCEPT, resProcessProposal.Status)
|
|
}
|
|
}
|
|
|
|
func TestABCI_HaltChain(t *testing.T) {
|
|
testCases := []struct {
|
|
name string
|
|
haltHeight uint64
|
|
haltTime uint64
|
|
blockHeight int64
|
|
blockTime int64
|
|
expHalt bool
|
|
}{
|
|
{"default", 0, 0, 10, 0, false},
|
|
{"halt-height-edge", 10, 0, 10, 0, false},
|
|
{"halt-height", 10, 0, 11, 0, true},
|
|
{"halt-time-edge", 0, 10, 1, 10, false},
|
|
{"halt-time", 0, 10, 1, 11, true},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
suite := NewBaseAppSuite(t, baseapp.SetHaltHeight(tc.haltHeight), baseapp.SetHaltTime(tc.haltTime))
|
|
_, err := suite.baseApp.InitChain(&abci.InitChainRequest{
|
|
ConsensusParams: &cmtproto.ConsensusParams{},
|
|
InitialHeight: tc.blockHeight,
|
|
})
|
|
require.NoError(t, err)
|
|
app := suite.baseApp
|
|
_, err = app.FinalizeBlock(&abci.FinalizeBlockRequest{
|
|
Height: tc.blockHeight,
|
|
Time: time.Unix(tc.blockTime, 0),
|
|
})
|
|
if !tc.expHalt {
|
|
require.NoError(t, err)
|
|
} else {
|
|
require.Error(t, err)
|
|
require.True(t, strings.HasPrefix(err.Error(), "halt per configuration"))
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestBaseApp_PreBlocker(t *testing.T) {
|
|
db := dbm.NewMemDB()
|
|
name := t.Name()
|
|
logger := log.NewTestLogger(t)
|
|
|
|
app := baseapp.NewBaseApp(name, logger, db, nil)
|
|
_, err := app.InitChain(&abci.InitChainRequest{})
|
|
require.NoError(t, err)
|
|
|
|
wasHookCalled := false
|
|
app.SetPreBlocker(func(ctx sdk.Context, req *abci.FinalizeBlockRequest) error {
|
|
wasHookCalled = true
|
|
return nil
|
|
})
|
|
app.Seal()
|
|
|
|
_, err = app.FinalizeBlock(&abci.FinalizeBlockRequest{Height: 1})
|
|
require.NoError(t, err)
|
|
require.Equal(t, true, wasHookCalled)
|
|
|
|
// Now try erroring
|
|
app = baseapp.NewBaseApp(name, logger, db, nil)
|
|
_, err = app.InitChain(&abci.InitChainRequest{})
|
|
require.NoError(t, err)
|
|
|
|
app.SetPreBlocker(func(ctx sdk.Context, req *abci.FinalizeBlockRequest) error {
|
|
return errors.New("some error")
|
|
})
|
|
app.Seal()
|
|
|
|
_, err = app.FinalizeBlock(&abci.FinalizeBlockRequest{Height: 1})
|
|
require.Error(t, err)
|
|
}
|
|
|
|
// TestBaseApp_VoteExtensions tests vote extensions using a price as an example.
|
|
func TestBaseApp_VoteExtensions(t *testing.T) {
|
|
ctrl := gomock.NewController(t)
|
|
valStore := mock.NewMockValidatorStore(ctrl)
|
|
|
|
// 10 good vote extensions, 2 bad ones from 12 total validators
|
|
numVals := 12
|
|
privKeys := make([]secp256k1.PrivKey, numVals)
|
|
vals := make([]sdk.ConsAddress, numVals)
|
|
for i := 0; i < numVals; i++ {
|
|
privKey := secp256k1.GenPrivKey()
|
|
privKeys[i] = privKey
|
|
|
|
pubKey := privKey.PubKey()
|
|
val := sdk.ConsAddress(pubKey.Bytes())
|
|
vals[i] = val
|
|
|
|
tmPk := cmtprotocrypto.PublicKey{
|
|
Sum: &cmtprotocrypto.PublicKey_Secp256K1{
|
|
Secp256K1: pubKey.Bytes(),
|
|
},
|
|
}
|
|
|
|
pk, err := cryptocodec.FromCmtProtoPublicKey(tmPk)
|
|
require.NoError(t, err)
|
|
valStore.EXPECT().GetPubKeyByConsAddr(gomock.Any(), val).Return(pk, nil)
|
|
}
|
|
|
|
baseappOpts := func(app *baseapp.BaseApp) {
|
|
app.SetExtendVoteHandler(func(sdk.Context, *abci.ExtendVoteRequest) (*abci.ExtendVoteResponse, error) {
|
|
// here we would have a process to get the price from an external source
|
|
price := 10000000 + rand.Int63n(1000000)
|
|
ve := make([]byte, 8)
|
|
binary.BigEndian.PutUint64(ve, uint64(price))
|
|
return &abci.ExtendVoteResponse{VoteExtension: ve}, nil
|
|
})
|
|
|
|
app.SetVerifyVoteExtensionHandler(func(_ sdk.Context, req *abci.VerifyVoteExtensionRequest) (*abci.VerifyVoteExtensionResponse, error) {
|
|
vePrice := binary.BigEndian.Uint64(req.VoteExtension)
|
|
// here we would do some price validation, must not be 0 and not too high
|
|
if vePrice > 11000000 || vePrice == 0 {
|
|
// usually application should always return ACCEPT unless they really want to discard the entire vote
|
|
return &abci.VerifyVoteExtensionResponse{Status: abci.VERIFY_VOTE_EXTENSION_STATUS_REJECT}, nil
|
|
}
|
|
|
|
return &abci.VerifyVoteExtensionResponse{Status: abci.VERIFY_VOTE_EXTENSION_STATUS_ACCEPT}, nil
|
|
})
|
|
|
|
app.SetPrepareProposal(func(ctx sdk.Context, req *abci.PrepareProposalRequest) (*abci.PrepareProposalResponse, error) {
|
|
txs := [][]byte{}
|
|
ctx = ctx.WithBlockHeight(req.Height).WithChainID(app.ChainID())
|
|
_, info := extendedCommitToLastCommit(req.LocalLastCommit)
|
|
ctx = ctx.WithCometInfo(info)
|
|
if err := baseapp.ValidateVoteExtensions(ctx, valStore, req.LocalLastCommit); err != nil {
|
|
return nil, err
|
|
}
|
|
// add all VE as txs (in a real scenario we would need to check signatures too)
|
|
for _, v := range req.LocalLastCommit.Votes {
|
|
if len(v.VoteExtension) == 8 {
|
|
// pretend this is a way to check if the VE is valid
|
|
if binary.BigEndian.Uint64(v.VoteExtension) < 11000000 && binary.BigEndian.Uint64(v.VoteExtension) > 0 {
|
|
txs = append(txs, v.VoteExtension)
|
|
}
|
|
}
|
|
}
|
|
|
|
return &abci.PrepareProposalResponse{Txs: txs}, nil
|
|
})
|
|
|
|
app.SetProcessProposal(func(ctx sdk.Context, req *abci.ProcessProposalRequest) (*abci.ProcessProposalResponse, error) {
|
|
// here we check if the proposal is valid, mainly if the vote extensions appended to the txs are valid
|
|
for _, v := range req.Txs {
|
|
// pretend this is a way to check if the tx is actually a VE
|
|
if len(v) == 8 {
|
|
// pretend this is a way to check if the VE is valid
|
|
if binary.BigEndian.Uint64(v) > 11000000 || binary.BigEndian.Uint64(v) == 0 {
|
|
return &abci.ProcessProposalResponse{Status: abci.PROCESS_PROPOSAL_STATUS_REJECT}, nil
|
|
}
|
|
}
|
|
}
|
|
|
|
return &abci.ProcessProposalResponse{Status: abci.PROCESS_PROPOSAL_STATUS_ACCEPT}, nil
|
|
})
|
|
|
|
app.SetPreBlocker(func(ctx sdk.Context, req *abci.FinalizeBlockRequest) error {
|
|
count := uint64(0)
|
|
pricesSum := uint64(0)
|
|
for _, v := range req.Txs {
|
|
// pretend this is a way to check if the tx is actually a VE
|
|
if len(v) == 8 {
|
|
count++
|
|
pricesSum += binary.BigEndian.Uint64(v)
|
|
}
|
|
}
|
|
|
|
if count > 0 {
|
|
// we process the average price and store it in the context to make it available for FinalizeBlock
|
|
avgPrice := pricesSum / count
|
|
buf := make([]byte, 8)
|
|
binary.BigEndian.PutUint64(buf, avgPrice)
|
|
ctx.KVStore(capKey1).Set([]byte("avgPrice"), buf)
|
|
}
|
|
|
|
return nil
|
|
})
|
|
}
|
|
|
|
suite := NewBaseAppSuite(t, baseappOpts)
|
|
|
|
_, err := suite.baseApp.InitChain(&abci.InitChainRequest{
|
|
ConsensusParams: &cmtproto.ConsensusParams{
|
|
Feature: &cmtproto.FeatureParams{
|
|
VoteExtensionsEnableHeight: &gogotypes.Int64Value{Value: 1},
|
|
},
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
allVEs := [][]byte{}
|
|
// simulate getting 10 vote extensions from 10 validators
|
|
for i := 0; i < 10; i++ {
|
|
ve, err := suite.baseApp.ExtendVote(context.TODO(), &abci.ExtendVoteRequest{Height: 1})
|
|
require.NoError(t, err)
|
|
allVEs = append(allVEs, ve.VoteExtension)
|
|
}
|
|
|
|
// add a couple of invalid vote extensions (in what regards to the check we are doing in VerifyVoteExtension/ProcessProposal)
|
|
// add a 0 price
|
|
ve := make([]byte, 8)
|
|
binary.BigEndian.PutUint64(ve, uint64(0))
|
|
allVEs = append(allVEs, ve)
|
|
|
|
// add a price too high
|
|
ve = make([]byte, 8)
|
|
binary.BigEndian.PutUint64(ve, uint64(13000000))
|
|
allVEs = append(allVEs, ve)
|
|
|
|
// verify all votes, only 10 should be accepted
|
|
successful := 0
|
|
for _, v := range allVEs {
|
|
res, err := suite.baseApp.VerifyVoteExtension(&abci.VerifyVoteExtensionRequest{
|
|
Height: 1,
|
|
VoteExtension: v,
|
|
})
|
|
require.NoError(t, err)
|
|
if res.Status == abci.VERIFY_VOTE_EXTENSION_STATUS_ACCEPT {
|
|
successful++
|
|
}
|
|
}
|
|
require.Equal(t, 10, successful)
|
|
|
|
extVotes := []abci.ExtendedVoteInfo{}
|
|
for _, val := range vals {
|
|
extVotes = append(extVotes, abci.ExtendedVoteInfo{
|
|
VoteExtension: allVEs[0],
|
|
BlockIdFlag: cmtproto.BlockIDFlagCommit,
|
|
ExtensionSignature: []byte{},
|
|
Validator: abci.Validator{
|
|
Address: val.Bytes(),
|
|
Power: 666,
|
|
},
|
|
},
|
|
)
|
|
}
|
|
|
|
prepPropReq := &abci.PrepareProposalRequest{
|
|
Height: 1,
|
|
LocalLastCommit: abci.ExtendedCommitInfo{
|
|
Round: 0,
|
|
Votes: extVotes,
|
|
},
|
|
}
|
|
|
|
// add all VEs to the local last commit, which will make PrepareProposal fail
|
|
// because it's not expecting to receive vote extensions when height == VoteExtensionsEnableHeight
|
|
for _, ve := range allVEs {
|
|
prepPropReq.LocalLastCommit.Votes = append(prepPropReq.LocalLastCommit.Votes, abci.ExtendedVoteInfo{
|
|
VoteExtension: ve,
|
|
BlockIdFlag: cmtproto.BlockIDFlagCommit,
|
|
ExtensionSignature: []byte{}, // doesn't matter, it's just to make the next PrepareProposal fail
|
|
})
|
|
}
|
|
resp, err := suite.baseApp.PrepareProposal(prepPropReq)
|
|
require.Len(t, resp.Txs, 0) // this is actually a failure, but we don't want to halt the chain
|
|
require.NoError(t, err) // we don't error here
|
|
|
|
prepPropReq.LocalLastCommit.Votes = []abci.ExtendedVoteInfo{} // reset votes
|
|
resp, err = suite.baseApp.PrepareProposal(prepPropReq)
|
|
require.NoError(t, err)
|
|
require.Len(t, resp.Txs, 0)
|
|
|
|
procPropRes, err := suite.baseApp.ProcessProposal(&abci.ProcessProposalRequest{Height: 1, Txs: resp.Txs})
|
|
require.NoError(t, err)
|
|
require.Equal(t, abci.PROCESS_PROPOSAL_STATUS_ACCEPT, procPropRes.Status)
|
|
|
|
_, err = suite.baseApp.FinalizeBlock(&abci.FinalizeBlockRequest{Height: 1, Txs: resp.Txs})
|
|
require.NoError(t, err)
|
|
|
|
// The average price will be nil during the first block, given that we don't have
|
|
// any vote extensions on block 1 in PrepareProposal
|
|
avgPrice := getFinalizeBlockStateCtx(suite.baseApp).KVStore(capKey1).Get([]byte("avgPrice"))
|
|
require.Nil(t, avgPrice)
|
|
_, err = suite.baseApp.Commit()
|
|
require.NoError(t, err)
|
|
|
|
// Now onto the second block, this time we process vote extensions from the
|
|
// previous block (which we sign now)
|
|
for i, ve := range allVEs {
|
|
cve := cmtproto.CanonicalVoteExtension{
|
|
Extension: ve,
|
|
Height: 1,
|
|
Round: int64(0),
|
|
ChainId: suite.baseApp.ChainID(),
|
|
}
|
|
|
|
bz, err := marshalDelimitedFn(&cve)
|
|
require.NoError(t, err)
|
|
|
|
privKey := privKeys[i]
|
|
extSig, err := privKey.Sign(bz)
|
|
require.NoError(t, err)
|
|
|
|
prepPropReq.LocalLastCommit.Votes = append(prepPropReq.LocalLastCommit.Votes, abci.ExtendedVoteInfo{
|
|
VoteExtension: ve,
|
|
BlockIdFlag: cmtproto.BlockIDFlagCommit,
|
|
ExtensionSignature: extSig,
|
|
Validator: abci.Validator{
|
|
Address: vals[i].Bytes(),
|
|
Power: 666,
|
|
},
|
|
})
|
|
}
|
|
|
|
prepPropReq.Height = 2
|
|
resp, err = suite.baseApp.PrepareProposal(prepPropReq)
|
|
require.NoError(t, err)
|
|
require.Len(t, resp.Txs, 10)
|
|
|
|
procPropRes, err = suite.baseApp.ProcessProposal(&abci.ProcessProposalRequest{Height: 2, Txs: resp.Txs})
|
|
require.NoError(t, err)
|
|
require.Equal(t, abci.PROCESS_PROPOSAL_STATUS_ACCEPT, procPropRes.Status)
|
|
|
|
_, err = suite.baseApp.FinalizeBlock(&abci.FinalizeBlockRequest{Height: 2, Txs: resp.Txs})
|
|
require.NoError(t, err)
|
|
|
|
// Check if the average price was available in FinalizeBlock's context
|
|
avgPrice = getFinalizeBlockStateCtx(suite.baseApp).KVStore(capKey1).Get([]byte("avgPrice"))
|
|
require.NotNil(t, avgPrice)
|
|
require.GreaterOrEqual(t, binary.BigEndian.Uint64(avgPrice), uint64(10000000))
|
|
require.Less(t, binary.BigEndian.Uint64(avgPrice), uint64(11000000))
|
|
|
|
_, err = suite.baseApp.Commit()
|
|
require.NoError(t, err)
|
|
|
|
// check if avgPrice was committed
|
|
committedAvgPrice := suite.baseApp.NewContext(true).KVStore(capKey1).Get([]byte("avgPrice"))
|
|
require.Equal(t, avgPrice, committedAvgPrice)
|
|
}
|
|
|
|
func TestABCI_PrepareProposal_Panic(t *testing.T) {
|
|
prepareOpt := func(bapp *baseapp.BaseApp) {
|
|
bapp.SetPrepareProposal(func(ctx sdk.Context, req *abci.PrepareProposalRequest) (*abci.PrepareProposalResponse, error) {
|
|
if len(req.Txs) == 3 {
|
|
panic("i don't like number 3, panic")
|
|
}
|
|
// return empty if no panic
|
|
return &abci.PrepareProposalResponse{}, nil
|
|
})
|
|
}
|
|
|
|
suite := NewBaseAppSuite(t, prepareOpt)
|
|
|
|
_, err := suite.baseApp.InitChain(&abci.InitChainRequest{
|
|
InitialHeight: 1,
|
|
ConsensusParams: &cmtproto.ConsensusParams{},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
txs := [][]byte{{1}, {2}}
|
|
reqPrepareProposal := abci.PrepareProposalRequest{
|
|
MaxTxBytes: 1000,
|
|
Height: 1, // this value can't be 0
|
|
Txs: txs,
|
|
}
|
|
resPrepareProposal, err := suite.baseApp.PrepareProposal(&reqPrepareProposal)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 0, len(resPrepareProposal.Txs))
|
|
|
|
// make it panic, and check if it returns 3 txs (because of panic recovery)
|
|
txs = [][]byte{{1}, {2}, {3}}
|
|
reqPrepareProposal.Txs = txs
|
|
resPrepareProposal, err = suite.baseApp.PrepareProposal(&reqPrepareProposal)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 3, len(resPrepareProposal.Txs))
|
|
}
|
|
|
|
func TestOptimisticExecution(t *testing.T) {
|
|
suite := NewBaseAppSuite(t, baseapp.SetOptimisticExecution())
|
|
|
|
_, err := suite.baseApp.InitChain(&abci.InitChainRequest{
|
|
ConsensusParams: &cmtproto.ConsensusParams{},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// run 50 blocks
|
|
for i := 0; i < 50; i++ {
|
|
tx := newTxCounter(t, suite.txConfig, 0, 1)
|
|
txBytes, err := suite.txConfig.TxEncoder()(tx)
|
|
require.NoError(t, err)
|
|
|
|
reqProcProp := abci.ProcessProposalRequest{
|
|
Txs: [][]byte{txBytes},
|
|
Height: suite.baseApp.LastBlockHeight() + 1,
|
|
Hash: []byte("some-hash" + strconv.FormatInt(suite.baseApp.LastBlockHeight()+1, 10)),
|
|
}
|
|
|
|
respProcProp, err := suite.baseApp.ProcessProposal(&reqProcProp)
|
|
require.Equal(t, abci.PROCESS_PROPOSAL_STATUS_ACCEPT, respProcProp.Status)
|
|
require.NoError(t, err)
|
|
|
|
reqFinalizeBlock := abci.FinalizeBlockRequest{
|
|
Height: reqProcProp.Height,
|
|
Txs: reqProcProp.Txs,
|
|
Hash: reqProcProp.Hash,
|
|
}
|
|
|
|
respFinalizeBlock, err := suite.baseApp.FinalizeBlock(&reqFinalizeBlock)
|
|
require.NoError(t, err)
|
|
require.Len(t, respFinalizeBlock.TxResults, 1)
|
|
|
|
_, err = suite.baseApp.Commit()
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
require.Equal(t, int64(50), suite.baseApp.LastBlockHeight())
|
|
}
|
|
|
|
func TestABCI_Proposal_FailReCheckTx(t *testing.T) {
|
|
pool := mempool.NewPriorityMempool[int64](mempool.PriorityNonceMempoolConfig[int64]{
|
|
TxPriority: mempool.NewDefaultTxPriority(),
|
|
MaxTx: 0,
|
|
SignerExtractor: mempool.NewDefaultSignerExtractionAdapter(),
|
|
})
|
|
|
|
anteOpt := func(bapp *baseapp.BaseApp) {
|
|
bapp.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx, simulate bool) (sdk.Context, error) {
|
|
// always fail on recheck, just to test the recheck logic
|
|
if ctx.IsReCheckTx() {
|
|
return ctx, errors.New("recheck failed in ante handler")
|
|
}
|
|
|
|
return ctx, nil
|
|
})
|
|
}
|
|
|
|
suite := NewBaseAppSuite(t, anteOpt, baseapp.SetMempool(pool))
|
|
baseapptestutil.RegisterKeyValueServer(suite.baseApp.MsgServiceRouter(), MsgKeyValueImpl{})
|
|
baseapptestutil.RegisterCounterServer(suite.baseApp.MsgServiceRouter(), NoopCounterServerImpl{})
|
|
|
|
_, err := suite.baseApp.InitChain(&abci.InitChainRequest{
|
|
ConsensusParams: &cmtproto.ConsensusParams{},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
tx := newTxCounter(t, suite.txConfig, 0, 1)
|
|
txBytes, err := suite.txConfig.TxEncoder()(tx)
|
|
require.NoError(t, err)
|
|
|
|
reqCheckTx := abci.CheckTxRequest{
|
|
Tx: txBytes,
|
|
Type: abci.CHECK_TX_TYPE_CHECK,
|
|
}
|
|
_, err = suite.baseApp.CheckTx(&reqCheckTx)
|
|
require.NoError(t, err)
|
|
|
|
tx2 := newTxCounter(t, suite.txConfig, 1, 1)
|
|
|
|
tx2Bytes, err := suite.txConfig.TxEncoder()(tx2)
|
|
require.NoError(t, err)
|
|
|
|
err = pool.Insert(sdk.Context{}, tx2)
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, 2, pool.CountTx())
|
|
|
|
// call prepareProposal before calling recheck tx, just as a sanity check
|
|
reqPrepareProposal := abci.PrepareProposalRequest{
|
|
MaxTxBytes: 1000,
|
|
Height: 1,
|
|
}
|
|
resPrepareProposal, err := suite.baseApp.PrepareProposal(&reqPrepareProposal)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 2, len(resPrepareProposal.Txs))
|
|
|
|
// call recheck on the first tx, it MUST return an error
|
|
reqReCheckTx := abci.CheckTxRequest{
|
|
Tx: txBytes,
|
|
Type: abci.CHECK_TX_TYPE_RECHECK,
|
|
}
|
|
resp, err := suite.baseApp.CheckTx(&reqReCheckTx)
|
|
require.NoError(t, err)
|
|
require.True(t, resp.IsErr())
|
|
require.Equal(t, "recheck failed in ante handler", resp.Log)
|
|
|
|
// call prepareProposal again, should return only the second tx
|
|
resPrepareProposal, err = suite.baseApp.PrepareProposal(&reqPrepareProposal)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 1, len(resPrepareProposal.Txs))
|
|
require.Equal(t, tx2Bytes, resPrepareProposal.Txs[0])
|
|
|
|
// check the mempool, it should have only the second tx
|
|
require.Equal(t, 1, pool.CountTx())
|
|
|
|
reqProposalTxBytes := [][]byte{
|
|
tx2Bytes,
|
|
}
|
|
reqProcessProposal := abci.ProcessProposalRequest{
|
|
Txs: reqProposalTxBytes,
|
|
Height: reqPrepareProposal.Height,
|
|
}
|
|
|
|
resProcessProposal, err := suite.baseApp.ProcessProposal(&reqProcessProposal)
|
|
require.NoError(t, err)
|
|
require.Equal(t, abci.PROCESS_PROPOSAL_STATUS_ACCEPT, resProcessProposal.Status)
|
|
|
|
// the same txs as in PrepareProposal
|
|
res, err := suite.baseApp.FinalizeBlock(&abci.FinalizeBlockRequest{
|
|
Height: suite.baseApp.LastBlockHeight() + 1,
|
|
Txs: reqProposalTxBytes,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, 0, pool.CountTx())
|
|
|
|
require.NotEmpty(t, res.TxResults[0].Events)
|
|
require.True(t, res.TxResults[0].IsOK(), fmt.Sprintf("%v", res))
|
|
}
|