Add role struct, storage, better tx validation

This commit is contained in:
Ethan Frey 2017-07-10 18:35:38 +02:00
parent 9eb3c3c7de
commit 3e52e6b959
6 changed files with 123 additions and 2 deletions

View File

@ -1,6 +1,8 @@
package basecoin package basecoin
import ( import (
"bytes"
wire "github.com/tendermint/go-wire" wire "github.com/tendermint/go-wire"
"github.com/tendermint/go-wire/data" "github.com/tendermint/go-wire/data"
"github.com/tendermint/tmlibs/log" "github.com/tendermint/tmlibs/log"
@ -21,10 +23,18 @@ func NewActor(app string, addr []byte) Actor {
return Actor{App: app, Address: addr} return Actor{App: app, Address: addr}
} }
// Bytes makes a binary coding, useful for turning this into a key in the store
func (a Actor) Bytes() []byte { func (a Actor) Bytes() []byte {
return wire.BinaryBytes(a) return wire.BinaryBytes(a)
} }
// Equals checks if two actors are the same
func (a Actor) Equals(b Actor) bool {
return a.ChainID == b.ChainID &&
a.App == b.App &&
bytes.Equal(a.Address, b.Address)
}
// Context is an interface, so we can implement "secure" variants that // Context is an interface, so we can implement "secure" variants that
// rely on private fields to control the actions // rely on private fields to control the actions
type Context interface { type Context interface {

View File

@ -15,8 +15,11 @@ var (
errInsufficientSigs = fmt.Errorf("Not enough signatures") errInsufficientSigs = fmt.Errorf("Not enough signatures")
errNoMembers = fmt.Errorf("No members specified") errNoMembers = fmt.Errorf("No members specified")
errTooManyMembers = fmt.Errorf("Too many members specified") errTooManyMembers = fmt.Errorf("Too many members specified")
errNotEnoughMembers = fmt.Errorf("Not enough members specified")
) )
// TODO: codegen?
// ex: err-gen NoRole,"No such role",CodeType_Unauthorized
func ErrNoRole() errors.TMError { func ErrNoRole() errors.TMError {
return errors.WithCode(errNoRole, abci.CodeType_Unauthorized) return errors.WithCode(errNoRole, abci.CodeType_Unauthorized)
} }
@ -58,3 +61,10 @@ func ErrTooManyMembers() errors.TMError {
func IsTooManyMembersErr(err error) bool { func IsTooManyMembersErr(err error) bool {
return errors.IsSameError(errTooManyMembers, err) return errors.IsSameError(errTooManyMembers, err)
} }
func ErrNotEnoughMembers() errors.TMError {
return errors.WithCode(errNotEnoughMembers, abci.CodeType_Unauthorized)
}
func IsNotEnoughMembersErr(err error) bool {
return errors.IsSameError(errNotEnoughMembers, err)
}

View File

@ -15,6 +15,37 @@ type Role struct {
Signers []basecoin.Actor `json:"signers"` Signers []basecoin.Actor `json:"signers"`
} }
func NewRole(min uint32, signers []basecoin.Actor) Role {
return Role{
MinSigs: min,
Signers: signers,
}
}
// IsSigner checks if the given Actor is allowed to sign this role
func (r Role) IsSigner(a basecoin.Actor) bool {
for _, s := range r.Signers {
if a.Equals(s) {
return true
}
}
return false
}
// IsAuthorized checks if the context has permission to assume the role
func (r Role) IsAuthorized(ctx basecoin.Context) bool {
needed := r.MinSigs
for _, s := range r.Signers {
if ctx.HasPermission(s) {
needed--
if needed <= 0 {
return true
}
}
}
return false
}
// MakeKey creates the lookup key for a role // MakeKey creates the lookup key for a role
func MakeKey(role []byte) []byte { func MakeKey(role []byte) []byte {
prefix := []byte(NameRole + "/") prefix := []byte(NameRole + "/")
@ -34,6 +65,7 @@ func loadRole(store state.KVStore, key []byte) (role Role, err error) {
return role, nil return role, nil
} }
// we only have create here, no update, since we don't allow update yet
func createRole(store state.KVStore, key []byte, role Role) error { func createRole(store state.KVStore, key []byte, role Role) error {
if _, err := loadRole(store, key); !IsNoRoleErr(err) { if _, err := loadRole(store, key); !IsNoRoleErr(err) {
return ErrRoleExists() return ErrRoleExists()

View File

@ -0,0 +1,60 @@
package roles_test
import (
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/tendermint/basecoin"
"github.com/tendermint/basecoin/modules/roles"
"github.com/tendermint/basecoin/stack"
)
func TestRole(t *testing.T) {
assert := assert.New(t)
// prepare some actors...
a := basecoin.Actor{App: "foo", Address: []byte("bar")}
b := basecoin.Actor{ChainID: "eth", App: "foo", Address: []byte("bar")}
c := basecoin.Actor{App: "foo", Address: []byte("baz")}
d := basecoin.Actor{App: "si-ly", Address: []byte("bar")}
e := basecoin.Actor{App: "si-ly", Address: []byte("big")}
f := basecoin.Actor{App: "sig", Address: []byte{1}}
g := basecoin.Actor{App: "sig", Address: []byte{2, 3, 4}}
cases := []struct {
sigs uint32
allowed []basecoin.Actor
signers []basecoin.Actor
valid bool
}{
// make sure simple compare is correct
{1, []basecoin.Actor{a}, []basecoin.Actor{a}, true},
{1, []basecoin.Actor{a}, []basecoin.Actor{b}, false},
{1, []basecoin.Actor{a}, []basecoin.Actor{c}, false},
{1, []basecoin.Actor{a}, []basecoin.Actor{d}, false},
// make sure multi-sig counts to 1
{1, []basecoin.Actor{a, b, c}, []basecoin.Actor{d, e, a, f}, true},
{1, []basecoin.Actor{a, b, c}, []basecoin.Actor{a, b, c, d}, true},
{1, []basecoin.Actor{a, b, c}, []basecoin.Actor{d, e, f}, false},
// make sure multi-sig counts higher
{2, []basecoin.Actor{b, e, g}, []basecoin.Actor{g, c, a, d, b}, true},
{2, []basecoin.Actor{b, e, g}, []basecoin.Actor{c, a, d, b}, false},
{3, []basecoin.Actor{a, b, c}, []basecoin.Actor{g}, false},
}
for idx, tc := range cases {
i := strconv.Itoa(idx)
// make sure IsSigner works
role := roles.NewRole(tc.sigs, tc.allowed)
for _, a := range tc.allowed {
assert.True(role.IsSigner(a), i)
}
// make sure IsAuthorized works
ctx := stack.MockContext("chain-id").WithPermissions(tc.signers...)
allowed := role.IsAuthorized(ctx)
assert.Equal(tc.valid, allowed, i)
}
}

View File

@ -7,6 +7,10 @@ import (
"github.com/tendermint/basecoin/errors" "github.com/tendermint/basecoin/errors"
) )
const (
MaxMembers = 10
)
// AssumeRoleTx is a layered tx that can wrap your normal tx to give it // AssumeRoleTx is a layered tx that can wrap your normal tx to give it
// the authority to use a given role. // the authority to use a given role.
type AssumeRoleTx struct { type AssumeRoleTx struct {
@ -61,6 +65,12 @@ func (tx CreateRoleTx) ValidateBasic() error {
if len(tx.Signers) == 0 { if len(tx.Signers) == 0 {
return ErrNoMembers() return ErrNoMembers()
} }
if len(tx.Signers) < int(tx.MinSigs) {
return ErrNotEnoughMembers()
}
if len(tx.Signers) > MaxMembers {
return ErrTooManyMembers()
}
return nil return nil
} }

View File

@ -1,7 +1,6 @@
package stack package stack
import ( import (
"bytes"
"math/rand" "math/rand"
"github.com/tendermint/tmlibs/log" "github.com/tendermint/tmlibs/log"
@ -52,7 +51,7 @@ func (c naiveContext) WithPermissions(perms ...basecoin.Actor) basecoin.Context
func (c naiveContext) HasPermission(perm basecoin.Actor) bool { func (c naiveContext) HasPermission(perm basecoin.Actor) bool {
for _, p := range c.perms { for _, p := range c.perms {
if perm.App == p.App && bytes.Equal(perm.Address, p.Address) { if p.Equals(perm) {
return true return true
} }
} }