Merge PR #1069: Oracle Module

fix prefixstore iterator
in progress
fix mock validator
fix NewContext
add to changelog
apply requests
fix mock
This commit is contained in:
Joon 2018-06-27 10:21:12 -07:00 committed by Christopher Goes
parent 6f140d7296
commit 6018e719d2
12 changed files with 894 additions and 1 deletions

View File

@ -36,6 +36,7 @@ FEATURES
* [tests] Add WaitForNextNBlocksTM helper method
* [types] Switches internal representation of Int/Uint/Rat to use pointers
* [gaiad] unsafe_reset_all now resets addrbook.json
* [democoin] add x/oracle, x/assoc
FIXES
* [gaia] Added self delegation for validators in the genesis creation

View File

@ -0,0 +1,122 @@
package mock
import (
"bytes"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/tendermint/go-crypto"
)
// Validator implements sdk.Validator
type Validator struct {
Address sdk.Address
Power sdk.Rat
}
// Implements sdk.Validator
func (v Validator) GetStatus() sdk.BondStatus {
return sdk.Bonded
}
// Implements sdk.Validator
func (v Validator) GetOwner() sdk.Address {
return v.Address
}
// Implements sdk.Validator
func (v Validator) GetPubKey() crypto.PubKey {
return nil
}
// Implements sdk.Validator
func (v Validator) GetPower() sdk.Rat {
return v.Power
}
// Implements sdk.Validator
func (v Validator) GetDelegatorShares() sdk.Rat {
return sdk.ZeroRat()
}
// Implements sdk.Validator
func (v Validator) GetBondHeight() int64 {
return 0
}
// Implements sdk.Validator
func (v Validator) GetMoniker() string {
return ""
}
// Implements sdk.Validator
type ValidatorSet struct {
Validators []Validator
}
// IterateValidators implements sdk.ValidatorSet
func (vs *ValidatorSet) IterateValidators(ctx sdk.Context, fn func(index int64, Validator sdk.Validator) bool) {
for i, val := range vs.Validators {
if fn(int64(i), val) {
break
}
}
}
// IterateValidatorsBonded implements sdk.ValidatorSet
func (vs *ValidatorSet) IterateValidatorsBonded(ctx sdk.Context, fn func(index int64, Validator sdk.Validator) bool) {
vs.IterateValidators(ctx, fn)
}
// Validator implements sdk.ValidatorSet
func (vs *ValidatorSet) Validator(ctx sdk.Context, addr sdk.Address) sdk.Validator {
for _, val := range vs.Validators {
if bytes.Equal(val.Address, addr) {
return val
}
}
return nil
}
// TotalPower implements sdk.ValidatorSet
func (vs *ValidatorSet) TotalPower(ctx sdk.Context) sdk.Rat {
res := sdk.ZeroRat()
for _, val := range vs.Validators {
res = res.Add(val.Power)
}
return res
}
// Helper function for adding new validator
func (vs *ValidatorSet) AddValidator(val Validator) {
vs.Validators = append(vs.Validators, val)
}
// Helper function for removing exsting validator
func (vs *ValidatorSet) RemoveValidator(addr sdk.Address) {
pos := -1
for i, val := range vs.Validators {
if bytes.Equal(val.Address, addr) {
pos = i
break
}
}
if pos == -1 {
return
}
vs.Validators = append(vs.Validators[:pos], vs.Validators[pos+1:]...)
}
// Implements sdk.ValidatorSet
func (vs *ValidatorSet) Slash(ctx sdk.Context, pubkey crypto.PubKey, height int64, amt sdk.Rat) {
panic("not implemented")
}
// Implements sdk.ValidatorSet
func (vs *ValidatorSet) Revoke(ctx sdk.Context, pubkey crypto.PubKey) {
panic("not implemented")
}
// Implements sdk.ValidatorSet
func (vs *ValidatorSet) Unrevoke(ctx sdk.Context, pubkey crypto.PubKey) {
panic("not implemented")
}

View File

@ -0,0 +1,107 @@
package assoc
import (
"bytes"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/wire"
)
// ValidatorSet defines
type ValidatorSet struct {
sdk.ValidatorSet
key sdk.KVStoreGetter
cdc *wire.Codec
maxAssoc int
addrLen int
}
var _ sdk.ValidatorSet = ValidatorSet{}
// NewValidatorSet returns new ValidatorSet with underlying ValidatorSet
func NewValidatorSet(cdc *wire.Codec, key sdk.KVStoreGetter, valset sdk.ValidatorSet, maxAssoc int, addrLen int) ValidatorSet {
if maxAssoc < 0 || addrLen < 0 {
panic("Cannot use negative integer for NewValidatorSet")
}
return ValidatorSet{
ValidatorSet: valset,
key: key,
cdc: cdc,
maxAssoc: maxAssoc,
addrLen: addrLen,
}
}
// Implements sdk.ValidatorSet
func (valset ValidatorSet) Validator(ctx sdk.Context, addr sdk.Address) (res sdk.Validator) {
store := valset.key.KVStore(ctx)
base := store.Get(GetBaseKey(addr))
res = valset.ValidatorSet.Validator(ctx, base)
if res == nil {
res = valset.ValidatorSet.Validator(ctx, addr)
}
return
}
// GetBaseKey :: sdk.Address -> sdk.Address
func GetBaseKey(addr sdk.Address) []byte {
return append([]byte{0x00}, addr...)
}
// GetAssocPrefix :: sdk.Address -> (sdk.Address -> byte)
func GetAssocPrefix(base sdk.Address) []byte {
return append([]byte{0x01}, base...)
}
// GetAssocKey :: (sdk.Address, sdk.Address) -> byte
func GetAssocKey(base sdk.Address, assoc sdk.Address) []byte {
return append(append([]byte{0x01}, base...), assoc...)
}
// Associate associates new address with validator address
func (valset ValidatorSet) Associate(ctx sdk.Context, base sdk.Address, assoc sdk.Address) bool {
if len(base) != valset.addrLen || len(assoc) != valset.addrLen {
return false
}
store := valset.key.KVStore(ctx)
// If someone already owns the associated address
if store.Get(GetBaseKey(assoc)) != nil {
return false
}
store.Set(GetBaseKey(assoc), base)
store.Set(GetAssocKey(base, assoc), []byte{0x00})
return true
}
// Dissociate removes association between addresses
func (valset ValidatorSet) Dissociate(ctx sdk.Context, base sdk.Address, assoc sdk.Address) bool {
if len(base) != valset.addrLen || len(assoc) != valset.addrLen {
return false
}
store := valset.key.KVStore(ctx)
// No associated address found for given validator
if !bytes.Equal(store.Get(GetBaseKey(assoc)), base) {
return false
}
store.Delete(GetBaseKey(assoc))
store.Delete(GetAssocKey(base, assoc))
return true
}
// Associations returns all associated addresses with a validator
func (valset ValidatorSet) Associations(ctx sdk.Context, base sdk.Address) (res []sdk.Address) {
store := valset.key.KVStore(ctx)
res = make([]sdk.Address, valset.maxAssoc)
iter := sdk.KVStorePrefixIterator(store, GetAssocPrefix(base))
i := 0
for ; iter.Valid(); iter.Next() {
key := iter.Key()
res[i] = key[len(key)-valset.addrLen:]
i++
}
return res[:i]
}

View File

@ -0,0 +1,71 @@
package assoc
import (
"bytes"
"testing"
"github.com/stretchr/testify/assert"
abci "github.com/tendermint/abci/types"
dbm "github.com/tendermint/tmlibs/db"
"github.com/cosmos/cosmos-sdk/examples/democoin/mock"
"github.com/cosmos/cosmos-sdk/store"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/wire"
)
func defaultContext(key sdk.StoreKey) sdk.Context {
db := dbm.NewMemDB()
cms := store.NewCommitMultiStore(db)
cms.MountStoreWithDB(key, sdk.StoreTypeIAVL, db)
cms.LoadLatestVersion()
ctx := sdk.NewContext(cms, abci.Header{}, false, nil)
return ctx
}
func TestValidatorSet(t *testing.T) {
key := sdk.NewKVStoreKey("test")
ctx := defaultContext(key)
addr1 := []byte("addr1")
addr2 := []byte("addr2")
base := &mock.ValidatorSet{[]mock.Validator{
{addr1, sdk.NewRat(1)},
{addr2, sdk.NewRat(2)},
}}
valset := NewValidatorSet(wire.NewCodec(), sdk.NewPrefixStoreGetter(key, []byte("assoc")), base, 1, 5)
assert.Equal(t, base.Validator(ctx, addr1), valset.Validator(ctx, addr1))
assert.Equal(t, base.Validator(ctx, addr2), valset.Validator(ctx, addr2))
assoc1 := []byte("asso1")
assoc2 := []byte("asso2")
assert.True(t, valset.Associate(ctx, addr1, assoc1))
assert.True(t, valset.Associate(ctx, addr2, assoc2))
assert.Equal(t, base.Validator(ctx, addr1), valset.Validator(ctx, assoc1))
assert.Equal(t, base.Validator(ctx, addr2), valset.Validator(ctx, assoc2))
assert.Equal(t, base.Validator(ctx, addr1), valset.Validator(ctx, addr1))
assert.Equal(t, base.Validator(ctx, addr2), valset.Validator(ctx, addr2))
assocs := valset.Associations(ctx, addr1)
assert.Equal(t, 1, len(assocs))
assert.True(t, bytes.Equal(assoc1, assocs[0]))
assert.False(t, valset.Associate(ctx, addr1, assoc2))
assert.False(t, valset.Associate(ctx, addr2, assoc1))
valset.Dissociate(ctx, addr1, assoc1)
valset.Dissociate(ctx, addr2, assoc2)
assert.Equal(t, base.Validator(ctx, addr1), valset.Validator(ctx, addr1))
assert.Equal(t, base.Validator(ctx, addr2), valset.Validator(ctx, addr2))
assert.Nil(t, valset.Validator(ctx, assoc1))
assert.Nil(t, valset.Validator(ctx, assoc2))
}

View File

@ -0,0 +1,58 @@
# Oracle Module
`x/oracle` provides a way to receive external information(real world price, events from other chains, etc.) with validators' vote. Each validator make transaction which contains those informations, and Oracle aggregates them until the supermajority signed on it. After then, Oracle sends the information to the actual module that processes the information, and prune the votes from the state.
## Integration
See `x/oracle/oracle_test.go` for the code that using Oracle
To use Oracle in your module, first define a `payload`. It should implement `oracle.Payload` and contain nessesary information for your module. Including nonce is recommended.
```go
type MyPayload struct {
Data int
Nonce int
}
```
When you write a payload, its `.Type()` should return same name with your module is registered on the router. It is because `oracle.Msg` inherits `.Type()` from its embedded payload and it should be handled on the user modules.
Then route every incoming `oracle.Msg` to `oracle.Keeper.Handler()` with the function that implements `oracle.Handler`.
```go
func NewHandler(keeper Keeper) sdk.Handler {
return func(ctx sdk.Context, msg sdk.Msg) sdk.Result {
switch msg := msg.(type) {
case oracle.Msg:
return keeper.oracle.Handle(ctx sdk.Context, p oracle.Payload) sdk.Error {
switch p := p.(type) {
case MyPayload:
return handleMyPayload(ctx, keeper, p)
}
}
}
}
}
```
In the previous example, the keeper has an `oracle.Keeper`. `oracle.Keeper`s are generated by `NewKeeper`.
```go
func NewKeeper(key sdk.StoreKey, cdc *wire.Codec, valset sdk.ValidatorSet, supermaj sdk.Rat, timeout int64) Keeper {
return Keeper {
cdc: cdc,
key: key,
// ValidatorSet to get validators infor
valset: valset,
// The keeper will pass payload
// when more than 2/3 signed on it
supermaj: supermaj,
// The keeper will prune votes after 100 blocks from last sign
timeout: timeout,
}
}
```
Now the validators can send `oracle.Msg`s with `MyPayload` when they want to witness external events.

View File

@ -0,0 +1,31 @@
package oracle
import (
sdk "github.com/cosmos/cosmos-sdk/types"
)
// Oracle errors reserve 1101-1199
const (
CodeNotValidator sdk.CodeType = 1101
CodeAlreadyProcessed sdk.CodeType = 1102
CodeAlreadySigned sdk.CodeType = 1103
CodeUnknownRequest sdk.CodeType = sdk.CodeUnknownRequest
)
// ----------------------------------------
// Error constructors
// ErrNotValidator called when the signer of a Msg is not a validator
func ErrNotValidator(codespace sdk.CodespaceType, address sdk.Address) sdk.Error {
return sdk.NewError(codespace, CodeNotValidator, address.String())
}
// ErrAlreadyProcessed called when a payload is already processed
func ErrAlreadyProcessed(codespace sdk.CodespaceType) sdk.Error {
return sdk.NewError(codespace, CodeAlreadyProcessed, "")
}
// ErrAlreadySigned called when the signer is trying to double signing
func ErrAlreadySigned(codespace sdk.CodespaceType) sdk.Error {
return sdk.NewError(codespace, CodeAlreadySigned, "")
}

View File

@ -0,0 +1,105 @@
package oracle
import (
"bytes"
sdk "github.com/cosmos/cosmos-sdk/types"
)
// Handler handles payload after it passes voting process
type Handler func(ctx sdk.Context, p Payload) sdk.Error
func (keeper Keeper) update(ctx sdk.Context, val sdk.Validator, valset sdk.ValidatorSet, p Payload, info Info) Info {
info.Power = info.Power.Add(val.GetPower())
// Return if the voted power is not bigger than required power
totalPower := valset.TotalPower(ctx)
requiredPower := totalPower.Mul(keeper.supermaj)
if !info.Power.GT(requiredPower) {
return info
}
// Check if the validators hash has been changed during the vote process
// and recalculate voted power
hash := ctx.BlockHeader().ValidatorsHash
if !bytes.Equal(hash, info.Hash) {
info.Power = sdk.ZeroRat()
info.Hash = hash
prefix := GetSignPrefix(p, keeper.cdc)
store := keeper.key.KVStore(ctx)
iter := sdk.KVStorePrefixIterator(store, prefix)
for ; iter.Valid(); iter.Next() {
if valset.Validator(ctx, iter.Value()) != nil {
store.Delete(iter.Key())
continue
}
info.Power = info.Power.Add(val.GetPower())
}
if !info.Power.GT(totalPower.Mul(keeper.supermaj)) {
return info
}
}
info.Status = Processed
return info
}
// Handle is used by other modules to handle Msg
func (keeper Keeper) Handle(h Handler, ctx sdk.Context, o Msg, codespace sdk.CodespaceType) sdk.Result {
valset := keeper.valset
signer := o.Signer
payload := o.Payload
// Check the oracle is not in process
info := keeper.Info(ctx, payload)
if info.Status != Pending {
return ErrAlreadyProcessed(codespace).Result()
}
// Check if it is reporting timeout
now := ctx.BlockHeight()
if now > info.LastSigned+keeper.timeout {
info = Info{Status: Timeout}
keeper.setInfo(ctx, payload, info)
keeper.clearSigns(ctx, payload)
return sdk.Result{}
}
info.LastSigned = ctx.BlockHeight()
// Check the signer is a validater
val := valset.Validator(ctx, signer)
if val == nil {
return ErrNotValidator(codespace, signer).Result()
}
// Check double signing
if keeper.signed(ctx, payload, signer) {
return ErrAlreadySigned(codespace).Result()
}
keeper.sign(ctx, payload, signer)
info = keeper.update(ctx, val, valset, payload, info)
if info.Status == Processed {
info = Info{Status: Processed}
}
keeper.setInfo(ctx, payload, info)
if info.Status == Processed {
keeper.clearSigns(ctx, payload)
cctx, write := ctx.CacheContext()
err := h(cctx, payload)
if err != nil {
return sdk.Result{
Code: sdk.ABCICodeOK,
Log: err.ABCILog(),
}
}
write()
}
return sdk.Result{}
}

View File

@ -0,0 +1,111 @@
package oracle
import (
"github.com/cosmos/cosmos-sdk/wire"
sdk "github.com/cosmos/cosmos-sdk/types"
)
// Keeper of the oracle store
type Keeper struct {
key sdk.KVStoreGetter
cdc *wire.Codec
valset sdk.ValidatorSet
supermaj sdk.Rat
timeout int64
}
// NewKeeper constructs a new keeper
func NewKeeper(key sdk.KVStoreGetter, cdc *wire.Codec, valset sdk.ValidatorSet, supermaj sdk.Rat, timeout int64) Keeper {
if timeout < 0 {
panic("Timeout should not be negative")
}
return Keeper{
key: key,
cdc: cdc,
valset: valset,
supermaj: supermaj,
timeout: timeout,
}
}
// InfoStatus - current status of an Info
type InfoStatus int8
// Define InfoStatus
const (
Pending = InfoStatus(iota)
Processed
Timeout
)
// Info for each payload
type Info struct {
Power sdk.Rat
Hash []byte
LastSigned int64
Status InfoStatus
}
// EmptyInfo construct an empty Info
func EmptyInfo(ctx sdk.Context) Info {
return Info{
Power: sdk.ZeroRat(),
Hash: ctx.BlockHeader().ValidatorsHash,
LastSigned: ctx.BlockHeight(),
Status: Pending,
}
}
// Info returns the information about a payload
func (keeper Keeper) Info(ctx sdk.Context, p Payload) (res Info) {
store := keeper.key.KVStore(ctx)
key := GetInfoKey(p, keeper.cdc)
bz := store.Get(key)
if bz == nil {
return EmptyInfo(ctx)
}
keeper.cdc.MustUnmarshalBinary(bz, &res)
return
}
func (keeper Keeper) setInfo(ctx sdk.Context, p Payload, info Info) {
store := keeper.key.KVStore(ctx)
key := GetInfoKey(p, keeper.cdc)
bz := keeper.cdc.MustMarshalBinary(info)
store.Set(key, bz)
}
func (keeper Keeper) sign(ctx sdk.Context, p Payload, signer sdk.Address) {
store := keeper.key.KVStore(ctx)
key := GetSignKey(p, signer, keeper.cdc)
store.Set(key, signer)
}
func (keeper Keeper) signed(ctx sdk.Context, p Payload, signer sdk.Address) bool {
store := keeper.key.KVStore(ctx)
key := GetSignKey(p, signer, keeper.cdc)
return store.Has(key)
}
func (keeper Keeper) clearSigns(ctx sdk.Context, p Payload) {
store := keeper.key.KVStore(ctx)
prefix := GetSignPrefix(p, keeper.cdc)
iter := sdk.KVStorePrefixIterator(store, prefix)
for ; iter.Valid(); iter.Next() {
store.Delete(iter.Key())
}
iter.Close()
}

View File

@ -0,0 +1,23 @@
package oracle
import (
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/wire"
)
// GetInfoKey returns the key for OracleInfo
func GetInfoKey(p Payload, cdc *wire.Codec) []byte {
bz := cdc.MustMarshalBinary(p)
return append([]byte{0x00}, bz...)
}
// GetSignPrefix returns the prefix for signs
func GetSignPrefix(p Payload, cdc *wire.Codec) []byte {
bz := cdc.MustMarshalBinary(p)
return append([]byte{0x01}, bz...)
}
// GetSignKey returns the key for sign
func GetSignKey(p Payload, signer sdk.Address, cdc *wire.Codec) []byte {
return append(GetSignPrefix(p, cdc), signer...)
}

View File

@ -0,0 +1,221 @@
package oracle
import (
"encoding/json"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
abci "github.com/tendermint/abci/types"
dbm "github.com/tendermint/tmlibs/db"
"github.com/cosmos/cosmos-sdk/examples/democoin/mock"
"github.com/cosmos/cosmos-sdk/store"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/wire"
)
func defaultContext(keys ...sdk.StoreKey) sdk.Context {
db := dbm.NewMemDB()
cms := store.NewCommitMultiStore(db)
for _, key := range keys {
cms.MountStoreWithDB(key, sdk.StoreTypeIAVL, db)
}
cms.LoadLatestVersion()
ctx := sdk.NewContext(cms, abci.Header{}, false, nil)
return ctx
}
type seqOracle struct {
Seq int
Nonce int
}
func (o seqOracle) Type() string {
return "seq"
}
func (o seqOracle) ValidateBasic() sdk.Error {
return nil
}
func makeCodec() *wire.Codec {
var cdc = wire.NewCodec()
cdc.RegisterInterface((*sdk.Msg)(nil), nil)
cdc.RegisterConcrete(Msg{}, "test/Oracle", nil)
cdc.RegisterInterface((*Payload)(nil), nil)
cdc.RegisterConcrete(seqOracle{}, "test/oracle/seqOracle", nil)
return cdc
}
func seqHandler(ork Keeper, key sdk.StoreKey, codespace sdk.CodespaceType) sdk.Handler {
return func(ctx sdk.Context, msg sdk.Msg) sdk.Result {
switch msg := msg.(type) {
case Msg:
return ork.Handle(func(ctx sdk.Context, p Payload) sdk.Error {
switch p := p.(type) {
case seqOracle:
return handleSeqOracle(ctx, key, p)
default:
return sdk.ErrUnknownRequest("")
}
}, ctx, msg, codespace)
default:
return sdk.ErrUnknownRequest("").Result()
}
}
}
func getSequence(ctx sdk.Context, key sdk.StoreKey) int {
store := ctx.KVStore(key)
seqbz := store.Get([]byte("seq"))
var seq int
if seqbz == nil {
seq = 0
} else {
wire.NewCodec().MustUnmarshalBinary(seqbz, &seq)
}
return seq
}
func handleSeqOracle(ctx sdk.Context, key sdk.StoreKey, o seqOracle) sdk.Error {
store := ctx.KVStore(key)
seq := getSequence(ctx, key)
if seq != o.Seq {
return sdk.NewError(sdk.CodespaceUndefined, 1, "")
}
bz := wire.NewCodec().MustMarshalBinary(seq + 1)
store.Set([]byte("seq"), bz)
return nil
}
func TestOracle(t *testing.T) {
cdc := makeCodec()
addr1 := []byte("addr1")
addr2 := []byte("addr2")
addr3 := []byte("addr3")
addr4 := []byte("addr4")
valset := &mock.ValidatorSet{[]mock.Validator{
mock.Validator{addr1, sdk.NewRat(7)},
mock.Validator{addr2, sdk.NewRat(7)},
mock.Validator{addr3, sdk.NewRat(1)},
}}
key := sdk.NewKVStoreKey("testkey")
ctx := defaultContext(key)
bz, err := json.Marshal(valset)
require.Nil(t, err)
ctx = ctx.WithBlockHeader(abci.Header{ValidatorsHash: bz})
ork := NewKeeper(sdk.NewPrefixStoreGetter(key, []byte("oracle")), cdc, valset, sdk.NewRat(2, 3), 100)
h := seqHandler(ork, key, sdk.CodespaceUndefined)
// Nonmock.Validator signed, transaction failed
msg := Msg{seqOracle{0, 0}, []byte("randomguy")}
res := h(ctx, msg)
assert.False(t, res.IsOK())
assert.Equal(t, 0, getSequence(ctx, key))
// Less than 2/3 signed, msg not processed
msg.Signer = addr1
res = h(ctx, msg)
assert.True(t, res.IsOK())
assert.Equal(t, 0, getSequence(ctx, key))
// Double signed, transaction failed
res = h(ctx, msg)
assert.False(t, res.IsOK())
assert.Equal(t, 0, getSequence(ctx, key))
// More than 2/3 signed, msg processed
msg.Signer = addr2
res = h(ctx, msg)
assert.True(t, res.IsOK())
assert.Equal(t, 1, getSequence(ctx, key))
// Already processed, transaction failed
msg.Signer = addr3
res = h(ctx, msg)
assert.False(t, res.IsOK())
assert.Equal(t, 1, getSequence(ctx, key))
// Less than 2/3 signed, msg not processed
msg = Msg{seqOracle{100, 1}, addr1}
res = h(ctx, msg)
assert.True(t, res.IsOK())
assert.Equal(t, 1, getSequence(ctx, key))
// More than 2/3 signed but payload is invalid
msg.Signer = addr2
res = h(ctx, msg)
assert.True(t, res.IsOK())
assert.NotEqual(t, "", res.Log)
assert.Equal(t, 1, getSequence(ctx, key))
// Already processed, transaction failed
msg.Signer = addr3
res = h(ctx, msg)
assert.False(t, res.IsOK())
assert.Equal(t, 1, getSequence(ctx, key))
// Should handle mock.Validator set change
valset.AddValidator(mock.Validator{addr4, sdk.NewRat(12)})
bz, err = json.Marshal(valset)
require.Nil(t, err)
ctx = ctx.WithBlockHeader(abci.Header{ValidatorsHash: bz})
// Less than 2/3 signed, msg not processed
msg = Msg{seqOracle{1, 2}, addr1}
res = h(ctx, msg)
assert.True(t, res.IsOK())
assert.Equal(t, 1, getSequence(ctx, key))
// Less than 2/3 signed, msg not processed
msg.Signer = addr2
res = h(ctx, msg)
assert.True(t, res.IsOK())
assert.Equal(t, 1, getSequence(ctx, key))
// More than 2/3 signed, msg processed
msg.Signer = addr4
res = h(ctx, msg)
assert.True(t, res.IsOK())
assert.Equal(t, 2, getSequence(ctx, key))
// Should handle mock.Validator set change while oracle process is happening
msg = Msg{seqOracle{2, 3}, addr4}
// Less than 2/3 signed, msg not processed
res = h(ctx, msg)
assert.True(t, res.IsOK())
assert.Equal(t, 2, getSequence(ctx, key))
// Signed mock.Validator is kicked out
valset.RemoveValidator(addr4)
bz, err = json.Marshal(valset)
require.Nil(t, err)
ctx = ctx.WithBlockHeader(abci.Header{ValidatorsHash: bz})
// Less than 2/3 signed, msg not processed
msg.Signer = addr1
res = h(ctx, msg)
assert.True(t, res.IsOK())
assert.Equal(t, 2, getSequence(ctx, key))
// More than 2/3 signed, msg processed
msg.Signer = addr2
res = h(ctx, msg)
assert.True(t, res.IsOK())
assert.Equal(t, 3, getSequence(ctx, key))
}

View File

@ -0,0 +1,33 @@
package oracle
import (
"encoding/json"
sdk "github.com/cosmos/cosmos-sdk/types"
)
// Msg - struct for voting on payloads
type Msg struct {
Payload
Signer sdk.Address
}
// GetSignBytes implements sdk.Msg
func (msg Msg) GetSignBytes() []byte {
bz, err := json.Marshal(msg)
if err != nil {
panic(err)
}
return bz
}
// GetSigners implements sdk.Msg
func (msg Msg) GetSigners() []sdk.Address {
return []sdk.Address{msg.Signer}
}
// Payload defines inner data for actual execution
type Payload interface {
Type() string
ValidateBasic() sdk.Error
}

View File

@ -46,14 +46,24 @@ func (s prefixStore) Prefix(prefix []byte) KVStore {
// Implements KVStore
func (s prefixStore) Iterator(start, end []byte) Iterator {
if end == nil {
end = sdk.PrefixEndBytes(s.prefix)
} else {
end = append(s.prefix, end...)
}
return prefixIterator{
prefix: s.prefix,
iter: s.store.Iterator(start, end),
iter: s.store.Iterator(append(s.prefix, start...), end),
}
}
// Implements KVStore
func (s prefixStore) ReverseIterator(start, end []byte) Iterator {
if end == nil {
end = sdk.PrefixEndBytes(s.prefix)
} else {
end = append(s.prefix, end...)
}
return prefixIterator{
prefix: s.prefix,
iter: s.store.ReverseIterator(start, end),