diff --git a/account/account.go b/account/account.go index f62c3206..5f7fd0b5 100644 --- a/account/account.go +++ b/account/account.go @@ -6,6 +6,7 @@ import ( "io" "github.com/tendermint/tendermint/binary" + . "github.com/tendermint/tendermint/common" "github.com/tendermint/tendermint/merkle" ) @@ -43,6 +44,8 @@ type Account struct { Balance int64 `json:"balance"` Code []byte `json:"code"` // VM code StorageRoot []byte `json:"storage_root"` // VM storage merkle root. + + Permissions Permissions `json:"permissions"` } func (acc *Account) Copy() *Account { @@ -51,7 +54,8 @@ func (acc *Account) Copy() *Account { } func (acc *Account) String() string { - return fmt.Sprintf("Account{%X:%v C:%v S:%X}", acc.Address, acc.PubKey, len(acc.Code), acc.StorageRoot) + // return fmt.Sprintf("Account{%X:%v C:%v S:%X}", acc.Address, acc.PubKey, len(acc.Code), acc.StorageRoot) + return fmt.Sprintf("Account{%X:%v C:%v S:%X P:(%s)}", acc.Address, acc.PubKey, len(acc.Code), acc.StorageRoot, acc.Permissions) } func AccountEncoder(o interface{}, w io.Writer, n *int64, err *error) { @@ -66,3 +70,103 @@ var AccountCodec = binary.Codec{ Encode: AccountEncoder, Decode: AccountDecoder, } + +//----------------------------------------------------------------------------- + +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{ + 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 167f0ca5..155cd6b7 100644 --- a/rpc/core/accounts.go +++ b/rpc/core/accounts.go @@ -22,6 +22,7 @@ func GetAccount(address []byte) (*acm.Account, error) { Balance: 0, Code: nil, StorageRoot: nil, + Permissions: cache.GetAccount(acm.GlobalPermissionsAddress).Permissions, } } return account, nil diff --git a/state/execution.go b/state/execution.go index c46ab67b..a5f9f6de 100644 --- a/state/execution.go +++ b/state/execution.go @@ -139,7 +139,7 @@ func execBlock(s *State, block *types.Block, blockPartsHeader types.PartSetHeade // account.PubKey.(type) != nil, (it must be known), // or it must be specified in the TxInput. If redeclared, // the TxInput is modified and input.PubKey set to nil. -func getOrMakeAccounts(state AccountGetter, ins []*types.TxInput, outs []*types.TxOutput) (map[string]*account.Account, error) { +func getInputs(state AccountGetter, ins []*types.TxInput) (map[string]*account.Account, error) { accounts := map[string]*account.Account{} for _, in := range ins { // Account shouldn't be duplicated @@ -156,6 +156,14 @@ func getOrMakeAccounts(state AccountGetter, ins []*types.TxInput, outs []*types. } accounts[string(in.Address)] = acc } + return accounts, nil +} + +func getOrMakeOutputs(state AccountGetter, accounts map[string]*account.Account, outs []*types.TxOutput) (map[string]*account.Account, error) { + if accounts == nil { + accounts = make(map[string]*account.Account) + } + for _, out := range outs { // Account shouldn't be duplicated if _, ok := accounts[string(out.Address)]; ok { @@ -165,10 +173,11 @@ func getOrMakeAccounts(state AccountGetter, ins []*types.TxInput, outs []*types. // output account may be nil (new) if acc == nil { acc = &account.Account{ - Address: out.Address, - PubKey: nil, - Sequence: 0, - Balance: 0, + Address: out.Address, + PubKey: nil, + Sequence: 0, + Balance: 0, + Permissions: state.GetAccount(account.GlobalPermissionsAddress).Permissions, } } accounts[string(out.Address)] = acc @@ -291,10 +300,22 @@ func ExecTx(blockCache *BlockCache, tx_ types.Tx, runCall bool, evc events.Firea // Exec tx switch tx := tx_.(type) { case *types.SendTx: - accounts, err := getOrMakeAccounts(blockCache, tx.Inputs, tx.Outputs) + accounts, err := getInputs(blockCache, tx.Inputs) if err != nil { return err } + + // ensure all inputs have send permissions + if !hasSendPermission(accounts) { + return fmt.Errorf("At least one input lacks permission for SendTx") + } + + // add outputs to accounts map + accounts, err = getOrMakeOutputs(blockCache, accounts, tx.Outputs) + if err != nil { + return err + } + signBytes := account.SignBytes(_s.ChainID, tx) inTotal, err := validateInputs(accounts, signBytes, tx.Inputs) if err != nil { @@ -331,6 +352,7 @@ func ExecTx(blockCache *BlockCache, tx_ types.Tx, runCall bool, evc events.Firea case *types.CallTx: var inAcc, outAcc *account.Account + var isDoug bool // is this a call to the gendoug? // Validate input inAcc = blockCache.GetAccount(tx.Input.Address) @@ -338,6 +360,18 @@ func ExecTx(blockCache *BlockCache, tx_ types.Tx, runCall bool, evc events.Firea log.Debug(Fmt("Can't find in account %X", tx.Input.Address)) return types.ErrTxInvalidAddress } + + createAccount := len(tx.Address) == 0 + if createAccount { + if !hasCreatePermission(inAcc) { + return fmt.Errorf("Account %X does not have Create permission", tx.Input.Address) + } + } else { + if !hasCallPermission(inAcc) { + return fmt.Errorf("Account %X does not have Call permission", tx.Input.Address) + } + } + // pubKey should be present in either "inAcc" or "tx.Input" if err := checkInputPubKey(inAcc, tx.Input); err != nil { log.Debug(Fmt("Can't find pubkey for %X", tx.Input.Address)) @@ -354,7 +388,6 @@ func ExecTx(blockCache *BlockCache, tx_ types.Tx, runCall bool, evc events.Firea return types.ErrTxInsufficientFunds } - createAccount := len(tx.Address) == 0 if !createAccount { // Validate output if len(tx.Address) != 20 { @@ -427,6 +460,15 @@ func ExecTx(blockCache *BlockCache, tx_ types.Tx, runCall bool, evc events.Firea vmach.SetFireable(evc) // NOTE: Call() transfers the value from caller to callee iff call succeeds. + if isDoug { + // we need to bind a copy of the accounts tree (in the txCache) + // so the gendoug can make a native call to create accounts and update + // permissions + setupDoug(vmach, txCache, _s) + } + // set the contracts permissions + vmach.SetPermissions(inAcc.Permissions.Send, inAcc.Permissions.Call, inAcc.Permissions.Create) + ret, err := vmach.Call(caller, callee, code, tx.Data, value, &gas) exception := "" if err != nil { @@ -589,11 +631,16 @@ func ExecTx(blockCache *BlockCache, tx_ types.Tx, runCall bool, evc events.Firea // add funds, merge UnbondTo outputs, and unbond validator. return errors.New("Adding coins to existing validators not yet supported") } - accounts, err := getOrMakeAccounts(blockCache, tx.Inputs, nil) + + accounts, err := getInputs(blockCache, tx.Inputs) if err != nil { return err } + if !hasBondPermission(accounts) { + return fmt.Errorf("At least one input lacks permission to bond") + } + signBytes := account.SignBytes(_s.ChainID, tx) inTotal, err := validateInputs(accounts, signBytes, tx.Inputs) if err != nil { @@ -740,3 +787,160 @@ func ExecTx(blockCache *BlockCache, tx_ types.Tx, runCall bool, evc events.Firea panic("Unknown Tx type") } } + +//--------------------------------------------------------------- +// TODO: for debug log the failed accounts + +func hasSendPermission(accs map[string]*account.Account) bool { + for _, acc := range accs { + if !acc.Permissions.Send { + return false + } + } + return true +} + +func hasCallPermission(acc *account.Account) bool { + if !acc.Permissions.Call { + return false + } + return true +} + +func hasCreatePermission(acc *account.Account) bool { + if !acc.Permissions.Create { + return false + } + return true +} + +func hasBondPermission(accs map[string]*account.Account) bool { + for _, acc := range accs { + if !acc.Permissions.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!) +func setupDoug(vmach *vm.VM, txCache *TxCache, _s *State) { + + // get takes (address, permissionNum Word256), returns a permission int + getFunc := func(args []byte, gas *uint64) (output []byte, err error) { + if len(args) != 2*32 { + return nil, fmt.Errorf("Get() takes two arguments (address, permission number)") + } + var addr, permNum Word256 + copy(addr[:], args[:32]) + copy(permNum[:], args[32:64]) + vmAcc := txCache.GetAccount(addr) + if vmAcc == nil { + return nil, fmt.Errorf("Unknown account %X", addr) + } + stAcc := toStateAccount(vmAcc) + permN := uint(Uint64FromWord256(permNum)) + perm, err := stAcc.Permissions.Get(permN) + if err != nil { + return nil, err + } + var permInt byte + if perm { + permInt = 0x1 + } else { + permInt = 0x0 + } + return LeftPadWord256([]byte{permInt}).Bytes(), nil + + } + + // set takes (address, permissionNum, permissionValue Word256), returns the permission value + setFunc := func(args []byte, gas *uint64) (output []byte, err error) { + if len(args) != 3*32 { + return nil, fmt.Errorf("Set() takes three arguments (address, permission number, permission value)") + } + var addr, permNum, perm Word256 + copy(addr[:], args[:32]) + copy(permNum[:], args[32:64]) + copy(perm[:], args[64:96]) + vmAcc := txCache.GetAccount(addr) + if vmAcc == nil { + return nil, fmt.Errorf("Unknown account %X", addr) + } + stAcc := toStateAccount(vmAcc) + permN := uint(Uint64FromWord256(permNum)) + permV := !perm.IsZero() + if err = stAcc.Permissions.Set(permN, permV); err != nil { + return nil, err + } + vmAcc = toVMAccount(stAcc) + txCache.UpdateAccount(vmAcc) + return perm.Bytes(), nil + } + + // add creates a new permission at the next available index and returns the index + addFunc := func(args []byte, gas *uint64) (output []byte, err error) { + if len(args) != 0 { + return nil, fmt.Errorf("Add() takes no arguments") + } + + accounts := _s.GetAccounts() + size := accounts.Size() + var l int + for i := uint64(0); i < size; i++ { + _, v := accounts.GetByIndex(uint64(i)) + acc := v.(*account.Account) + + if i == 0 { + l = len(acc.Permissions.Other) + } else if l != len(acc.Permissions.Other) { + panic(Fmt("Accounts have different numbers of permissions: %v, %v", l, acc.Permissions.Other)) + } + if _, err := acc.Permissions.Add(false); err != nil { + return nil, err + } + txCache.UpdateAccount(toVMAccount(acc)) + } + return Uint64ToWord256(uint64(l)).Bytes(), nil + } + + // rm takes (permissionNum) and removes the corresponding permission, shortening the `Other` list. + // returns the permissionNum + rmFunc := func(args []byte, gas *uint64) (output []byte, err error) { + if len(args) != 32 { + return nil, fmt.Errorf("Get() takes one argument (permissionNum)") + } + var permNum Word256 + copy(permNum[:], args[:32]) + permN := uint(Uint64FromWord256(permNum)) // danger? + + accounts := _s.GetAccounts() + size := accounts.Size() + var l int + for i := uint64(0); i < size; i++ { + _, v := accounts.GetByIndex(uint64(i)) + acc := v.(*account.Account) + + if i == 0 { + l = len(acc.Permissions.Other) + } else if l != len(acc.Permissions.Other) { + panic(Fmt("Accounts have different numbers of permissions: %v, %v", l, acc.Permissions.Other)) + } + acc.Permissions.Remove(permN) + txCache.UpdateAccount(toVMAccount(acc)) + } + return args, nil + + } + + // Set the native contract addresses and functions + vmach.SetDougFunc(RightPadWord256([]byte("get")), vm.NativeContract(getFunc)) + vmach.SetDougFunc(RightPadWord256([]byte("set")), vm.NativeContract(setFunc)) + vmach.SetDougFunc(RightPadWord256([]byte("add")), vm.NativeContract(addFunc)) + vmach.SetDougFunc(RightPadWord256([]byte("rm")), vm.NativeContract(rmFunc)) + + // must be called or else functions not accessible + vmach.EnableDoug() +} diff --git a/state/genesis.go b/state/genesis.go index 91f99f5e..085076ca 100644 --- a/state/genesis.go +++ b/state/genesis.go @@ -14,8 +14,11 @@ import ( ) type GenesisAccount struct { - Address []byte `json:"address"` - Amount int64 `json:"amount"` + 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 } type GenesisValidator struct { @@ -24,9 +27,17 @@ type GenesisValidator struct { UnbondTo []GenesisAccount `json:"unbond_to"` } +type GenesisParams struct { + // Default permissions for newly created accounts + GlobalPermissions *account.Permissions `json:"global_permissions"` + + // TODO: other params we may want to tweak? +} + type GenesisDoc struct { GenesisTime time.Time `json:"genesis_time"` ChainID string `json:"chain_id"` + Params *GenesisParams `json:"params"` // pointer so optional Accounts []GenesisAccount `json:"accounts"` Validators []GenesisValidator `json:"validators"` } @@ -63,15 +74,35 @@ 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 + if genAcc.Permissions != nil { + perm = *(genAcc.Permissions) + } acc := &account.Account{ - Address: genAcc.Address, - PubKey: nil, - Sequence: 0, - Balance: genAcc.Amount, + Address: genAcc.Address, + PubKey: nil, + Sequence: 0, + Balance: genAcc.Amount, + Permissions: perm, } accounts.Set(acc.Address, acc) } + // global permissions are saved as the 0 address + // so they are included in the accounts tree + globalPerms := account.DefaultPermissions + if genDoc.Params != nil && genDoc.Params.GlobalPermissions != nil { + globalPerms = *(genDoc.Params.GlobalPermissions) + } + permsAcc := &account.Account{ + Address: account.GlobalPermissionsAddress, + PubKey: nil, + Sequence: 0, + Balance: 1337, + Permissions: globalPerms, + } + accounts.Set(permsAcc.Address, permsAcc) + // Make validatorInfos state tree && validators slice validatorInfos := merkle.NewIAVLTree(binary.BasicCodec, ValidatorInfoCodec, 0, db) validators := make([]*Validator, len(genDoc.Validators)) diff --git a/state/state.go b/state/state.go index 526ad8b3..081d8d7d 100644 --- a/state/state.go +++ b/state/state.go @@ -241,8 +241,8 @@ func (s *State) releaseValidator(val *Validator) { s.SetValidatorInfo(valInfo) // Send coins back to UnbondTo outputs - accounts, err := getOrMakeAccounts(s, nil, valInfo.UnbondTo) // SANITY CHECK + accounts, err := getOrMakeOutputs(s, nil, valInfo.UnbondTo) if err != nil { panic("Couldn't get or make unbondTo accounts") } diff --git a/state/test.go b/state/test.go index a235e762..b0e74ee2 100644 --- a/state/test.go +++ b/state/test.go @@ -25,10 +25,11 @@ func Tempfile(prefix string) (*os.File, string) { func RandAccount(randBalance bool, minBalance int64) (*account.Account, *account.PrivAccount) { privAccount := account.GenPrivAccount() acc := &account.Account{ - Address: privAccount.PubKey.Address(), - PubKey: privAccount.PubKey, - Sequence: RandInt(), - Balance: minBalance, + Address: privAccount.PubKey.Address(), + PubKey: privAccount.PubKey, + Sequence: RandInt(), + Balance: minBalance, + Permissions: account.DefaultPermissions, } if randBalance { acc.Balance += int64(RandUint32()) diff --git a/state/tx_cache.go b/state/tx_cache.go index 7f956711..c38b8a9d 100644 --- a/state/tx_cache.go +++ b/state/tx_cache.go @@ -79,6 +79,7 @@ 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}, } cache.accounts[addr] = vmAccountInfo{account, false} return account @@ -154,6 +155,12 @@ 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{ @@ -162,16 +169,20 @@ func toVMAccount(acc *ac.Account) *vm.Account { Code: acc.Code, // This is crazy. Nonce: int64(acc.Sequence), StorageRoot: LeftPadWord256(acc.StorageRoot), - Other: acc.PubKey, + Other: otherAccountInfo{acc.PubKey, acc.Permissions}, } } // Converts vm.Account to backend.Account struct. func toStateAccount(acc *vm.Account) *ac.Account { - pubKey, ok := acc.Other.(ac.PubKey) + otherInfo, ok := acc.Other.(otherAccountInfo) if !ok { - pubKey = nil + panic("vm.Account.Other should be type state.otherAccountInfo") } + + pubKey := otherInfo.PubKey + perms := otherInfo.Permissions + var storageRoot []byte if acc.StorageRoot.IsZero() { storageRoot = nil @@ -185,6 +196,7 @@ func toStateAccount(acc *vm.Account) *ac.Account { Code: acc.Code, Sequence: int(acc.Nonce), StorageRoot: storageRoot, + Permissions: perms, } } diff --git a/vm/native.go b/vm/native.go index 003f557f..9fc44419 100644 --- a/vm/native.go +++ b/vm/native.go @@ -89,3 +89,13 @@ func identityFunc(input []byte, gas *int64) (output []byte, err error) { // Return identity return input, nil } + +//----------------------------------------------------------------------------- +// Doug Contracts are stateful and must be set with closures wrapping the current tx cache +// Note they should be reset to refresh the closure or it will be stale + +var dougContracts = make(map[Word256]NativeContract) + +func (vm *VM) SetDougFunc(n Word256, f NativeContract) { + dougContracts[n] = f +} diff --git a/vm/vm.go b/vm/vm.go index ba5c3c3e..cafe56f8 100644 --- a/vm/vm.go +++ b/vm/vm.go @@ -27,6 +27,14 @@ var ( ErrInvalidContract = errors.New("Invalid contract") ) +type ErrPermission struct { + typ string +} + +func (err ErrPermission) Error() string { + return fmt.Sprintf("Contract does not have permission to %s", err.typ) +} + type Debug bool const ( @@ -51,6 +59,10 @@ type VM struct { callDepth int evc events.Fireable + + doug bool // is this the gendoug contract + + sendPerm, callPerm, createPerm bool // this contract's permissions } func NewVM(appState AppState, params Params, origin Word256, txid []byte) *VM { @@ -60,6 +72,7 @@ func NewVM(appState AppState, params Params, origin Word256, txid []byte) *VM { origin: origin, callDepth: 0, txid: txid, + doug: false, } } @@ -68,6 +81,19 @@ func (vm *VM) SetFireable(evc events.Fireable) { vm.evc = evc } +// to allow calls to native DougContracts (off by default) +func (vm *VM) EnableDoug() { + vm.doug = true +} + +// 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 +} + // 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. @@ -659,6 +685,9 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas dbg.Printf(" => %v\n", log) case CREATE: // 0xF0 + if !vm.createPerm { + return nil, ErrPermission{"create"} + } contractValue := stack.Pop64() offset, size := stack.Pop64(), stack.Pop64() input, ok := subslice(memory, offset, size) @@ -684,6 +713,9 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas } case CALL, CALLCODE: // 0xF1, 0xF2 + if !vm.callPerm { + return nil, ErrPermission{"call"} + } gasLimit := stack.Pop64() addr, value := stack.Pop(), stack.Pop64() inOffset, inSize := stack.Pop64(), stack.Pop64() // inputs @@ -710,6 +742,9 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas if nativeContract := nativeContracts[addr]; nativeContract != nil { // Native contract ret, err = nativeContract(args, &gasLimit) + } else if dougContract := dougContracts[addr]; vm.doug && dougContract != nil { + // This is Doug and we're calling a doug contract + ret, err = dougContract(args, &gasLimit) } else { // EVM contract if ok = useGas(gas, GasGetAccount); !ok {