mirror of
https://github.com/tendermint/tendermint.git
synced 2026-09-25 01:14:28 +00:00
account permissions and some gendoug
This commit is contained in:
+212
-8
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user