diff --git a/account/account.go b/account/account.go index 64b9a927..ecad6c8c 100644 --- a/account/account.go +++ b/account/account.go @@ -45,7 +45,7 @@ type Account struct { Code []byte `json:"code"` // VM code StorageRoot []byte `json:"storage_root"` // VM storage merkle root. - Permissions *ptypes.Permissions `json:"permissions"` + Permissions *ptypes.AccountPermissions `json:"permissions"` } func (acc *Account) Copy() *Account { @@ -70,13 +70,3 @@ var AccountCodec = binary.Codec{ Encode: AccountEncoder, Decode: AccountDecoder, } - -//----------------------------------------------------------------------------- - -// defaults for a Big Bad Public Blockchain -var DefaultPermissions = ptypes.Permissions{ - Send: true, - Call: true, - Create: true, - Bond: true, -} diff --git a/permission/types/errors.go b/permission/types/errors.go new file mode 100644 index 00000000..5a170a7c --- /dev/null +++ b/permission/types/errors.go @@ -0,0 +1,44 @@ +package types + +import ( + "fmt" +) + +//------------------------------------------------------------------------------------------------ +// Some errors + +// permission number out of bounds +type ErrInvalidPermission PermFlag + +func (e ErrInvalidPermission) Error() string { + return fmt.Sprintf("invalid permission %d", e) +} + +// unknown string for permission +type ErrInvalidPermissionString string + +func (e ErrInvalidPermissionString) Error() string { + return fmt.Sprintf("invalid permission '%s'", e) +} + +// already exists (err on add) +type ErrPermissionExists string + +func (e ErrPermissionExists) Error() string { + return fmt.Sprintf("permission '%s' already exists", e) +} + +// unknown string for snative contract +type ErrInvalidSNativeString string + +func (e ErrInvalidSNativeString) Error() string { + return fmt.Sprintf("invalid snative contract '%s'", e) +} + +// set=false. This error should be caught and the global +// value fetched for the permission by the caller +type ErrValueNotSet PermFlag + +func (e ErrValueNotSet) Error() string { + return fmt.Sprintf("the value for permission %d is not set", e) +} diff --git a/permission/types/permissions.go b/permission/types/permissions.go new file mode 100644 index 00000000..2ddaa480 --- /dev/null +++ b/permission/types/permissions.go @@ -0,0 +1,165 @@ +package types + +import ( + "fmt" + . "github.com/tendermint/tendermint/common" +) + +//------------------------------------------------------------------------------------------------ + +var ( + GlobalPermissionsAddress = Zero256[:20] + GlobalPermissionsAddress256 = Zero256 + DougAddress = append([]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, []byte("THISISDOUG")...) + DougAddress256 = LeftPadWord256(DougAddress) +) + +// A particular permission +type PermFlag uint16 + +// Base permission references are like unix (the index is already bit shifted) +const ( + Send PermFlag = 1 << iota // 1 + Call // 2 + Create // 4 + Bond // 8 + Root // 16 + + DefaultBBPB = Send | Call | Create | Bond + + NumBasePermissions uint = 5 + TopBasePermission PermFlag = 1 << (NumBasePermissions - 1) + AllSet PermFlag = 1< TopBasePermission { + return false, ErrInvalidPermission(ty) + } + if p.SetBit&ty == 0 { + return false, ErrValueNotSet(ty) + } + return p.Perms&ty > 0, nil +} + +// Set a permission bit. Will set the permission's set bit to true. +func (p *BasePermissions) Set(ty PermFlag, value bool) error { + if ty > TopBasePermission { + return ErrInvalidPermission(ty) + } + + p.SetBit |= ty + if value { + p.Perms |= ty + } else { + p.Perms &= ^ty + } + return nil +} + +// Set the permission's set bit to false +func (p *BasePermissions) Unset(ty PermFlag) error { + if ty > TopBasePermission { + return ErrInvalidPermission(ty) + } + p.SetBit &= ^ty + return nil +} + +func (p *BasePermissions) Copy() *BasePermissions { + return &BasePermissions{ + Perms: p.Perms, + SetBit: p.SetBit, + } +} + +func (p *BasePermissions) String() string { + return fmt.Sprintf("Base: %b; Set: %b", p.Perms, p.SetBit) +} + +//--------------------------------------------------------------------------------------------- + +type AccountPermissions struct { + Base *BasePermissions + Roles []string +} + +func NewAccountPermissions() *AccountPermissions { + return &AccountPermissions{ + Base: NewBasePermissions(), + Roles: []string{}, + } +} + +// Returns true if the role is found +func (aP *AccountPermissions) HasRole(role string) bool { + for _, r := range aP.Roles { + if r == role { + return true + } + } + return false +} + +// Returns true if the role is added, and false if it already exists +func (aP *AccountPermissions) AddRole(role string) bool { + for _, r := range aP.Roles { + if r == role { + return false + } + } + aP.Roles = append(aP.Roles, role) + return true +} + +// Returns true if the role is removed, and false if it is not found +func (aP *AccountPermissions) RmRole(role string) bool { + for i, r := range aP.Roles { + if r == role { + post := []string{} + if len(aP.Roles) > i+1 { + post = aP.Roles[i+1:] + } + aP.Roles = append(aP.Roles[:i], post...) + return true + } + } + return false +} + +func (aP *AccountPermissions) Copy() *AccountPermissions { + r := make([]string, len(aP.Roles)) + copy(r, aP.Roles) + return &AccountPermissions{ + Base: aP.Base.Copy(), + Roles: r, + } +} + +func NewDefaultAccountPermissions() *AccountPermissions { + return &AccountPermissions{ + Base: &BasePermissions{ + Perms: DefaultBBPB, + SetBit: AllSet, + }, + Roles: []string{}, + } +} diff --git a/state/execution.go b/state/execution.go index 1edbad27..679936f1 100644 --- a/state/execution.go +++ b/state/execution.go @@ -178,7 +178,7 @@ func getOrMakeOutputs(state AccountGetter, accounts map[string]*account.Account, PubKey: nil, Sequence: 0, Balance: 0, - Permissions: state.GetAccount(ptypes.GlobalPermissionsAddress).Permissions, + Permissions: ptypes.NewAccountPermissions(), } } accounts[string(out.Address)] = acc @@ -307,7 +307,7 @@ func ExecTx(blockCache *BlockCache, tx_ types.Tx, runCall bool, evc events.Firea } // ensure all inputs have send permissions - if !hasSendPermission(accounts) { + if !hasSendPermission(blockCache, accounts) { return fmt.Errorf("At least one input lacks permission for SendTx") } @@ -364,11 +364,11 @@ func ExecTx(blockCache *BlockCache, tx_ types.Tx, runCall bool, evc events.Firea createAccount := len(tx.Address) == 0 if createAccount { - if !hasCreatePermission(inAcc) { + if !hasCreatePermission(blockCache, inAcc) { return fmt.Errorf("Account %X does not have Create permission", tx.Input.Address) } } else { - if !hasCallPermission(inAcc) { + if !hasCallPermission(blockCache, inAcc) { return fmt.Errorf("Account %X does not have Call permission", tx.Input.Address) } } @@ -465,7 +465,7 @@ func ExecTx(blockCache *BlockCache, tx_ types.Tx, runCall bool, evc events.Firea // we need to bind a copy of the accounts tree (from the txCache) // so the gendoug can make a native call to create accounts and update // permissions - setupDoug(vmach, txCache, _s) + // setupDoug(vmach, txCache, _s) } ret, err := vmach.Call(caller, callee, code, tx.Data, value, &gas) @@ -636,7 +636,7 @@ func ExecTx(blockCache *BlockCache, tx_ types.Tx, runCall bool, evc events.Firea return err } - if !hasBondPermission(accounts) { + if !hasBondPermission(blockCache, accounts) { return fmt.Errorf("At least one input lacks permission to bond") } @@ -790,38 +790,49 @@ func ExecTx(blockCache *BlockCache, tx_ types.Tx, runCall bool, evc events.Firea //--------------------------------------------------------------- // TODO: for debug log the failed accounts -func hasSendPermission(accs map[string]*account.Account) bool { +// Get permission on an account or fall back to global value +func HasPermission(state AccountGetter, acc *account.Account, perm ptypes.PermFlag) bool { + v, err := acc.Permissions.Base.Get(perm) + fmt.Printf("has permission? %x %v %b %v %v\n", acc.Address, acc.Permissions, perm, v, err) + if _, ok := err.(ptypes.ErrValueNotSet); ok { + return HasPermission(state, state.GetAccount(ptypes.GlobalPermissionsAddress), perm) + } + return v +} + +func hasSendPermission(state AccountGetter, accs map[string]*account.Account) bool { for _, acc := range accs { - if !acc.Permissions.Send { + if !HasPermission(state, acc, ptypes.Send) { return false } } return true } -func hasCallPermission(acc *account.Account) bool { - if !acc.Permissions.Call { +func hasCallPermission(state AccountGetter, acc *account.Account) bool { + if !HasPermission(state, acc, ptypes.Call) { return false } return true } -func hasCreatePermission(acc *account.Account) bool { - if !acc.Permissions.Create { +func hasCreatePermission(state AccountGetter, acc *account.Account) bool { + if !HasPermission(state, acc, ptypes.Create) { return false } return true } -func hasBondPermission(accs map[string]*account.Account) bool { +func hasBondPermission(state AccountGetter, accs map[string]*account.Account) bool { for _, acc := range accs { - if !acc.Permissions.Bond { + if !HasPermission(state, acc, ptypes.Bond) { return false } } return true } +/* // permission management functions // get/set closures which bind the txCache (for modifying an accounts permissions) // add/rm closures which bind txCache & state (for creating/removing permissions on *all* accounts - expensive!) @@ -841,7 +852,7 @@ func setupDoug(vmach *vm.VM, txCache *TxCache, _s *State) { } stAcc := toStateAccount(vmAcc) permN := uint(Uint64FromWord256(permNum)) - perm, err := stAcc.Permissions.Get(permN) + perm, err := stAcc.Permissions.Base.Get(permN) if err != nil { return nil, err } @@ -870,7 +881,7 @@ func setupDoug(vmach *vm.VM, txCache *TxCache, _s *State) { stAcc := toStateAccount(vmAcc) permN := uint(Uint64FromWord256(permNum)) permV := !perm.IsZero() - if err = stAcc.Permissions.Set(permN, permV); err != nil { + if err = stAcc.Permissions.Base.Set(permN, permV); err != nil { return nil, err } vmAcc = toVMAccount(stAcc) @@ -942,3 +953,4 @@ func setupDoug(vmach *vm.VM, txCache *TxCache, _s *State) { // must be called or else functions not accessible vmach.EnableDoug() } +*/ diff --git a/state/genesis.go b/state/genesis.go index bb225c40..c3017532 100644 --- a/state/genesis.go +++ b/state/genesis.go @@ -15,9 +15,9 @@ import ( ) type GenesisAccount struct { - Address []byte `json:"address"` - Amount uint64 `json:"amount"` - Permissions *ptypes.Permissions `json:"global_permissions"` // pointer so optional + Address []byte `json:"address"` + Amount uint64 `json:"amount"` + Permissions *ptypes.AccountPermissions `json:"global_permissions"` // pointer so optional } type GenesisValidator struct { @@ -28,7 +28,7 @@ type GenesisValidator struct { type GenesisParams struct { // Default permissions for newly created accounts - GlobalPermissions *ptypes.Permissions `json:"global_permissions"` + GlobalPermissions *ptypes.AccountPermissions `json:"global_permissions"` // TODO: other params we may want to tweak? } @@ -73,8 +73,7 @@ func MakeGenesisState(db dbm.DB, genDoc *GenesisDoc) *State { // Make accounts state tree accounts := merkle.NewIAVLTree(binary.BasicCodec, account.AccountCodec, defaultAccountsCacheCapacity, db) for _, genAcc := range genDoc.Accounts { - perm_ := account.DefaultPermissions - perm := &perm_ + perm := ptypes.NewDefaultAccountPermissions() if genAcc.Permissions != nil { perm = genAcc.Permissions } @@ -90,10 +89,11 @@ func MakeGenesisState(db dbm.DB, genDoc *GenesisDoc) *State { // global permissions are saved as the 0 address // so they are included in the accounts tree - globalPerms_ := account.DefaultPermissions - globalPerms := &globalPerms_ + globalPerms := ptypes.NewDefaultAccountPermissions() if genDoc.Params != nil && genDoc.Params.GlobalPermissions != nil { globalPerms = genDoc.Params.GlobalPermissions + // XXX: make sure the set bits are all true + globalPerms.Base.SetBit = ptypes.AllSet } permsAcc := &account.Account{ Address: ptypes.GlobalPermissionsAddress, diff --git a/state/permissions_test.go b/state/permissions_test.go index ded8596c..4de30a8a 100644 --- a/state/permissions_test.go +++ b/state/permissions_test.go @@ -62,29 +62,23 @@ func makeUsers(n int) []*account.PrivAccount { } var ( - PermsAllFalse = ptypes.Permissions{ - Send: false, - Call: false, - Create: false, - Bond: false, - } + PermsAllFalse = ptypes.NewAccountPermissions() ) -func newBaseGenDoc(globalPerm, accountPerm ptypes.Permissions) GenesisDoc { +func newBaseGenDoc(globalPerm, accountPerm *ptypes.AccountPermissions) GenesisDoc { genAccounts := []GenesisAccount{} for _, u := range user[:5] { - accPerm := accountPerm genAccounts = append(genAccounts, GenesisAccount{ Address: u.Address, Amount: 1000000, - Permissions: &accPerm, + Permissions: accountPerm.Copy(), }) } return GenesisDoc{ GenesisTime: time.Now(), Params: &GenesisParams{ - GlobalPermissions: &globalPerm, + GlobalPermissions: globalPerm, }, Accounts: genAccounts, Validators: []GenesisValidator{ @@ -104,9 +98,9 @@ func newBaseGenDoc(globalPerm, accountPerm ptypes.Permissions) GenesisDoc { func TestSendFails(t *testing.T) { stateDB := dbm.GetDB("state") genDoc := newBaseGenDoc(PermsAllFalse, PermsAllFalse) - genDoc.Accounts[1].Permissions.Send = true - genDoc.Accounts[2].Permissions.Call = true - genDoc.Accounts[3].Permissions.Create = true + genDoc.Accounts[1].Permissions.Base.Set(ptypes.Send, true) + genDoc.Accounts[2].Permissions.Base.Set(ptypes.Call, true) + genDoc.Accounts[3].Permissions.Base.Set(ptypes.Create, true) st := MakeGenesisState(stateDB, &genDoc) blockCache := NewBlockCache(st) @@ -156,9 +150,9 @@ func TestSendFails(t *testing.T) { func TestCallFails(t *testing.T) { stateDB := dbm.GetDB("state") genDoc := newBaseGenDoc(PermsAllFalse, PermsAllFalse) - genDoc.Accounts[1].Permissions.Send = true - genDoc.Accounts[2].Permissions.Call = true - genDoc.Accounts[3].Permissions.Create = true + genDoc.Accounts[1].Permissions.Base.Set(ptypes.Send, true) + genDoc.Accounts[2].Permissions.Base.Set(ptypes.Call, true) + genDoc.Accounts[3].Permissions.Base.Set(ptypes.Create, true) st := MakeGenesisState(stateDB, &genDoc) blockCache := NewBlockCache(st) @@ -226,7 +220,7 @@ func TestCallFails(t *testing.T) { func TestSendPermission(t *testing.T) { stateDB := dbm.GetDB("state") genDoc := newBaseGenDoc(PermsAllFalse, PermsAllFalse) - genDoc.Accounts[0].Permissions.Send = true // give the 0 account permission + genDoc.Accounts[0].Permissions.Base.Set(ptypes.Send, true) // give the 0 account permission st := MakeGenesisState(stateDB, &genDoc) blockCache := NewBlockCache(st) @@ -275,7 +269,7 @@ func callContractCode(contractAddr []byte) []byte { func TestCallPermission(t *testing.T) { stateDB := dbm.GetDB("state") genDoc := newBaseGenDoc(PermsAllFalse, PermsAllFalse) - genDoc.Accounts[0].Permissions.Call = true // give the 0 account permission + genDoc.Accounts[0].Permissions.Base.Set(ptypes.Call, true) // give the 0 account permission st := MakeGenesisState(stateDB, &genDoc) blockCache := NewBlockCache(st) @@ -291,7 +285,7 @@ func TestCallPermission(t *testing.T) { Code: []byte{0x60}, Sequence: 0, StorageRoot: Zero256.Bytes(), - Permissions: ptypes.NilPermissions.Copy(), + Permissions: ptypes.NewAccountPermissions(), } st.UpdateAccount(simpleAcc) @@ -315,7 +309,7 @@ func TestCallPermission(t *testing.T) { Code: contractCode, Sequence: 0, StorageRoot: Zero256.Bytes(), - Permissions: ptypes.NilPermissions.Copy(), + Permissions: ptypes.NewAccountPermissions(), } blockCache.UpdateAccount(caller1Acc) @@ -334,7 +328,7 @@ func TestCallPermission(t *testing.T) { fmt.Println("##### CALL TO SIMPLE CONTRACT (PASS)") // A single input, having the permission, and the contract has permission - caller1Acc.Permissions.Call = true + caller1Acc.Permissions.Base.Set(ptypes.Call, true) blockCache.UpdateAccount(caller1Acc) tx, _ = NewCallTx(blockCache, user[0].PubKey, caller1ContractAddr, nil, 100, 10000, 100) SignCallTx(tx, user[0]) @@ -359,10 +353,10 @@ func TestCallPermission(t *testing.T) { Code: contractCode2, Sequence: 0, StorageRoot: Zero256.Bytes(), - Permissions: ptypes.NilPermissions.Copy(), + Permissions: ptypes.NewAccountPermissions(), } - caller1Acc.Permissions.Call = false - caller2Acc.Permissions.Call = true + caller1Acc.Permissions.Base.Set(ptypes.Call, false) + caller2Acc.Permissions.Base.Set(ptypes.Call, true) blockCache.UpdateAccount(caller1Acc) blockCache.UpdateAccount(caller2Acc) @@ -381,7 +375,7 @@ func TestCallPermission(t *testing.T) { // both caller1 and caller2 have permission fmt.Println("##### CALL TO CONTRACT CALLING SIMPLE CONTRACT (PASS)") - caller1Acc.Permissions.Call = true + caller1Acc.Permissions.Base.Set(ptypes.Call, true) blockCache.UpdateAccount(caller1Acc) tx, _ = NewCallTx(blockCache, user[0].PubKey, caller2ContractAddr, nil, 100, 10000, 100) diff --git a/state/test.go b/state/test.go index a741e95d..7aced480 100644 --- a/state/test.go +++ b/state/test.go @@ -7,6 +7,7 @@ import ( "github.com/tendermint/tendermint/account" . "github.com/tendermint/tendermint/common" dbm "github.com/tendermint/tendermint/db" + ptypes "github.com/tendermint/tendermint/permission/types" "github.com/tendermint/tendermint/types" "io/ioutil" @@ -24,13 +25,13 @@ func Tempfile(prefix string) (*os.File, string) { func RandAccount(randBalance bool, minBalance int64) (*account.Account, *account.PrivAccount) { privAccount := account.GenPrivAccount() - perms := account.DefaultPermissions + perms := ptypes.NewDefaultAccountPermissions() acc := &account.Account{ Address: privAccount.PubKey.Address(), PubKey: privAccount.PubKey, Sequence: RandInt(), Balance: minBalance, - Permissions: &perms, + Permissions: perms, } if randBalance { acc.Balance += int64(RandUint32()) @@ -75,8 +76,9 @@ func RandGenesisState(numAccounts int, randBalance bool, minBalance int64, numVa for i := 0; i < numAccounts; i++ { account, privAccount := RandAccount(randBalance, minBalance) accounts[i] = GenesisAccount{ - Address: account.Address, - Amount: account.Balance, + Address: account.Address, + Amount: account.Balance, + Permissions: ptypes.NewDefaultAccountPermissions(), } privAccounts[i] = privAccount } @@ -102,6 +104,9 @@ func RandGenesisState(numAccounts int, randBalance bool, minBalance int64, numVa ChainID: "tendermint_test", Accounts: accounts, Validators: validators, + Params: &GenesisParams{ + GlobalPermissions: ptypes.NewDefaultAccountPermissions(), + }, }) s0.Save() return s0, privAccounts, privValidators diff --git a/vm/types.go b/vm/types.go index 92cfd0e2..e0aa4520 100644 --- a/vm/types.go +++ b/vm/types.go @@ -17,7 +17,7 @@ type Account struct { StorageRoot Word256 Other interface{} // For holding all other data. - Permissions *ptypes.Permissions + Permissions *ptypes.AccountPermissions } func (acc *Account) String() string { diff --git a/vm/vm.go b/vm/vm.go index 4ae455de..cc63eb5f 100644 --- a/vm/vm.go +++ b/vm/vm.go @@ -8,6 +8,7 @@ import ( . "github.com/tendermint/tendermint/common" "github.com/tendermint/tendermint/events" + ptypes "github.com/tendermint/tendermint/permission/types" "github.com/tendermint/tendermint/types" "github.com/tendermint/tendermint/vm/sha3" ) @@ -90,6 +91,14 @@ func (vm *VM) EnablePermissions() { vm.perms = true } +func (vm *VM) HasPermission(acc *Account, perm ptypes.PermFlag) bool { + v, err := acc.Permissions.Base.Get(perm) + if _, ok := err.(ptypes.ErrValueNotSet); ok { + return vm.HasPermission(vm.appState.GetAccount(ptypes.GlobalPermissionsAddress256), perm) + } + return v +} + // CONTRACT appState is aware of caller and callee, so we can just mutate them. // value: To be transferred from caller to callee. Refunded upon error. // gas: Available gas. No refunds for gas. @@ -681,7 +690,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas dbg.Printf(" => %v\n", log) case CREATE: // 0xF0 - if vm.perms && !callee.Permissions.Create { + if vm.perms && !vm.HasPermission(callee, ptypes.Create) { return nil, ErrPermission{"create"} } contractValue := stack.Pop64() @@ -709,7 +718,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas } case CALL, CALLCODE: // 0xF1, 0xF2 - if vm.perms && !callee.Permissions.Call { + if vm.perms && !vm.HasPermission(callee, ptypes.Call) { return nil, ErrPermission{"call"} } gasLimit := stack.Pop64()