Browse Source

Merge pull request #119 from tendermint/snatives

Snatives
pull/122/head
Jae Kwon 9 years ago
parent
commit
c13b67253c
13 changed files with 690 additions and 372 deletions
  1. +10
    -0
      common/byteslice.go
  2. +6
    -5
      common/word.go
  3. +56
    -18
      permission/types/permissions.go
  4. +96
    -17
      permission/types/snatives.go
  5. +117
    -24
      state/execution.go
  6. +202
    -198
      state/permissions_test.go
  7. +4
    -0
      types/events.go
  8. +29
    -0
      types/tx.go
  9. +43
    -0
      types/tx_test.go
  10. +36
    -0
      types/tx_utils.go
  11. +20
    -10
      vm/native.go
  12. +38
    -64
      vm/snative.go
  13. +33
    -36
      vm/vm.go

+ 10
- 0
common/byteslice.go View File

@ -1,5 +1,9 @@
package common
import (
"bytes"
)
func Fingerprint(slice []byte) []byte {
fingerprint := make([]byte, 6)
copy(fingerprint, slice)
@ -32,3 +36,9 @@ func LeftPadBytes(slice []byte, l int) []byte {
copy(padded[l-len(slice):], slice)
return padded
}
func TrimmedString(b []byte) string {
trimSet := string([]byte{0})
return string(bytes.TrimLeft(b, trimSet))
}

+ 6
- 5
common/word.go View File

@ -12,11 +12,12 @@ var (
type Word256 [32]byte
func (w Word256) String() string { return string(w[:]) }
func (w Word256) Copy() Word256 { return w }
func (w Word256) Bytes() []byte { return w[:] } // copied.
func (w Word256) Prefix(n int) []byte { return w[:n] }
func (w Word256) Postfix(n int) []byte { return w[32-n:] }
func (w Word256) String() string { return string(w[:]) }
func (w Word256) TrimmedString() string { return TrimmedString(w.Bytes()) }
func (w Word256) Copy() Word256 { return w }
func (w Word256) Bytes() []byte { return w[:] } // copied.
func (w Word256) Prefix(n int) []byte { return w[:n] }
func (w Word256) Postfix(n int) []byte { return w[32-n:] }
func (w Word256) IsZero() bool {
accum := byte(0)
for _, byt := range w {


+ 56
- 18
permission/types/permissions.go View File

@ -10,8 +10,6 @@ import (
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
@ -19,19 +17,29 @@ type PermFlag uint64
// Base permission references are like unix (the index is already bit shifted)
const (
Root PermFlag = 1 << iota // 1
Send // 2
Call // 4
CreateContract // 8
CreateAccount // 16
Bond // 32
Name // 64
NumBasePermissions uint = 7 // NOTE Adjust this too.
TopBasePermFlag PermFlag = 1 << (NumBasePermissions - 1)
AllBasePermFlags PermFlag = TopBasePermFlag | (TopBasePermFlag - 1)
AllPermFlags PermFlag = AllBasePermFlags | AllSNativePermFlags
DefaultBasePermFlags PermFlag = Send | Call | CreateContract | CreateAccount | Bond | Name
// chain permissions
Root PermFlag = 1 << iota // 1
Send // 2
Call // 4
CreateContract // 8
CreateAccount // 16
Bond // 32
Name // 64
// moderator permissions
HasBase
SetBase
UnsetBase
SetGlobal
HasRole
AddRole
RmRole
NumPermissions uint = 14 // NOTE Adjust this too. We can support upto 64
TopPermFlag PermFlag = 1 << (NumPermissions - 1)
AllPermFlags PermFlag = TopPermFlag | (TopPermFlag - 1)
DefaultPermFlags PermFlag = Send | Call | CreateContract | CreateAccount | Bond | Name | HasBase | HasRole
)
var (
@ -41,7 +49,7 @@ var (
}
DefaultAccountPermissions = AccountPermissions{
Base: BasePermissions{
Perms: DefaultBasePermFlags,
Perms: DefaultPermFlags,
SetBit: AllPermFlags,
},
Roles: []string{},
@ -154,8 +162,10 @@ func (aP *AccountPermissions) RmRole(role string) bool {
}
//--------------------------------------------------------------------------------
// string utilities
func PermFlagToString(pf PermFlag) (perm string, err error) {
// PermFlagToString assumes the permFlag is valid, else returns "#-UNKNOWN-#"
func PermFlagToString(pf PermFlag) (perm string) {
switch pf {
case Root:
perm = "root"
@ -171,8 +181,22 @@ func PermFlagToString(pf PermFlag) (perm string, err error) {
perm = "bond"
case Name:
perm = "name"
case HasBase:
perm = "has_base"
case SetBase:
perm = "set_base"
case UnsetBase:
perm = "unset_base"
case SetGlobal:
perm = "set_global"
case HasRole:
perm = "has_role"
case AddRole:
perm = "add_role"
case RmRole:
perm = "rm_role"
default:
err = fmt.Errorf("Unknown permission flag %b", pf)
perm = "#-UNKNOWN-#"
}
return
}
@ -193,6 +217,20 @@ func PermStringToFlag(perm string) (pf PermFlag, err error) {
pf = Bond
case "name":
pf = Name
case "has_base":
pf = HasBase
case "set_base":
pf = SetBase
case "unset_base":
pf = UnsetBase
case "set_global":
pf = SetGlobal
case "has_role":
pf = HasRole
case "add_role":
pf = AddRole
case "rm_role":
pf = RmRole
default:
err = fmt.Errorf("Unknown permission %s", perm)
}


+ 96
- 17
permission/types/snatives.go View File

@ -1,23 +1,102 @@
package types
const (
// first 32 bits of BasePermission are for chain, second 32 are for snative
FirstSNativePermFlag PermFlag = 1 << 32
import (
"github.com/tendermint/tendermint/binary"
)
// we need to reset iota with no const block
//---------------------------------------------------------------------------------------------------
// PermissionsTx.PermArgs interface and argument encoding
// Arguments are a registered interface in the PermissionsTx,
// so binary handles the arguments and each permission function gets a type-byte
// PermFlag() maps the type-byte to the permission
// The account sending the PermissionsTx must have this PermFlag set
type PermArgs interface {
PermFlag() PermFlag
}
const (
// each snative has an associated permission flag
HasBasePerm PermFlag = FirstSNativePermFlag << iota
SetBasePerm
UnsetBasePerm
SetGlobalPerm
ClearBasePerm
HasRole
AddRole
RmRole
NumSNativePermissions uint = 8 // NOTE adjust this too
TopSNativePermFlag PermFlag = FirstSNativePermFlag << (NumSNativePermissions - 1)
AllSNativePermFlags PermFlag = (TopSNativePermFlag | (TopSNativePermFlag - 1)) &^ (FirstSNativePermFlag - 1)
PermArgsTypeHasBase = byte(0x01)
PermArgsTypeSetBase = byte(0x02)
PermArgsTypeUnsetBase = byte(0x03)
PermArgsTypeSetGlobal = byte(0x04)
PermArgsTypeHasRole = byte(0x05)
PermArgsTypeAddRole = byte(0x06)
PermArgsTypeRmRole = byte(0x07)
)
// for binary.readReflect
var _ = binary.RegisterInterface(
struct{ PermArgs }{},
binary.ConcreteType{&HasBaseArgs{}, PermArgsTypeHasBase},
binary.ConcreteType{&SetBaseArgs{}, PermArgsTypeSetBase},
binary.ConcreteType{&UnsetBaseArgs{}, PermArgsTypeUnsetBase},
binary.ConcreteType{&SetGlobalArgs{}, PermArgsTypeSetGlobal},
binary.ConcreteType{&HasRoleArgs{}, PermArgsTypeHasRole},
binary.ConcreteType{&AddRoleArgs{}, PermArgsTypeAddRole},
binary.ConcreteType{&RmRoleArgs{}, PermArgsTypeRmRole},
)
type HasBaseArgs struct {
Address []byte `json:"address"`
Permission PermFlag `json:"permission"`
}
func (*HasBaseArgs) PermFlag() PermFlag {
return HasBase
}
type SetBaseArgs struct {
Address []byte `json:"address"`
Permission PermFlag `json:"permission"`
Value bool `json:"value"`
}
func (*SetBaseArgs) PermFlag() PermFlag {
return SetBase
}
type UnsetBaseArgs struct {
Address []byte `json:"address"`
Permission PermFlag `json:"permission"`
}
func (*UnsetBaseArgs) PermFlag() PermFlag {
return UnsetBase
}
type SetGlobalArgs struct {
Permission PermFlag `json:"permission"`
Value bool `json:"value"`
}
func (*SetGlobalArgs) PermFlag() PermFlag {
return SetGlobal
}
type HasRoleArgs struct {
Address []byte `json:"address"`
Role string `json:"role"`
}
func (*HasRoleArgs) PermFlag() PermFlag {
return HasRole
}
type AddRoleArgs struct {
Address []byte `json:"address"`
Role string `json:"role"`
}
func (*AddRoleArgs) PermFlag() PermFlag {
return AddRole
}
type RmRoleArgs struct {
Address []byte `json:"address"`
Role string `json:"role"`
}
func (*RmRoleArgs) PermFlag() PermFlag {
return RmRole
}

+ 117
- 24
state/execution.go View File

@ -392,7 +392,6 @@ func ExecTx(blockCache *BlockCache, tx types.Tx, runCall bool, evc events.Fireab
// this may be nil if we are still in mempool and contract was created in same block as this tx
// but that's fine, because the account will be created properly when the create tx runs in the block
// and then this won't return nil. otherwise, we take their fee
// it may also be nil if its an snative (not a "real" account)
outAcc = blockCache.GetAccount(tx.Address)
}
@ -423,27 +422,26 @@ func ExecTx(blockCache *BlockCache, tx types.Tx, runCall bool, evc events.Fireab
if !createAccount {
if outAcc == nil || len(outAcc.Code) == 0 {
// check if its an snative
if _, ok := vm.RegisteredSNativeContracts[LeftPadWord256(tx.Address)]; ok {
// set the outAcc (simply a placeholder until we reach the call)
outAcc = &acm.Account{Address: tx.Address}
// check if its a native contract
if vm.RegisteredNativeContract(LeftPadWord256(tx.Address)) {
return fmt.Errorf("NativeContracts can not be called using CallTx. Use a contract or the appropriate tx type (eg. PermissionsTx, NameTx)")
}
// if you call an account that doesn't exist
// or an account with no code then we take fees (sorry pal)
// NOTE: it's fine to create a contract and call it within one
// block (nonce will prevent re-ordering of those txs)
// but to create with one account and call with another
// you have to wait a block to avoid a re-ordering attack
// that will take your fees
inAcc.Balance -= tx.Fee
blockCache.UpdateAccount(inAcc)
if outAcc == nil {
log.Info(Fmt("Cannot find destination address %X. Deducting fee from caller", tx.Address))
} else {
// if you call an account that doesn't exist
// or an account with no code then we take fees (sorry pal)
// NOTE: it's fine to create a contract and call it within one
// block (nonce will prevent re-ordering of those txs)
// but to create with one account and call with another
// you have to wait a block to avoid a re-ordering attack
// that will take your fees
inAcc.Balance -= tx.Fee
blockCache.UpdateAccount(inAcc)
if outAcc == nil {
log.Info(Fmt("Cannot find destination address %X. Deducting fee from caller", tx.Address))
} else {
log.Info(Fmt("Attempting to call an account (%X) with no code. Deducting fee from caller", tx.Address))
}
return types.ErrTxInvalidAddress
log.Info(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
@ -697,6 +695,7 @@ func ExecTx(blockCache *BlockCache, tx types.Tx, runCall bool, evc events.Fireab
PanicCrisis("Failed to add validator")
}
if evc != nil {
// TODO: fire for all inputs
evc.FireEvent(types.EventStringBond(), tx)
}
return nil
@ -793,6 +792,98 @@ func ExecTx(blockCache *BlockCache, tx types.Tx, runCall bool, evc events.Fireab
}
return nil
case *types.PermissionsTx:
var inAcc *acm.Account
// Validate input
inAcc = blockCache.GetAccount(tx.Input.Address)
if inAcc == nil {
log.Debug(Fmt("Can't find in account %X", tx.Input.Address))
return types.ErrTxInvalidAddress
}
permFlag := tx.PermArgs.PermFlag()
// check permission
if !HasPermission(blockCache, inAcc, permFlag) {
return fmt.Errorf("Account %X does not have moderator permission %s (%b)", tx.Input.Address, ptypes.PermFlagToString(permFlag), permFlag)
}
// 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))
return err
}
signBytes := acm.SignBytes(_s.ChainID, tx)
err := validateInput(inAcc, signBytes, tx.Input)
if err != nil {
log.Debug(Fmt("validateInput failed on %X: %v", tx.Input.Address, err))
return err
}
value := tx.Input.Amount
log.Debug("New PermissionsTx", "function", ptypes.PermFlagToString(permFlag), "args", tx.PermArgs)
var permAcc *acm.Account
switch args := tx.PermArgs.(type) {
case *ptypes.HasBaseArgs:
// this one doesn't make sense from txs
return fmt.Errorf("HasBase is for contracts, not humans. Just look at the blockchain")
case *ptypes.SetBaseArgs:
if permAcc = blockCache.GetAccount(args.Address); permAcc == nil {
return fmt.Errorf("Trying to update permissions for unknown account %X", args.Address)
}
err = permAcc.Permissions.Base.Set(args.Permission, args.Value)
case *ptypes.UnsetBaseArgs:
if permAcc = blockCache.GetAccount(args.Address); permAcc == nil {
return fmt.Errorf("Trying to update permissions for unknown account %X", args.Address)
}
err = permAcc.Permissions.Base.Unset(args.Permission)
case *ptypes.SetGlobalArgs:
if permAcc = blockCache.GetAccount(ptypes.GlobalPermissionsAddress); permAcc == nil {
PanicSanity("can't find global permissions account")
}
err = permAcc.Permissions.Base.Set(args.Permission, args.Value)
case *ptypes.HasRoleArgs:
return fmt.Errorf("HasRole is for contracts, not humans. Just look at the blockchain")
case *ptypes.AddRoleArgs:
if permAcc = blockCache.GetAccount(args.Address); permAcc == nil {
return fmt.Errorf("Trying to update roles for unknown account %X", args.Address)
}
if !permAcc.Permissions.AddRole(args.Role) {
return fmt.Errorf("Role (%s) already exists for account %X", args.Role, args.Address)
}
case *ptypes.RmRoleArgs:
if permAcc = blockCache.GetAccount(args.Address); permAcc == nil {
return fmt.Errorf("Trying to update roles for unknown account %X", args.Address)
}
if !permAcc.Permissions.RmRole(args.Role) {
return fmt.Errorf("Role (%s) does not exist for account %X", args.Role, args.Address)
}
default:
PanicSanity(Fmt("invalid permission function: %s", ptypes.PermFlagToString(permFlag)))
}
// TODO: maybe we want to take funds on error and allow txs in that don't do anythingi?
if err != nil {
return err
}
// Good!
inAcc.Sequence += 1
inAcc.Balance -= value
blockCache.UpdateAccount(inAcc)
if permAcc != nil {
blockCache.UpdateAccount(permAcc)
}
if evc != nil {
evc.FireEvent(types.EventStringAccInput(tx.Input.Address), tx)
evc.FireEvent(types.EventStringPermissions(ptypes.PermFlagToString(permFlag)), tx)
}
return nil
default:
// binary decoding should not let this happen
PanicSanity("Unknown Tx type")
@ -804,7 +895,7 @@ func ExecTx(blockCache *BlockCache, tx types.Tx, runCall bool, evc events.Fireab
// Get permission on an account or fall back to global value
func HasPermission(state AccountGetter, acc *acm.Account, perm ptypes.PermFlag) bool {
if perm > ptypes.AllBasePermFlags {
if perm > ptypes.AllPermFlags {
PanicSanity("Checking an unknown permission in state should never happen")
}
@ -813,17 +904,19 @@ func HasPermission(state AccountGetter, acc *acm.Account, perm ptypes.PermFlag)
// this needs to fall back to global or do some other specific things
// eg. a bondAcc may be nil and so can only bond if global bonding is true
}
permString := ptypes.PermFlagToString(perm)
v, err := acc.Permissions.Base.Get(perm)
if _, ok := err.(ptypes.ErrValueNotSet); ok {
log.Info("Account does not have permission", "account", acc, "accPermissions", acc.Permissions, "perm", perm)
if state == nil {
PanicSanity("All known global permissions should be set!")
}
log.Info("Querying GlobalPermissionsAddress")
log.Info("Permission for account is not set. Querying GlobalPermissionsAddress", "perm", permString)
return HasPermission(nil, state.GetAccount(ptypes.GlobalPermissionsAddress), perm)
} else if v {
log.Info("Account has permission", "address", Fmt("%X", acc.Address), "perm", permString)
} else {
log.Info("Account has permission", "account", acc, "accPermissions", acc.Permissions, "perm", perm)
log.Info("Account does not have permission", "address", Fmt("%X", acc.Address), "perm", permString)
}
return v
}


+ 202
- 198
state/permissions_test.go View File

@ -13,7 +13,6 @@ import (
"github.com/tendermint/tendermint/events"
ptypes "github.com/tendermint/tendermint/permission/types"
"github.com/tendermint/tendermint/types"
"github.com/tendermint/tendermint/vm"
)
/*
@ -344,7 +343,7 @@ func TestCallPermission(t *testing.T) {
//------------------------------
// call to simple contract
fmt.Println("##### SIMPLE CONTRACT")
fmt.Println("\n##### SIMPLE CONTRACT")
// create simple contract
simpleContractAddr := NewContractAddress(user[0].Address, 100)
@ -367,14 +366,14 @@ func TestCallPermission(t *testing.T) {
//----------------------------------------------------------
// call to contract that calls simple contract - without perm
fmt.Println("##### CALL TO SIMPLE CONTRACT (FAIL)")
fmt.Println("\n##### CALL TO SIMPLE CONTRACT (FAIL)")
// create contract that calls the simple contract
contractCode := callContractCode(simpleContractAddr)
caller1ContractAddr := NewContractAddress(user[0].Address, 101)
caller1Acc := &acm.Account{
Address: caller1ContractAddr,
Balance: 0,
Balance: 10000,
Code: contractCode,
Sequence: 0,
StorageRoot: Zero256.Bytes(),
@ -394,7 +393,7 @@ func TestCallPermission(t *testing.T) {
//----------------------------------------------------------
// call to contract that calls simple contract - with perm
fmt.Println("##### CALL TO SIMPLE CONTRACT (PASS)")
fmt.Println("\n##### CALL TO SIMPLE CONTRACT (PASS)")
// A single input, having the permission, and the contract has permission
caller1Acc.Permissions.Base.Set(ptypes.Call, true)
@ -412,7 +411,7 @@ func TestCallPermission(t *testing.T) {
// 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)")
fmt.Println("\n##### CALL TO CONTRACT CALLING SIMPLE CONTRACT (FAIL)")
contractCode2 := callContractCode(caller1ContractAddr)
caller2ContractAddr := NewContractAddress(user[0].Address, 102)
@ -442,7 +441,7 @@ func TestCallPermission(t *testing.T) {
// 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)")
fmt.Println("\n##### CALL TO CONTRACT CALLING SIMPLE CONTRACT (PASS)")
caller1Acc.Permissions.Base.Set(ptypes.Call, true)
blockCache.UpdateAccount(caller1Acc)
@ -467,7 +466,7 @@ func TestCreatePermission(t *testing.T) {
//------------------------------
// create a simple contract
fmt.Println("##### CREATE SIMPLE CONTRACT")
fmt.Println("\n##### CREATE SIMPLE CONTRACT")
contractCode := []byte{0x60}
createCode := wrapContractForCreate(contractCode)
@ -490,7 +489,7 @@ func TestCreatePermission(t *testing.T) {
//------------------------------
// create contract that uses the CREATE op
fmt.Println("##### CREATE FACTORY")
fmt.Println("\n##### CREATE FACTORY")
contractCode = []byte{0x60}
createCode = wrapContractForCreate(contractCode)
@ -515,7 +514,7 @@ func TestCreatePermission(t *testing.T) {
//------------------------------
// call the contract (should FAIL)
fmt.Println("###### CALL THE FACTORY (FAIL)")
fmt.Println("\n###### CALL THE FACTORY (FAIL)")
// A single input, having the permission, should succeed
tx, _ = types.NewCallTx(blockCache, user[0].PubKey, contractAddr, createCode, 100, 100, 100)
@ -528,7 +527,7 @@ func TestCreatePermission(t *testing.T) {
//------------------------------
// call the contract (should PASS)
fmt.Println("###### CALL THE FACTORY (PASS)")
fmt.Println("\n###### CALL THE FACTORY (PASS)")
contractAcc.Permissions.Base.Set(ptypes.CreateContract, true)
blockCache.UpdateAccount(contractAcc)
@ -543,7 +542,7 @@ func TestCreatePermission(t *testing.T) {
}
//--------------------------------
fmt.Println("##### CALL to empty address")
fmt.Println("\n##### CALL to empty address")
zeroAddr := LeftPadBytes([]byte{}, 20)
code := callContractCode(zeroAddr)
@ -837,6 +836,9 @@ func TestCreateAccountPermission(t *testing.T) {
}
// holla at my boy
var DougAddress = append([]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, []byte("THISISDOUG")...)
func TestSNativeCALL(t *testing.T) {
stateDB := dbm.GetDB("state")
genDoc := newBaseGenDoc(PermsAllFalse, PermsAllFalse)
@ -852,7 +854,7 @@ func TestSNativeCALL(t *testing.T) {
// make the main contract once
doug := &acm.Account{
Address: ptypes.DougAddress,
Address: DougAddress,
Balance: 0,
Code: nil,
Sequence: 0,
@ -860,11 +862,12 @@ func TestSNativeCALL(t *testing.T) {
Permissions: ptypes.ZeroAccountPermissions,
}
doug.Permissions.Base.Set(ptypes.Call, true)
//doug.Permissions.Base.Set(ptypes.HasBase, true)
blockCache.UpdateAccount(doug)
fmt.Println("#### hasBasePerm")
// hasBasePerm
snativeAddress, data := snativePermTestInput("hasBasePerm", user[3], ptypes.Bond, false)
fmt.Println("\n#### HasBase")
// HasBase
snativeAddress, data := snativePermTestInputCALL("has_base", user[3], ptypes.Bond, false)
testSNativeCALLExpectFail(t, blockCache, doug, snativeAddress, data)
testSNativeCALLExpectPass(t, blockCache, doug, snativeAddress, data, func(ret []byte) error {
// return value should be true or false as a 32 byte array...
@ -874,12 +877,12 @@ func TestSNativeCALL(t *testing.T) {
return nil
})
fmt.Println("#### setBasePerm")
// setBasePerm
snativeAddress, data = snativePermTestInput("setBasePerm", user[3], ptypes.Bond, false)
fmt.Println("\n#### SetBase")
// SetBase
snativeAddress, data = snativePermTestInputCALL("set_base", user[3], ptypes.Bond, false)
testSNativeCALLExpectFail(t, blockCache, doug, snativeAddress, data)
testSNativeCALLExpectPass(t, blockCache, doug, snativeAddress, data, func(ret []byte) error { return nil })
snativeAddress, data = snativePermTestInput("hasBasePerm", user[3], ptypes.Bond, false)
snativeAddress, data = snativePermTestInputCALL("has_base", user[3], ptypes.Bond, false)
testSNativeCALLExpectPass(t, blockCache, doug, snativeAddress, data, func(ret []byte) error {
// return value should be true or false as a 32 byte array...
if !IsZeros(ret) {
@ -887,9 +890,9 @@ func TestSNativeCALL(t *testing.T) {
}
return nil
})
snativeAddress, data = snativePermTestInput("setBasePerm", user[3], ptypes.CreateContract, true)
snativeAddress, data = snativePermTestInputCALL("set_base", user[3], ptypes.CreateContract, true)
testSNativeCALLExpectPass(t, blockCache, doug, snativeAddress, data, func(ret []byte) error { return nil })
snativeAddress, data = snativePermTestInput("hasBasePerm", user[3], ptypes.CreateContract, false)
snativeAddress, data = snativePermTestInputCALL("has_base", user[3], ptypes.CreateContract, false)
testSNativeCALLExpectPass(t, blockCache, doug, snativeAddress, data, func(ret []byte) error {
// return value should be true or false as a 32 byte array...
if !IsZeros(ret[:31]) || ret[31] != byte(1) {
@ -898,12 +901,12 @@ func TestSNativeCALL(t *testing.T) {
return nil
})
fmt.Println("#### unsetBasePerm")
// unsetBasePerm
snativeAddress, data = snativePermTestInput("unsetBasePerm", user[3], ptypes.CreateContract, false)
fmt.Println("\n#### UnsetBase")
// UnsetBase
snativeAddress, data = snativePermTestInputCALL("unset_base", user[3], ptypes.CreateContract, false)
testSNativeCALLExpectFail(t, blockCache, doug, snativeAddress, data)
testSNativeCALLExpectPass(t, blockCache, doug, snativeAddress, data, func(ret []byte) error { return nil })
snativeAddress, data = snativePermTestInput("hasBasePerm", user[3], ptypes.CreateContract, false)
snativeAddress, data = snativePermTestInputCALL("has_base", user[3], ptypes.CreateContract, false)
testSNativeCALLExpectPass(t, blockCache, doug, snativeAddress, data, func(ret []byte) error {
if !IsZeros(ret) {
return fmt.Errorf("Expected 0. Got %X", ret)
@ -911,12 +914,12 @@ func TestSNativeCALL(t *testing.T) {
return nil
})
fmt.Println("#### setGlobalPerm")
// setGlobalPerm
snativeAddress, data = snativePermTestInput("setGlobalPerm", user[3], ptypes.CreateContract, true)
fmt.Println("\n#### SetGlobal")
// SetGlobalPerm
snativeAddress, data = snativePermTestInputCALL("set_global", user[3], ptypes.CreateContract, true)
testSNativeCALLExpectFail(t, blockCache, doug, snativeAddress, data)
testSNativeCALLExpectPass(t, blockCache, doug, snativeAddress, data, func(ret []byte) error { return nil })
snativeAddress, data = snativePermTestInput("hasBasePerm", user[3], ptypes.CreateContract, false)
snativeAddress, data = snativePermTestInputCALL("has_base", user[3], ptypes.CreateContract, false)
testSNativeCALLExpectPass(t, blockCache, doug, snativeAddress, data, func(ret []byte) error {
// return value should be true or false as a 32 byte array...
if !IsZeros(ret[:31]) || ret[31] != byte(1) {
@ -925,12 +928,9 @@ func TestSNativeCALL(t *testing.T) {
return nil
})
// clearBasePerm
// TODO
fmt.Println("#### hasRole")
// hasRole
snativeAddress, data = snativeRoleTestInput("hasRole", user[3], "bumble")
fmt.Println("\n#### HasRole")
// HasRole
snativeAddress, data = snativeRoleTestInputCALL("has_role", user[3], "bumble")
testSNativeCALLExpectFail(t, blockCache, doug, snativeAddress, data)
testSNativeCALLExpectPass(t, blockCache, doug, snativeAddress, data, func(ret []byte) error {
if !IsZeros(ret[:31]) || ret[31] != byte(1) {
@ -939,19 +939,19 @@ func TestSNativeCALL(t *testing.T) {
return nil
})
fmt.Println("#### addRole")
// addRole
snativeAddress, data = snativeRoleTestInput("hasRole", user[3], "chuck")
fmt.Println("\n#### AddRole")
// AddRole
snativeAddress, data = snativeRoleTestInputCALL("has_role", user[3], "chuck")
testSNativeCALLExpectPass(t, blockCache, doug, snativeAddress, data, func(ret []byte) error {
if !IsZeros(ret) {
return fmt.Errorf("Expected 0. Got %X", ret)
}
return nil
})
snativeAddress, data = snativeRoleTestInput("addRole", user[3], "chuck")
snativeAddress, data = snativeRoleTestInputCALL("add_role", user[3], "chuck")
testSNativeCALLExpectFail(t, blockCache, doug, snativeAddress, data)
testSNativeCALLExpectPass(t, blockCache, doug, snativeAddress, data, func(ret []byte) error { return nil })
snativeAddress, data = snativeRoleTestInput("hasRole", user[3], "chuck")
snativeAddress, data = snativeRoleTestInputCALL("has_role", user[3], "chuck")
testSNativeCALLExpectPass(t, blockCache, doug, snativeAddress, data, func(ret []byte) error {
if !IsZeros(ret[:31]) || ret[31] != byte(1) {
return fmt.Errorf("Expected 1. Got %X", ret)
@ -959,12 +959,12 @@ func TestSNativeCALL(t *testing.T) {
return nil
})
fmt.Println("#### rmRole")
// rmRole
snativeAddress, data = snativeRoleTestInput("rmRole", user[3], "chuck")
fmt.Println("\n#### RmRole")
// RmRole
snativeAddress, data = snativeRoleTestInputCALL("rm_role", user[3], "chuck")
testSNativeCALLExpectFail(t, blockCache, doug, snativeAddress, data)
testSNativeCALLExpectPass(t, blockCache, doug, snativeAddress, data, func(ret []byte) error { return nil })
snativeAddress, data = snativeRoleTestInput("hasRole", user[3], "chuck")
snativeAddress, data = snativeRoleTestInputCALL("has_role", user[3], "chuck")
testSNativeCALLExpectPass(t, blockCache, doug, snativeAddress, data, func(ret []byte) error {
if !IsZeros(ret) {
return fmt.Errorf("Expected 0. Got %X", ret)
@ -973,7 +973,7 @@ func TestSNativeCALL(t *testing.T) {
})
}
func TestSNativeCallTx(t *testing.T) {
func TestSNativeTx(t *testing.T) {
stateDB := dbm.GetDB("state")
genDoc := newBaseGenDoc(PermsAllFalse, PermsAllFalse)
genDoc.Accounts[0].Permissions.Base.Set(ptypes.Call, true) // give the 0 account permission
@ -984,123 +984,70 @@ func TestSNativeCallTx(t *testing.T) {
blockCache := NewBlockCache(st)
//----------------------------------------------------------
// Test CallTx to SNative contracts
var doug *acm.Account = nil
fmt.Println("#### hasBasePerm")
// hasBasePerm
snativeAddress, data := snativePermTestInput("hasBasePerm", user[3], ptypes.Bond, false)
testSNativeCALLExpectFail(t, blockCache, doug, snativeAddress, data)
testSNativeCALLExpectPass(t, blockCache, doug, snativeAddress, data, func(ret []byte) error {
// return value should be true or false as a 32 byte array...
if !IsZeros(ret[:31]) || ret[31] != byte(1) {
return fmt.Errorf("Expected 1. Got %X", ret)
}
return nil
})
fmt.Println("#### setBasePerm")
// setBasePerm
snativeAddress, data = snativePermTestInput("setBasePerm", user[3], ptypes.Bond, false)
testSNativeCALLExpectFail(t, blockCache, doug, snativeAddress, data)
testSNativeCALLExpectPass(t, blockCache, doug, snativeAddress, data, func(ret []byte) error { return nil })
snativeAddress, data = snativePermTestInput("hasBasePerm", user[3], ptypes.Bond, false)
testSNativeCALLExpectPass(t, blockCache, doug, snativeAddress, data, func(ret []byte) error {
// return value should be true or false as a 32 byte array...
if !IsZeros(ret) {
return fmt.Errorf("Expected 0. Got %X", ret)
}
return nil
})
snativeAddress, data = snativePermTestInput("setBasePerm", user[3], ptypes.CreateContract, true)
testSNativeCALLExpectPass(t, blockCache, doug, snativeAddress, data, func(ret []byte) error { return nil })
snativeAddress, data = snativePermTestInput("hasBasePerm", user[3], ptypes.CreateContract, false)
testSNativeCALLExpectPass(t, blockCache, doug, snativeAddress, data, func(ret []byte) error {
// return value should be true or false as a 32 byte array...
if !IsZeros(ret[:31]) || ret[31] != byte(1) {
return fmt.Errorf("Expected 1. Got %X", ret)
}
return nil
})
fmt.Println("#### unsetBasePerm")
// unsetBasePerm
snativeAddress, data = snativePermTestInput("unsetBasePerm", user[3], ptypes.CreateContract, false)
testSNativeCALLExpectFail(t, blockCache, doug, snativeAddress, data)
testSNativeCALLExpectPass(t, blockCache, doug, snativeAddress, data, func(ret []byte) error { return nil })
snativeAddress, data = snativePermTestInput("hasBasePerm", user[3], ptypes.CreateContract, false)
testSNativeCALLExpectPass(t, blockCache, doug, snativeAddress, data, func(ret []byte) error {
if !IsZeros(ret) {
return fmt.Errorf("Expected 0. Got %X", ret)
}
return nil
})
fmt.Println("#### setGlobalPerm")
// setGlobalPerm
snativeAddress, data = snativePermTestInput("setGlobalPerm", user[3], ptypes.CreateContract, true)
testSNativeCALLExpectFail(t, blockCache, doug, snativeAddress, data)
testSNativeCALLExpectPass(t, blockCache, doug, snativeAddress, data, func(ret []byte) error { return nil })
snativeAddress, data = snativePermTestInput("hasBasePerm", user[3], ptypes.CreateContract, false)
testSNativeCALLExpectPass(t, blockCache, doug, snativeAddress, data, func(ret []byte) error {
// return value should be true or false as a 32 byte array...
if !IsZeros(ret[:31]) || ret[31] != byte(1) {
return fmt.Errorf("Expected 1. Got %X", ret)
}
return nil
})
// clearBasePerm
// TODO
fmt.Println("#### hasRole")
// hasRole
snativeAddress, data = snativeRoleTestInput("hasRole", user[3], "bumble")
testSNativeCALLExpectFail(t, blockCache, doug, snativeAddress, data)
testSNativeCALLExpectPass(t, blockCache, doug, snativeAddress, data, func(ret []byte) error {
if !IsZeros(ret[:31]) || ret[31] != byte(1) {
return fmt.Errorf("Expected 1. Got %X", ret)
}
return nil
})
fmt.Println("#### addRole")
// addRole
snativeAddress, data = snativeRoleTestInput("hasRole", user[3], "chuck")
testSNativeCALLExpectPass(t, blockCache, doug, snativeAddress, data, func(ret []byte) error {
if !IsZeros(ret) {
return fmt.Errorf("Expected 0. Got %X", ret)
}
return nil
})
snativeAddress, data = snativeRoleTestInput("addRole", user[3], "chuck")
testSNativeCALLExpectFail(t, blockCache, doug, snativeAddress, data)
testSNativeCALLExpectPass(t, blockCache, doug, snativeAddress, data, func(ret []byte) error { return nil })
snativeAddress, data = snativeRoleTestInput("hasRole", user[3], "chuck")
testSNativeCALLExpectPass(t, blockCache, doug, snativeAddress, data, func(ret []byte) error {
if !IsZeros(ret[:31]) || ret[31] != byte(1) {
return fmt.Errorf("Expected 1. Got %X", ret)
}
return nil
})
// Test SNativeTx
fmt.Println("#### rmRole")
// rmRole
snativeAddress, data = snativeRoleTestInput("rmRole", user[3], "chuck")
testSNativeCALLExpectFail(t, blockCache, doug, snativeAddress, data)
testSNativeCALLExpectPass(t, blockCache, doug, snativeAddress, data, func(ret []byte) error { return nil })
snativeAddress, data = snativeRoleTestInput("hasRole", user[3], "chuck")
testSNativeCALLExpectPass(t, blockCache, doug, snativeAddress, data, func(ret []byte) error {
if !IsZeros(ret) {
return fmt.Errorf("Expected 0. Got %X", ret)
}
return nil
})
fmt.Println("\n#### SetBase")
// SetBase
snativeArgs := snativePermTestInputTx("set_base", user[3], ptypes.Bond, false)
testSNativeTxExpectFail(t, blockCache, snativeArgs)
testSNativeTxExpectPass(t, blockCache, ptypes.SetBase, snativeArgs)
acc := blockCache.GetAccount(user[3].Address)
if v, _ := acc.Permissions.Base.Get(ptypes.Bond); v {
t.Fatal("expected permission to be set false")
}
snativeArgs = snativePermTestInputTx("set_base", user[3], ptypes.CreateContract, true)
testSNativeTxExpectPass(t, blockCache, ptypes.SetBase, snativeArgs)
acc = blockCache.GetAccount(user[3].Address)
if v, _ := acc.Permissions.Base.Get(ptypes.CreateContract); !v {
t.Fatal("expected permission to be set true")
}
fmt.Println("\n#### UnsetBase")
// UnsetBase
snativeArgs = snativePermTestInputTx("unset_base", user[3], ptypes.CreateContract, false)
testSNativeTxExpectFail(t, blockCache, snativeArgs)
testSNativeTxExpectPass(t, blockCache, ptypes.UnsetBase, snativeArgs)
acc = blockCache.GetAccount(user[3].Address)
if v, _ := acc.Permissions.Base.Get(ptypes.CreateContract); v {
t.Fatal("expected permission to be set false")
}
fmt.Println("\n#### SetGlobal")
// SetGlobalPerm
snativeArgs = snativePermTestInputTx("set_global", user[3], ptypes.CreateContract, true)
testSNativeTxExpectFail(t, blockCache, snativeArgs)
testSNativeTxExpectPass(t, blockCache, ptypes.SetGlobal, snativeArgs)
acc = blockCache.GetAccount(ptypes.GlobalPermissionsAddress)
if v, _ := acc.Permissions.Base.Get(ptypes.CreateContract); !v {
t.Fatal("expected permission to be set true")
}
fmt.Println("\n#### AddRole")
// AddRole
snativeArgs = snativeRoleTestInputTx("add_role", user[3], "chuck")
testSNativeTxExpectFail(t, blockCache, snativeArgs)
testSNativeTxExpectPass(t, blockCache, ptypes.AddRole, snativeArgs)
acc = blockCache.GetAccount(user[3].Address)
if v := acc.Permissions.HasRole("chuck"); !v {
t.Fatal("expected role to be added")
}
fmt.Println("\n#### RmRole")
// RmRole
snativeArgs = snativeRoleTestInputTx("rm_role", user[3], "chuck")
testSNativeTxExpectFail(t, blockCache, snativeArgs)
testSNativeTxExpectPass(t, blockCache, ptypes.RmRole, snativeArgs)
acc = blockCache.GetAccount(user[3].Address)
if v := acc.Permissions.HasRole("chuck"); v {
t.Fatal("expected role to be removed")
}
}
//-------------------------------------------------------------------------------------
// helpers
var ExceptionTimeOut = "timed out waiting for event"
// run ExecTx and wait for the Receive event on given addr
// returns the msg data and an error/exception
func execTxWaitEvent(t *testing.T, blockCache *BlockCache, tx types.Tx, eventid string) (interface{}, string) {
@ -1117,7 +1064,14 @@ func execTxWaitEvent(t *testing.T, blockCache *BlockCache, tx types.Tx, eventid
}
evc.Flush()
}()
msg := <-ch
ticker := time.NewTicker(5 * time.Second)
var msg interface{}
select {
case msg = <-ch:
case <-ticker.C:
return nil, ExceptionTimeOut
}
switch ev := msg.(type) {
case types.EventMsgCallTx:
return ev, ev.Exception
@ -1130,52 +1084,77 @@ func execTxWaitEvent(t *testing.T, blockCache *BlockCache, tx types.Tx, eventid
}
}
// give a contract perms for an snative, call it, it calls the snative, but shouldn't have permission
func testSNativeCALLExpectFail(t *testing.T, blockCache *BlockCache, doug *acm.Account, snativeAddress, data []byte) {
testSNativeCALL(t, false, blockCache, doug, snativeAddress, data, nil)
}
// give a contract perms for an snative, call it, it calls the snative, ensure the check funciton (f) succeeds
func testSNativeCALLExpectPass(t *testing.T, blockCache *BlockCache, doug *acm.Account, snativeAddress, data []byte, f func([]byte) error) {
perm := vm.RegisteredSNativePermissions[LeftPadWord256(snativeAddress)]
var addr []byte
if doug != nil {
contractCode := callContractCode(snativeAddress)
doug.Code = contractCode
testSNativeCALL(t, true, blockCache, doug, snativeAddress, data, f)
}
func testSNativeCALL(t *testing.T, expectPass bool, blockCache *BlockCache, doug *acm.Account, snativeAddress, data []byte, f func([]byte) error) {
if expectPass {
perm, err := ptypes.PermStringToFlag(TrimmedString(snativeAddress))
if err != nil {
t.Fatal(err)
}
doug.Permissions.Base.Set(perm, true)
blockCache.UpdateAccount(doug)
addr = doug.Address
} else {
acc := blockCache.GetAccount(user[0].Address)
acc.Permissions.Base.Set(perm, true)
blockCache.UpdateAccount(acc)
addr = snativeAddress
}
var addr []byte
contractCode := callContractCode(snativeAddress)
doug.Code = contractCode
blockCache.UpdateAccount(doug)
addr = doug.Address
tx, _ := types.NewCallTx(blockCache, user[0].PubKey, addr, data, 100, 10000, 100)
tx.Sign(chainID, user[0])
ev, exception := execTxWaitEvent(t, blockCache, tx, types.EventStringAccReceive(snativeAddress)) //
if exception != "" {
t.Fatal("Unexpected exception", exception)
fmt.Println("subscribing to", types.EventStringAccReceive(snativeAddress))
ev, exception := execTxWaitEvent(t, blockCache, tx, types.EventStringAccReceive(snativeAddress))
if exception == ExceptionTimeOut {
t.Fatal("Timed out waiting for event")
}
evv := ev.(types.EventMsgCall)
ret := evv.Return
if err := f(ret); err != nil {
t.Fatal(err)
if expectPass {
if exception != "" {
t.Fatal("Unexpected exception", exception)
}
evv := ev.(types.EventMsgCall)
ret := evv.Return
if err := f(ret); err != nil {
t.Fatal(err)
}
} else {
if exception == "" {
t.Fatal("Expected exception")
}
}
}
// assumes the contract has not been given the permission. calls the it, it calls the snative, expects to fail
func testSNativeCALLExpectFail(t *testing.T, blockCache *BlockCache, doug *acm.Account, snativeAddress, data []byte) {
var addr []byte
if doug != nil {
contractCode := callContractCode(snativeAddress)
doug.Code = contractCode
blockCache.UpdateAccount(doug)
addr = doug.Address
} else {
addr = snativeAddress
func testSNativeTxExpectFail(t *testing.T, blockCache *BlockCache, snativeArgs ptypes.PermArgs) {
testSNativeTx(t, false, blockCache, 0, snativeArgs)
}
func testSNativeTxExpectPass(t *testing.T, blockCache *BlockCache, perm ptypes.PermFlag, snativeArgs ptypes.PermArgs) {
testSNativeTx(t, true, blockCache, perm, snativeArgs)
}
func testSNativeTx(t *testing.T, expectPass bool, blockCache *BlockCache, perm ptypes.PermFlag, snativeArgs ptypes.PermArgs) {
if expectPass {
acc := blockCache.GetAccount(user[0].Address)
acc.Permissions.Base.Set(perm, true)
blockCache.UpdateAccount(acc)
}
tx, _ := types.NewCallTx(blockCache, user[0].PubKey, addr, data, 100, 10000, 100)
tx, _ := types.NewPermissionsTx(blockCache, user[0].PubKey, snativeArgs)
tx.Sign(chainID, user[0])
fmt.Println("subscribing to", types.EventStringAccReceive(snativeAddress))
_, exception := execTxWaitEvent(t, blockCache, tx, types.EventStringAccReceive(snativeAddress))
if exception == "" {
t.Fatal("Expected exception")
err := ExecTx(blockCache, tx, true, nil)
if expectPass {
if err != nil {
t.Fatal("Unexpected exception", err)
}
} else {
if err == nil {
t.Fatal("Expected exception")
}
}
}
@ -1189,31 +1168,56 @@ func boolToWord256(v bool) Word256 {
return LeftPadWord256([]byte{vint})
}
func snativePermTestInput(name string, user *acm.PrivAccount, perm ptypes.PermFlag, val bool) (addr []byte, data []byte) {
func snativePermTestInputCALL(name string, user *acm.PrivAccount, perm ptypes.PermFlag, val bool) (addr []byte, data []byte) {
addr = LeftPadWord256([]byte(name)).Postfix(20)
switch name {
case "hasBasePerm", "unsetBasePerm":
case "has_base", "unset_base":
data = LeftPadBytes(user.Address, 32)
data = append(data, Uint64ToWord256(uint64(perm)).Bytes()...)
case "setBasePerm":
case "set_base":
data = LeftPadBytes(user.Address, 32)
data = append(data, Uint64ToWord256(uint64(perm)).Bytes()...)
data = append(data, boolToWord256(val).Bytes()...)
case "setGlobalPerm":
case "set_global":
data = Uint64ToWord256(uint64(perm)).Bytes()
data = append(data, boolToWord256(val).Bytes()...)
case "clearBasePerm":
}
return
}
func snativeRoleTestInput(name string, user *acm.PrivAccount, role string) (addr []byte, data []byte) {
func snativePermTestInputTx(name string, user *acm.PrivAccount, perm ptypes.PermFlag, val bool) (snativeArgs ptypes.PermArgs) {
switch name {
case "has_base":
snativeArgs = &ptypes.HasBaseArgs{user.Address, perm}
case "unset_base":
snativeArgs = &ptypes.UnsetBaseArgs{user.Address, perm}
case "set_base":
snativeArgs = &ptypes.SetBaseArgs{user.Address, perm, val}
case "set_global":
snativeArgs = &ptypes.SetGlobalArgs{perm, val}
}
return
}
func snativeRoleTestInputCALL(name string, user *acm.PrivAccount, role string) (addr []byte, data []byte) {
addr = LeftPadWord256([]byte(name)).Postfix(20)
data = LeftPadBytes(user.Address, 32)
data = append(data, LeftPadBytes([]byte(role), 32)...)
return
}
func snativeRoleTestInputTx(name string, user *acm.PrivAccount, role string) (snativeArgs ptypes.PermArgs) {
switch name {
case "has_role":
snativeArgs = &ptypes.HasRoleArgs{user.Address, role}
case "add_role":
snativeArgs = &ptypes.AddRoleArgs{user.Address, role}
case "rm_role":
snativeArgs = &ptypes.RmRoleArgs{user.Address, role}
}
return
}
// convenience function for contract that calls a given address
func callContractCode(contractAddr []byte) []byte {
// calldatacopy into mem and use as input to call


+ 4
- 0
types/events.go View File

@ -22,6 +22,10 @@ func EventStringLogEvent(addr []byte) string {
return fmt.Sprintf("Log/%X", addr)
}
func EventStringPermissions(name string) string {
return fmt.Sprintf("Permissions/%s", name)
}
func EventStringBond() string {
return "Bond"
}


+ 29
- 0
types/tx.go View File

@ -8,6 +8,7 @@ import (
acm "github.com/tendermint/tendermint/account"
"github.com/tendermint/tendermint/binary"
. "github.com/tendermint/tendermint/common"
ptypes "github.com/tendermint/tendermint/permission/types"
)
var (
@ -44,7 +45,11 @@ Validation Txs:
- BondTx New validator posts a bond
- UnbondTx Validator leaves
- DupeoutTx Validator dupes out (equivocates)
Admin Txs:
- PermissionsTx
*/
type Tx interface {
WriteSignBytes(chainID string, w io.Writer, n *int64, err *error)
}
@ -61,6 +66,9 @@ const (
TxTypeUnbond = byte(0x12)
TxTypeRebond = byte(0x13)
TxTypeDupeout = byte(0x14)
// Admin transactions
TxTypePermissions = byte(0x20)
)
// for binary.readReflect
@ -73,6 +81,7 @@ var _ = binary.RegisterInterface(
binary.ConcreteType{&UnbondTx{}, TxTypeUnbond},
binary.ConcreteType{&RebondTx{}, TxTypeRebond},
binary.ConcreteType{&DupeoutTx{}, TxTypeDupeout},
binary.ConcreteType{&PermissionsTx{}, TxTypePermissions},
)
//-----------------------------------------------------------------------------
@ -314,6 +323,26 @@ func (tx *DupeoutTx) String() string {
//-----------------------------------------------------------------------------
type PermissionsTx struct {
Input *TxInput `json:"input"`
PermArgs ptypes.PermArgs `json:"args"`
}
func (tx *PermissionsTx) WriteSignBytes(chainID string, w io.Writer, n *int64, err *error) {
binary.WriteTo([]byte(Fmt(`{"chain_id":%s`, jsonEscape(chainID))), w, n, err)
binary.WriteTo([]byte(Fmt(`,"tx":[%v,{"args":"`, TxTypePermissions)), w, n, err)
binary.WriteJSON(tx.PermArgs, w, n, err)
binary.WriteTo([]byte(`","input":`), w, n, err)
tx.Input.WriteSignBytes(w, n, err)
binary.WriteTo([]byte(`}]}`), w, n, err)
}
func (tx *PermissionsTx) String() string {
return Fmt("PermissionsTx{%v -> %v}", tx.Input, tx.PermArgs)
}
//-----------------------------------------------------------------------------
// This should match the leaf hashes of Block.Data.Hash()'s SimpleMerkleTree.
func TxID(chainID string, tx Tx) []byte {
signBytes := acm.SignBytes(chainID, tx)


+ 43
- 0
types/tx_test.go View File

@ -6,6 +6,7 @@ import (
acm "github.com/tendermint/tendermint/account"
. "github.com/tendermint/tendermint/common"
_ "github.com/tendermint/tendermint/config/tendermint_test"
ptypes "github.com/tendermint/tendermint/permission/types"
)
var chainID string
@ -69,6 +70,26 @@ func TestCallTxSignable(t *testing.T) {
}
}
func TestNameTxSignable(t *testing.T) {
nameTx := &NameTx{
Input: &TxInput{
Address: []byte("input1"),
Amount: 12345,
Sequence: 250,
},
Name: "google.com",
Data: "secretly.not.google.com",
Fee: 1000,
}
signBytes := acm.SignBytes(chainID, nameTx)
signStr := string(signBytes)
expected := Fmt(`{"chain_id":"%s","tx":[3,{"data":"secretly.not.google.com","fee":1000,"input":{"address":"696E70757431","amount":12345,"sequence":250},"name":"google.com"}]}`,
config.GetString("chain_id"))
if signStr != expected {
t.Errorf("Got unexpected sign string for CallTx. Expected:\n%v\nGot:\n%v", expected, signStr)
}
}
func TestBondTxSignable(t *testing.T) {
privKeyBytes := make([]byte, 64)
var privKeyArray [64]byte
@ -135,3 +156,25 @@ func TestRebondTxSignable(t *testing.T) {
t.Errorf("Got unexpected sign string for RebondTx")
}
}
func TestPermissionsTxSignable(t *testing.T) {
permsTx := &PermissionsTx{
Input: &TxInput{
Address: []byte("input1"),
Amount: 12345,
Sequence: 250,
},
PermArgs: &ptypes.SetBaseArgs{
Address: []byte("address1"),
Permission: 1,
Value: true,
},
}
signBytes := acm.SignBytes(chainID, permsTx)
signStr := string(signBytes)
expected := Fmt(`{"chain_id":"%s","tx":[32,{"args":"[2,{"address":"6164647265737331","permission":1,"value":true}]","input":{"address":"696E70757431","amount":12345,"sequence":250}}]}`,
config.GetString("chain_id"))
if signStr != expected {
t.Errorf("Got unexpected sign string for CallTx. Expected:\n%v\nGot:\n%v", expected, signStr)
}
}

+ 36
- 0
types/tx_utils.go View File

@ -3,6 +3,7 @@ package types
import (
"fmt"
acm "github.com/tendermint/tendermint/account"
ptypes "github.com/tendermint/tendermint/permission/types"
)
type AccountGetter interface {
@ -222,3 +223,38 @@ func NewRebondTx(addr []byte, height int) *RebondTx {
func (tx *RebondTx) Sign(chainID string, privAccount *acm.PrivAccount) {
tx.Signature = privAccount.Sign(chainID, tx).(acm.SignatureEd25519)
}
//----------------------------------------------------------------------------
// PermissionsTx interface for creating tx
func NewPermissionsTx(st AccountGetter, from acm.PubKey, args ptypes.PermArgs) (*PermissionsTx, error) {
addr := from.Address()
acc := st.GetAccount(addr)
if acc == nil {
return nil, fmt.Errorf("Invalid address %X from pubkey %X", addr, from)
}
nonce := acc.Sequence + 1
return NewPermissionsTxWithNonce(from, args, nonce), nil
}
func NewPermissionsTxWithNonce(from acm.PubKey, args ptypes.PermArgs, nonce int) *PermissionsTx {
addr := from.Address()
input := &TxInput{
Address: addr,
Amount: 1, // NOTE: amounts can't be 0 ...
Sequence: nonce,
Signature: acm.SignatureEd25519{},
PubKey: from,
}
return &PermissionsTx{
Input: input,
PermArgs: args,
}
}
func (tx *PermissionsTx) Sign(chainID string, privAccount *acm.PrivAccount) {
tx.Input.PubKey = privAccount.PubKey
tx.Input.Signature = privAccount.Sign(chainID, tx)
}

+ 20
- 10
vm/native.go View File

@ -8,20 +8,30 @@ import (
"github.com/tendermint/tendermint/vm/sha3"
)
var nativeContracts = make(map[Word256]NativeContract)
var registeredNativeContracts = make(map[Word256]NativeContract)
func RegisteredNativeContract(addr Word256) bool {
_, ok := registeredNativeContracts[addr]
return ok
}
func init() {
nativeContracts[Int64ToWord256(1)] = ecrecoverFunc
nativeContracts[Int64ToWord256(2)] = sha256Func
nativeContracts[Int64ToWord256(3)] = ripemd160Func
nativeContracts[Int64ToWord256(4)] = identityFunc
registerNativeContracts()
registerSNativeContracts()
}
func registerNativeContracts() {
registeredNativeContracts[Int64ToWord256(1)] = ecrecoverFunc
registeredNativeContracts[Int64ToWord256(2)] = sha256Func
registeredNativeContracts[Int64ToWord256(3)] = ripemd160Func
registeredNativeContracts[Int64ToWord256(4)] = identityFunc
}
//-----------------------------------------------------------------------------
type NativeContract func(input []byte, gas *int64) (output []byte, err error)
type NativeContract func(appState AppState, caller *Account, input []byte, gas *int64) (output []byte, err error)
func ecrecoverFunc(input []byte, gas *int64) (output []byte, err error) {
func ecrecoverFunc(appState AppState, caller *Account, input []byte, gas *int64) (output []byte, err error) {
// Deduct gas
gasRequired := GasEcRecover
if *gas < gasRequired {
@ -42,7 +52,7 @@ func ecrecoverFunc(input []byte, gas *int64) (output []byte, err error) {
return LeftPadBytes(hashed, 32), nil
}
func sha256Func(input []byte, gas *int64) (output []byte, err error) {
func sha256Func(appState AppState, caller *Account, input []byte, gas *int64) (output []byte, err error) {
// Deduct gas
gasRequired := int64((len(input)+31)/32)*GasSha256Word + GasSha256Base
if *gas < gasRequired {
@ -57,7 +67,7 @@ func sha256Func(input []byte, gas *int64) (output []byte, err error) {
return hasher.Sum(nil), nil
}
func ripemd160Func(input []byte, gas *int64) (output []byte, err error) {
func ripemd160Func(appState AppState, caller *Account, input []byte, gas *int64) (output []byte, err error) {
// Deduct gas
gasRequired := int64((len(input)+31)/32)*GasRipemd160Word + GasRipemd160Base
if *gas < gasRequired {
@ -72,7 +82,7 @@ func ripemd160Func(input []byte, gas *int64) (output []byte, err error) {
return LeftPadBytes(hasher.Sum(nil), 32), nil
}
func identityFunc(input []byte, gas *int64) (output []byte, err error) {
func identityFunc(appState AppState, caller *Account, input []byte, gas *int64) (output []byte, err error) {
// Deduct gas
gasRequired := int64((len(input)+31)/32)*GasIdentityWord + GasIdentityBase
if *gas < gasRequired {


+ 38
- 64
vm/snative.go View File

@ -7,36 +7,31 @@ import (
ptypes "github.com/tendermint/tendermint/permission/types"
)
// TODO: ABI
//------------------------------------------------------------------------------------------------
// Registered SNative contracts
var RegisteredSNativeContracts = map[Word256]SNativeContract{
LeftPadWord256([]byte("hasBasePerm")): hasBasePerm,
LeftPadWord256([]byte("setBasePerm")): setBasePerm,
LeftPadWord256([]byte("unsetBasePerm")): unsetBasePerm,
LeftPadWord256([]byte("setGlobalPerm")): setGlobalPerm,
LeftPadWord256([]byte("hasRole")): hasRole,
LeftPadWord256([]byte("addRole")): addRole,
LeftPadWord256([]byte("rmRole")): rmRole,
func registerSNativeContracts() {
registeredNativeContracts[LeftPadWord256([]byte("has_base"))] = hasBasePerm
registeredNativeContracts[LeftPadWord256([]byte("set_base"))] = setBasePerm
registeredNativeContracts[LeftPadWord256([]byte("unset_base"))] = unsetBasePerm
registeredNativeContracts[LeftPadWord256([]byte("set_global"))] = setGlobalPerm
registeredNativeContracts[LeftPadWord256([]byte("has_role"))] = hasRole
registeredNativeContracts[LeftPadWord256([]byte("add_role"))] = addRole
registeredNativeContracts[LeftPadWord256([]byte("rm_role"))] = rmRole
}
// Takes an appState so it can lookup/update accounts,
// an account to check for permission to access the snative contract
// and some input bytes (presumably 32byte words)
type SNativeContract func(appState AppState, acc *Account, input []byte) (output []byte, err error)
//-----------------------------------------------------------------------------
// snative are native contracts that can access and manipulate the chain state
// (in particular the permissions values)
// snative are native contracts that can access and modify an account's permissions
// TODO: catch errors, log em, return 0s to the vm (should some errors cause exceptions though?)
func hasBasePerm(appState AppState, acc *Account, args []byte) (output []byte, err error) {
if !HasPermission(appState, acc, ptypes.HasBasePerm) {
return nil, ErrInvalidPermission{acc.Address, "HasBasePerm"}
func hasBasePerm(appState AppState, caller *Account, args []byte, gas *int64) (output []byte, err error) {
if !HasPermission(appState, caller, ptypes.HasBase) {
return nil, ErrInvalidPermission{caller.Address, "has_base"}
}
if len(args) != 2*32 {
return nil, fmt.Errorf("hasBasePerm() takes two arguments (address, permission number)")
return nil, fmt.Errorf("hasBasePerm() takes two arguments (address, permFlag)")
}
addr, permNum := returnTwoArgs(args)
vmAcc := appState.GetAccount(addr)
@ -57,12 +52,12 @@ func hasBasePerm(appState AppState, acc *Account, args []byte) (output []byte, e
return LeftPadWord256([]byte{permInt}).Bytes(), nil
}
func setBasePerm(appState AppState, acc *Account, args []byte) (output []byte, err error) {
if !HasPermission(appState, acc, ptypes.SetBasePerm) {
return nil, ErrInvalidPermission{acc.Address, "SetBasePerm"}
func setBasePerm(appState AppState, caller *Account, args []byte, gas *int64) (output []byte, err error) {
if !HasPermission(appState, caller, ptypes.SetBase) {
return nil, ErrInvalidPermission{caller.Address, "set_base"}
}
if len(args) != 3*32 {
return nil, fmt.Errorf("setBasePerm() takes three arguments (address, permission number, permission value)")
return nil, fmt.Errorf("setBase() takes three arguments (address, permFlag, permission value)")
}
addr, permNum, perm := returnThreeArgs(args)
vmAcc := appState.GetAccount(addr)
@ -82,12 +77,12 @@ func setBasePerm(appState AppState, acc *Account, args []byte) (output []byte, e
return perm.Bytes(), nil
}
func unsetBasePerm(appState AppState, acc *Account, args []byte) (output []byte, err error) {
if !HasPermission(appState, acc, ptypes.UnsetBasePerm) {
return nil, ErrInvalidPermission{acc.Address, "UnsetBasePerm"}
func unsetBasePerm(appState AppState, caller *Account, args []byte, gas *int64) (output []byte, err error) {
if !HasPermission(appState, caller, ptypes.UnsetBase) {
return nil, ErrInvalidPermission{caller.Address, "unset_base"}
}
if len(args) != 2*32 {
return nil, fmt.Errorf("unsetBasePerm() takes two arguments (address, permission number)")
return nil, fmt.Errorf("unsetBase() takes two arguments (address, permFlag)")
}
addr, permNum := returnTwoArgs(args)
vmAcc := appState.GetAccount(addr)
@ -106,12 +101,12 @@ func unsetBasePerm(appState AppState, acc *Account, args []byte) (output []byte,
return permNum.Bytes(), nil
}
func setGlobalPerm(appState AppState, acc *Account, args []byte) (output []byte, err error) {
if !HasPermission(appState, acc, ptypes.SetGlobalPerm) {
return nil, ErrInvalidPermission{acc.Address, "SetGlobalPerm"}
func setGlobalPerm(appState AppState, caller *Account, args []byte, gas *int64) (output []byte, err error) {
if !HasPermission(appState, caller, ptypes.SetGlobal) {
return nil, ErrInvalidPermission{caller.Address, "set_global"}
}
if len(args) != 2*32 {
return nil, fmt.Errorf("setGlobalPerm() takes two arguments (permission number, permission value)")
return nil, fmt.Errorf("setGlobal() takes two arguments (permFlag, permission value)")
}
permNum, perm := returnTwoArgs(args)
vmAcc := appState.GetAccount(ptypes.GlobalPermissionsAddress256)
@ -131,17 +126,9 @@ func setGlobalPerm(appState AppState, acc *Account, args []byte) (output []byte,
return perm.Bytes(), nil
}
// TODO: needs access to an iterator ...
func clearPerm(appState AppState, acc *Account, args []byte) (output []byte, err error) {
if !HasPermission(appState, acc, ptypes.ClearBasePerm) {
return nil, ErrInvalidPermission{acc.Address, "ClearPerm"}
}
return nil, nil
}
func hasRole(appState AppState, acc *Account, args []byte) (output []byte, err error) {
if !HasPermission(appState, acc, ptypes.HasRole) {
return nil, ErrInvalidPermission{acc.Address, "HasRole"}
func hasRole(appState AppState, caller *Account, args []byte, gas *int64) (output []byte, err error) {
if !HasPermission(appState, caller, ptypes.HasRole) {
return nil, ErrInvalidPermission{caller.Address, "has_role"}
}
if len(args) != 2*32 {
return nil, fmt.Errorf("hasRole() takes two arguments (address, role)")
@ -162,9 +149,9 @@ func hasRole(appState AppState, acc *Account, args []byte) (output []byte, err e
return LeftPadWord256([]byte{permInt}).Bytes(), nil
}
func addRole(appState AppState, acc *Account, args []byte) (output []byte, err error) {
if !HasPermission(appState, acc, ptypes.AddRole) {
return nil, ErrInvalidPermission{acc.Address, "AddRole"}
func addRole(appState AppState, caller *Account, args []byte, gas *int64) (output []byte, err error) {
if !HasPermission(appState, caller, ptypes.AddRole) {
return nil, ErrInvalidPermission{caller.Address, "add_role"}
}
if len(args) != 2*32 {
return nil, fmt.Errorf("addRole() takes two arguments (address, role)")
@ -186,9 +173,9 @@ func addRole(appState AppState, acc *Account, args []byte) (output []byte, err e
return LeftPadWord256([]byte{permInt}).Bytes(), nil
}
func rmRole(appState AppState, acc *Account, args []byte) (output []byte, err error) {
if !HasPermission(appState, acc, ptypes.RmRole) {
return nil, ErrInvalidPermission{acc.Address, "RmRole"}
func rmRole(appState AppState, caller *Account, args []byte, gas *int64) (output []byte, err error) {
if !HasPermission(appState, caller, ptypes.RmRole) {
return nil, ErrInvalidPermission{caller.Address, "rm_role"}
}
if len(args) != 2*32 {
return nil, fmt.Errorf("rmRole() takes two arguments (address, role)")
@ -224,36 +211,23 @@ func (e ErrInvalidPermission) Error() string {
// Checks if a permission flag is valid (a known base chain or snative permission)
func ValidPermN(n ptypes.PermFlag) bool {
if n > ptypes.TopBasePermFlag && n < ptypes.FirstSNativePermFlag {
return false
} else if n > ptypes.TopSNativePermFlag {
if n > ptypes.TopPermFlag {
return false
}
return true
}
// assumes length has already been checked
// CONTRACT: length has already been checked
func returnTwoArgs(args []byte) (a Word256, b Word256) {
copy(a[:], args[:32])
copy(b[:], args[32:64])
return
}
// assumes length has already been checked
// CONTRACT: length has already been checked
func returnThreeArgs(args []byte) (a Word256, b Word256, c Word256) {
copy(a[:], args[:32])
copy(b[:], args[32:64])
copy(c[:], args[64:96])
return
}
// mostly a convenience for testing
var RegisteredSNativePermissions = map[Word256]ptypes.PermFlag{
LeftPadWord256([]byte("hasBasePerm")): ptypes.HasBasePerm,
LeftPadWord256([]byte("setBasePerm")): ptypes.SetBasePerm,
LeftPadWord256([]byte("unsetBasePerm")): ptypes.UnsetBasePerm,
LeftPadWord256([]byte("setGlobalPerm")): ptypes.SetGlobalPerm,
LeftPadWord256([]byte("hasRole")): ptypes.HasRole,
LeftPadWord256([]byte("addRole")): ptypes.AddRole,
LeftPadWord256([]byte("rmRole")): ptypes.RmRole,
}

+ 33
- 36
vm/vm.go View File

@ -95,6 +95,19 @@ func HasPermission(appState AppState, acc *Account, perm ptypes.PermFlag) bool {
return v
}
func (vm *VM) fireEvent(exception *string, output *[]byte, caller, callee *Account, input []byte, value int64, gas *int64) {
// fire the post call event (including exception if applicable)
if vm.evc != nil {
vm.evc.FireEvent(types.EventStringAccReceive(callee.Address.Postfix(20)), types.EventMsgCall{
&types.CallData{caller.Address.Postfix(20), callee.Address.Postfix(20), input, value, *gas},
vm.origin.Postfix(20),
vm.txid,
*output,
*exception,
})
}
}
// 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.
@ -102,30 +115,8 @@ func HasPermission(appState AppState, acc *Account, perm ptypes.PermFlag) bool {
func (vm *VM) Call(caller, callee *Account, code, input []byte, value int64, gas *int64) (output []byte, err error) {
exception := new(string)
defer func() {
if vm.evc != nil {
vm.evc.FireEvent(types.EventStringAccReceive(callee.Address.Postfix(20)), types.EventMsgCall{
&types.CallData{caller.Address.Postfix(20), callee.Address.Postfix(20), input, value, *gas},
vm.origin.Postfix(20),
vm.txid,
output,
*exception,
})
}
}()
// SNATIVE ACCESS
// if code is empty, callee may be snative contract
if len(code) == 0 {
if snativeContract, ok := RegisteredSNativeContracts[callee.Address]; ok {
output, err = snativeContract(vm.appState, caller, input)
if err != nil {
*exception = err.Error()
}
return
}
}
// SNATIVE ACCESS END
// fire the post call event (including exception if applicable)
defer vm.fireEvent(exception, &output, caller, callee, input, value, gas)
if err = transfer(caller, callee, value); err != nil {
*exception = err.Error()
@ -759,9 +750,16 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas
// Begin execution
var ret []byte
var err error
if nativeContract := nativeContracts[addr]; nativeContract != nil {
if nativeContract := registeredNativeContracts[addr]; nativeContract != nil {
// Native contract
ret, err = nativeContract(args, &gasLimit)
ret, err = nativeContract(vm.appState, callee, args, &gasLimit)
// for now we fire the Receive event. maybe later we'll fire more particulars
var exception string
if err != nil {
exception = err.Error()
}
vm.fireEvent(&exception, &ret, callee, &Account{Address: addr}, args, value, gas)
} else {
// EVM contract
if ok = useGas(gas, GasGetAccount); !ok {
@ -779,19 +777,18 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas
ret, err = vm.Call(callee, callee, acc.Code, args, value, gas)
} else {
if acc == nil {
if _, ok := RegisteredSNativeContracts[addr]; ok {
acc = &Account{Address: addr}
} else {
// if we have not seen the account before, create it
// so we can send funds
if !HasPermission(vm.appState, caller, ptypes.CreateAccount) {
return nil, ErrPermission{"create_account"}
}
acc = &Account{Address: addr}
// nil account means we're sending funds to a new account
if !HasPermission(vm.appState, caller, ptypes.CreateAccount) {
return nil, ErrPermission{"create_account"}
}
acc = &Account{Address: addr}
vm.appState.UpdateAccount(acc)
// send funds to new account
ret, err = vm.Call(callee, acc, acc.Code, args, value, gas)
} else {
// call standard contract
ret, err = vm.Call(callee, acc, acc.Code, args, value, gas)
}
ret, err = vm.Call(callee, acc, acc.Code, args, value, gas)
}
}


Loading…
Cancel
Save