Browse Source

account permissions and some gendoug

pull/102/head
Ethan Buchman 10 years ago
committed by Jae Kwon
parent
commit
a0f3b94e83
9 changed files with 421 additions and 23 deletions
  1. +105
    -1
      account/account.go
  2. +1
    -0
      rpc/core/accounts.go
  3. +212
    -8
      state/execution.go
  4. +37
    -6
      state/genesis.go
  5. +1
    -1
      state/state.go
  6. +5
    -4
      state/test.go
  7. +15
    -3
      state/tx_cache.go
  8. +10
    -0
      vm/native.go
  9. +35
    -0
      vm/vm.go

+ 105
- 1
account/account.go View File

@ -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)
}

+ 1
- 0
rpc/core/accounts.go View File

@ -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


+ 212
- 8
state/execution.go View File

@ -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()
}

+ 37
- 6
state/genesis.go View File

@ -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))


+ 1
- 1
state/state.go View File

@ -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")
}


+ 5
- 4
state/test.go View File

@ -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())


+ 15
- 3
state/tx_cache.go View File

@ -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,
}
}


+ 10
- 0
vm/native.go View File

@ -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
}

+ 35
- 0
vm/vm.go View File

@ -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 {


Loading…
Cancel
Save