Deduplicate validator changes in EndBlock and test this
This commit is contained in:
parent
ff658f0326
commit
70fe2444ab
26
app/app.go
26
app/app.go
|
@ -1,6 +1,7 @@
|
||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
@ -110,9 +111,7 @@ func (app *Basecoin) DeliverTx(txBytes []byte) abci.Result {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Result(err)
|
return errors.Result(err)
|
||||||
}
|
}
|
||||||
if len(res.Diff) > 0 {
|
app.addValChange(res.Diff)
|
||||||
app.pending = append(app.pending, res.Diff...)
|
|
||||||
}
|
|
||||||
return res.ToABCI()
|
return res.ToABCI()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -181,6 +180,27 @@ func (app *Basecoin) EndBlock(height uint64) (res abci.ResponseEndBlock) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (app *Basecoin) addValChange(diffs []*abci.Validator) {
|
||||||
|
for _, d := range diffs {
|
||||||
|
idx := findVal(d, app.pending)
|
||||||
|
if idx >= 0 {
|
||||||
|
app.pending[idx] = d
|
||||||
|
} else {
|
||||||
|
app.pending = append(app.pending, d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// return index of list with validator of same PubKey, or -1 if no match
|
||||||
|
func findVal(val *abci.Validator, list []*abci.Validator) int {
|
||||||
|
for i, v := range list {
|
||||||
|
if bytes.Equal(val.PubKey, v.PubKey) {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
//TODO move split key to tmlibs?
|
//TODO move split key to tmlibs?
|
||||||
|
|
||||||
// Splits the string at the first '/'.
|
// Splits the string at the first '/'.
|
||||||
|
|
|
@ -0,0 +1,137 @@
|
||||||
|
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/tendermint/basecoin"
|
||||||
|
"github.com/tendermint/basecoin/errors"
|
||||||
|
"github.com/tendermint/basecoin/state"
|
||||||
|
)
|
||||||
|
|
||||||
|
//--------------------------------
|
||||||
|
// Setup tx and handler for validation test cases
|
||||||
|
|
||||||
|
const (
|
||||||
|
ValName = "val"
|
||||||
|
TypeValChange = ValName + "/change"
|
||||||
|
ByteValChange = 0xfe
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
basecoin.TxMapper.RegisterImplementation(ValChangeTx{}, TypeValChange, ByteValChange)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ValSetHandler struct {
|
||||||
|
basecoin.NopCheck
|
||||||
|
basecoin.NopInitState
|
||||||
|
basecoin.NopInitValidate
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ basecoin.Handler = ValSetHandler{}
|
||||||
|
|
||||||
|
func (ValSetHandler) Name() string {
|
||||||
|
return ValName
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ValSetHandler) DeliverTx(ctx basecoin.Context, store state.SimpleDB,
|
||||||
|
tx basecoin.Tx) (res basecoin.DeliverResult, err error) {
|
||||||
|
change, ok := tx.Unwrap().(ValChangeTx)
|
||||||
|
if !ok {
|
||||||
|
return res, errors.ErrUnknownTxType(tx)
|
||||||
|
}
|
||||||
|
res.Diff = change.Diff
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type ValChangeTx struct {
|
||||||
|
Diff []*abci.Validator
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v ValChangeTx) Wrap() basecoin.Tx {
|
||||||
|
return basecoin.Tx{v}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v ValChangeTx) ValidateBasic() error { return nil }
|
||||||
|
|
||||||
|
//-----------------------------------
|
||||||
|
// Test cases start here
|
||||||
|
|
||||||
|
func power() uint64 {
|
||||||
|
// % can return negative numbers, so this ensures result is positive
|
||||||
|
return uint64(cmn.RandInt()%50 + 60)
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeVal() *abci.Validator {
|
||||||
|
return &abci.Validator{
|
||||||
|
PubKey: cmn.RandBytes(10),
|
||||||
|
Power: power(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// newPower returns a copy of the validator with a different power
|
||||||
|
func newPower(val *abci.Validator) *abci.Validator {
|
||||||
|
res := *val
|
||||||
|
res.Power = power()
|
||||||
|
if res.Power == val.Power {
|
||||||
|
panic("no no")
|
||||||
|
}
|
||||||
|
return &res
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEndBlock(t *testing.T) {
|
||||||
|
assert, require := assert.New(t), require.New(t)
|
||||||
|
|
||||||
|
logger := log.NewNopLogger()
|
||||||
|
store := MockStore()
|
||||||
|
handler := ValSetHandler{}
|
||||||
|
app := NewBasecoin(handler, store, logger)
|
||||||
|
|
||||||
|
val1 := makeVal()
|
||||||
|
val2 := makeVal()
|
||||||
|
val3 := makeVal()
|
||||||
|
val1a := newPower(val1)
|
||||||
|
val2a := newPower(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(nil, nil)
|
||||||
|
for _, c := range tc.changes {
|
||||||
|
tx := 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)
|
||||||
|
}
|
||||||
|
}
|
10
app/store.go
10
app/store.go
|
@ -36,6 +36,16 @@ type ChainState struct {
|
||||||
Height uint64
|
Height uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MockStore returns an in-memory store only intended for testing
|
||||||
|
func MockStore() *Store {
|
||||||
|
res, err := NewStore("", 0, log.NewNopLogger())
|
||||||
|
if err != nil {
|
||||||
|
// should never happen, abort test if it does
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
// NewStore initializes an in-memory IAVLTree, or attempts to load a persistant
|
// NewStore initializes an in-memory IAVLTree, or attempts to load a persistant
|
||||||
// tree from disk
|
// tree from disk
|
||||||
func NewStore(dbName string, cacheSize int, logger log.Logger) (*Store, error) {
|
func NewStore(dbName string, cacheSize int, logger log.Logger) (*Store, error) {
|
||||||
|
|
Loading…
Reference in New Issue