From 94f21ad01203b78ab5cdf7a604f6a362c7ab30a3 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sat, 16 May 2015 00:48:04 -0400 Subject: [PATCH] move perms to vm.Account --- account/account.go | 96 +------------------------- rpc/core/accounts.go | 3 +- state/execution.go | 13 +--- state/genesis.go | 23 ++++--- state/permissions_test.go | 140 ++------------------------------------ state/test.go | 3 +- state/tx_cache.go | 22 +++--- vm/test/vm_test.go | 6 +- vm/types.go | 3 + vm/vm.go | 50 ++++---------- 10 files changed, 55 insertions(+), 304 deletions(-) diff --git a/account/account.go b/account/account.go index 5f7fd0b5f..64b9a9276 100644 --- a/account/account.go +++ b/account/account.go @@ -6,8 +6,8 @@ import ( "io" "github.com/tendermint/tendermint/binary" - . "github.com/tendermint/tendermint/common" "github.com/tendermint/tendermint/merkle" + ptypes "github.com/tendermint/tendermint/permission/types" ) // Signable is an interface for all signable things. @@ -45,7 +45,7 @@ type Account struct { Code []byte `json:"code"` // VM code StorageRoot []byte `json:"storage_root"` // VM storage merkle root. - Permissions Permissions `json:"permissions"` + Permissions *ptypes.Permissions `json:"permissions"` } func (acc *Account) Copy() *Account { @@ -73,100 +73,10 @@ var AccountCodec = binary.Codec{ //----------------------------------------------------------------------------- -var GlobalPermissionsAddress = LeftPadBytes([]byte{}, 20) -var DougAddress = GlobalPermissionsAddress - -type Permission uint - -const ( - SendPermission Permission = iota - CallPermission - CreatePermission - BondPermission - - NumBasePermissions = 4 -) - -type Permissions struct { - Send bool - Call bool - Create bool - Bond bool - Other []bool -} - -func (p Permissions) Get(ty uint) (bool, error) { - tyP := Permission(ty) - switch tyP { - case SendPermission: - return p.Send, nil - case CallPermission: - return p.Call, nil - case CreatePermission: - return p.Create, nil - case BondPermission: - return p.Bond, nil - default: - ty = ty - 4 - if ty <= uint(len(p.Other)-1) { - return p.Other[ty], nil - } - return false, fmt.Errorf("Unknown permission number %v", ty) - } -} - -func (p Permissions) Set(ty uint, val bool) error { - tyP := Permission(ty) - switch tyP { - case SendPermission: - p.Send = val - case CallPermission: - p.Call = val - case CreatePermission: - p.Create = val - case BondPermission: - p.Bond = val - default: - ty = ty - 4 - if ty <= uint(len(p.Other)-1) { - p.Other[ty] = val - return nil - } - return fmt.Errorf("Unknown permission number %v", ty) - } - return nil -} - -// Add should be called on all accounts in tandem -func (p Permissions) Add(val bool) (uint, error) { - l := len(p.Other) - p.Other = append(p.Other, val) - return uint(l), nil -} - -// Remove should be called on all accounts in tandem -func (p Permissions) Remove(ty uint) error { - if ty < uint(NumBasePermissions) || ty >= uint(len(p.Other)) { - return fmt.Errorf("Invalid permission number %v", ty) - } - - // pop the permission out of the array - perms := p.Other[:ty] - if ty+1 < uint(len(p.Other)) { - perms = append(perms, p.Other[ty+1:]...) - } - p.Other = perms - return nil -} - // defaults for a Big Bad Public Blockchain -var DefaultPermissions = Permissions{ +var DefaultPermissions = ptypes.Permissions{ Send: true, Call: true, Create: true, Bond: true, } - -func (p Permissions) String() string { - return fmt.Sprintf("CanSend:%v, CanCall:%v, CanCreate:%v, CanBond:%v", p.Send, p.Call, p.Create, p.Bond) -} diff --git a/rpc/core/accounts.go b/rpc/core/accounts.go index 155cd6b75..5b27e05ef 100644 --- a/rpc/core/accounts.go +++ b/rpc/core/accounts.go @@ -4,6 +4,7 @@ import ( "fmt" acm "github.com/tendermint/tendermint/account" . "github.com/tendermint/tendermint/common" + ptypes "github.com/tendermint/tendermint/permission/types" ctypes "github.com/tendermint/tendermint/rpc/core/types" ) @@ -22,7 +23,7 @@ func GetAccount(address []byte) (*acm.Account, error) { Balance: 0, Code: nil, StorageRoot: nil, - Permissions: cache.GetAccount(acm.GlobalPermissionsAddress).Permissions, + Permissions: cache.GetAccount(ptypes.GlobalPermissionsAddress).Permissions, } } return account, nil diff --git a/state/execution.go b/state/execution.go index 1dcade791..1edbad275 100644 --- a/state/execution.go +++ b/state/execution.go @@ -7,6 +7,7 @@ import ( "github.com/tendermint/tendermint/account" . "github.com/tendermint/tendermint/common" "github.com/tendermint/tendermint/events" + ptypes "github.com/tendermint/tendermint/permission/types" // for GlobalPermissionAddress ... "github.com/tendermint/tendermint/types" "github.com/tendermint/tendermint/vm" ) @@ -177,7 +178,7 @@ func getOrMakeOutputs(state AccountGetter, accounts map[string]*account.Account, PubKey: nil, Sequence: 0, Balance: 0, - Permissions: state.GetAccount(account.GlobalPermissionsAddress).Permissions, + Permissions: state.GetAccount(ptypes.GlobalPermissionsAddress).Permissions, } } accounts[string(out.Address)] = acc @@ -442,7 +443,6 @@ func ExecTx(blockCache *BlockCache, tx_ types.Tx, runCall bool, evc events.Firea log.Debug(Fmt("Attempting to call an account (%X) with no code. Deducting fee from caller", tx.Address)) } return types.ErrTxInvalidAddress - } callee = toVMAccount(outAcc) code = callee.Code @@ -458,6 +458,7 @@ func ExecTx(blockCache *BlockCache, tx_ types.Tx, runCall bool, evc events.Firea txCache.UpdateAccount(callee) // because we adjusted by input above. vmach := vm.NewVM(txCache, params, caller.Address, account.HashSignBytes(_s.ChainID, tx)) vmach.SetFireable(evc) + vmach.EnablePermissions() // NOTE: Call() transfers the value from caller to callee iff call succeeds. if isDoug { @@ -466,14 +467,6 @@ func ExecTx(blockCache *BlockCache, tx_ types.Tx, runCall bool, evc events.Firea // permissions setupDoug(vmach, txCache, _s) } - // set the contract's permissions - // (the vm doesn't know about account.Account or account.Permissions - vmach.SetPermissionsGetter(func(vmAcc *vm.Account) (bool, bool, bool) { - stAcc := toStateAccount(vmAcc) - p := stAcc.Permissions - return p.Send, p.Call, p.Create - }) - vmach.SetPermissions(callee) ret, err := vmach.Call(caller, callee, code, tx.Data, value, &gas) exception := "" diff --git a/state/genesis.go b/state/genesis.go index 085076cac..bb225c401 100644 --- a/state/genesis.go +++ b/state/genesis.go @@ -10,15 +10,14 @@ import ( . "github.com/tendermint/tendermint/common" dbm "github.com/tendermint/tendermint/db" "github.com/tendermint/tendermint/merkle" + ptypes "github.com/tendermint/tendermint/permission/types" "github.com/tendermint/tendermint/types" ) type GenesisAccount struct { - Address []byte `json:"address"` - Amount int64 `json:"amount"` - Address []byte `json:"address"` - Amount uint64 `json:"amount"` - Permissions *account.Permissions `json:"global_permissions"` // pointer so optional + Address []byte `json:"address"` + Amount uint64 `json:"amount"` + Permissions *ptypes.Permissions `json:"global_permissions"` // pointer so optional } type GenesisValidator struct { @@ -29,7 +28,7 @@ type GenesisValidator struct { type GenesisParams struct { // Default permissions for newly created accounts - GlobalPermissions *account.Permissions `json:"global_permissions"` + GlobalPermissions *ptypes.Permissions `json:"global_permissions"` // TODO: other params we may want to tweak? } @@ -74,9 +73,10 @@ 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_ := account.DefaultPermissions + perm := &perm_ if genAcc.Permissions != nil { - perm = *(genAcc.Permissions) + perm = genAcc.Permissions } acc := &account.Account{ Address: genAcc.Address, @@ -90,12 +90,13 @@ 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_ := account.DefaultPermissions + globalPerms := &globalPerms_ if genDoc.Params != nil && genDoc.Params.GlobalPermissions != nil { - globalPerms = *(genDoc.Params.GlobalPermissions) + globalPerms = genDoc.Params.GlobalPermissions } permsAcc := &account.Account{ - Address: account.GlobalPermissionsAddress, + Address: ptypes.GlobalPermissionsAddress, PubKey: nil, Sequence: 0, Balance: 1337, diff --git a/state/permissions_test.go b/state/permissions_test.go index 77c0ab6be..ded8596c0 100644 --- a/state/permissions_test.go +++ b/state/permissions_test.go @@ -10,6 +10,7 @@ import ( . "github.com/tendermint/tendermint/common" dbm "github.com/tendermint/tendermint/db" "github.com/tendermint/tendermint/events" + ptypes "github.com/tendermint/tendermint/permission/types" "github.com/tendermint/tendermint/types" ) @@ -61,7 +62,7 @@ func makeUsers(n int) []*account.PrivAccount { } var ( - PermsAllFalse = account.Permissions{ + PermsAllFalse = ptypes.Permissions{ Send: false, Call: false, Create: false, @@ -69,7 +70,7 @@ var ( } ) -func newBaseGenDoc(globalPerm, accountPerm account.Permissions) GenesisDoc { +func newBaseGenDoc(globalPerm, accountPerm ptypes.Permissions) GenesisDoc { genAccounts := []GenesisAccount{} for _, u := range user[:5] { accPerm := accountPerm @@ -290,6 +291,7 @@ func TestCallPermission(t *testing.T) { Code: []byte{0x60}, Sequence: 0, StorageRoot: Zero256.Bytes(), + Permissions: ptypes.NilPermissions.Copy(), } st.UpdateAccount(simpleAcc) @@ -313,6 +315,7 @@ func TestCallPermission(t *testing.T) { Code: contractCode, Sequence: 0, StorageRoot: Zero256.Bytes(), + Permissions: ptypes.NilPermissions.Copy(), } blockCache.UpdateAccount(caller1Acc) @@ -356,6 +359,7 @@ func TestCallPermission(t *testing.T) { Code: contractCode2, Sequence: 0, StorageRoot: Zero256.Bytes(), + Permissions: ptypes.NilPermissions.Copy(), } caller1Acc.Permissions.Call = false caller2Acc.Permissions.Call = true @@ -418,135 +422,3 @@ func execTxWaitEvent(t *testing.T, blockCache *BlockCache, tx types.Tx, addr []b return "" } - -/* -- CallTx (Create) -x - 1 input, no perm, send perm, call perm - - CallTx to create new contract, perm - - contract runs create but doesn't have create perm - - contract runs create but has perm - - contract runs call with empty address (has call perm, not create perm) - - contract runs call with empty address (has call perm, and create perm) - - contract runs call (with perm), runs contract that runs create (without perm) - - contract runs call (with perm), runs contract that runs create (with perm) -*/ - -// TODO: this is just a copy of CALL... -func TestCreatePermission(t *testing.T) { - stateDB := dbm.GetDB("state") - genDoc := newBaseGenDoc(PermsAllFalse, PermsAllFalse) - genDoc.Accounts[0].Permissions.Call = true // give the 0 account permission - st := MakeGenesisState(stateDB, &genDoc) - blockCache := NewBlockCache(st) - - //------------------------------ - // create simple contract - fmt.Println("##### SIMPLE CONTRACT") - - // create simple contract - simpleContractAddr := NewContractAddress(user[0].Address, 100) - simpleAcc := &account.Account{ - Address: simpleContractAddr, - Balance: 0, - Code: []byte{0x60}, - Sequence: 0, - StorageRoot: Zero256.Bytes(), - } - st.UpdateAccount(simpleAcc) - - // A single input, having the permission, should succeed - tx, _ := NewCallTx(blockCache, user[0].PubKey, simpleContractAddr, nil, 100, 100, 100) - SignCallTx(tx, user[0]) - if err := ExecTx(blockCache, tx, true, nil); err != nil { - t.Fatal("Transaction failed", err) - } - - //---------------------------------------------------------- - // call to contract that calls simple contract - without perm - fmt.Println("##### CALL TO SIMPLE CONTRACT (FAIL)") - - // create contract that calls the simple contract - contractCode := callContractCode(simpleContractAddr) - caller1ContractAddr := NewContractAddress(user[0].Address, 101) - caller1Acc := &account.Account{ - Address: caller1ContractAddr, - Balance: 0, - Code: contractCode, - Sequence: 0, - StorageRoot: Zero256.Bytes(), - } - blockCache.UpdateAccount(caller1Acc) - - // A single input, having the permission, but the contract doesn't have permission - tx, _ = NewCallTx(blockCache, user[0].PubKey, caller1ContractAddr, nil, 100, 10000, 100) - SignCallTx(tx, user[0]) - - // we need to subscribe to the Receive event to detect the exception - exception := execTxWaitEvent(t, blockCache, tx, caller1ContractAddr) // - if exception == "" { - t.Fatal("Expected exception") - } - - //---------------------------------------------------------- - // call to contract that calls simple contract - with perm - fmt.Println("##### CALL TO SIMPLE CONTRACT (PASS)") - - // A single input, having the permission, and the contract has permission - caller1Acc.Permissions.Call = true - blockCache.UpdateAccount(caller1Acc) - tx, _ = NewCallTx(blockCache, user[0].PubKey, caller1ContractAddr, nil, 100, 10000, 100) - SignCallTx(tx, user[0]) - - // we need to subscribe to the Receive event to detect the exception - exception = execTxWaitEvent(t, blockCache, tx, caller1ContractAddr) // - if exception != "" { - t.Fatal("Unexpected exception:", exception) - } - - //---------------------------------------------------------- - // call to contract that calls contract that calls simple contract - without perm - // caller1Contract calls simpleContract. caller2Contract calls caller1Contract. - // caller1Contract does not have call perms, but caller2Contract does. - fmt.Println("##### CALL TO CONTRACT CALLING SIMPLE CONTRACT (FAIL)") - - contractCode2 := callContractCode(caller1ContractAddr) - caller2ContractAddr := NewContractAddress(user[0].Address, 102) - caller2Acc := &account.Account{ - Address: caller2ContractAddr, - Balance: 1000, - Code: contractCode2, - Sequence: 0, - StorageRoot: Zero256.Bytes(), - } - caller1Acc.Permissions.Call = false - caller2Acc.Permissions.Call = true - blockCache.UpdateAccount(caller1Acc) - blockCache.UpdateAccount(caller2Acc) - - tx, _ = NewCallTx(blockCache, user[0].PubKey, caller2ContractAddr, nil, 100, 10000, 100) - SignCallTx(tx, user[0]) - - // we need to subscribe to the Receive event to detect the exception - exception = execTxWaitEvent(t, blockCache, tx, caller1ContractAddr) // - if exception == "" { - t.Fatal("Expected exception") - } - - //---------------------------------------------------------- - // call to contract that calls contract that calls simple contract - without perm - // caller1Contract calls simpleContract. caller2Contract calls caller1Contract. - // both caller1 and caller2 have permission - fmt.Println("##### CALL TO CONTRACT CALLING SIMPLE CONTRACT (PASS)") - - caller1Acc.Permissions.Call = true - blockCache.UpdateAccount(caller1Acc) - - tx, _ = NewCallTx(blockCache, user[0].PubKey, caller2ContractAddr, nil, 100, 10000, 100) - SignCallTx(tx, user[0]) - - // we need to subscribe to the Receive event to detect the exception - exception = execTxWaitEvent(t, blockCache, tx, caller1ContractAddr) // - if exception != "" { - t.Fatal("Unexpected exception", exception) - } -} diff --git a/state/test.go b/state/test.go index b0e74ee2c..a741e95d1 100644 --- a/state/test.go +++ b/state/test.go @@ -24,12 +24,13 @@ func Tempfile(prefix string) (*os.File, string) { func RandAccount(randBalance bool, minBalance int64) (*account.Account, *account.PrivAccount) { privAccount := account.GenPrivAccount() + perms := account.DefaultPermissions acc := &account.Account{ Address: privAccount.PubKey.Address(), PubKey: privAccount.PubKey, Sequence: RandInt(), Balance: minBalance, - Permissions: account.DefaultPermissions, + Permissions: &perms, } if randBalance { acc.Balance += int64(RandUint32()) diff --git a/state/tx_cache.go b/state/tx_cache.go index c38b8a9d7..c1d8da075 100644 --- a/state/tx_cache.go +++ b/state/tx_cache.go @@ -3,6 +3,7 @@ package state import ( ac "github.com/tendermint/tendermint/account" . "github.com/tendermint/tendermint/common" + ptypes "github.com/tendermint/tendermint/permission/types" // for GlobalPermissionAddress ... "github.com/tendermint/tendermint/vm" "github.com/tendermint/tendermint/vm/sha3" ) @@ -79,7 +80,8 @@ func (cache *TxCache) CreateAccount(creator *vm.Account) *vm.Account { Code: nil, Nonce: 0, StorageRoot: Zero256, - Other: otherAccountInfo{nil, toStateAccount(cache.GetAccount(LeftPadWord256(ac.GlobalPermissionsAddress))).Permissions}, + Permissions: cache.GetAccount(ptypes.GlobalPermissionsAddress256).Permissions, + Other: nil, } cache.accounts[addr] = vmAccountInfo{account, false} return account @@ -155,12 +157,6 @@ func NewContractAddress(caller []byte, nonce int) []byte { return sha3.Sha3(temp)[:20] } -// struct for carrying data the vm need not know about -type otherAccountInfo struct { - PubKey ac.PubKey - Permissions ac.Permissions -} - // Converts backend.Account to vm.Account struct. func toVMAccount(acc *ac.Account) *vm.Account { return &vm.Account{ @@ -169,20 +165,18 @@ func toVMAccount(acc *ac.Account) *vm.Account { Code: acc.Code, // This is crazy. Nonce: int64(acc.Sequence), StorageRoot: LeftPadWord256(acc.StorageRoot), - Other: otherAccountInfo{acc.PubKey, acc.Permissions}, + Permissions: acc.Permissions.Copy(), + Other: acc.PubKey, } } // Converts vm.Account to backend.Account struct. func toStateAccount(acc *vm.Account) *ac.Account { - otherInfo, ok := acc.Other.(otherAccountInfo) + pubKey, ok := acc.Other.(ac.PubKey) if !ok { - panic("vm.Account.Other should be type state.otherAccountInfo") + pubKey = nil } - pubKey := otherInfo.PubKey - perms := otherInfo.Permissions - var storageRoot []byte if acc.StorageRoot.IsZero() { storageRoot = nil @@ -196,7 +190,7 @@ func toStateAccount(acc *vm.Account) *ac.Account { Code: acc.Code, Sequence: int(acc.Nonce), StorageRoot: storageRoot, - Permissions: perms, + Permissions: acc.Permissions, } } diff --git a/vm/test/vm_test.go b/vm/test/vm_test.go index 716643c4d..00ba6de8b 100644 --- a/vm/test/vm_test.go +++ b/vm/test/vm_test.go @@ -53,9 +53,7 @@ func TestVM(t *testing.T) { N := []byte{0x0f, 0x0f} // Loop N times code := []byte{0x60, 0x00, 0x60, 0x20, 0x52, 0x5B, byte(0x60 + len(N) - 1)} - for i := 0; i < len(N); i++ { - code = append(code, N[i]) - } + code = append(code, N...) code = append(code, []byte{0x60, 0x20, 0x51, 0x12, 0x15, 0x60, byte(0x1b + len(N)), 0x57, 0x60, 0x01, 0x60, 0x20, 0x51, 0x01, 0x60, 0x20, 0x52, 0x60, 0x05, 0x56, 0x5B}...) start := time.Now() output, err := ourVm.Call(account1, account2, code, []byte{}, 0, &gas) @@ -120,6 +118,7 @@ func TestSendCall(t *testing.T) { //---------------------------------------------- // account2 has insufficient balance, should fail + fmt.Println("Should fail with insufficient balance") exception := runVMWaitEvents(t, ourVm, account1, account2, addr, contractCode, 1000) if exception == "" { @@ -137,6 +136,7 @@ func TestSendCall(t *testing.T) { //---------------------------------------------- // insufficient gas, should fail + fmt.Println("Should fail with insufficient gas") account2.Balance = 100000 exception = runVMWaitEvents(t, ourVm, account1, account2, addr, contractCode, 100) diff --git a/vm/types.go b/vm/types.go index fe872c836..92cfd0e28 100644 --- a/vm/types.go +++ b/vm/types.go @@ -2,6 +2,7 @@ package vm import ( . "github.com/tendermint/tendermint/common" + ptypes "github.com/tendermint/tendermint/permission/types" ) const ( @@ -15,6 +16,8 @@ type Account struct { Nonce int64 StorageRoot Word256 Other interface{} // For holding all other data. + + Permissions *ptypes.Permissions } func (acc *Account) String() string { diff --git a/vm/vm.go b/vm/vm.go index 94579636b..4ae455de2 100644 --- a/vm/vm.go +++ b/vm/vm.go @@ -60,24 +60,18 @@ type VM struct { evc events.Fireable - doug bool // is this the gendoug contract - - sendPerm, callPerm, createPerm bool // this contract's permissions - - getPerms func(*Account) (bool, bool, bool) // gets permissions out of an account (wraps toStateFunction to get perms out of Other) + doug bool // is this the gendoug contract + perms bool // permission checking can be turned off } func NewVM(appState AppState, params Params, origin Word256, txid []byte) *VM { return &VM{ - appState: appState, - params: params, - origin: origin, - callDepth: 0, - txid: txid, - doug: false, - sendPerm: true, - callPerm: true, - createPerm: true, + appState: appState, + params: params, + origin: origin, + callDepth: 0, + txid: txid, + doug: false, } } @@ -91,23 +85,9 @@ func (vm *VM) EnableDoug() { vm.doug = true } -func (vm *VM) SetPermissionsGetter(getPerms func(acc *Account) (bool, bool, bool)) { - vm.getPerms = getPerms -} - -func (vm *VM) SetPermissions(acc *Account) { - if vm.getPerms != nil { - send, call, create := vm.getPerms(acc) - vm.setPermissions(send, call, create) - } -} - -// set the contract's generic permissions -func (vm *VM) setPermissions(send, call, create bool) { - // TODO: distinction between send and call not defined at the VM yet (it's all through a CALL!) - vm.sendPerm = send - vm.callPerm = call - vm.createPerm = create +// run permission checks before call and create +func (vm *VM) EnablePermissions() { + vm.perms = true } // CONTRACT appState is aware of caller and callee, so we can just mutate them. @@ -701,7 +681,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas dbg.Printf(" => %v\n", log) case CREATE: // 0xF0 - if !vm.createPerm { + if vm.perms && !callee.Permissions.Create { return nil, ErrPermission{"create"} } contractValue := stack.Pop64() @@ -729,7 +709,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas } case CALL, CALLCODE: // 0xF1, 0xF2 - if !vm.callPerm { + if vm.perms && !callee.Permissions.Call { return nil, ErrPermission{"call"} } gasLimit := stack.Pop64() @@ -786,11 +766,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas } vm.appState.UpdateAccount(acc) } - // copy permissions and set permissions for new contract - send, call, create := vm.sendPerm, vm.callPerm, vm.createPerm - vm.SetPermissions(acc) ret, err = vm.Call(callee, acc, acc.Code, args, value, gas) - vm.sendPerm, vm.callPerm, vm.createPerm = send, call, create } }