1002 lines
30 KiB
Go
1002 lines
30 KiB
Go
package baseapp
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"github.com/cosmos/cosmos-sdk/x/bank"
|
|
"os"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
abci "github.com/tendermint/abci/types"
|
|
crypto "github.com/tendermint/go-crypto"
|
|
tmtypes "github.com/tendermint/tendermint/types"
|
|
cmn "github.com/tendermint/tmlibs/common"
|
|
dbm "github.com/tendermint/tmlibs/db"
|
|
"github.com/tendermint/tmlibs/log"
|
|
|
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
"github.com/cosmos/cosmos-sdk/wire"
|
|
"github.com/cosmos/cosmos-sdk/x/auth"
|
|
)
|
|
|
|
func defaultLogger() log.Logger {
|
|
return log.NewTMLogger(log.NewSyncWriter(os.Stdout)).With("module", "sdk/app")
|
|
}
|
|
|
|
func newBaseApp(name string) *BaseApp {
|
|
logger := defaultLogger()
|
|
db := dbm.NewMemDB()
|
|
codec := wire.NewCodec()
|
|
auth.RegisterBaseAccount(codec)
|
|
return NewBaseApp(name, codec, logger, db)
|
|
}
|
|
|
|
func TestMountStores(t *testing.T) {
|
|
name := t.Name()
|
|
app := newBaseApp(name)
|
|
assert.Equal(t, name, app.Name())
|
|
|
|
// make some cap keys
|
|
capKey1 := sdk.NewKVStoreKey("key1")
|
|
capKey2 := sdk.NewKVStoreKey("key2")
|
|
|
|
// no stores are mounted
|
|
assert.Panics(t, func() { app.LoadLatestVersion(capKey1) })
|
|
|
|
app.MountStoresIAVL(capKey1, capKey2)
|
|
|
|
// stores are mounted
|
|
err := app.LoadLatestVersion(capKey1)
|
|
assert.Nil(t, err)
|
|
|
|
// check both stores
|
|
store1 := app.cms.GetCommitKVStore(capKey1)
|
|
assert.NotNil(t, store1)
|
|
store2 := app.cms.GetCommitKVStore(capKey2)
|
|
assert.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()
|
|
db := dbm.NewMemDB()
|
|
name := t.Name()
|
|
app := NewBaseApp(name, nil, logger, db)
|
|
|
|
// make a cap key and mount the store
|
|
capKey := sdk.NewKVStoreKey("main")
|
|
app.MountStoresIAVL(capKey)
|
|
err := app.LoadLatestVersion(capKey) // needed to make stores non-nil
|
|
assert.Nil(t, err)
|
|
|
|
emptyCommitID := sdk.CommitID{}
|
|
|
|
lastHeight := app.LastBlockHeight()
|
|
lastID := app.LastCommitID()
|
|
assert.Equal(t, int64(0), lastHeight)
|
|
assert.Equal(t, emptyCommitID, lastID)
|
|
|
|
// execute some blocks
|
|
header := abci.Header{Height: 1}
|
|
app.BeginBlock(abci.RequestBeginBlock{Header: header})
|
|
res := app.Commit()
|
|
commitID1 := sdk.CommitID{1, res.Data}
|
|
header = abci.Header{Height: 2}
|
|
app.BeginBlock(abci.RequestBeginBlock{Header: header})
|
|
res = app.Commit()
|
|
commitID2 := sdk.CommitID{2, res.Data}
|
|
|
|
// reload with LoadLatestVersion
|
|
app = NewBaseApp(name, nil, logger, db)
|
|
app.MountStoresIAVL(capKey)
|
|
err = app.LoadLatestVersion(capKey)
|
|
assert.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, nil, logger, db)
|
|
app.MountStoresIAVL(capKey)
|
|
err = app.LoadVersion(1, capKey)
|
|
assert.Nil(t, err)
|
|
testLoadVersionHelper(t, app, int64(1), commitID1)
|
|
app.BeginBlock(abci.RequestBeginBlock{Header: header})
|
|
app.Commit()
|
|
testLoadVersionHelper(t, app, int64(2), commitID2)
|
|
}
|
|
|
|
func testLoadVersionHelper(t *testing.T, app *BaseApp, expectedHeight int64, expectedID sdk.CommitID) {
|
|
lastHeight := app.LastBlockHeight()
|
|
lastID := app.LastCommitID()
|
|
assert.Equal(t, expectedHeight, lastHeight)
|
|
assert.Equal(t, expectedID, lastID)
|
|
}
|
|
|
|
// Test that the app hash is static
|
|
// TODO: https://github.com/cosmos/cosmos-sdk/issues/520
|
|
/*func TestStaticAppHash(t *testing.T) {
|
|
app := newBaseApp(t.Name())
|
|
|
|
// make a cap key and mount the store
|
|
capKey := sdk.NewKVStoreKey("main")
|
|
app.MountStoresIAVL(capKey)
|
|
err := app.LoadLatestVersion(capKey) // needed to make stores non-nil
|
|
assert.Nil(t, err)
|
|
|
|
// execute some blocks
|
|
header := abci.Header{Height: 1}
|
|
app.BeginBlock(abci.RequestBeginBlock{Header: header})
|
|
res := app.Commit()
|
|
commitID1 := sdk.CommitID{1, res.Data}
|
|
|
|
header = abci.Header{Height: 2}
|
|
app.BeginBlock(abci.RequestBeginBlock{Header: header})
|
|
res = app.Commit()
|
|
commitID2 := sdk.CommitID{2, res.Data}
|
|
|
|
assert.Equal(t, commitID1.Hash, commitID2.Hash)
|
|
}
|
|
*/
|
|
|
|
// Test that txs can be unmarshalled and read and that
|
|
// correct error codes are returned when not
|
|
func TestTxDecoder(t *testing.T) {
|
|
// TODO
|
|
}
|
|
|
|
// 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)
|
|
assert.Equal(t, []uint8(nil), res.LastBlockAppHash)
|
|
|
|
// ----- test a proper response -------
|
|
// TODO
|
|
|
|
}
|
|
|
|
func TestInitChainer(t *testing.T) {
|
|
name := t.Name()
|
|
db := dbm.NewMemDB()
|
|
logger := defaultLogger()
|
|
app := NewBaseApp(name, nil, logger, db)
|
|
// make cap keys and mount the stores
|
|
// NOTE/TODO: mounting multiple stores is broken
|
|
// see https://github.com/cosmos/cosmos-sdk/issues/532
|
|
capKey := sdk.NewKVStoreKey("main")
|
|
capKey2 := sdk.NewKVStoreKey("key2")
|
|
app.MountStoresIAVL(capKey, capKey2)
|
|
err := app.LoadLatestVersion(capKey) // needed to make stores non-nil
|
|
assert.Nil(t, err)
|
|
|
|
key, value := []byte("hello"), []byte("goodbye")
|
|
|
|
// initChainer sets a value in the store
|
|
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)
|
|
assert.Equal(t, 0, len(res.Value))
|
|
|
|
// set initChainer and try again - should see the value
|
|
app.SetInitChainer(initChainer)
|
|
app.InitChain(abci.RequestInitChain{AppStateBytes: []byte("{}")}) // must have valid JSON genesis file, even if empty
|
|
app.Commit()
|
|
res = app.Query(query)
|
|
assert.Equal(t, value, res.Value)
|
|
|
|
// reload app
|
|
app = NewBaseApp(name, nil, logger, db)
|
|
app.MountStoresIAVL(capKey, capKey2)
|
|
err = app.LoadLatestVersion(capKey) // needed to make stores non-nil
|
|
assert.Nil(t, err)
|
|
app.SetInitChainer(initChainer)
|
|
|
|
// ensure we can still query after reloading
|
|
res = app.Query(query)
|
|
assert.Equal(t, value, res.Value)
|
|
|
|
// commit and ensure we can still query
|
|
app.BeginBlock(abci.RequestBeginBlock{})
|
|
app.Commit()
|
|
res = app.Query(query)
|
|
assert.Equal(t, value, res.Value)
|
|
}
|
|
|
|
func getStateCheckingHandler(t *testing.T, capKey *sdk.KVStoreKey, txPerHeight int, checkHeader bool) func(ctx sdk.Context, msg sdk.Msg) sdk.Result {
|
|
counter := 0
|
|
return func(ctx sdk.Context, msg sdk.Msg) sdk.Result {
|
|
store := ctx.KVStore(capKey)
|
|
// Checking state gets updated between checkTx's / DeliverTx's
|
|
// on the store within a block.
|
|
if counter > 0 {
|
|
// check previous value in store
|
|
counterBytes := []byte{byte(counter - 1)}
|
|
prevBytes := store.Get(counterBytes)
|
|
assert.Equal(t, counterBytes, prevBytes)
|
|
}
|
|
|
|
// set the current counter in the store
|
|
counterBytes := []byte{byte(counter)}
|
|
store.Set(counterBytes, counterBytes)
|
|
|
|
// check that we can see the current header
|
|
// wrapped in an if, so it can be reused between CheckTx and DeliverTx tests.
|
|
if checkHeader {
|
|
thisHeader := ctx.BlockHeader()
|
|
height := int64((counter / txPerHeight) + 1)
|
|
assert.Equal(t, height, thisHeader.Height)
|
|
}
|
|
|
|
counter++
|
|
return sdk.Result{}
|
|
}
|
|
}
|
|
|
|
// A mock transaction that has a validation which can fail.
|
|
type testTx struct {
|
|
positiveNum int64
|
|
}
|
|
|
|
const msgType2 = "testTx"
|
|
|
|
func (tx testTx) Type() string { return msgType2 }
|
|
func (tx testTx) GetMemo() string { return "" }
|
|
func (tx testTx) GetMsgs() []sdk.Msg { return []sdk.Msg{tx} }
|
|
func (tx testTx) GetSignBytes() []byte { return nil }
|
|
func (tx testTx) GetSigners() []sdk.Address { return nil }
|
|
func (tx testTx) GetSignatures() []auth.StdSignature { return nil }
|
|
func (tx testTx) ValidateBasic() sdk.Error {
|
|
if tx.positiveNum >= 0 {
|
|
return nil
|
|
}
|
|
return sdk.ErrTxDecode("positiveNum should be a non-negative integer.")
|
|
}
|
|
|
|
// 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) {
|
|
// Initialize an app for testing
|
|
app := newBaseApp(t.Name())
|
|
// make a cap key and mount the store
|
|
capKey := sdk.NewKVStoreKey("main")
|
|
app.MountStoresIAVL(capKey)
|
|
err := app.LoadLatestVersion(capKey) // needed to make stores non-nil
|
|
assert.Nil(t, err)
|
|
app.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx) (newCtx sdk.Context, res sdk.Result, abort bool) { return })
|
|
|
|
txPerHeight := 3
|
|
app.Router().AddRoute(msgType, getStateCheckingHandler(t, capKey, txPerHeight, false)).
|
|
AddRoute(msgType2, func(ctx sdk.Context, msg sdk.Msg) (res sdk.Result) { return })
|
|
tx := testUpdatePowerTx{} // doesn't matter
|
|
for i := 0; i < txPerHeight; i++ {
|
|
app.Check(tx)
|
|
}
|
|
// If it gets to this point, then successive CheckTx's can see the effects of
|
|
// other CheckTx's on the block. The following checks that if another block
|
|
// is committed, the CheckTx State will reset.
|
|
app.BeginBlock(abci.RequestBeginBlock{})
|
|
tx2 := testTx{}
|
|
for i := 0; i < txPerHeight; i++ {
|
|
app.Deliver(tx2)
|
|
}
|
|
app.EndBlock(abci.RequestEndBlock{})
|
|
app.Commit()
|
|
|
|
checkStateStore := app.checkState.ctx.KVStore(capKey)
|
|
for i := 0; i < txPerHeight; i++ {
|
|
storedValue := checkStateStore.Get([]byte{byte(i)})
|
|
assert.Nil(t, storedValue)
|
|
}
|
|
}
|
|
|
|
// Test that successive DeliverTx can see each others' effects
|
|
// on the store, both within and across blocks.
|
|
func TestDeliverTx(t *testing.T) {
|
|
app := newBaseApp(t.Name())
|
|
|
|
// make a cap key and mount the store
|
|
capKey := sdk.NewKVStoreKey("main")
|
|
app.MountStoresIAVL(capKey)
|
|
err := app.LoadLatestVersion(capKey) // needed to make stores non-nil
|
|
assert.Nil(t, err)
|
|
|
|
txPerHeight := 2
|
|
app.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx) (newCtx sdk.Context, res sdk.Result, abort bool) { return })
|
|
app.Router().AddRoute(msgType, getStateCheckingHandler(t, capKey, txPerHeight, true))
|
|
|
|
tx := testUpdatePowerTx{} // doesn't matter
|
|
header := abci.Header{AppHash: []byte("apphash")}
|
|
|
|
nBlocks := 3
|
|
for blockN := 0; blockN < nBlocks; blockN++ {
|
|
// block1
|
|
header.Height = int64(blockN + 1)
|
|
app.BeginBlock(abci.RequestBeginBlock{Header: header})
|
|
for i := 0; i < txPerHeight; i++ {
|
|
app.Deliver(tx)
|
|
}
|
|
app.EndBlock(abci.RequestEndBlock{})
|
|
app.Commit()
|
|
}
|
|
}
|
|
|
|
func TestSimulateTx(t *testing.T) {
|
|
app := newBaseApp(t.Name())
|
|
|
|
// make a cap key and mount the store
|
|
capKey := sdk.NewKVStoreKey("main")
|
|
app.MountStoresIAVL(capKey)
|
|
err := app.LoadLatestVersion(capKey) // needed to make stores non-nil
|
|
assert.Nil(t, err)
|
|
|
|
counter := 0
|
|
app.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx) (newCtx sdk.Context, res sdk.Result, abort bool) { return })
|
|
app.Router().AddRoute(msgType, func(ctx sdk.Context, msg sdk.Msg) sdk.Result {
|
|
ctx.GasMeter().ConsumeGas(10, "test")
|
|
store := ctx.KVStore(capKey)
|
|
// ensure store is never written
|
|
require.Nil(t, store.Get([]byte("key")))
|
|
store.Set([]byte("key"), []byte("value"))
|
|
// check we can see the current header
|
|
thisHeader := ctx.BlockHeader()
|
|
height := int64(counter)
|
|
assert.Equal(t, height, thisHeader.Height)
|
|
counter++
|
|
return sdk.Result{}
|
|
})
|
|
|
|
tx := testUpdatePowerTx{} // doesn't matter
|
|
header := abci.Header{AppHash: []byte("apphash")}
|
|
|
|
app.SetTxDecoder(func(txBytes []byte) (sdk.Tx, sdk.Error) {
|
|
var ttx testUpdatePowerTx
|
|
fromJSON(txBytes, &ttx)
|
|
return ttx, nil
|
|
})
|
|
|
|
nBlocks := 3
|
|
for blockN := 0; blockN < nBlocks; blockN++ {
|
|
// block1
|
|
header.Height = int64(blockN + 1)
|
|
app.BeginBlock(abci.RequestBeginBlock{Header: header})
|
|
result := app.Simulate(tx)
|
|
require.Equal(t, result.Code, sdk.ABCICodeOK)
|
|
require.Equal(t, int64(80), result.GasUsed)
|
|
counter--
|
|
encoded, err := json.Marshal(tx)
|
|
require.Nil(t, err)
|
|
query := abci.RequestQuery{
|
|
Path: "/app/simulate",
|
|
Data: encoded,
|
|
}
|
|
queryResult := app.Query(query)
|
|
require.Equal(t, queryResult.Code, uint32(sdk.ABCICodeOK))
|
|
var res sdk.Result
|
|
app.cdc.MustUnmarshalBinary(queryResult.Value, &res)
|
|
require.Equal(t, sdk.ABCICodeOK, res.Code)
|
|
require.Equal(t, int64(160), res.GasUsed)
|
|
app.EndBlock(abci.RequestEndBlock{})
|
|
app.Commit()
|
|
}
|
|
}
|
|
|
|
func TestRunInvalidTransaction(t *testing.T) {
|
|
// Initialize an app for testing
|
|
app := newBaseApp(t.Name())
|
|
// make a cap key and mount the store
|
|
capKey := sdk.NewKVStoreKey("main")
|
|
app.MountStoresIAVL(capKey)
|
|
err := app.LoadLatestVersion(capKey) // needed to make stores non-nil
|
|
assert.Nil(t, err)
|
|
app.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx) (newCtx sdk.Context, res sdk.Result, abort bool) { return })
|
|
app.Router().AddRoute(msgType2, func(ctx sdk.Context, msg sdk.Msg) (res sdk.Result) { return })
|
|
app.BeginBlock(abci.RequestBeginBlock{})
|
|
// Transaction where validate fails
|
|
invalidTx := testTx{-1}
|
|
err1 := app.Deliver(invalidTx)
|
|
assert.Equal(t, sdk.ToABCICode(sdk.CodespaceRoot, sdk.CodeTxDecode), err1.Code)
|
|
// Transaction with no known route
|
|
unknownRouteTx := testUpdatePowerTx{}
|
|
err2 := app.Deliver(unknownRouteTx)
|
|
assert.Equal(t, sdk.ToABCICode(sdk.CodespaceRoot, sdk.CodeUnknownRequest), err2.Code)
|
|
}
|
|
|
|
// Test that transactions exceeding gas limits fail
|
|
func TestTxGasLimits(t *testing.T) {
|
|
logger := defaultLogger()
|
|
db := dbm.NewMemDB()
|
|
app := NewBaseApp(t.Name(), nil, logger, db)
|
|
|
|
// make a cap key and mount the store
|
|
capKey := sdk.NewKVStoreKey("main")
|
|
app.MountStoresIAVL(capKey)
|
|
err := app.LoadLatestVersion(capKey) // needed to make stores non-nil
|
|
assert.Nil(t, err)
|
|
|
|
app.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx) (newCtx sdk.Context, res sdk.Result, abort bool) {
|
|
newCtx = ctx.WithGasMeter(sdk.NewGasMeter(0))
|
|
return
|
|
})
|
|
app.Router().AddRoute(msgType, func(ctx sdk.Context, msg sdk.Msg) sdk.Result {
|
|
ctx.GasMeter().ConsumeGas(10, "counter")
|
|
return sdk.Result{}
|
|
})
|
|
|
|
tx := testUpdatePowerTx{} // doesn't matter
|
|
header := abci.Header{AppHash: []byte("apphash")}
|
|
|
|
app.BeginBlock(abci.RequestBeginBlock{Header: header})
|
|
res := app.Deliver(tx)
|
|
assert.Equal(t, res.Code, sdk.ToABCICode(sdk.CodespaceRoot, sdk.CodeOutOfGas), "Expected transaction to run out of gas")
|
|
app.EndBlock(abci.RequestEndBlock{})
|
|
app.Commit()
|
|
}
|
|
|
|
// Test that we can only query from the latest committed state.
|
|
func TestQuery(t *testing.T) {
|
|
app := newBaseApp(t.Name())
|
|
|
|
// make a cap key and mount the store
|
|
capKey := sdk.NewKVStoreKey("main")
|
|
app.MountStoresIAVL(capKey)
|
|
err := app.LoadLatestVersion(capKey) // needed to make stores non-nil
|
|
assert.Nil(t, err)
|
|
|
|
key, value := []byte("hello"), []byte("goodbye")
|
|
|
|
app.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx) (newCtx sdk.Context, res sdk.Result, abort bool) { return })
|
|
app.Router().AddRoute(msgType, func(ctx sdk.Context, msg sdk.Msg) sdk.Result {
|
|
store := ctx.KVStore(capKey)
|
|
store.Set(key, value)
|
|
return sdk.Result{}
|
|
})
|
|
|
|
query := abci.RequestQuery{
|
|
Path: "/store/main/key",
|
|
Data: key,
|
|
}
|
|
|
|
// query is empty before we do anything
|
|
res := app.Query(query)
|
|
assert.Equal(t, 0, len(res.Value))
|
|
|
|
tx := testUpdatePowerTx{} // doesn't matter
|
|
|
|
// query is still empty after a CheckTx
|
|
app.Check(tx)
|
|
res = app.Query(query)
|
|
assert.Equal(t, 0, len(res.Value))
|
|
|
|
// query is still empty after a DeliverTx before we commit
|
|
app.BeginBlock(abci.RequestBeginBlock{})
|
|
app.Deliver(tx)
|
|
res = app.Query(query)
|
|
assert.Equal(t, 0, len(res.Value))
|
|
|
|
// query returns correct value after Commit
|
|
app.Commit()
|
|
res = app.Query(query)
|
|
assert.Equal(t, value, res.Value)
|
|
}
|
|
|
|
// Test p2p filter queries
|
|
func TestP2PQuery(t *testing.T) {
|
|
app := newBaseApp(t.Name())
|
|
|
|
// make a cap key and mount the store
|
|
capKey := sdk.NewKVStoreKey("main")
|
|
app.MountStoresIAVL(capKey)
|
|
err := app.LoadLatestVersion(capKey) // needed to make stores non-nil
|
|
assert.Nil(t, err)
|
|
|
|
app.SetAddrPeerFilter(func(addrport string) abci.ResponseQuery {
|
|
require.Equal(t, "1.1.1.1:8000", addrport)
|
|
return abci.ResponseQuery{Code: uint32(3)}
|
|
})
|
|
|
|
app.SetPubKeyPeerFilter(func(pubkey string) abci.ResponseQuery {
|
|
require.Equal(t, "testpubkey", pubkey)
|
|
return abci.ResponseQuery{Code: uint32(4)}
|
|
})
|
|
|
|
addrQuery := abci.RequestQuery{
|
|
Path: "/p2p/filter/addr/1.1.1.1:8000",
|
|
}
|
|
res := app.Query(addrQuery)
|
|
require.Equal(t, uint32(3), res.Code)
|
|
|
|
pubkeyQuery := abci.RequestQuery{
|
|
Path: "/p2p/filter/pubkey/testpubkey",
|
|
}
|
|
res = app.Query(pubkeyQuery)
|
|
require.Equal(t, uint32(4), res.Code)
|
|
}
|
|
|
|
//----------------------
|
|
// TODO: clean this up
|
|
|
|
// A mock transaction to update a validator's voting power.
|
|
type testUpdatePowerTx struct {
|
|
Addr []byte
|
|
NewPower int64
|
|
}
|
|
|
|
const msgType = "testUpdatePowerTx"
|
|
|
|
func (tx testUpdatePowerTx) Type() string { return msgType }
|
|
func (tx testUpdatePowerTx) GetMemo() string { return "" }
|
|
func (tx testUpdatePowerTx) GetMsgs() []sdk.Msg { return []sdk.Msg{tx} }
|
|
func (tx testUpdatePowerTx) GetSignBytes() []byte { return nil }
|
|
func (tx testUpdatePowerTx) ValidateBasic() sdk.Error { return nil }
|
|
func (tx testUpdatePowerTx) GetSigners() []sdk.Address { return nil }
|
|
func (tx testUpdatePowerTx) GetSignatures() []auth.StdSignature { return nil }
|
|
|
|
func TestValidatorChange(t *testing.T) {
|
|
|
|
// Create app.
|
|
app := newBaseApp(t.Name())
|
|
capKey := sdk.NewKVStoreKey("key")
|
|
app.MountStoresIAVL(capKey)
|
|
app.SetTxDecoder(func(txBytes []byte) (sdk.Tx, sdk.Error) {
|
|
var ttx testUpdatePowerTx
|
|
fromJSON(txBytes, &ttx)
|
|
return ttx, nil
|
|
})
|
|
|
|
app.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx) (newCtx sdk.Context, res sdk.Result, abort bool) { return })
|
|
app.Router().AddRoute(msgType, func(ctx sdk.Context, msg sdk.Msg) sdk.Result {
|
|
// TODO
|
|
return sdk.Result{}
|
|
})
|
|
|
|
// Load latest state, which should be empty.
|
|
err := app.LoadLatestVersion(capKey)
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, app.LastBlockHeight(), int64(0))
|
|
|
|
// Create the validators
|
|
var numVals = 3
|
|
var valSet = make([]abci.Validator, numVals)
|
|
for i := 0; i < numVals; i++ {
|
|
valSet[i] = makeVal(secret(i))
|
|
}
|
|
|
|
// Initialize the chain
|
|
app.InitChain(abci.RequestInitChain{
|
|
Validators: valSet,
|
|
})
|
|
|
|
// Simulate the start of a block.
|
|
app.BeginBlock(abci.RequestBeginBlock{})
|
|
|
|
// Add 1 to each validator's voting power.
|
|
for i, val := range valSet {
|
|
tx := testUpdatePowerTx{
|
|
Addr: makePubKey(secret(i)).Address(),
|
|
NewPower: val.Power + 1,
|
|
}
|
|
txBytes := toJSON(tx)
|
|
res := app.DeliverTx(txBytes)
|
|
assert.True(t, res.IsOK(), "%#v\nABCI log: %s", res, res.Log)
|
|
}
|
|
|
|
// Simulate the end of a block.
|
|
// Get the summary of validator updates.
|
|
res := app.EndBlock(abci.RequestEndBlock{})
|
|
valUpdates := res.ValidatorUpdates
|
|
|
|
// Assert that validator updates are correct.
|
|
for _, val := range valSet {
|
|
|
|
pubkey, err := tmtypes.PB2TM.PubKey(val.PubKey)
|
|
// Sanity
|
|
assert.Nil(t, err)
|
|
|
|
// Find matching update and splice it out.
|
|
for j := 0; j < len(valUpdates); j++ {
|
|
valUpdate := valUpdates[j]
|
|
|
|
updatePubkey, err := tmtypes.PB2TM.PubKey(valUpdate.PubKey)
|
|
assert.Nil(t, err)
|
|
|
|
// Matched.
|
|
if updatePubkey.Equals(pubkey) {
|
|
assert.Equal(t, valUpdate.Power, val.Power+1)
|
|
if j < len(valUpdates)-1 {
|
|
// Splice it out.
|
|
valUpdates = append(valUpdates[:j], valUpdates[j+1:]...)
|
|
}
|
|
break
|
|
}
|
|
|
|
// Not matched.
|
|
}
|
|
}
|
|
assert.Equal(t, len(valUpdates), 0, "Some validator updates were unexpected")
|
|
}
|
|
|
|
//----------------------------------------
|
|
|
|
// Use burn and send msg types to test multiple msgs in one tx
|
|
type testBurnMsg struct {
|
|
Addr sdk.Address
|
|
Amount sdk.Coins
|
|
}
|
|
|
|
const msgType3 = "burn"
|
|
|
|
func (msg testBurnMsg) Type() string { return msgType3 }
|
|
func (msg testBurnMsg) GetSignBytes() []byte {
|
|
bz, _ := json.Marshal(msg)
|
|
return bz
|
|
}
|
|
func (msg testBurnMsg) ValidateBasic() sdk.Error {
|
|
if msg.Addr == nil {
|
|
return sdk.ErrInvalidAddress("Cannot use nil as Address")
|
|
}
|
|
return nil
|
|
}
|
|
func (msg testBurnMsg) GetSigners() []sdk.Address {
|
|
return []sdk.Address{msg.Addr}
|
|
}
|
|
|
|
type testSendMsg struct {
|
|
Sender sdk.Address
|
|
Receiver sdk.Address
|
|
Amount sdk.Coins
|
|
}
|
|
|
|
const msgType4 = "send"
|
|
|
|
func (msg testSendMsg) Type() string { return msgType4 }
|
|
func (msg testSendMsg) GetSignBytes() []byte {
|
|
bz, _ := json.Marshal(msg)
|
|
return bz
|
|
}
|
|
func (msg testSendMsg) ValidateBasic() sdk.Error {
|
|
if msg.Sender == nil || msg.Receiver == nil {
|
|
return sdk.ErrInvalidAddress("Cannot use nil as Address")
|
|
}
|
|
return nil
|
|
}
|
|
func (msg testSendMsg) GetSigners() []sdk.Address {
|
|
return []sdk.Address{msg.Sender}
|
|
}
|
|
|
|
// Simple Handlers for burn and send
|
|
|
|
func newHandleBurn(keeper bank.Keeper) sdk.Handler {
|
|
return func(ctx sdk.Context, msg sdk.Msg) sdk.Result {
|
|
burnMsg := msg.(testBurnMsg)
|
|
_, _, err := keeper.SubtractCoins(ctx, burnMsg.Addr, burnMsg.Amount)
|
|
if err != nil {
|
|
return err.Result()
|
|
}
|
|
return sdk.Result{}
|
|
}
|
|
}
|
|
|
|
func newHandleSpend(keeper bank.Keeper) sdk.Handler {
|
|
return func(ctx sdk.Context, msg sdk.Msg) sdk.Result {
|
|
spendMsg := msg.(testSendMsg)
|
|
_, _, err := keeper.SubtractCoins(ctx, spendMsg.Sender, spendMsg.Amount)
|
|
if err != nil {
|
|
return err.Result()
|
|
}
|
|
_, _, err = keeper.AddCoins(ctx, spendMsg.Receiver, spendMsg.Amount)
|
|
if err != nil {
|
|
return err.Result()
|
|
}
|
|
return sdk.Result{}
|
|
}
|
|
}
|
|
|
|
// generate a signed transaction
|
|
func GenTx(chainID string, msgs []sdk.Msg, accnums []int64, seq []int64, priv ...crypto.PrivKey) auth.StdTx {
|
|
// make the transaction free
|
|
fee := auth.StdFee{
|
|
sdk.Coins{{"foocoin", sdk.NewInt(0)}},
|
|
100000,
|
|
}
|
|
|
|
sigs := make([]auth.StdSignature, len(priv))
|
|
for i, p := range priv {
|
|
sigs[i] = auth.StdSignature{
|
|
PubKey: p.PubKey(),
|
|
Signature: p.Sign(auth.StdSignBytes(chainID, accnums[i], seq[i], fee, msgs, "")),
|
|
AccountNumber: accnums[i],
|
|
Sequence: seq[i],
|
|
}
|
|
}
|
|
return auth.NewStdTx(msgs, fee, sigs, "")
|
|
}
|
|
|
|
// spin up simple app for testing
|
|
type testApp struct {
|
|
*BaseApp
|
|
accountMapper auth.AccountMapper
|
|
accountKeeper bank.Keeper
|
|
}
|
|
|
|
func newTestApp(name string) testApp {
|
|
return testApp{
|
|
BaseApp: newBaseApp(name),
|
|
}
|
|
}
|
|
|
|
func MakeCodec() *wire.Codec {
|
|
cdc := wire.NewCodec()
|
|
cdc.RegisterInterface((*sdk.Msg)(nil), nil)
|
|
crypto.RegisterAmino(cdc)
|
|
cdc.RegisterInterface((*auth.Account)(nil), nil)
|
|
cdc.RegisterConcrete(&auth.BaseAccount{}, "cosmos-sdk/BaseAccount", nil)
|
|
return cdc
|
|
}
|
|
|
|
// tests multiple msgs of same type from same address in single tx
|
|
func TestMultipleBurn(t *testing.T) {
|
|
// Create app.
|
|
app := newTestApp(t.Name())
|
|
capKey := sdk.NewKVStoreKey("key")
|
|
app.MountStoresIAVL(capKey)
|
|
app.SetTxDecoder(func(txBytes []byte) (sdk.Tx, sdk.Error) {
|
|
var tx auth.StdTx
|
|
fromJSON(txBytes, &tx)
|
|
return tx, nil
|
|
})
|
|
|
|
err := app.LoadLatestVersion(capKey)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
app.accountMapper = auth.NewAccountMapper(app.cdc, capKey, &auth.BaseAccount{})
|
|
app.accountKeeper = bank.NewKeeper(app.accountMapper)
|
|
|
|
app.SetAnteHandler(auth.NewAnteHandler(app.accountMapper, auth.FeeCollectionKeeper{}))
|
|
|
|
app.Router().
|
|
AddRoute("burn", newHandleBurn(app.accountKeeper)).
|
|
AddRoute("send", newHandleSpend(app.accountKeeper))
|
|
|
|
app.InitChain(abci.RequestInitChain{})
|
|
app.BeginBlock(abci.RequestBeginBlock{})
|
|
|
|
// Set chain-id
|
|
app.deliverState.ctx = app.deliverState.ctx.WithChainID(t.Name())
|
|
|
|
priv := makePrivKey("my secret")
|
|
addr := priv.PubKey().Address()
|
|
|
|
app.accountKeeper.AddCoins(app.deliverState.ctx, addr, sdk.Coins{{"foocoin", sdk.NewInt(100)}})
|
|
assert.Equal(t, sdk.Coins{{"foocoin", sdk.NewInt(100)}}, app.accountKeeper.GetCoins(app.deliverState.ctx, addr), "Balance did not update")
|
|
|
|
msg := testBurnMsg{addr, sdk.Coins{{"foocoin", sdk.NewInt(50)}}}
|
|
tx := GenTx(t.Name(), []sdk.Msg{msg, msg}, []int64{0}, []int64{0}, priv)
|
|
|
|
res := app.Deliver(tx)
|
|
|
|
assert.Equal(t, true, res.IsOK(), res.Log)
|
|
assert.Equal(t, sdk.Coins(nil), app.accountKeeper.GetCoins(app.deliverState.ctx, addr), "Double burn did not work")
|
|
}
|
|
|
|
// tests multiples msgs of same type from different addresses in single tx
|
|
func TestBurnMultipleOwners(t *testing.T) {
|
|
// Create app.
|
|
app := newTestApp(t.Name())
|
|
capKey := sdk.NewKVStoreKey("key")
|
|
app.MountStoresIAVL(capKey)
|
|
app.SetTxDecoder(func(txBytes []byte) (sdk.Tx, sdk.Error) {
|
|
var tx auth.StdTx
|
|
fromJSON(txBytes, &tx)
|
|
return tx, nil
|
|
})
|
|
|
|
err := app.LoadLatestVersion(capKey)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
app.accountMapper = auth.NewAccountMapper(app.cdc, capKey, &auth.BaseAccount{})
|
|
app.accountKeeper = bank.NewKeeper(app.accountMapper)
|
|
|
|
app.SetAnteHandler(auth.NewAnteHandler(app.accountMapper, auth.FeeCollectionKeeper{}))
|
|
|
|
app.Router().
|
|
AddRoute("burn", newHandleBurn(app.accountKeeper)).
|
|
AddRoute("send", newHandleSpend(app.accountKeeper))
|
|
|
|
app.InitChain(abci.RequestInitChain{})
|
|
app.BeginBlock(abci.RequestBeginBlock{})
|
|
|
|
// Set chain-id
|
|
app.deliverState.ctx = app.deliverState.ctx.WithChainID(t.Name())
|
|
|
|
priv1 := makePrivKey("my secret 1")
|
|
addr1 := priv1.PubKey().Address()
|
|
|
|
priv2 := makePrivKey("my secret 2")
|
|
addr2 := priv2.PubKey().Address()
|
|
|
|
// fund accounts
|
|
app.accountKeeper.AddCoins(app.deliverState.ctx, addr1, sdk.Coins{{"foocoin", sdk.NewInt(100)}})
|
|
app.accountKeeper.AddCoins(app.deliverState.ctx, addr2, sdk.Coins{{"foocoin", sdk.NewInt(100)}})
|
|
|
|
assert.Equal(t, sdk.Coins{{"foocoin", sdk.NewInt(100)}}, app.accountKeeper.GetCoins(app.deliverState.ctx, addr1), "Balance1 did not update")
|
|
assert.Equal(t, sdk.Coins{{"foocoin", sdk.NewInt(100)}}, app.accountKeeper.GetCoins(app.deliverState.ctx, addr2), "Balance2 did not update")
|
|
|
|
msg1 := testBurnMsg{addr1, sdk.Coins{{"foocoin", sdk.NewInt(100)}}}
|
|
msg2 := testBurnMsg{addr2, sdk.Coins{{"foocoin", sdk.NewInt(100)}}}
|
|
|
|
// test wrong signers: Address 1 signs both messages
|
|
tx := GenTx(t.Name(), []sdk.Msg{msg1, msg2}, []int64{0, 0}, []int64{0, 0}, priv1, priv1)
|
|
|
|
res := app.Deliver(tx)
|
|
assert.Equal(t, sdk.ABCICodeType(0x10003), res.Code, "Wrong signatures passed")
|
|
|
|
assert.Equal(t, sdk.Coins{{"foocoin", sdk.NewInt(100)}}, app.accountKeeper.GetCoins(app.deliverState.ctx, addr1), "Balance1 changed after invalid sig")
|
|
assert.Equal(t, sdk.Coins{{"foocoin", sdk.NewInt(100)}}, app.accountKeeper.GetCoins(app.deliverState.ctx, addr2), "Balance2 changed after invalid sig")
|
|
|
|
// test valid tx
|
|
tx = GenTx(t.Name(), []sdk.Msg{msg1, msg2}, []int64{0, 1}, []int64{1, 0}, priv1, priv2)
|
|
|
|
res = app.Deliver(tx)
|
|
assert.Equal(t, true, res.IsOK(), res.Log)
|
|
|
|
assert.Equal(t, sdk.Coins(nil), app.accountKeeper.GetCoins(app.deliverState.ctx, addr1), "Balance1 did not change after valid tx")
|
|
assert.Equal(t, sdk.Coins(nil), app.accountKeeper.GetCoins(app.deliverState.ctx, addr2), "Balance2 did not change after valid tx")
|
|
}
|
|
|
|
// tests different msg types in single tx with different addresses
|
|
func TestSendBurn(t *testing.T) {
|
|
// Create app.
|
|
app := newTestApp(t.Name())
|
|
capKey := sdk.NewKVStoreKey("key")
|
|
app.MountStoresIAVL(capKey)
|
|
app.SetTxDecoder(func(txBytes []byte) (sdk.Tx, sdk.Error) {
|
|
var tx auth.StdTx
|
|
fromJSON(txBytes, &tx)
|
|
return tx, nil
|
|
})
|
|
|
|
err := app.LoadLatestVersion(capKey)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
app.accountMapper = auth.NewAccountMapper(app.cdc, capKey, &auth.BaseAccount{})
|
|
app.accountKeeper = bank.NewKeeper(app.accountMapper)
|
|
|
|
app.SetAnteHandler(auth.NewAnteHandler(app.accountMapper, auth.FeeCollectionKeeper{}))
|
|
|
|
app.Router().
|
|
AddRoute("burn", newHandleBurn(app.accountKeeper)).
|
|
AddRoute("send", newHandleSpend(app.accountKeeper))
|
|
|
|
app.InitChain(abci.RequestInitChain{})
|
|
app.BeginBlock(abci.RequestBeginBlock{})
|
|
|
|
// Set chain-id
|
|
app.deliverState.ctx = app.deliverState.ctx.WithChainID(t.Name())
|
|
|
|
priv1 := makePrivKey("my secret 1")
|
|
addr1 := priv1.PubKey().Address()
|
|
|
|
priv2 := makePrivKey("my secret 2")
|
|
addr2 := priv2.PubKey().Address()
|
|
|
|
// fund accounts
|
|
app.accountKeeper.AddCoins(app.deliverState.ctx, addr1, sdk.Coins{{"foocoin", sdk.NewInt(100)}})
|
|
acc := app.accountMapper.NewAccountWithAddress(app.deliverState.ctx, addr2)
|
|
app.accountMapper.SetAccount(app.deliverState.ctx, acc)
|
|
|
|
assert.Equal(t, sdk.Coins{{"foocoin", sdk.NewInt(100)}}, app.accountKeeper.GetCoins(app.deliverState.ctx, addr1), "Balance1 did not update")
|
|
|
|
sendMsg := testSendMsg{addr1, addr2, sdk.Coins{{"foocoin", sdk.NewInt(50)}}}
|
|
|
|
msg1 := testBurnMsg{addr1, sdk.Coins{{"foocoin", sdk.NewInt(50)}}}
|
|
msg2 := testBurnMsg{addr2, sdk.Coins{{"foocoin", sdk.NewInt(50)}}}
|
|
|
|
// send then burn
|
|
tx := GenTx(t.Name(), []sdk.Msg{sendMsg, msg2, msg1}, []int64{0, 1}, []int64{0, 0}, priv1, priv2)
|
|
|
|
res := app.Deliver(tx)
|
|
assert.Equal(t, true, res.IsOK(), res.Log)
|
|
|
|
assert.Equal(t, sdk.Coins(nil), app.accountKeeper.GetCoins(app.deliverState.ctx, addr1), "Balance1 did not change after valid tx")
|
|
assert.Equal(t, sdk.Coins(nil), app.accountKeeper.GetCoins(app.deliverState.ctx, addr2), "Balance2 did not change after valid tx")
|
|
|
|
// Check that state is only updated if all msgs in tx pass.
|
|
app.accountKeeper.AddCoins(app.deliverState.ctx, addr1, sdk.Coins{{"foocoin", sdk.NewInt(50)}})
|
|
|
|
// burn then send
|
|
tx = GenTx(t.Name(), []sdk.Msg{msg1, sendMsg}, []int64{0}, []int64{1}, priv1)
|
|
|
|
res = app.Deliver(tx)
|
|
|
|
// Double check that state is correct after Commit.
|
|
app.EndBlock(abci.RequestEndBlock{})
|
|
app.Commit()
|
|
|
|
app.BeginBlock(abci.RequestBeginBlock{})
|
|
app.deliverState.ctx = app.deliverState.ctx.WithChainID(t.Name())
|
|
|
|
assert.Equal(t, sdk.ABCICodeType(0x1000a), res.Code, "Allowed tx to pass with insufficient funds")
|
|
|
|
assert.Equal(t, sdk.Coins{{"foocoin", sdk.NewInt(50)}}, app.accountKeeper.GetCoins(app.deliverState.ctx, addr1), "Allowed valid msg to pass in invalid tx")
|
|
assert.Equal(t, sdk.Coins(nil), app.accountKeeper.GetCoins(app.deliverState.ctx, addr2), "Balance2 changed after invalid tx")
|
|
}
|
|
|
|
//----------------------------------------
|
|
|
|
func randPower() int64 {
|
|
return cmn.RandInt64()
|
|
}
|
|
|
|
func makeVal(secret string) abci.Validator {
|
|
return abci.Validator{
|
|
PubKey: tmtypes.TM2PB.PubKey(makePubKey(secret)),
|
|
Power: randPower(),
|
|
}
|
|
}
|
|
|
|
func makePubKey(secret string) crypto.PubKey {
|
|
return makePrivKey(secret).PubKey()
|
|
}
|
|
|
|
func makePrivKey(secret string) crypto.PrivKey {
|
|
privKey := crypto.GenPrivKeyEd25519FromSecret([]byte(secret))
|
|
return privKey
|
|
}
|
|
|
|
func secret(index int) string {
|
|
return fmt.Sprintf("secret%d", index)
|
|
}
|
|
|
|
func copyVal(val abci.Validator) abci.Validator {
|
|
// val2 := *val
|
|
// return &val2
|
|
return val
|
|
}
|
|
|
|
func toJSON(o interface{}) []byte {
|
|
bz, err := json.Marshal(o)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
// fmt.Println(">> toJSON:", string(bz))
|
|
return bz
|
|
}
|
|
|
|
func fromJSON(bz []byte, ptr interface{}) {
|
|
// fmt.Println(">> fromJSON:", string(bz))
|
|
err := json.Unmarshal(bz, ptr)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
}
|