WIP: Implementing App w/ tests
This commit is contained in:
parent
45c2e9c30b
commit
5c06e56c24
@ -36,7 +36,7 @@ type App struct {
|
||||
handler sdk.Handler
|
||||
|
||||
// Cached validator changes from DeliverTx
|
||||
valSetDiff []abci.Validator
|
||||
valUpdates []abci.Validator
|
||||
}
|
||||
|
||||
var _ abci.Application = &App{}
|
||||
@ -57,14 +57,21 @@ func (app *App) SetHandler(handler Handler) {
|
||||
}
|
||||
|
||||
func (app *App) LoadLatestVersion() error {
|
||||
curVersion := app.store.NextVersion()
|
||||
app.
|
||||
store := app.store
|
||||
store.LoadLastVersion()
|
||||
return app.initFromStore()
|
||||
}
|
||||
|
||||
func (app *App) LoadVersion(version int64) error {
|
||||
store := app.store
|
||||
store.LoadVersion(version)
|
||||
return app.initFromStore()
|
||||
}
|
||||
|
||||
// Initializes the remaining logic from app.store.
|
||||
func (app *App) initFromStore() error {
|
||||
store := app.store
|
||||
lastCommitID := store.LastCommitID()
|
||||
curVersion := store.NextVersion()
|
||||
main := store.GetKVStore("main")
|
||||
header := (*abci.Header)(nil)
|
||||
storeCheck := store.CacheMultiStore()
|
||||
@ -74,12 +81,7 @@ func (app *App) LoadVersion(version int64) error {
|
||||
return errors.New("App expects MultiStore with 'main' KVStore")
|
||||
}
|
||||
|
||||
// Basic sanity check.
|
||||
if curVersion != lastCommitID.Version+1 {
|
||||
return errors.New("NextVersion != LastCommitID.Version+1")
|
||||
}
|
||||
|
||||
// If we've committed before, we expect store(main)/<mainKeyHeader>.
|
||||
// If we've committed before, we expect main://<mainKeyHeader>.
|
||||
if !lastCommitID.IsZero() {
|
||||
headerBytes, ok := main.Get(mainKeyHeader)
|
||||
if !ok {
|
||||
@ -90,11 +92,18 @@ func (app *App) LoadVersion(version int64) error {
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Failed to parse Header")
|
||||
}
|
||||
if header.Height != curVersion-1 {
|
||||
errStr := fmt.Sprintf("Expected header.Height %v but got %v", version, headerHeight)
|
||||
if header.Height != lastCommitID.Version {
|
||||
errStr := fmt.Sprintf("Expected main://%s.Height %v but got %v", mainKeyHeader, version, headerHeight)
|
||||
return errors.New(errStr)
|
||||
}
|
||||
}
|
||||
|
||||
// Set App state.
|
||||
app.header = header
|
||||
app.storeCheck = app.store.CacheMultiStore()
|
||||
app.valUpdates = nil
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
//----------------------------------------
|
||||
@ -112,10 +121,8 @@ func (app *App) DeliverTx(txBytes []byte) abci.ResponseDeliverTx {
|
||||
var result = app.handler(ctx, app.store, tx)
|
||||
|
||||
// After-handler hooks.
|
||||
// TODO move to app.afterHandler(...).
|
||||
if result.Code == abci.CodeType_OK {
|
||||
// XXX No longer "diff", we need to replace old entries.
|
||||
app.ValSetDiff = append(app.ValSetDiff, result.ValSetDiff)
|
||||
app.valUpdates = append(app.valUpdates, result.ValUpdate)
|
||||
} else {
|
||||
// Even though the Code is not OK, there will be some side effects,
|
||||
// like those caused by fee deductions or sequence incrementations.
|
||||
@ -247,8 +254,8 @@ func (app *App) BeginBlock(req abci.RequestBeginBlock) {
|
||||
// Returns a list of all validator changes made in this block
|
||||
func (app *App) EndBlock(height uint64) (res abci.ResponseEndBlock) {
|
||||
// XXX Update to res.Updates.
|
||||
res.Diffs = app.valSetDiff
|
||||
app.valSetDiff = nil
|
||||
res.Diffs = app.valUpdates
|
||||
app.valUpdates = nil
|
||||
return
|
||||
}
|
||||
|
||||
140
app/app_test.go
Normal file
140
app/app_test.go
Normal file
@ -0,0 +1,140 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk"
|
||||
abci "github.com/tendermint/abci/types"
|
||||
"github.com/tendermint/go-crypto"
|
||||
cmn "github.com/tendermint/tmlibs/common"
|
||||
)
|
||||
|
||||
func TestBasic(t *testing.T) {
|
||||
|
||||
// A mock transaction to update a validator's voting power.
|
||||
type testTx struct {
|
||||
Addr []byte
|
||||
NewPower int64
|
||||
}
|
||||
|
||||
// Create app.
|
||||
app := sdk.NewApp(t.Name())
|
||||
app.SetStore(mockMultiStore())
|
||||
app.SetHandler(func(ctx Context, store MultiStore, tx Tx) Result {
|
||||
|
||||
// This could be a decorator.
|
||||
fromJSON(ctx.TxBytes(), &tx)
|
||||
|
||||
fmt.Println(">>", tx)
|
||||
})
|
||||
|
||||
// Load latest state, which should be empty.
|
||||
err := app.LoadLatestVersion()
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, app.NextVersion(), 1)
|
||||
|
||||
// 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 := testTx{
|
||||
Addr: makePubKey(secret(i)).Address(),
|
||||
NewPower: val.Power + 1,
|
||||
}
|
||||
txBytes := toJSON(tx)
|
||||
res := app.DeliverTx(txBytes)
|
||||
require.True(res.IsOK(), "%#v", res)
|
||||
}
|
||||
|
||||
// Simulate the end of a block.
|
||||
// Get the summary of validator updates.
|
||||
res := app.EndBlock(app.height)
|
||||
valUpdates := res.ValidatorUpdates
|
||||
|
||||
// Assert that validator updates are correct.
|
||||
for _, val := range valSet {
|
||||
|
||||
// Find matching update and splice it out.
|
||||
for j := 0; j < len(valUpdates); {
|
||||
assert.NotEqual(len(valUpdates.PubKey), 0)
|
||||
|
||||
// Matched.
|
||||
if bytes.Equal(valUpdate.PubKey, val.PubKey) {
|
||||
assert.Equal(valUpdate.NewPower, val.Power+1)
|
||||
if j < len(valUpdates)-1 {
|
||||
// Splice it out.
|
||||
valUpdates = append(valUpdates[:j], valUpdates[j+1:]...)
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
// Not matched.
|
||||
j += 1
|
||||
}
|
||||
}
|
||||
assert.Equal(t, len(valUpdates), 0, "Some validator updates were unexpected")
|
||||
}
|
||||
|
||||
//----------------------------------------
|
||||
|
||||
func randPower() int64 {
|
||||
return cmn.RandInt64()
|
||||
}
|
||||
|
||||
func makeVal(secret string) *abci.Validator {
|
||||
return &abci.Validator{
|
||||
PubKey: makePubKey(string).Bytes(),
|
||||
Power: randPower(),
|
||||
}
|
||||
}
|
||||
|
||||
func makePubKey(secret string) crypto.PubKey {
|
||||
return makePrivKey(secret).PubKey()
|
||||
}
|
||||
|
||||
func makePrivKey(secret string) crypto.PrivKey {
|
||||
return crypto.GenPrivKeyEd25519FromSecret([]byte(id))
|
||||
}
|
||||
|
||||
func secret(index int) []byte {
|
||||
return []byte(fmt.Sprintf("secret%d", index))
|
||||
}
|
||||
|
||||
func copyVal(val *abci.Validator) *abci.Validator {
|
||||
val2 := *val
|
||||
return &val2
|
||||
}
|
||||
|
||||
func toJSON(o interface{}) []byte {
|
||||
bytes, err := json.Marshal(o)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return bytes
|
||||
}
|
||||
|
||||
func fromJSON(bytes []byte, ptr interface{}) {
|
||||
err := json.Unmarshal(bytes, ptr)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
@ -1,88 +0,0 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
abci "github.com/tendermint/abci/types"
|
||||
wire "github.com/tendermint/go-wire"
|
||||
cmn "github.com/tendermint/tmlibs/common"
|
||||
"github.com/tendermint/tmlibs/log"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/modules/base"
|
||||
)
|
||||
|
||||
//-----------------------------------
|
||||
// Test cases start here
|
||||
|
||||
func randPower() uint64 {
|
||||
return uint64(cmn.RandInt()%50 + 60)
|
||||
}
|
||||
|
||||
func makeVal() *abci.Validator {
|
||||
return &abci.Validator{
|
||||
PubKey: cmn.RandBytes(10),
|
||||
Power: randPower(),
|
||||
}
|
||||
}
|
||||
|
||||
// withNewPower returns a copy of the validator with a different power
|
||||
func withNewPower(val *abci.Validator) *abci.Validator {
|
||||
res := *val
|
||||
res.Power = randPower()
|
||||
return &res
|
||||
}
|
||||
|
||||
func TestEndBlock(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
|
||||
logger := log.NewNopLogger()
|
||||
handler := base.ValSetHandler{}
|
||||
store, err := MockStoreApp("vals", logger)
|
||||
require.Nil(err, "%+v", err)
|
||||
app := NewBaseApp(store, handler, nil)
|
||||
|
||||
val1 := makeVal()
|
||||
val2 := makeVal()
|
||||
val3 := makeVal()
|
||||
val1a := withNewPower(val1)
|
||||
val2a := withNewPower(val2)
|
||||
|
||||
cases := [...]struct {
|
||||
changes [][]*abci.Validator
|
||||
expected []*abci.Validator
|
||||
}{
|
||||
// Nothing in, nothing out, no crash
|
||||
0: {},
|
||||
// One in, one out, no problem
|
||||
1: {
|
||||
changes: [][]*abci.Validator{{val1}},
|
||||
expected: []*abci.Validator{val1},
|
||||
},
|
||||
// Combine a few ones
|
||||
2: {
|
||||
changes: [][]*abci.Validator{{val1}, {val2, val3}},
|
||||
expected: []*abci.Validator{val1, val2, val3},
|
||||
},
|
||||
// Make sure changes all to one validators are squished into one diff
|
||||
3: {
|
||||
changes: [][]*abci.Validator{{val1}, {val2, val1a}, {val2a, val3}},
|
||||
expected: []*abci.Validator{val1a, val2a, val3},
|
||||
},
|
||||
}
|
||||
|
||||
for i, tc := range cases {
|
||||
app.BeginBlock(abci.RequestBeginBlock{})
|
||||
for _, c := range tc.changes {
|
||||
tx := base.ValChangeTx{c}.Wrap()
|
||||
txBytes := wire.BinaryBytes(tx)
|
||||
res := app.DeliverTx(txBytes)
|
||||
require.True(res.IsOK(), "%#v", res)
|
||||
}
|
||||
diff := app.EndBlock(app.height)
|
||||
// TODO: don't care about order here...
|
||||
assert.Equal(tc.expected, diff.Diffs, "%d", i)
|
||||
}
|
||||
}
|
||||
@ -6,4 +6,4 @@ import (
|
||||
|
||||
// Handler handles both ABCI DeliverTx and CheckTx requests.
|
||||
// Iff ABCI.CheckTx, ctx.IsCheckTx() returns true.
|
||||
type Handler func(ctx Context, ms store.MultiStore, tx Tx) Result
|
||||
type Handler func(ctx Context, store store.MultiStore, tx Tx) Result
|
||||
|
||||
@ -1,14 +1,10 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
cmn "github.com/tendemrint/tmlibs/common"
|
||||
abci "github.com/tendermint/abci/types"
|
||||
)
|
||||
|
||||
type KVPair struct {
|
||||
Key []byte
|
||||
Value []byte
|
||||
}
|
||||
|
||||
// Result is the union of ResponseDeliverTx and ResponseCheckTx.
|
||||
type Result struct {
|
||||
|
||||
@ -35,5 +31,5 @@ type Result struct {
|
||||
ValSetDiff []abci.Validator
|
||||
|
||||
// Tags are used for transaction indexing and pubsub.
|
||||
Tags []KVPair
|
||||
Tags []cmn.KVPair
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user