Add role struct, storage, better tx validation
This commit is contained in:
parent
9eb3c3c7de
commit
3e52e6b959
10
context.go
10
context.go
|
@ -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 {
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue