<!-- < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < ☺ v ✰ Thanks for creating a PR! ✰ v Before smashing the submit button please review the checkboxes. v If a checkbox is n/a - please still include it but + a little note why ☺ > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > --> ## Description <!-- Add a description of the changes that this PR introduces and the files that are the most critical to review. --> closes: #9239 --- Before we can merge this PR, please make sure that all the following items have been checked off. If any of the checklist items are not applicable, please leave them but write a little note why. - [ ] Targeted PR against correct branch (see [CONTRIBUTING.md](https://github.com/cosmos/cosmos-sdk/blob/master/CONTRIBUTING.md#pr-targeting)) - [ ] Linked to Github issue with discussion and accepted design OR link to spec that describes this work. - [ ] Code follows the [module structure standards](https://github.com/cosmos/cosmos-sdk/blob/master/docs/building-modules/structure.md). - [ ] Wrote unit and integration [tests](https://github.com/cosmos/cosmos-sdk/blob/master/CONTRIBUTING.md#testing) - [ ] Updated relevant documentation (`docs/`) or specification (`x/<module>/spec/`) - [ ] Added relevant `godoc` [comments](https://blog.golang.org/godoc-documenting-go-code). - [ ] Added a relevant changelog entry to the `Unreleased` section in `CHANGELOG.md` - [ ] Re-reviewed `Files changed` in the Github PR explorer - [ ] Review `Codecov Report` in the comment section below once CI passes
2042 lines
59 KiB
Go
2042 lines
59 KiB
Go
package baseapp
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"math/rand"
|
|
"os"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/gogo/protobuf/jsonpb"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
abci "github.com/tendermint/tendermint/abci/types"
|
|
"github.com/tendermint/tendermint/libs/log"
|
|
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
|
|
dbm "github.com/tendermint/tm-db"
|
|
|
|
"github.com/cosmos/cosmos-sdk/codec"
|
|
"github.com/cosmos/cosmos-sdk/snapshots"
|
|
snapshottypes "github.com/cosmos/cosmos-sdk/snapshots/types"
|
|
"github.com/cosmos/cosmos-sdk/store/rootmulti"
|
|
store "github.com/cosmos/cosmos-sdk/store/types"
|
|
"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/x/auth/migrations/legacytx"
|
|
)
|
|
|
|
var (
|
|
capKey1 = sdk.NewKVStoreKey("key1")
|
|
capKey2 = sdk.NewKVStoreKey("key2")
|
|
)
|
|
|
|
type paramStore struct {
|
|
db *dbm.MemDB
|
|
}
|
|
|
|
func (ps *paramStore) Set(_ sdk.Context, key []byte, value interface{}) {
|
|
bz, err := json.Marshal(value)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
ps.db.Set(key, bz)
|
|
}
|
|
|
|
func (ps *paramStore) Has(_ sdk.Context, key []byte) bool {
|
|
ok, err := ps.db.Has(key)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
return ok
|
|
}
|
|
|
|
func (ps *paramStore) Get(_ sdk.Context, key []byte, ptr interface{}) {
|
|
bz, err := ps.db.Get(key)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
if len(bz) == 0 {
|
|
return
|
|
}
|
|
|
|
if err := json.Unmarshal(bz, ptr); err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
func defaultLogger() log.Logger {
|
|
return log.NewTMLogger(log.NewSyncWriter(os.Stdout)).With("module", "sdk/app")
|
|
}
|
|
|
|
func newBaseApp(name string, options ...func(*BaseApp)) *BaseApp {
|
|
logger := defaultLogger()
|
|
db := dbm.NewMemDB()
|
|
codec := codec.NewLegacyAmino()
|
|
registerTestCodec(codec)
|
|
return NewBaseApp(name, logger, db, testTxDecoder(codec), options...)
|
|
}
|
|
|
|
func registerTestCodec(cdc *codec.LegacyAmino) {
|
|
// register Tx, Msg
|
|
sdk.RegisterLegacyAminoCodec(cdc)
|
|
|
|
// register test types
|
|
cdc.RegisterConcrete(&txTest{}, "cosmos-sdk/baseapp/txTest", nil)
|
|
cdc.RegisterConcrete(&msgCounter{}, "cosmos-sdk/baseapp/msgCounter", nil)
|
|
cdc.RegisterConcrete(&msgCounter2{}, "cosmos-sdk/baseapp/msgCounter2", nil)
|
|
cdc.RegisterConcrete(&msgKeyValue{}, "cosmos-sdk/baseapp/msgKeyValue", nil)
|
|
cdc.RegisterConcrete(&msgNoRoute{}, "cosmos-sdk/baseapp/msgNoRoute", nil)
|
|
}
|
|
|
|
// aminoTxEncoder creates a amino TxEncoder for testing purposes.
|
|
func aminoTxEncoder() sdk.TxEncoder {
|
|
cdc := codec.NewLegacyAmino()
|
|
registerTestCodec(cdc)
|
|
|
|
return legacytx.StdTxConfig{Cdc: cdc}.TxEncoder()
|
|
}
|
|
|
|
// simple one store baseapp
|
|
func setupBaseApp(t *testing.T, options ...func(*BaseApp)) *BaseApp {
|
|
app := newBaseApp(t.Name(), options...)
|
|
require.Equal(t, t.Name(), app.Name())
|
|
|
|
app.MountStores(capKey1, capKey2)
|
|
app.SetParamStore(¶mStore{db: dbm.NewMemDB()})
|
|
|
|
// stores are mounted
|
|
err := app.LoadLatestVersion()
|
|
require.Nil(t, err)
|
|
return app
|
|
}
|
|
|
|
// simple one store baseapp with data and snapshots. Each tx is 1 MB in size (uncompressed).
|
|
func setupBaseAppWithSnapshots(t *testing.T, blocks uint, blockTxs int, options ...func(*BaseApp)) (*BaseApp, func()) {
|
|
codec := codec.NewLegacyAmino()
|
|
registerTestCodec(codec)
|
|
routerOpt := func(bapp *BaseApp) {
|
|
bapp.Router().AddRoute(sdk.NewRoute(routeMsgKeyValue, func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) {
|
|
kv := msg.(*msgKeyValue)
|
|
bapp.cms.GetCommitKVStore(capKey2).Set(kv.Key, kv.Value)
|
|
return &sdk.Result{}, nil
|
|
}))
|
|
}
|
|
|
|
snapshotInterval := uint64(2)
|
|
snapshotTimeout := 1 * time.Minute
|
|
snapshotDir, err := ioutil.TempDir("", "baseapp")
|
|
require.NoError(t, err)
|
|
snapshotStore, err := snapshots.NewStore(dbm.NewMemDB(), snapshotDir)
|
|
require.NoError(t, err)
|
|
teardown := func() {
|
|
os.RemoveAll(snapshotDir)
|
|
}
|
|
|
|
app := setupBaseApp(t, append(options,
|
|
SetSnapshotStore(snapshotStore),
|
|
SetSnapshotInterval(snapshotInterval),
|
|
SetPruning(sdk.PruningOptions{KeepEvery: 1}),
|
|
routerOpt)...)
|
|
|
|
app.InitChain(abci.RequestInitChain{})
|
|
|
|
r := rand.New(rand.NewSource(3920758213583))
|
|
keyCounter := 0
|
|
for height := int64(1); height <= int64(blocks); height++ {
|
|
app.BeginBlock(abci.RequestBeginBlock{Header: tmproto.Header{Height: height}})
|
|
for txNum := 0; txNum < blockTxs; txNum++ {
|
|
tx := txTest{Msgs: []sdk.Msg{}}
|
|
for msgNum := 0; msgNum < 100; msgNum++ {
|
|
key := []byte(fmt.Sprintf("%v", keyCounter))
|
|
value := make([]byte, 10000)
|
|
_, err := r.Read(value)
|
|
require.NoError(t, err)
|
|
tx.Msgs = append(tx.Msgs, msgKeyValue{Key: key, Value: value})
|
|
keyCounter++
|
|
}
|
|
txBytes, err := codec.Marshal(tx)
|
|
require.NoError(t, err)
|
|
resp := app.DeliverTx(abci.RequestDeliverTx{Tx: txBytes})
|
|
require.True(t, resp.IsOK(), "%v", resp.String())
|
|
}
|
|
app.EndBlock(abci.RequestEndBlock{Height: height})
|
|
app.Commit()
|
|
|
|
// Wait for snapshot to be taken, since it happens asynchronously.
|
|
if uint64(height)%snapshotInterval == 0 {
|
|
start := time.Now()
|
|
for {
|
|
if time.Since(start) > snapshotTimeout {
|
|
t.Errorf("timed out waiting for snapshot after %v", snapshotTimeout)
|
|
}
|
|
snapshot, err := snapshotStore.Get(uint64(height), snapshottypes.CurrentFormat)
|
|
require.NoError(t, err)
|
|
if snapshot != nil {
|
|
break
|
|
}
|
|
time.Sleep(100 * time.Millisecond)
|
|
}
|
|
}
|
|
}
|
|
|
|
return app, teardown
|
|
}
|
|
|
|
func TestMountStores(t *testing.T) {
|
|
app := setupBaseApp(t)
|
|
|
|
// check both stores
|
|
store1 := app.cms.GetCommitKVStore(capKey1)
|
|
require.NotNil(t, store1)
|
|
store2 := app.cms.GetCommitKVStore(capKey2)
|
|
require.NotNil(t, store2)
|
|
}
|
|
|
|
// Test that we can make commits and then reload old versions.
|
|
// Test that LoadLatestVersion actually does.
|
|
func TestLoadVersion(t *testing.T) {
|
|
logger := defaultLogger()
|
|
pruningOpt := SetPruning(store.PruneNothing)
|
|
db := dbm.NewMemDB()
|
|
name := t.Name()
|
|
app := NewBaseApp(name, logger, db, nil, pruningOpt)
|
|
|
|
// make a cap key and mount the store
|
|
err := app.LoadLatestVersion() // needed to make stores non-nil
|
|
require.Nil(t, err)
|
|
|
|
emptyCommitID := sdk.CommitID{}
|
|
|
|
// fresh store has zero/empty last commit
|
|
lastHeight := app.LastBlockHeight()
|
|
lastID := app.LastCommitID()
|
|
require.Equal(t, int64(0), lastHeight)
|
|
require.Equal(t, emptyCommitID, lastID)
|
|
|
|
// execute a block, collect commit ID
|
|
header := tmproto.Header{Height: 1}
|
|
app.BeginBlock(abci.RequestBeginBlock{Header: header})
|
|
res := app.Commit()
|
|
commitID1 := sdk.CommitID{Version: 1, Hash: res.Data}
|
|
|
|
// execute a block, collect commit ID
|
|
header = tmproto.Header{Height: 2}
|
|
app.BeginBlock(abci.RequestBeginBlock{Header: header})
|
|
res = app.Commit()
|
|
commitID2 := sdk.CommitID{Version: 2, Hash: res.Data}
|
|
|
|
// reload with LoadLatestVersion
|
|
app = NewBaseApp(name, logger, db, nil, pruningOpt)
|
|
app.MountStores()
|
|
err = app.LoadLatestVersion()
|
|
require.Nil(t, err)
|
|
testLoadVersionHelper(t, app, int64(2), commitID2)
|
|
|
|
// reload with LoadVersion, see if you can commit the same block and get
|
|
// the same result
|
|
app = NewBaseApp(name, logger, db, nil, pruningOpt)
|
|
err = app.LoadVersion(1)
|
|
require.Nil(t, err)
|
|
testLoadVersionHelper(t, app, int64(1), commitID1)
|
|
app.BeginBlock(abci.RequestBeginBlock{Header: header})
|
|
app.Commit()
|
|
testLoadVersionHelper(t, app, int64(2), commitID2)
|
|
}
|
|
|
|
func useDefaultLoader(app *BaseApp) {
|
|
app.SetStoreLoader(DefaultStoreLoader)
|
|
}
|
|
|
|
func initStore(t *testing.T, db dbm.DB, storeKey string, k, v []byte) {
|
|
rs := rootmulti.NewStore(db)
|
|
rs.SetPruning(store.PruneNothing)
|
|
key := sdk.NewKVStoreKey(storeKey)
|
|
rs.MountStoreWithDB(key, store.StoreTypeIAVL, nil)
|
|
err := rs.LoadLatestVersion()
|
|
require.Nil(t, err)
|
|
require.Equal(t, int64(0), rs.LastCommitID().Version)
|
|
|
|
// write some data in substore
|
|
kv, _ := rs.GetStore(key).(store.KVStore)
|
|
require.NotNil(t, kv)
|
|
kv.Set(k, v)
|
|
commitID := rs.Commit()
|
|
require.Equal(t, int64(1), commitID.Version)
|
|
}
|
|
|
|
func checkStore(t *testing.T, db dbm.DB, ver int64, storeKey string, k, v []byte) {
|
|
rs := rootmulti.NewStore(db)
|
|
rs.SetPruning(store.PruneDefault)
|
|
key := sdk.NewKVStoreKey(storeKey)
|
|
rs.MountStoreWithDB(key, store.StoreTypeIAVL, nil)
|
|
err := rs.LoadLatestVersion()
|
|
require.Nil(t, err)
|
|
require.Equal(t, ver, rs.LastCommitID().Version)
|
|
|
|
// query data in substore
|
|
kv, _ := rs.GetStore(key).(store.KVStore)
|
|
require.NotNil(t, kv)
|
|
require.Equal(t, v, kv.Get(k))
|
|
}
|
|
|
|
// Test that we can make commits and then reload old versions.
|
|
// Test that LoadLatestVersion actually does.
|
|
func TestSetLoader(t *testing.T) {
|
|
cases := map[string]struct {
|
|
setLoader func(*BaseApp)
|
|
origStoreKey string
|
|
loadStoreKey string
|
|
}{
|
|
"don't set loader": {
|
|
origStoreKey: "foo",
|
|
loadStoreKey: "foo",
|
|
},
|
|
"default loader": {
|
|
setLoader: useDefaultLoader,
|
|
origStoreKey: "foo",
|
|
loadStoreKey: "foo",
|
|
},
|
|
}
|
|
|
|
k := []byte("key")
|
|
v := []byte("value")
|
|
|
|
for name, tc := range cases {
|
|
tc := tc
|
|
t.Run(name, func(t *testing.T) {
|
|
// prepare a db with some data
|
|
db := dbm.NewMemDB()
|
|
initStore(t, db, tc.origStoreKey, k, v)
|
|
|
|
// load the app with the existing db
|
|
opts := []func(*BaseApp){SetPruning(store.PruneNothing)}
|
|
if tc.setLoader != nil {
|
|
opts = append(opts, tc.setLoader)
|
|
}
|
|
app := NewBaseApp(t.Name(), defaultLogger(), db, nil, opts...)
|
|
app.MountStores(sdk.NewKVStoreKey(tc.loadStoreKey))
|
|
err := app.LoadLatestVersion()
|
|
require.Nil(t, err)
|
|
|
|
// "execute" one block
|
|
app.BeginBlock(abci.RequestBeginBlock{Header: tmproto.Header{Height: 2}})
|
|
res := app.Commit()
|
|
require.NotNil(t, res.Data)
|
|
|
|
// check db is properly updated
|
|
checkStore(t, db, 2, tc.loadStoreKey, k, v)
|
|
checkStore(t, db, 2, tc.loadStoreKey, []byte("foo"), nil)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestVersionSetterGetter(t *testing.T) {
|
|
logger := defaultLogger()
|
|
pruningOpt := SetPruning(store.PruneDefault)
|
|
db := dbm.NewMemDB()
|
|
name := t.Name()
|
|
app := NewBaseApp(name, logger, db, nil, pruningOpt)
|
|
|
|
require.Equal(t, "", app.Version())
|
|
res := app.Query(abci.RequestQuery{Path: "app/version"})
|
|
require.True(t, res.IsOK())
|
|
require.Equal(t, "", string(res.Value))
|
|
|
|
versionString := "1.0.0"
|
|
app.SetVersion(versionString)
|
|
require.Equal(t, versionString, app.Version())
|
|
res = app.Query(abci.RequestQuery{Path: "app/version"})
|
|
require.True(t, res.IsOK())
|
|
require.Equal(t, versionString, string(res.Value))
|
|
}
|
|
|
|
func TestLoadVersionInvalid(t *testing.T) {
|
|
logger := log.NewNopLogger()
|
|
pruningOpt := SetPruning(store.PruneNothing)
|
|
db := dbm.NewMemDB()
|
|
name := t.Name()
|
|
app := NewBaseApp(name, logger, db, nil, pruningOpt)
|
|
|
|
err := app.LoadLatestVersion()
|
|
require.Nil(t, err)
|
|
|
|
// require error when loading an invalid version
|
|
err = app.LoadVersion(-1)
|
|
require.Error(t, err)
|
|
|
|
header := tmproto.Header{Height: 1}
|
|
app.BeginBlock(abci.RequestBeginBlock{Header: header})
|
|
res := app.Commit()
|
|
commitID1 := sdk.CommitID{Version: 1, Hash: res.Data}
|
|
|
|
// create a new app with the stores mounted under the same cap key
|
|
app = NewBaseApp(name, logger, db, nil, pruningOpt)
|
|
|
|
// require we can load the latest version
|
|
err = app.LoadVersion(1)
|
|
require.Nil(t, err)
|
|
testLoadVersionHelper(t, app, int64(1), commitID1)
|
|
|
|
// require error when loading an invalid version
|
|
err = app.LoadVersion(2)
|
|
require.Error(t, err)
|
|
}
|
|
|
|
func TestLoadVersionPruning(t *testing.T) {
|
|
logger := log.NewNopLogger()
|
|
pruningOptions := store.PruningOptions{
|
|
KeepRecent: 2,
|
|
KeepEvery: 3,
|
|
Interval: 1,
|
|
}
|
|
pruningOpt := SetPruning(pruningOptions)
|
|
db := dbm.NewMemDB()
|
|
name := t.Name()
|
|
app := NewBaseApp(name, logger, db, nil, pruningOpt)
|
|
|
|
// make a cap key and mount the store
|
|
capKey := sdk.NewKVStoreKey("key1")
|
|
app.MountStores(capKey)
|
|
|
|
err := app.LoadLatestVersion() // needed to make stores non-nil
|
|
require.Nil(t, err)
|
|
|
|
emptyCommitID := sdk.CommitID{}
|
|
|
|
// fresh store has zero/empty last commit
|
|
lastHeight := app.LastBlockHeight()
|
|
lastID := app.LastCommitID()
|
|
require.Equal(t, int64(0), lastHeight)
|
|
require.Equal(t, emptyCommitID, lastID)
|
|
|
|
var lastCommitID sdk.CommitID
|
|
|
|
// Commit seven blocks, of which 7 (latest) is kept in addition to 6, 5
|
|
// (keep recent) and 3 (keep every).
|
|
for i := int64(1); i <= 7; i++ {
|
|
app.BeginBlock(abci.RequestBeginBlock{Header: tmproto.Header{Height: i}})
|
|
res := app.Commit()
|
|
lastCommitID = sdk.CommitID{Version: i, Hash: res.Data}
|
|
}
|
|
|
|
for _, v := range []int64{1, 2, 4} {
|
|
_, err = app.cms.CacheMultiStoreWithVersion(v)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
for _, v := range []int64{3, 5, 6, 7} {
|
|
_, err = app.cms.CacheMultiStoreWithVersion(v)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
// reload with LoadLatestVersion, check it loads last version
|
|
app = NewBaseApp(name, logger, db, nil, pruningOpt)
|
|
app.MountStores(capKey)
|
|
|
|
err = app.LoadLatestVersion()
|
|
require.Nil(t, err)
|
|
testLoadVersionHelper(t, app, int64(7), lastCommitID)
|
|
}
|
|
|
|
func testLoadVersionHelper(t *testing.T, app *BaseApp, expectedHeight int64, expectedID sdk.CommitID) {
|
|
lastHeight := app.LastBlockHeight()
|
|
lastID := app.LastCommitID()
|
|
require.Equal(t, expectedHeight, lastHeight)
|
|
require.Equal(t, expectedID, lastID)
|
|
}
|
|
|
|
func TestOptionFunction(t *testing.T) {
|
|
logger := defaultLogger()
|
|
db := dbm.NewMemDB()
|
|
bap := NewBaseApp("starting name", logger, db, nil, testChangeNameHelper("new name"))
|
|
require.Equal(t, bap.name, "new name", "BaseApp should have had name changed via option function")
|
|
}
|
|
|
|
func testChangeNameHelper(name string) func(*BaseApp) {
|
|
return func(bap *BaseApp) {
|
|
bap.name = name
|
|
}
|
|
}
|
|
|
|
// Test that txs can be unmarshalled and read and that
|
|
// correct error codes are returned when not
|
|
func TestTxDecoder(t *testing.T) {
|
|
codec := codec.NewLegacyAmino()
|
|
registerTestCodec(codec)
|
|
|
|
app := newBaseApp(t.Name())
|
|
tx := newTxCounter(1, 0)
|
|
txBytes := codec.MustMarshal(tx)
|
|
|
|
dTx, err := app.txDecoder(txBytes)
|
|
require.NoError(t, err)
|
|
|
|
cTx := dTx.(txTest)
|
|
require.Equal(t, tx.Counter, cTx.Counter)
|
|
}
|
|
|
|
// Test that Info returns the latest committed state.
|
|
func TestInfo(t *testing.T) {
|
|
app := newBaseApp(t.Name())
|
|
|
|
// ----- test an empty response -------
|
|
reqInfo := abci.RequestInfo{}
|
|
res := app.Info(reqInfo)
|
|
|
|
// should be empty
|
|
assert.Equal(t, "", res.Version)
|
|
assert.Equal(t, t.Name(), res.GetData())
|
|
assert.Equal(t, int64(0), res.LastBlockHeight)
|
|
require.Equal(t, []uint8(nil), res.LastBlockAppHash)
|
|
require.Equal(t, app.AppVersion(), res.AppVersion)
|
|
// ----- test a proper response -------
|
|
// TODO
|
|
}
|
|
|
|
func TestBaseAppOptionSeal(t *testing.T) {
|
|
app := setupBaseApp(t)
|
|
|
|
require.Panics(t, func() {
|
|
app.SetName("")
|
|
})
|
|
require.Panics(t, func() {
|
|
app.SetVersion("")
|
|
})
|
|
require.Panics(t, func() {
|
|
app.SetDB(nil)
|
|
})
|
|
require.Panics(t, func() {
|
|
app.SetCMS(nil)
|
|
})
|
|
require.Panics(t, func() {
|
|
app.SetInitChainer(nil)
|
|
})
|
|
require.Panics(t, func() {
|
|
app.SetBeginBlocker(nil)
|
|
})
|
|
require.Panics(t, func() {
|
|
app.SetEndBlocker(nil)
|
|
})
|
|
require.Panics(t, func() {
|
|
app.SetAnteHandler(nil)
|
|
})
|
|
require.Panics(t, func() {
|
|
app.SetAddrPeerFilter(nil)
|
|
})
|
|
require.Panics(t, func() {
|
|
app.SetIDPeerFilter(nil)
|
|
})
|
|
require.Panics(t, func() {
|
|
app.SetFauxMerkleMode()
|
|
})
|
|
require.Panics(t, func() {
|
|
app.SetRouter(NewRouter())
|
|
})
|
|
}
|
|
|
|
func TestSetMinGasPrices(t *testing.T) {
|
|
minGasPrices := sdk.DecCoins{sdk.NewInt64DecCoin("stake", 5000)}
|
|
app := newBaseApp(t.Name(), SetMinGasPrices(minGasPrices.String()))
|
|
require.Equal(t, minGasPrices, app.minGasPrices)
|
|
}
|
|
|
|
func TestInitChainer(t *testing.T) {
|
|
name := t.Name()
|
|
// keep the db and logger ourselves so
|
|
// we can reload the same app later
|
|
db := dbm.NewMemDB()
|
|
logger := defaultLogger()
|
|
app := NewBaseApp(name, logger, db, nil)
|
|
capKey := sdk.NewKVStoreKey("main")
|
|
capKey2 := sdk.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.RequestInitChain) abci.ResponseInitChain {
|
|
store := ctx.KVStore(capKey)
|
|
store.Set(key, value)
|
|
return abci.ResponseInitChain{}
|
|
}
|
|
|
|
query := abci.RequestQuery{
|
|
Path: "/store/main/key",
|
|
Data: key,
|
|
}
|
|
|
|
// initChainer is nil - nothing happens
|
|
app.InitChain(abci.RequestInitChain{})
|
|
res := app.Query(query)
|
|
require.Equal(t, 0, len(res.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 := app.InitChain(abci.RequestInitChain{AppStateBytes: []byte("{}"), ChainId: "test-chain-id"}) // must have valid JSON genesis file, even if empty
|
|
|
|
// The AppHash returned by a new chain is the sha256 hash of "".
|
|
// $ echo -n '' | sha256sum
|
|
// e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
|
|
require.Equal(
|
|
t,
|
|
[]byte{0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, 0x9a, 0xfb, 0xf4, 0xc8, 0x99, 0x6f, 0xb9, 0x24, 0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c, 0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55},
|
|
initChainRes.AppHash,
|
|
)
|
|
|
|
// assert that chainID is set correctly in InitChain
|
|
chainID := app.deliverState.ctx.ChainID()
|
|
require.Equal(t, "test-chain-id", chainID, "ChainID in deliverState not set correctly in InitChain")
|
|
|
|
chainID = app.checkState.ctx.ChainID()
|
|
require.Equal(t, "test-chain-id", chainID, "ChainID in checkState not set correctly in InitChain")
|
|
|
|
app.Commit()
|
|
res = app.Query(query)
|
|
require.Equal(t, int64(1), app.LastBlockHeight())
|
|
require.Equal(t, value, res.Value)
|
|
|
|
// reload app
|
|
app = 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
|
|
res = app.Query(query)
|
|
require.Equal(t, value, res.Value)
|
|
|
|
// commit and ensure we can still query
|
|
header := tmproto.Header{Height: app.LastBlockHeight() + 1}
|
|
app.BeginBlock(abci.RequestBeginBlock{Header: header})
|
|
app.Commit()
|
|
|
|
res = app.Query(query)
|
|
require.Equal(t, value, res.Value)
|
|
}
|
|
|
|
func TestInitChain_WithInitialHeight(t *testing.T) {
|
|
name := t.Name()
|
|
db := dbm.NewMemDB()
|
|
logger := defaultLogger()
|
|
app := NewBaseApp(name, logger, db, nil)
|
|
|
|
app.InitChain(
|
|
abci.RequestInitChain{
|
|
InitialHeight: 3,
|
|
},
|
|
)
|
|
app.Commit()
|
|
|
|
require.Equal(t, int64(3), app.LastBlockHeight())
|
|
}
|
|
|
|
func TestBeginBlock_WithInitialHeight(t *testing.T) {
|
|
name := t.Name()
|
|
db := dbm.NewMemDB()
|
|
logger := defaultLogger()
|
|
app := NewBaseApp(name, logger, db, nil)
|
|
|
|
app.InitChain(
|
|
abci.RequestInitChain{
|
|
InitialHeight: 3,
|
|
},
|
|
)
|
|
|
|
require.PanicsWithError(t, "invalid height: 4; expected: 3", func() {
|
|
app.BeginBlock(abci.RequestBeginBlock{
|
|
Header: tmproto.Header{
|
|
Height: 4,
|
|
},
|
|
})
|
|
})
|
|
|
|
app.BeginBlock(abci.RequestBeginBlock{
|
|
Header: tmproto.Header{
|
|
Height: 3,
|
|
},
|
|
})
|
|
app.Commit()
|
|
|
|
require.Equal(t, int64(3), app.LastBlockHeight())
|
|
}
|
|
|
|
// Simple tx with a list of Msgs.
|
|
type txTest struct {
|
|
Msgs []sdk.Msg
|
|
Counter int64
|
|
FailOnAnte bool
|
|
}
|
|
|
|
func (tx *txTest) setFailOnAnte(fail bool) {
|
|
tx.FailOnAnte = fail
|
|
}
|
|
|
|
func (tx *txTest) setFailOnHandler(fail bool) {
|
|
for i, msg := range tx.Msgs {
|
|
tx.Msgs[i] = msgCounter{msg.(msgCounter).Counter, fail}
|
|
}
|
|
}
|
|
|
|
// Implements Tx
|
|
func (tx txTest) GetMsgs() []sdk.Msg { return tx.Msgs }
|
|
func (tx txTest) ValidateBasic() error { return nil }
|
|
|
|
const (
|
|
routeMsgCounter = "msgCounter"
|
|
routeMsgCounter2 = "msgCounter2"
|
|
routeMsgKeyValue = "msgKeyValue"
|
|
)
|
|
|
|
// ValidateBasic() fails on negative counters.
|
|
// Otherwise it's up to the handlers
|
|
type msgCounter struct {
|
|
Counter int64
|
|
FailOnHandler bool
|
|
}
|
|
|
|
// dummy implementation of proto.Message
|
|
func (msg msgCounter) Reset() {}
|
|
func (msg msgCounter) String() string { return "TODO" }
|
|
func (msg msgCounter) ProtoMessage() {}
|
|
|
|
// Implements Msg
|
|
func (msg msgCounter) Route() string { return routeMsgCounter }
|
|
func (msg msgCounter) Type() string { return "counter1" }
|
|
func (msg msgCounter) GetSignBytes() []byte { return nil }
|
|
func (msg msgCounter) GetSigners() []string { return nil }
|
|
func (msg msgCounter) ValidateBasic() error {
|
|
if msg.Counter >= 0 {
|
|
return nil
|
|
}
|
|
return sdkerrors.Wrap(sdkerrors.ErrInvalidSequence, "counter should be a non-negative integer")
|
|
}
|
|
|
|
func newTxCounter(counter int64, msgCounters ...int64) *txTest {
|
|
msgs := make([]sdk.Msg, 0, len(msgCounters))
|
|
for _, c := range msgCounters {
|
|
msgs = append(msgs, msgCounter{c, false})
|
|
}
|
|
|
|
return &txTest{msgs, counter, false}
|
|
}
|
|
|
|
// a msg we dont know how to route
|
|
type msgNoRoute struct {
|
|
msgCounter
|
|
}
|
|
|
|
func (tx msgNoRoute) Route() string { return "noroute" }
|
|
|
|
// a msg we dont know how to decode
|
|
type msgNoDecode struct {
|
|
msgCounter
|
|
}
|
|
|
|
func (tx msgNoDecode) Route() string { return routeMsgCounter }
|
|
|
|
// Another counter msg. Duplicate of msgCounter
|
|
type msgCounter2 struct {
|
|
Counter int64
|
|
}
|
|
|
|
// dummy implementation of proto.Message
|
|
func (msg msgCounter2) Reset() {}
|
|
func (msg msgCounter2) String() string { return "TODO" }
|
|
func (msg msgCounter2) ProtoMessage() {}
|
|
|
|
// Implements Msg
|
|
func (msg msgCounter2) Route() string { return routeMsgCounter2 }
|
|
func (msg msgCounter2) Type() string { return "counter2" }
|
|
func (msg msgCounter2) GetSignBytes() []byte { return nil }
|
|
func (msg msgCounter2) GetSigners() []string { return nil }
|
|
func (msg msgCounter2) ValidateBasic() error {
|
|
if msg.Counter >= 0 {
|
|
return nil
|
|
}
|
|
return sdkerrors.Wrap(sdkerrors.ErrInvalidSequence, "counter should be a non-negative integer")
|
|
}
|
|
|
|
// A msg that sets a key/value pair.
|
|
type msgKeyValue struct {
|
|
Key []byte
|
|
Value []byte
|
|
}
|
|
|
|
func (msg msgKeyValue) Reset() {}
|
|
func (msg msgKeyValue) String() string { return "TODO" }
|
|
func (msg msgKeyValue) ProtoMessage() {}
|
|
func (msg msgKeyValue) Route() string { return routeMsgKeyValue }
|
|
func (msg msgKeyValue) Type() string { return "keyValue" }
|
|
func (msg msgKeyValue) GetSignBytes() []byte { return nil }
|
|
func (msg msgKeyValue) GetSigners() []string { return nil }
|
|
func (msg msgKeyValue) ValidateBasic() error {
|
|
if msg.Key == nil {
|
|
return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "key cannot be nil")
|
|
}
|
|
if msg.Value == nil {
|
|
return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "value cannot be nil")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// amino decode
|
|
func testTxDecoder(cdc *codec.LegacyAmino) sdk.TxDecoder {
|
|
return func(txBytes []byte) (sdk.Tx, error) {
|
|
var tx txTest
|
|
if len(txBytes) == 0 {
|
|
return nil, sdkerrors.Wrap(sdkerrors.ErrTxDecode, "tx bytes are empty")
|
|
}
|
|
|
|
err := cdc.Unmarshal(txBytes, &tx)
|
|
if err != nil {
|
|
return nil, sdkerrors.ErrTxDecode
|
|
}
|
|
|
|
return tx, nil
|
|
}
|
|
}
|
|
|
|
func anteHandlerTxTest(t *testing.T, capKey sdk.StoreKey, storeKey []byte) sdk.AnteHandler {
|
|
return func(ctx sdk.Context, tx sdk.Tx, simulate bool) (sdk.Context, error) {
|
|
store := ctx.KVStore(capKey)
|
|
txTest := tx.(txTest)
|
|
|
|
if txTest.FailOnAnte {
|
|
return ctx, sdkerrors.Wrap(sdkerrors.ErrUnauthorized, "ante handler failure")
|
|
}
|
|
|
|
_, err := incrementingCounter(t, store, storeKey, txTest.Counter)
|
|
if err != nil {
|
|
return ctx, err
|
|
}
|
|
|
|
ctx.EventManager().EmitEvents(
|
|
counterEvent("ante_handler", txTest.Counter),
|
|
)
|
|
|
|
return ctx, nil
|
|
}
|
|
}
|
|
|
|
func counterEvent(evType string, msgCount int64) sdk.Events {
|
|
return sdk.Events{
|
|
sdk.NewEvent(
|
|
evType,
|
|
sdk.NewAttribute("update_counter", fmt.Sprintf("%d", msgCount)),
|
|
),
|
|
}
|
|
}
|
|
|
|
func handlerMsgCounter(t *testing.T, capKey sdk.StoreKey, deliverKey []byte) sdk.Handler {
|
|
return func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) {
|
|
ctx = ctx.WithEventManager(sdk.NewEventManager())
|
|
store := ctx.KVStore(capKey)
|
|
var msgCount int64
|
|
|
|
switch m := msg.(type) {
|
|
case *msgCounter:
|
|
if m.FailOnHandler {
|
|
return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "message handler failure")
|
|
}
|
|
|
|
msgCount = m.Counter
|
|
case *msgCounter2:
|
|
msgCount = m.Counter
|
|
}
|
|
|
|
ctx.EventManager().EmitEvents(
|
|
counterEvent(sdk.EventTypeMessage, msgCount),
|
|
)
|
|
|
|
res, err := incrementingCounter(t, store, deliverKey, msgCount)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
res.Events = ctx.EventManager().Events().ToABCIEvents()
|
|
return res, nil
|
|
}
|
|
}
|
|
|
|
func getIntFromStore(store sdk.KVStore, key []byte) int64 {
|
|
bz := store.Get(key)
|
|
if len(bz) == 0 {
|
|
return 0
|
|
}
|
|
i, err := binary.ReadVarint(bytes.NewBuffer(bz))
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return i
|
|
}
|
|
|
|
func setIntOnStore(store sdk.KVStore, key []byte, i int64) {
|
|
bz := make([]byte, 8)
|
|
n := binary.PutVarint(bz, i)
|
|
store.Set(key, bz[:n])
|
|
}
|
|
|
|
// check counter matches what's in store.
|
|
// increment and store
|
|
func incrementingCounter(t *testing.T, store sdk.KVStore, counterKey []byte, counter int64) (*sdk.Result, error) {
|
|
storedCounter := getIntFromStore(store, counterKey)
|
|
require.Equal(t, storedCounter, counter)
|
|
setIntOnStore(store, counterKey, counter+1)
|
|
return &sdk.Result{}, nil
|
|
}
|
|
|
|
//---------------------------------------------------------------------
|
|
// Tx processing - CheckTx, DeliverTx, SimulateTx.
|
|
// These tests use the serialized tx as input, while most others will use the
|
|
// Check(), Deliver(), Simulate() methods directly.
|
|
// Ensure that Check/Deliver/Simulate work as expected with the store.
|
|
|
|
// Test that successive CheckTx can see each others' effects
|
|
// on the store within a block, and that the CheckTx state
|
|
// gets reset to the latest committed state during Commit
|
|
func TestCheckTx(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.
|
|
counterKey := []byte("counter-key")
|
|
|
|
anteOpt := func(bapp *BaseApp) { bapp.SetAnteHandler(anteHandlerTxTest(t, capKey1, counterKey)) }
|
|
routerOpt := func(bapp *BaseApp) {
|
|
// TODO: can remove this once CheckTx doesnt process msgs.
|
|
bapp.Router().AddRoute(sdk.NewRoute(routeMsgCounter, func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) {
|
|
return &sdk.Result{}, nil
|
|
}))
|
|
}
|
|
|
|
app := setupBaseApp(t, anteOpt, routerOpt)
|
|
|
|
nTxs := int64(5)
|
|
app.InitChain(abci.RequestInitChain{})
|
|
|
|
// Create same codec used in txDecoder
|
|
codec := codec.NewLegacyAmino()
|
|
registerTestCodec(codec)
|
|
|
|
for i := int64(0); i < nTxs; i++ {
|
|
tx := newTxCounter(i, 0) // no messages
|
|
txBytes, err := codec.Marshal(tx)
|
|
require.NoError(t, err)
|
|
r := app.CheckTx(abci.RequestCheckTx{Tx: txBytes})
|
|
require.Empty(t, r.GetEvents())
|
|
require.True(t, r.IsOK(), fmt.Sprintf("%v", r))
|
|
}
|
|
|
|
checkStateStore := app.checkState.ctx.KVStore(capKey1)
|
|
storedCounter := getIntFromStore(checkStateStore, counterKey)
|
|
|
|
// Ensure AnteHandler ran
|
|
require.Equal(t, nTxs, storedCounter)
|
|
|
|
// If a block is committed, CheckTx state should be reset.
|
|
header := tmproto.Header{Height: 1}
|
|
app.BeginBlock(abci.RequestBeginBlock{Header: header, Hash: []byte("hash")})
|
|
|
|
require.NotNil(t, app.checkState.ctx.BlockGasMeter(), "block gas meter should have been set to checkState")
|
|
require.NotEmpty(t, app.checkState.ctx.HeaderHash())
|
|
|
|
app.EndBlock(abci.RequestEndBlock{})
|
|
app.Commit()
|
|
|
|
checkStateStore = app.checkState.ctx.KVStore(capKey1)
|
|
storedBytes := checkStateStore.Get(counterKey)
|
|
require.Nil(t, storedBytes)
|
|
}
|
|
|
|
// Test that successive DeliverTx can see each others' effects
|
|
// on the store, both within and across blocks.
|
|
func TestDeliverTx(t *testing.T) {
|
|
// test increments in the ante
|
|
anteKey := []byte("ante-key")
|
|
anteOpt := func(bapp *BaseApp) { bapp.SetAnteHandler(anteHandlerTxTest(t, capKey1, anteKey)) }
|
|
|
|
// test increments in the handler
|
|
deliverKey := []byte("deliver-key")
|
|
routerOpt := func(bapp *BaseApp) {
|
|
r := sdk.NewRoute(routeMsgCounter, handlerMsgCounter(t, capKey1, deliverKey))
|
|
bapp.Router().AddRoute(r)
|
|
}
|
|
|
|
app := setupBaseApp(t, anteOpt, routerOpt)
|
|
app.InitChain(abci.RequestInitChain{})
|
|
|
|
// Create same codec used in txDecoder
|
|
codec := codec.NewLegacyAmino()
|
|
registerTestCodec(codec)
|
|
|
|
nBlocks := 3
|
|
txPerHeight := 5
|
|
|
|
for blockN := 0; blockN < nBlocks; blockN++ {
|
|
header := tmproto.Header{Height: int64(blockN) + 1}
|
|
app.BeginBlock(abci.RequestBeginBlock{Header: header})
|
|
|
|
for i := 0; i < txPerHeight; i++ {
|
|
counter := int64(blockN*txPerHeight + i)
|
|
tx := newTxCounter(counter, counter)
|
|
|
|
txBytes, err := codec.Marshal(tx)
|
|
require.NoError(t, err)
|
|
|
|
res := app.DeliverTx(abci.RequestDeliverTx{Tx: txBytes})
|
|
require.True(t, res.IsOK(), fmt.Sprintf("%v", res))
|
|
events := res.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], events[2], "msg handler update counter event")
|
|
}
|
|
|
|
app.EndBlock(abci.RequestEndBlock{})
|
|
app.Commit()
|
|
}
|
|
}
|
|
|
|
// Number of messages doesn't matter to CheckTx.
|
|
func TestMultiMsgCheckTx(t *testing.T) {
|
|
// TODO: ensure we get the same results
|
|
// with one message or many
|
|
}
|
|
|
|
// One call to DeliverTx should process all the messages, in order.
|
|
func TestMultiMsgDeliverTx(t *testing.T) {
|
|
// increment the tx counter
|
|
anteKey := []byte("ante-key")
|
|
anteOpt := func(bapp *BaseApp) { bapp.SetAnteHandler(anteHandlerTxTest(t, capKey1, anteKey)) }
|
|
|
|
// increment the msg counter
|
|
deliverKey := []byte("deliver-key")
|
|
deliverKey2 := []byte("deliver-key2")
|
|
routerOpt := func(bapp *BaseApp) {
|
|
r1 := sdk.NewRoute(routeMsgCounter, handlerMsgCounter(t, capKey1, deliverKey))
|
|
r2 := sdk.NewRoute(routeMsgCounter2, handlerMsgCounter(t, capKey1, deliverKey2))
|
|
bapp.Router().AddRoute(r1)
|
|
bapp.Router().AddRoute(r2)
|
|
}
|
|
|
|
app := setupBaseApp(t, anteOpt, routerOpt)
|
|
|
|
// Create same codec used in txDecoder
|
|
codec := codec.NewLegacyAmino()
|
|
registerTestCodec(codec)
|
|
|
|
// run a multi-msg tx
|
|
// with all msgs the same route
|
|
|
|
header := tmproto.Header{Height: 1}
|
|
app.BeginBlock(abci.RequestBeginBlock{Header: header})
|
|
tx := newTxCounter(0, 0, 1, 2)
|
|
txBytes, err := codec.Marshal(tx)
|
|
require.NoError(t, err)
|
|
res := app.DeliverTx(abci.RequestDeliverTx{Tx: txBytes})
|
|
require.True(t, res.IsOK(), fmt.Sprintf("%v", res))
|
|
|
|
store := app.deliverState.ctx.KVStore(capKey1)
|
|
|
|
// tx counter only incremented once
|
|
txCounter := getIntFromStore(store, anteKey)
|
|
require.Equal(t, int64(1), txCounter)
|
|
|
|
// msg counter incremented three times
|
|
msgCounter := getIntFromStore(store, deliverKey)
|
|
require.Equal(t, int64(3), msgCounter)
|
|
|
|
// replace the second message with a msgCounter2
|
|
|
|
tx = newTxCounter(1, 3)
|
|
tx.Msgs = append(tx.Msgs, msgCounter2{0})
|
|
tx.Msgs = append(tx.Msgs, msgCounter2{1})
|
|
txBytes, err = codec.Marshal(tx)
|
|
require.NoError(t, err)
|
|
res = app.DeliverTx(abci.RequestDeliverTx{Tx: txBytes})
|
|
require.True(t, res.IsOK(), fmt.Sprintf("%v", res))
|
|
|
|
store = app.deliverState.ctx.KVStore(capKey1)
|
|
|
|
// tx counter only incremented once
|
|
txCounter = getIntFromStore(store, anteKey)
|
|
require.Equal(t, int64(2), txCounter)
|
|
|
|
// original counter increments by one
|
|
// new counter increments by two
|
|
msgCounter = getIntFromStore(store, deliverKey)
|
|
require.Equal(t, int64(4), msgCounter)
|
|
msgCounter2 := getIntFromStore(store, deliverKey2)
|
|
require.Equal(t, int64(2), msgCounter2)
|
|
}
|
|
|
|
// Interleave calls to Check and Deliver and ensure
|
|
// that there is no cross-talk. Check sees results of the previous Check calls
|
|
// and Deliver sees that of the previous Deliver calls, but they don't see eachother.
|
|
func TestConcurrentCheckDeliver(t *testing.T) {
|
|
// TODO
|
|
}
|
|
|
|
// Simulate a transaction that uses gas to compute the gas.
|
|
// Simulate() and Query("/app/simulate", txBytes) should give
|
|
// the same results.
|
|
func TestSimulateTx(t *testing.T) {
|
|
gasConsumed := uint64(5)
|
|
|
|
anteOpt := func(bapp *BaseApp) {
|
|
bapp.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, err error) {
|
|
newCtx = ctx.WithGasMeter(sdk.NewGasMeter(gasConsumed))
|
|
return
|
|
})
|
|
}
|
|
|
|
routerOpt := func(bapp *BaseApp) {
|
|
r := sdk.NewRoute(routeMsgCounter, func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) {
|
|
ctx.GasMeter().ConsumeGas(gasConsumed, "test")
|
|
return &sdk.Result{}, nil
|
|
})
|
|
bapp.Router().AddRoute(r)
|
|
}
|
|
|
|
app := setupBaseApp(t, anteOpt, routerOpt)
|
|
|
|
app.InitChain(abci.RequestInitChain{})
|
|
|
|
// Create same codec used in txDecoder
|
|
cdc := codec.NewLegacyAmino()
|
|
registerTestCodec(cdc)
|
|
|
|
nBlocks := 3
|
|
for blockN := 0; blockN < nBlocks; blockN++ {
|
|
count := int64(blockN + 1)
|
|
header := tmproto.Header{Height: count}
|
|
app.BeginBlock(abci.RequestBeginBlock{Header: header})
|
|
|
|
tx := newTxCounter(count, count)
|
|
txBytes, err := cdc.Marshal(tx)
|
|
require.Nil(t, err)
|
|
|
|
// simulate a message, check gas reported
|
|
gInfo, result, err := app.Simulate(txBytes)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, result)
|
|
require.Equal(t, gasConsumed, gInfo.GasUsed)
|
|
|
|
// simulate again, same result
|
|
gInfo, result, err = app.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.RequestQuery{
|
|
Path: "/app/simulate",
|
|
Data: txBytes,
|
|
}
|
|
queryResult := app.Query(query)
|
|
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))
|
|
|
|
app.EndBlock(abci.RequestEndBlock{})
|
|
app.Commit()
|
|
}
|
|
}
|
|
|
|
func TestRunInvalidTransaction(t *testing.T) {
|
|
anteOpt := func(bapp *BaseApp) {
|
|
bapp.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, err error) {
|
|
return
|
|
})
|
|
}
|
|
routerOpt := func(bapp *BaseApp) {
|
|
r := sdk.NewRoute(routeMsgCounter, func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) {
|
|
return &sdk.Result{}, nil
|
|
})
|
|
bapp.Router().AddRoute(r)
|
|
}
|
|
|
|
app := setupBaseApp(t, anteOpt, routerOpt)
|
|
|
|
header := tmproto.Header{Height: 1}
|
|
app.BeginBlock(abci.RequestBeginBlock{Header: header})
|
|
|
|
// transaction with no messages
|
|
{
|
|
emptyTx := &txTest{}
|
|
_, result, err := app.Deliver(aminoTxEncoder(), emptyTx)
|
|
require.Error(t, err)
|
|
require.Nil(t, result)
|
|
|
|
space, code, _ := sdkerrors.ABCIInfo(err, false)
|
|
require.EqualValues(t, sdkerrors.ErrInvalidRequest.Codespace(), space, err)
|
|
require.EqualValues(t, sdkerrors.ErrInvalidRequest.ABCICode(), code, err)
|
|
}
|
|
|
|
// transaction where ValidateBasic fails
|
|
{
|
|
testCases := []struct {
|
|
tx *txTest
|
|
fail bool
|
|
}{
|
|
{newTxCounter(0, 0), false},
|
|
{newTxCounter(-1, 0), false},
|
|
{newTxCounter(100, 100), false},
|
|
{newTxCounter(100, 5, 4, 3, 2, 1), false},
|
|
|
|
{newTxCounter(0, -1), true},
|
|
{newTxCounter(0, 1, -2), true},
|
|
{newTxCounter(0, 1, 2, -10, 5), true},
|
|
}
|
|
|
|
for _, testCase := range testCases {
|
|
tx := testCase.tx
|
|
_, result, err := app.Deliver(aminoTxEncoder(), tx)
|
|
|
|
if testCase.fail {
|
|
require.Error(t, err)
|
|
|
|
space, code, _ := sdkerrors.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
|
|
{
|
|
unknownRouteTx := txTest{[]sdk.Msg{msgNoRoute{}}, 0, false}
|
|
_, result, err := app.Deliver(aminoTxEncoder(), unknownRouteTx)
|
|
require.Error(t, err)
|
|
require.Nil(t, result)
|
|
|
|
space, code, _ := sdkerrors.ABCIInfo(err, false)
|
|
require.EqualValues(t, sdkerrors.ErrUnknownRequest.Codespace(), space, err)
|
|
require.EqualValues(t, sdkerrors.ErrUnknownRequest.ABCICode(), code, err)
|
|
|
|
unknownRouteTx = txTest{[]sdk.Msg{msgCounter{}, msgNoRoute{}}, 0, false}
|
|
_, result, err = app.Deliver(aminoTxEncoder(), unknownRouteTx)
|
|
require.Error(t, err)
|
|
require.Nil(t, result)
|
|
|
|
space, code, _ = sdkerrors.ABCIInfo(err, false)
|
|
require.EqualValues(t, sdkerrors.ErrUnknownRequest.Codespace(), space, err)
|
|
require.EqualValues(t, sdkerrors.ErrUnknownRequest.ABCICode(), code, err)
|
|
}
|
|
|
|
// Transaction with an unregistered message
|
|
{
|
|
tx := newTxCounter(0, 0)
|
|
tx.Msgs = append(tx.Msgs, msgNoDecode{})
|
|
|
|
// new codec so we can encode the tx, but we shouldn't be able to decode
|
|
newCdc := codec.NewLegacyAmino()
|
|
registerTestCodec(newCdc)
|
|
newCdc.RegisterConcrete(&msgNoDecode{}, "cosmos-sdk/baseapp/msgNoDecode", nil)
|
|
|
|
txBytes, err := newCdc.Marshal(tx)
|
|
require.NoError(t, err)
|
|
|
|
res := app.DeliverTx(abci.RequestDeliverTx{Tx: txBytes})
|
|
require.EqualValues(t, sdkerrors.ErrTxDecode.ABCICode(), res.Code)
|
|
require.EqualValues(t, sdkerrors.ErrTxDecode.Codespace(), res.Codespace)
|
|
}
|
|
}
|
|
|
|
// Test that transactions exceeding gas limits fail
|
|
func TestTxGasLimits(t *testing.T) {
|
|
gasGranted := uint64(10)
|
|
anteOpt := func(bapp *BaseApp) {
|
|
bapp.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, err error) {
|
|
newCtx = ctx.WithGasMeter(sdk.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 sdk.ErrorOutOfGas:
|
|
err = sdkerrors.Wrapf(sdkerrors.ErrOutOfGas, "out of gas in location: %v", rType.Descriptor)
|
|
default:
|
|
panic(r)
|
|
}
|
|
}
|
|
}()
|
|
|
|
count := tx.(txTest).Counter
|
|
newCtx.GasMeter().ConsumeGas(uint64(count), "counter-ante")
|
|
|
|
return newCtx, nil
|
|
})
|
|
|
|
}
|
|
|
|
routerOpt := func(bapp *BaseApp) {
|
|
r := sdk.NewRoute(routeMsgCounter, func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) {
|
|
count := msg.(*msgCounter).Counter
|
|
ctx.GasMeter().ConsumeGas(uint64(count), "counter-handler")
|
|
return &sdk.Result{}, nil
|
|
})
|
|
bapp.Router().AddRoute(r)
|
|
}
|
|
|
|
app := setupBaseApp(t, anteOpt, routerOpt)
|
|
|
|
header := tmproto.Header{Height: 1}
|
|
app.BeginBlock(abci.RequestBeginBlock{Header: header})
|
|
|
|
testCases := []struct {
|
|
tx *txTest
|
|
gasUsed uint64
|
|
fail bool
|
|
}{
|
|
{newTxCounter(0, 0), 0, false},
|
|
{newTxCounter(1, 1), 2, false},
|
|
{newTxCounter(9, 1), 10, false},
|
|
{newTxCounter(1, 9), 10, false},
|
|
{newTxCounter(10, 0), 10, false},
|
|
{newTxCounter(0, 10), 10, false},
|
|
{newTxCounter(0, 8, 2), 10, false},
|
|
{newTxCounter(0, 5, 1, 1, 1, 1, 1), 10, false},
|
|
{newTxCounter(0, 5, 1, 1, 1, 1), 9, false},
|
|
|
|
{newTxCounter(9, 2), 11, true},
|
|
{newTxCounter(2, 9), 11, true},
|
|
{newTxCounter(9, 1, 1), 11, true},
|
|
{newTxCounter(1, 8, 1, 1), 11, true},
|
|
{newTxCounter(11, 0), 11, true},
|
|
{newTxCounter(0, 11), 11, true},
|
|
{newTxCounter(0, 5, 11), 16, true},
|
|
}
|
|
|
|
for i, tc := range testCases {
|
|
tx := tc.tx
|
|
gInfo, result, err := app.Deliver(aminoTxEncoder(), tx)
|
|
|
|
// check gas used and wanted
|
|
require.Equal(t, tc.gasUsed, gInfo.GasUsed, fmt.Sprintf("tc #%d; gas: %v, result: %v, err: %s", i, gInfo, result, err))
|
|
|
|
// check for out of gas
|
|
if !tc.fail {
|
|
require.NotNil(t, result, fmt.Sprintf("%d: %v, %v", i, tc, err))
|
|
} else {
|
|
require.Error(t, err)
|
|
require.Nil(t, result)
|
|
|
|
space, code, _ := sdkerrors.ABCIInfo(err, false)
|
|
require.EqualValues(t, sdkerrors.ErrOutOfGas.Codespace(), space, err)
|
|
require.EqualValues(t, sdkerrors.ErrOutOfGas.ABCICode(), code, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Test that transactions exceeding gas limits fail
|
|
func TestMaxBlockGasLimits(t *testing.T) {
|
|
gasGranted := uint64(10)
|
|
anteOpt := func(bapp *BaseApp) {
|
|
bapp.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, err error) {
|
|
newCtx = ctx.WithGasMeter(sdk.NewGasMeter(gasGranted))
|
|
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
switch rType := r.(type) {
|
|
case sdk.ErrorOutOfGas:
|
|
err = sdkerrors.Wrapf(sdkerrors.ErrOutOfGas, "out of gas in location: %v", rType.Descriptor)
|
|
default:
|
|
panic(r)
|
|
}
|
|
}
|
|
}()
|
|
|
|
count := tx.(txTest).Counter
|
|
newCtx.GasMeter().ConsumeGas(uint64(count), "counter-ante")
|
|
|
|
return
|
|
})
|
|
}
|
|
|
|
routerOpt := func(bapp *BaseApp) {
|
|
r := sdk.NewRoute(routeMsgCounter, func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) {
|
|
count := msg.(*msgCounter).Counter
|
|
ctx.GasMeter().ConsumeGas(uint64(count), "counter-handler")
|
|
return &sdk.Result{}, nil
|
|
})
|
|
bapp.Router().AddRoute(r)
|
|
}
|
|
|
|
app := setupBaseApp(t, anteOpt, routerOpt)
|
|
app.InitChain(abci.RequestInitChain{
|
|
ConsensusParams: &abci.ConsensusParams{
|
|
Block: &abci.BlockParams{
|
|
MaxGas: 100,
|
|
},
|
|
},
|
|
})
|
|
|
|
testCases := []struct {
|
|
tx *txTest
|
|
numDelivers int
|
|
gasUsedPerDeliver uint64
|
|
fail bool
|
|
failAfterDeliver int
|
|
}{
|
|
{newTxCounter(0, 0), 0, 0, false, 0},
|
|
{newTxCounter(9, 1), 2, 10, false, 0},
|
|
{newTxCounter(10, 0), 3, 10, false, 0},
|
|
{newTxCounter(10, 0), 10, 10, false, 0},
|
|
{newTxCounter(2, 7), 11, 9, false, 0},
|
|
{newTxCounter(10, 0), 10, 10, false, 0}, // hit the limit but pass
|
|
|
|
{newTxCounter(10, 0), 11, 10, true, 10},
|
|
{newTxCounter(10, 0), 15, 10, true, 10},
|
|
{newTxCounter(9, 0), 12, 9, true, 11}, // fly past the limit
|
|
}
|
|
|
|
for i, tc := range testCases {
|
|
tx := tc.tx
|
|
|
|
// reset the block gas
|
|
header := tmproto.Header{Height: app.LastBlockHeight() + 1}
|
|
app.BeginBlock(abci.RequestBeginBlock{Header: header})
|
|
|
|
// execute the transaction multiple times
|
|
for j := 0; j < tc.numDelivers; j++ {
|
|
_, result, err := app.Deliver(aminoTxEncoder(), tx)
|
|
|
|
ctx := app.getState(runTxModeDeliver).ctx
|
|
|
|
// 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, result, fmt.Sprintf("tc #%d; result: %v, err: %s", i, result, err))
|
|
|
|
space, code, _ := sdkerrors.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, result, fmt.Sprintf("tc #%d; currDeliver: %d, result: %v, err: %s", i, j, result, err))
|
|
require.False(t, ctx.BlockGasMeter().IsPastLimit())
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Test custom panic handling within app.DeliverTx method
|
|
func TestCustomRunTxPanicHandler(t *testing.T) {
|
|
const customPanicMsg = "test panic"
|
|
anteErr := sdkerrors.Register("fakeModule", 100500, "fakeError")
|
|
|
|
anteOpt := func(bapp *BaseApp) {
|
|
bapp.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, err error) {
|
|
panic(sdkerrors.Wrap(anteErr, "anteHandler"))
|
|
})
|
|
}
|
|
routerOpt := func(bapp *BaseApp) {
|
|
r := sdk.NewRoute(routeMsgCounter, func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) {
|
|
return &sdk.Result{}, nil
|
|
})
|
|
bapp.Router().AddRoute(r)
|
|
}
|
|
|
|
app := setupBaseApp(t, anteOpt, routerOpt)
|
|
|
|
header := tmproto.Header{Height: 1}
|
|
app.BeginBlock(abci.RequestBeginBlock{Header: header})
|
|
|
|
app.AddRunTxRecoveryHandler(func(recoveryObj interface{}) error {
|
|
err, ok := recoveryObj.(error)
|
|
if !ok {
|
|
return nil
|
|
}
|
|
|
|
if anteErr.Is(err) {
|
|
panic(customPanicMsg)
|
|
} else {
|
|
return nil
|
|
}
|
|
})
|
|
|
|
// Transaction should panic with custom handler above
|
|
{
|
|
tx := newTxCounter(0, 0)
|
|
|
|
require.PanicsWithValue(t, customPanicMsg, func() { app.Deliver(aminoTxEncoder(), tx) })
|
|
}
|
|
}
|
|
|
|
func TestBaseAppAnteHandler(t *testing.T) {
|
|
anteKey := []byte("ante-key")
|
|
anteOpt := func(bapp *BaseApp) {
|
|
bapp.SetAnteHandler(anteHandlerTxTest(t, capKey1, anteKey))
|
|
}
|
|
|
|
deliverKey := []byte("deliver-key")
|
|
routerOpt := func(bapp *BaseApp) {
|
|
r := sdk.NewRoute(routeMsgCounter, handlerMsgCounter(t, capKey1, deliverKey))
|
|
bapp.Router().AddRoute(r)
|
|
}
|
|
|
|
cdc := codec.NewLegacyAmino()
|
|
app := setupBaseApp(t, anteOpt, routerOpt)
|
|
|
|
app.InitChain(abci.RequestInitChain{})
|
|
registerTestCodec(cdc)
|
|
|
|
header := tmproto.Header{Height: app.LastBlockHeight() + 1}
|
|
app.BeginBlock(abci.RequestBeginBlock{Header: header})
|
|
|
|
// execute a tx that will fail ante handler execution
|
|
//
|
|
// NOTE: State should not be mutated here. This will be implicitly checked by
|
|
// the next txs ante handler execution (anteHandlerTxTest).
|
|
tx := newTxCounter(0, 0)
|
|
tx.setFailOnAnte(true)
|
|
txBytes, err := cdc.Marshal(tx)
|
|
require.NoError(t, err)
|
|
res := app.DeliverTx(abci.RequestDeliverTx{Tx: txBytes})
|
|
require.Empty(t, res.Events)
|
|
require.False(t, res.IsOK(), fmt.Sprintf("%v", res))
|
|
|
|
ctx := app.getState(runTxModeDeliver).ctx
|
|
store := ctx.KVStore(capKey1)
|
|
require.Equal(t, int64(0), getIntFromStore(store, anteKey))
|
|
|
|
// execute at tx that will pass the ante handler (the checkTx state should
|
|
// mutate) but will fail the message handler
|
|
tx = newTxCounter(0, 0)
|
|
tx.setFailOnHandler(true)
|
|
|
|
txBytes, err = cdc.Marshal(tx)
|
|
require.NoError(t, err)
|
|
|
|
res = app.DeliverTx(abci.RequestDeliverTx{Tx: txBytes})
|
|
require.Empty(t, res.Events)
|
|
require.False(t, res.IsOK(), fmt.Sprintf("%v", res))
|
|
|
|
ctx = app.getState(runTxModeDeliver).ctx
|
|
store = ctx.KVStore(capKey1)
|
|
require.Equal(t, int64(1), getIntFromStore(store, anteKey))
|
|
require.Equal(t, int64(0), getIntFromStore(store, deliverKey))
|
|
|
|
// execute a successful ante handler and message execution where state is
|
|
// implicitly checked by previous tx executions
|
|
tx = newTxCounter(1, 0)
|
|
|
|
txBytes, err = cdc.Marshal(tx)
|
|
require.NoError(t, err)
|
|
|
|
res = app.DeliverTx(abci.RequestDeliverTx{Tx: txBytes})
|
|
require.NotEmpty(t, res.Events)
|
|
require.True(t, res.IsOK(), fmt.Sprintf("%v", res))
|
|
|
|
ctx = app.getState(runTxModeDeliver).ctx
|
|
store = ctx.KVStore(capKey1)
|
|
require.Equal(t, int64(2), getIntFromStore(store, anteKey))
|
|
require.Equal(t, int64(1), getIntFromStore(store, deliverKey))
|
|
|
|
// commit
|
|
app.EndBlock(abci.RequestEndBlock{})
|
|
app.Commit()
|
|
}
|
|
|
|
func TestGasConsumptionBadTx(t *testing.T) {
|
|
gasWanted := uint64(5)
|
|
anteOpt := func(bapp *BaseApp) {
|
|
bapp.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, err error) {
|
|
newCtx = ctx.WithGasMeter(sdk.NewGasMeter(gasWanted))
|
|
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
switch rType := r.(type) {
|
|
case sdk.ErrorOutOfGas:
|
|
log := fmt.Sprintf("out of gas in location: %v", rType.Descriptor)
|
|
err = sdkerrors.Wrap(sdkerrors.ErrOutOfGas, log)
|
|
default:
|
|
panic(r)
|
|
}
|
|
}
|
|
}()
|
|
|
|
txTest := tx.(txTest)
|
|
newCtx.GasMeter().ConsumeGas(uint64(txTest.Counter), "counter-ante")
|
|
if txTest.FailOnAnte {
|
|
return newCtx, sdkerrors.Wrap(sdkerrors.ErrUnauthorized, "ante handler failure")
|
|
}
|
|
|
|
return
|
|
})
|
|
}
|
|
|
|
routerOpt := func(bapp *BaseApp) {
|
|
r := sdk.NewRoute(routeMsgCounter, func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) {
|
|
count := msg.(*msgCounter).Counter
|
|
ctx.GasMeter().ConsumeGas(uint64(count), "counter-handler")
|
|
return &sdk.Result{}, nil
|
|
})
|
|
bapp.Router().AddRoute(r)
|
|
}
|
|
|
|
cdc := codec.NewLegacyAmino()
|
|
registerTestCodec(cdc)
|
|
|
|
app := setupBaseApp(t, anteOpt, routerOpt)
|
|
app.InitChain(abci.RequestInitChain{
|
|
ConsensusParams: &abci.ConsensusParams{
|
|
Block: &abci.BlockParams{
|
|
MaxGas: 9,
|
|
},
|
|
},
|
|
})
|
|
|
|
app.InitChain(abci.RequestInitChain{})
|
|
|
|
header := tmproto.Header{Height: app.LastBlockHeight() + 1}
|
|
app.BeginBlock(abci.RequestBeginBlock{Header: header})
|
|
|
|
tx := newTxCounter(5, 0)
|
|
tx.setFailOnAnte(true)
|
|
txBytes, err := cdc.Marshal(tx)
|
|
require.NoError(t, err)
|
|
|
|
res := app.DeliverTx(abci.RequestDeliverTx{Tx: txBytes})
|
|
require.False(t, res.IsOK(), fmt.Sprintf("%v", res))
|
|
|
|
// require next tx to fail due to black gas limit
|
|
tx = newTxCounter(5, 0)
|
|
txBytes, err = cdc.Marshal(tx)
|
|
require.NoError(t, err)
|
|
|
|
res = app.DeliverTx(abci.RequestDeliverTx{Tx: txBytes})
|
|
require.False(t, res.IsOK(), fmt.Sprintf("%v", res))
|
|
}
|
|
|
|
// Test that we can only query from the latest committed state.
|
|
func TestQuery(t *testing.T) {
|
|
key, value := []byte("hello"), []byte("goodbye")
|
|
anteOpt := func(bapp *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
|
|
})
|
|
}
|
|
|
|
routerOpt := func(bapp *BaseApp) {
|
|
r := sdk.NewRoute(routeMsgCounter, func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) {
|
|
store := ctx.KVStore(capKey1)
|
|
store.Set(key, value)
|
|
return &sdk.Result{}, nil
|
|
})
|
|
bapp.Router().AddRoute(r)
|
|
}
|
|
|
|
app := setupBaseApp(t, anteOpt, routerOpt)
|
|
|
|
app.InitChain(abci.RequestInitChain{})
|
|
|
|
// NOTE: "/store/key1" tells us KVStore
|
|
// and the final "/key" says to use the data as the
|
|
// key in the given KVStore ...
|
|
query := abci.RequestQuery{
|
|
Path: "/store/key1/key",
|
|
Data: key,
|
|
}
|
|
tx := newTxCounter(0, 0)
|
|
|
|
// query is empty before we do anything
|
|
res := app.Query(query)
|
|
require.Equal(t, 0, len(res.Value))
|
|
|
|
// query is still empty after a CheckTx
|
|
_, resTx, err := app.Check(aminoTxEncoder(), tx)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, resTx)
|
|
res = app.Query(query)
|
|
require.Equal(t, 0, len(res.Value))
|
|
|
|
// query is still empty after a DeliverTx before we commit
|
|
header := tmproto.Header{Height: app.LastBlockHeight() + 1}
|
|
app.BeginBlock(abci.RequestBeginBlock{Header: header})
|
|
|
|
_, resTx, err = app.Deliver(aminoTxEncoder(), tx)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, resTx)
|
|
res = app.Query(query)
|
|
require.Equal(t, 0, len(res.Value))
|
|
|
|
// query returns correct value after Commit
|
|
app.Commit()
|
|
res = app.Query(query)
|
|
require.Equal(t, value, res.Value)
|
|
}
|
|
|
|
func TestGRPCQuery(t *testing.T) {
|
|
grpcQueryOpt := func(bapp *BaseApp) {
|
|
testdata.RegisterQueryServer(
|
|
bapp.GRPCQueryRouter(),
|
|
testdata.QueryImpl{},
|
|
)
|
|
}
|
|
|
|
app := setupBaseApp(t, grpcQueryOpt)
|
|
|
|
app.InitChain(abci.RequestInitChain{})
|
|
header := tmproto.Header{Height: app.LastBlockHeight() + 1}
|
|
app.BeginBlock(abci.RequestBeginBlock{Header: header})
|
|
app.Commit()
|
|
|
|
req := testdata.SayHelloRequest{Name: "foo"}
|
|
reqBz, err := req.Marshal()
|
|
require.NoError(t, err)
|
|
|
|
reqQuery := abci.RequestQuery{
|
|
Data: reqBz,
|
|
Path: "/testdata.Query/SayHello",
|
|
}
|
|
|
|
resQuery := app.Query(reqQuery)
|
|
|
|
require.Equal(t, abci.CodeTypeOK, resQuery.Code, resQuery)
|
|
|
|
var res testdata.SayHelloResponse
|
|
err = res.Unmarshal(resQuery.Value)
|
|
require.NoError(t, err)
|
|
require.Equal(t, "Hello foo!", res.Greeting)
|
|
}
|
|
|
|
// Test p2p filter queries
|
|
func TestP2PQuery(t *testing.T) {
|
|
addrPeerFilterOpt := func(bapp *BaseApp) {
|
|
bapp.SetAddrPeerFilter(func(addrport string) abci.ResponseQuery {
|
|
require.Equal(t, "1.1.1.1:8000", addrport)
|
|
return abci.ResponseQuery{Code: uint32(3)}
|
|
})
|
|
}
|
|
|
|
idPeerFilterOpt := func(bapp *BaseApp) {
|
|
bapp.SetIDPeerFilter(func(id string) abci.ResponseQuery {
|
|
require.Equal(t, "testid", id)
|
|
return abci.ResponseQuery{Code: uint32(4)}
|
|
})
|
|
}
|
|
|
|
app := setupBaseApp(t, addrPeerFilterOpt, idPeerFilterOpt)
|
|
|
|
addrQuery := abci.RequestQuery{
|
|
Path: "/p2p/filter/addr/1.1.1.1:8000",
|
|
}
|
|
res := app.Query(addrQuery)
|
|
require.Equal(t, uint32(3), res.Code)
|
|
|
|
idQuery := abci.RequestQuery{
|
|
Path: "/p2p/filter/id/testid",
|
|
}
|
|
res = app.Query(idQuery)
|
|
require.Equal(t, uint32(4), res.Code)
|
|
}
|
|
|
|
func TestGetMaximumBlockGas(t *testing.T) {
|
|
app := setupBaseApp(t)
|
|
app.InitChain(abci.RequestInitChain{})
|
|
ctx := app.NewContext(true, tmproto.Header{})
|
|
|
|
app.StoreConsensusParams(ctx, &abci.ConsensusParams{Block: &abci.BlockParams{MaxGas: 0}})
|
|
require.Equal(t, uint64(0), app.getMaximumBlockGas(ctx))
|
|
|
|
app.StoreConsensusParams(ctx, &abci.ConsensusParams{Block: &abci.BlockParams{MaxGas: -1}})
|
|
require.Equal(t, uint64(0), app.getMaximumBlockGas(ctx))
|
|
|
|
app.StoreConsensusParams(ctx, &abci.ConsensusParams{Block: &abci.BlockParams{MaxGas: 5000000}})
|
|
require.Equal(t, uint64(5000000), app.getMaximumBlockGas(ctx))
|
|
|
|
app.StoreConsensusParams(ctx, &abci.ConsensusParams{Block: &abci.BlockParams{MaxGas: -5000000}})
|
|
require.Panics(t, func() { app.getMaximumBlockGas(ctx) })
|
|
}
|
|
|
|
func TestListSnapshots(t *testing.T) {
|
|
app, teardown := setupBaseAppWithSnapshots(t, 5, 4)
|
|
defer teardown()
|
|
|
|
resp := app.ListSnapshots(abci.RequestListSnapshots{})
|
|
for _, s := range resp.Snapshots {
|
|
assert.NotEmpty(t, s.Hash)
|
|
assert.NotEmpty(t, s.Metadata)
|
|
s.Hash = nil
|
|
s.Metadata = nil
|
|
}
|
|
assert.Equal(t, abci.ResponseListSnapshots{Snapshots: []*abci.Snapshot{
|
|
{Height: 4, Format: 1, Chunks: 2},
|
|
{Height: 2, Format: 1, Chunks: 1},
|
|
}}, resp)
|
|
}
|
|
|
|
func TestLoadSnapshotChunk(t *testing.T) {
|
|
app, teardown := setupBaseAppWithSnapshots(t, 2, 5)
|
|
defer teardown()
|
|
|
|
testcases := map[string]struct {
|
|
height uint64
|
|
format uint32
|
|
chunk uint32
|
|
expectEmpty bool
|
|
}{
|
|
"Existing snapshot": {2, 1, 1, false},
|
|
"Missing height": {100, 1, 1, true},
|
|
"Missing format": {2, 2, 1, true},
|
|
"Missing chunk": {2, 1, 9, true},
|
|
"Zero height": {0, 1, 1, true},
|
|
"Zero format": {2, 0, 1, true},
|
|
"Zero chunk": {2, 1, 0, false},
|
|
}
|
|
for name, tc := range testcases {
|
|
tc := tc
|
|
t.Run(name, func(t *testing.T) {
|
|
resp := app.LoadSnapshotChunk(abci.RequestLoadSnapshotChunk{
|
|
Height: tc.height,
|
|
Format: tc.format,
|
|
Chunk: tc.chunk,
|
|
})
|
|
if tc.expectEmpty {
|
|
assert.Equal(t, abci.ResponseLoadSnapshotChunk{}, resp)
|
|
return
|
|
}
|
|
assert.NotEmpty(t, resp.Chunk)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestOfferSnapshot_Errors(t *testing.T) {
|
|
// Set up app before test cases, since it's fairly expensive.
|
|
app, teardown := setupBaseAppWithSnapshots(t, 0, 0)
|
|
defer teardown()
|
|
|
|
m := snapshottypes.Metadata{ChunkHashes: [][]byte{{1}, {2}, {3}}}
|
|
metadata, err := m.Marshal()
|
|
require.NoError(t, err)
|
|
hash := []byte{1, 2, 3}
|
|
|
|
testcases := map[string]struct {
|
|
snapshot *abci.Snapshot
|
|
result abci.ResponseOfferSnapshot_Result
|
|
}{
|
|
"nil snapshot": {nil, abci.ResponseOfferSnapshot_REJECT},
|
|
"invalid format": {&abci.Snapshot{
|
|
Height: 1, Format: 9, Chunks: 3, Hash: hash, Metadata: metadata,
|
|
}, abci.ResponseOfferSnapshot_REJECT_FORMAT},
|
|
"incorrect chunk count": {&abci.Snapshot{
|
|
Height: 1, Format: 1, Chunks: 2, Hash: hash, Metadata: metadata,
|
|
}, abci.ResponseOfferSnapshot_REJECT},
|
|
"no chunks": {&abci.Snapshot{
|
|
Height: 1, Format: 1, Chunks: 0, Hash: hash, Metadata: metadata,
|
|
}, abci.ResponseOfferSnapshot_REJECT},
|
|
"invalid metadata serialization": {&abci.Snapshot{
|
|
Height: 1, Format: 1, Chunks: 0, Hash: hash, Metadata: []byte{3, 1, 4},
|
|
}, abci.ResponseOfferSnapshot_REJECT},
|
|
}
|
|
for name, tc := range testcases {
|
|
tc := tc
|
|
t.Run(name, func(t *testing.T) {
|
|
resp := app.OfferSnapshot(abci.RequestOfferSnapshot{Snapshot: tc.snapshot})
|
|
assert.Equal(t, tc.result, resp.Result)
|
|
})
|
|
}
|
|
|
|
// Offering a snapshot after one has been accepted should error
|
|
resp := app.OfferSnapshot(abci.RequestOfferSnapshot{Snapshot: &abci.Snapshot{
|
|
Height: 1,
|
|
Format: snapshottypes.CurrentFormat,
|
|
Chunks: 3,
|
|
Hash: []byte{1, 2, 3},
|
|
Metadata: metadata,
|
|
}})
|
|
require.Equal(t, abci.ResponseOfferSnapshot{Result: abci.ResponseOfferSnapshot_ACCEPT}, resp)
|
|
|
|
resp = app.OfferSnapshot(abci.RequestOfferSnapshot{Snapshot: &abci.Snapshot{
|
|
Height: 2,
|
|
Format: snapshottypes.CurrentFormat,
|
|
Chunks: 3,
|
|
Hash: []byte{1, 2, 3},
|
|
Metadata: metadata,
|
|
}})
|
|
require.Equal(t, abci.ResponseOfferSnapshot{Result: abci.ResponseOfferSnapshot_ABORT}, resp)
|
|
}
|
|
|
|
func TestApplySnapshotChunk(t *testing.T) {
|
|
source, teardown := setupBaseAppWithSnapshots(t, 4, 10)
|
|
defer teardown()
|
|
|
|
target, teardown := setupBaseAppWithSnapshots(t, 0, 0)
|
|
defer teardown()
|
|
|
|
// Fetch latest snapshot to restore
|
|
respList := source.ListSnapshots(abci.RequestListSnapshots{})
|
|
require.NotEmpty(t, respList.Snapshots)
|
|
snapshot := respList.Snapshots[0]
|
|
|
|
// Make sure the snapshot has at least 3 chunks
|
|
require.GreaterOrEqual(t, snapshot.Chunks, uint32(3), "Not enough snapshot chunks")
|
|
|
|
// Begin a snapshot restoration in the target
|
|
respOffer := target.OfferSnapshot(abci.RequestOfferSnapshot{Snapshot: snapshot})
|
|
require.Equal(t, abci.ResponseOfferSnapshot{Result: abci.ResponseOfferSnapshot_ACCEPT}, respOffer)
|
|
|
|
// We should be able to pass an invalid chunk and get a verify failure, before reapplying it.
|
|
respApply := target.ApplySnapshotChunk(abci.RequestApplySnapshotChunk{
|
|
Index: 0,
|
|
Chunk: []byte{9},
|
|
Sender: "sender",
|
|
})
|
|
require.Equal(t, abci.ResponseApplySnapshotChunk{
|
|
Result: abci.ResponseApplySnapshotChunk_RETRY,
|
|
RefetchChunks: []uint32{0},
|
|
RejectSenders: []string{"sender"},
|
|
}, respApply)
|
|
|
|
// Fetch each chunk from the source and apply it to the target
|
|
for index := uint32(0); index < snapshot.Chunks; index++ {
|
|
respChunk := source.LoadSnapshotChunk(abci.RequestLoadSnapshotChunk{
|
|
Height: snapshot.Height,
|
|
Format: snapshot.Format,
|
|
Chunk: index,
|
|
})
|
|
require.NotNil(t, respChunk.Chunk)
|
|
respApply := target.ApplySnapshotChunk(abci.RequestApplySnapshotChunk{
|
|
Index: index,
|
|
Chunk: respChunk.Chunk,
|
|
})
|
|
require.Equal(t, abci.ResponseApplySnapshotChunk{
|
|
Result: abci.ResponseApplySnapshotChunk_ACCEPT,
|
|
}, respApply)
|
|
}
|
|
|
|
// The target should now have the same hash as the source
|
|
assert.Equal(t, source.LastCommitID(), target.LastCommitID())
|
|
}
|
|
|
|
// NOTE: represents a new custom router for testing purposes of WithRouter()
|
|
type testCustomRouter struct {
|
|
routes sync.Map
|
|
}
|
|
|
|
func (rtr *testCustomRouter) AddRoute(route sdk.Route) sdk.Router {
|
|
rtr.routes.Store(route.Path(), route.Handler())
|
|
return rtr
|
|
}
|
|
|
|
func (rtr *testCustomRouter) Route(ctx sdk.Context, path string) sdk.Handler {
|
|
if v, ok := rtr.routes.Load(path); ok {
|
|
if h, ok := v.(sdk.Handler); ok {
|
|
return h
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func TestWithRouter(t *testing.T) {
|
|
// test increments in the ante
|
|
anteKey := []byte("ante-key")
|
|
anteOpt := func(bapp *BaseApp) { bapp.SetAnteHandler(anteHandlerTxTest(t, capKey1, anteKey)) }
|
|
|
|
// test increments in the handler
|
|
deliverKey := []byte("deliver-key")
|
|
routerOpt := func(bapp *BaseApp) {
|
|
bapp.SetRouter(&testCustomRouter{routes: sync.Map{}})
|
|
r := sdk.NewRoute(routeMsgCounter, handlerMsgCounter(t, capKey1, deliverKey))
|
|
bapp.Router().AddRoute(r)
|
|
}
|
|
|
|
app := setupBaseApp(t, anteOpt, routerOpt)
|
|
app.InitChain(abci.RequestInitChain{})
|
|
|
|
// Create same codec used in txDecoder
|
|
codec := codec.NewLegacyAmino()
|
|
registerTestCodec(codec)
|
|
|
|
nBlocks := 3
|
|
txPerHeight := 5
|
|
|
|
for blockN := 0; blockN < nBlocks; blockN++ {
|
|
header := tmproto.Header{Height: int64(blockN) + 1}
|
|
app.BeginBlock(abci.RequestBeginBlock{Header: header})
|
|
|
|
for i := 0; i < txPerHeight; i++ {
|
|
counter := int64(blockN*txPerHeight + i)
|
|
tx := newTxCounter(counter, counter)
|
|
|
|
txBytes, err := codec.Marshal(tx)
|
|
require.NoError(t, err)
|
|
|
|
res := app.DeliverTx(abci.RequestDeliverTx{Tx: txBytes})
|
|
require.True(t, res.IsOK(), fmt.Sprintf("%v", res))
|
|
}
|
|
|
|
app.EndBlock(abci.RequestEndBlock{})
|
|
app.Commit()
|
|
}
|
|
}
|
|
|
|
func TestBaseApp_EndBlock(t *testing.T) {
|
|
db := dbm.NewMemDB()
|
|
name := t.Name()
|
|
logger := defaultLogger()
|
|
|
|
cp := &abci.ConsensusParams{
|
|
Block: &abci.BlockParams{
|
|
MaxGas: 5000000,
|
|
},
|
|
}
|
|
|
|
app := NewBaseApp(name, logger, db, nil)
|
|
app.SetParamStore(¶mStore{db: dbm.NewMemDB()})
|
|
app.InitChain(abci.RequestInitChain{
|
|
ConsensusParams: cp,
|
|
})
|
|
|
|
app.SetEndBlocker(func(ctx sdk.Context, req abci.RequestEndBlock) abci.ResponseEndBlock {
|
|
return abci.ResponseEndBlock{
|
|
ValidatorUpdates: []abci.ValidatorUpdate{
|
|
{Power: 100},
|
|
},
|
|
}
|
|
})
|
|
app.Seal()
|
|
|
|
res := app.EndBlock(abci.RequestEndBlock{})
|
|
require.Len(t, res.GetValidatorUpdates(), 1)
|
|
require.Equal(t, int64(100), res.GetValidatorUpdates()[0].Power)
|
|
require.Equal(t, cp.Block.MaxGas, res.ConsensusParamUpdates.Block.MaxGas)
|
|
}
|