From e037093740b05b0817b2439145802e1e2b685f51 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Mon, 20 Jul 2015 14:24:57 -0400 Subject: [PATCH] snative refactor and SNativeTx --- common/byteslice.go | 10 + common/word.go | 11 +- permission/types/permissions.go | 13 +- permission/types/snatives.go | 176 +++++++++++++- state/execution.go | 148 ++++++++++-- state/permissions_test.go | 399 ++++++++++++++++---------------- types/events.go | 4 + types/tx.go | 30 +++ types/tx_utils.go | 36 +++ vm/snative.go | 107 +++------ vm/vm.go | 84 ++++--- 11 files changed, 688 insertions(+), 330 deletions(-) diff --git a/common/byteslice.go b/common/byteslice.go index da42db810..be828f065 100644 --- a/common/byteslice.go +++ b/common/byteslice.go @@ -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)) + +} diff --git a/common/word.go b/common/word.go index 39e4dbb1c..4072482b8 100644 --- a/common/word.go +++ b/common/word.go @@ -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 { diff --git a/permission/types/permissions.go b/permission/types/permissions.go index 2516efc4e..cee1a43ba 100644 --- a/permission/types/permissions.go +++ b/permission/types/permissions.go @@ -154,8 +154,15 @@ func (aP *AccountPermissions) RmRole(role string) bool { } //-------------------------------------------------------------------------------- +func PermFlagToString(pf PermFlag) string { + if pf < FirstSNativePermFlag { + return BasePermFlagToString(pf) + } else { + return SNativePermFlagToString(pf) + } +} -func PermFlagToString(pf PermFlag) (perm string, err error) { +func BasePermFlagToString(pf PermFlag) (perm string) { switch pf { case Root: perm = "root" @@ -171,13 +178,11 @@ func PermFlagToString(pf PermFlag) (perm string, err error) { perm = "bond" case Name: perm = "name" - default: - err = fmt.Errorf("Unknown permission flag %b", pf) } return } -func PermStringToFlag(perm string) (pf PermFlag, err error) { +func BasePermStringToFlag(perm string) (pf PermFlag, err error) { switch perm { case "root": pf = Root diff --git a/permission/types/snatives.go b/permission/types/snatives.go index be1d0b249..047edde03 100644 --- a/permission/types/snatives.go +++ b/permission/types/snatives.go @@ -1,5 +1,14 @@ package types +import ( + "fmt" + + "github.com/tendermint/tendermint/binary" +) + +//--------------------------------------------------------------------------------------------------- +// snative permissions + const ( // first 32 bits of BasePermission are for chain, second 32 are for snative FirstSNativePermFlag PermFlag = 1 << 32 @@ -8,11 +17,11 @@ const ( // we need to reset iota with no const block const ( // each snative has an associated permission flag - HasBasePerm PermFlag = FirstSNativePermFlag << iota - SetBasePerm - UnsetBasePerm - SetGlobalPerm - ClearBasePerm + HasBase PermFlag = FirstSNativePermFlag << iota + SetBase + UnsetBase + SetGlobal + ClearBase HasRole AddRole RmRole @@ -21,3 +30,160 @@ const ( TopSNativePermFlag PermFlag = FirstSNativePermFlag << (NumSNativePermissions - 1) AllSNativePermFlags PermFlag = (TopSNativePermFlag | (TopSNativePermFlag - 1)) &^ (FirstSNativePermFlag - 1) ) + +//--------------------------------------------------------------------------------------------------- +// snative tx interface and argument encoding + +// SNatives are represented as an interface in the SNative tx, +// so binary does the work of handling args and determining which snative we're calling +// NOTE: we're definining an implicit map from registration bytes to perm flags +type SNativeArgs interface { + PermFlag() PermFlag +} + +const ( + SNativeArgsTypeHasBase = byte(0x01) + SNativeArgsTypeSetBase = byte(0x02) + SNativeArgsTypeUnsetBase = byte(0x03) + SNativeArgsTypeSetGlobal = byte(0x04) + SNativeArgsTypeClearBase = byte(0x05) + SNativeArgsTypeHasRole = byte(0x06) + SNativeArgsTypeAddRole = byte(0x07) + SNativeArgsTypeRmRole = byte(0x08) +) + +// for binary.readReflect +var _ = binary.RegisterInterface( + struct{ SNativeArgs }{}, + binary.ConcreteType{&HasBaseArgs{}, SNativeArgsTypeHasBase}, + binary.ConcreteType{&SetBaseArgs{}, SNativeArgsTypeSetBase}, + binary.ConcreteType{&UnsetBaseArgs{}, SNativeArgsTypeUnsetBase}, + binary.ConcreteType{&SetGlobalArgs{}, SNativeArgsTypeSetGlobal}, + binary.ConcreteType{&ClearBaseArgs{}, SNativeArgsTypeClearBase}, + binary.ConcreteType{&HasRoleArgs{}, SNativeArgsTypeHasRole}, + binary.ConcreteType{&AddRoleArgs{}, SNativeArgsTypeAddRole}, + binary.ConcreteType{&RmRoleArgs{}, SNativeArgsTypeRmRole}, +) + +type HasBaseArgs struct { + Address []byte + Permission PermFlag +} + +func (*HasBaseArgs) PermFlag() PermFlag { + return HasBase +} + +type SetBaseArgs struct { + Address []byte + Permission PermFlag + Value bool +} + +func (*SetBaseArgs) PermFlag() PermFlag { + return SetBase +} + +type UnsetBaseArgs struct { + Address []byte + Permission PermFlag +} + +func (*UnsetBaseArgs) PermFlag() PermFlag { + return UnsetBase +} + +type SetGlobalArgs struct { + Permission PermFlag + Value bool +} + +func (*SetGlobalArgs) PermFlag() PermFlag { + return SetGlobal +} + +type ClearBaseArgs struct { + // +} + +func (*ClearBaseArgs) PermFlag() PermFlag { + return ClearBase +} + +type HasRoleArgs struct { + Address []byte + Role string +} + +func (*HasRoleArgs) PermFlag() PermFlag { + return HasRole +} + +type AddRoleArgs struct { + Address []byte + Role string +} + +func (*AddRoleArgs) PermFlag() PermFlag { + return AddRole +} + +type RmRoleArgs struct { + Address []byte + Role string +} + +func (*RmRoleArgs) PermFlag() PermFlag { + return RmRole +} + +//------------------------------------------------------------ +// string utilities + +func SNativePermFlagToString(pF PermFlag) (perm string) { + switch pF { + case HasBase: + perm = "HasBase" + case SetBase: + perm = "SetBase" + case UnsetBase: + perm = "UnsetBase" + case SetGlobal: + perm = "SetGlobal" + case ClearBase: + perm = "ClearBase" + case HasRole: + perm = "HasRole" + case AddRole: + perm = "AddRole" + case RmRole: + perm = "RmRole" + default: + perm = "#-UNKNOWN-#" + } + return +} + +func SNativeStringToPermFlag(perm string) (pF PermFlag, err error) { + switch perm { + case "HasBase": + pF = HasBase + case "SetBase": + pF = SetBase + case "UnsetBase": + pF = UnsetBase + case "SetGlobal": + pF = SetGlobal + case "ClearBase": + pF = ClearBase + case "HasRole": + pF = HasRole + case "AddRole": + pF = AddRole + case "RmRole": + pF = RmRole + default: + err = fmt.Errorf("Unknown permission %s", perm) + } + return +} diff --git a/state/execution.go b/state/execution.go index e04f92293..586c20ee5 100644 --- a/state/execution.go +++ b/state/execution.go @@ -424,26 +424,26 @@ func ExecTx(blockCache *BlockCache, tx types.Tx, runCall bool, evc events.Fireab 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} - } 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 + trimmedAddr := TrimmedString(tx.Address) + if _, unknownPermErr := ptypes.SNativeStringToPermFlag(string(trimmedAddr)); unknownPermErr == nil { + return fmt.Errorf("SNatives can not be called using CallTx. Either use a contract or a SNativeTx") } + + // 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 } callee = toVMAccount(outAcc) code = callee.Code @@ -697,6 +697,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 +794,100 @@ func ExecTx(blockCache *BlockCache, tx types.Tx, runCall bool, evc events.Fireab } return nil + case *types.SNativeTx: + 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.SNative.PermFlag() + // check permission + if !hasSNativePermission(blockCache, inAcc, permFlag) { + return fmt.Errorf("Account %X does not have permission to call snative %s (%b)", tx.Input.Address, tx.SNative, 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 SNativeTx", "snative", ptypes.SNativePermFlagToString(permFlag), "args", tx.SNative) + + var permAcc *acm.Account + switch args := tx.SNative.(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.ClearBaseArgs: + // + 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("invalid snative") + } + + // 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.EventStringSNative(ptypes.SNativePermFlagToString(permFlag)), tx) + } + + return nil + default: // binary decoding should not let this happen PanicSanity("Unknown Tx type") @@ -804,7 +899,8 @@ 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.AllBasePermFlags && perm < ptypes.FirstSNativePermFlag) || + (perm > ptypes.AllSNativePermFlags) { PanicSanity("Checking an unknown permission in state should never happen") } @@ -813,17 +909,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 } @@ -873,3 +971,7 @@ func hasBondOrSendPermission(state AccountGetter, accs map[string]*acm.Account) } return true } + +func hasSNativePermission(state AccountGetter, acc *acm.Account, permFlag ptypes.PermFlag) bool { + return HasPermission(state, acc, permFlag) +} diff --git a/state/permissions_test.go b/state/permissions_test.go index ad3199a9c..5a4a6d534 100644 --- a/state/permissions_test.go +++ b/state/permissions_test.go @@ -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) @@ -860,11 +859,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("HasBase", 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 +874,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("SetBase", 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("HasBase", 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 +887,9 @@ func TestSNativeCALL(t *testing.T) { } return nil }) - snativeAddress, data = snativePermTestInput("setBasePerm", user[3], ptypes.CreateContract, true) + snativeAddress, data = snativePermTestInputCALL("SetBase", 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("HasBase", 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 +898,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("UnsetBase", 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("HasBase", 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 +911,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("SetGlobal", 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("HasBase", 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 +925,12 @@ func TestSNativeCALL(t *testing.T) { return nil }) - // clearBasePerm + // ClearBase // TODO - fmt.Println("#### hasRole") - // hasRole - snativeAddress, data = snativeRoleTestInput("hasRole", user[3], "bumble") + fmt.Println("\n#### HasRole") + // HasRole + snativeAddress, data = snativeRoleTestInputCALL("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) { @@ -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("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") + snativeAddress, data = snativeRoleTestInputCALL("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") + snativeAddress, data = snativeRoleTestInputCALL("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) @@ -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("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") + snativeAddress, data = snativeRoleTestInputCALL("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) @@ -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,73 @@ func TestSNativeCallTx(t *testing.T) { blockCache := NewBlockCache(st) //---------------------------------------------------------- - // Test CallTx to SNative contracts - var doug *acm.Account = nil + // Test SNativeTx - 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("\n#### SetBase") + // SetBase + snativeArgs := snativePermTestInputTx("SetBase", 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("SetBase", 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("#### 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("\n#### UnsetBase") + // UnsetBase + snativeArgs = snativePermTestInputTx("UnsetBase", 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("#### 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("\n#### SetGlobal") + // SetGlobalPerm + snativeArgs = snativePermTestInputTx("SetGlobal", 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("#### 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 + // ClearBase // 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("\n#### AddRole") + // AddRole + snativeArgs = snativeRoleTestInputTx("AddRole", 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("#### 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 - }) - - 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#### RmRole") + // RmRole + snativeArgs = snativeRoleTestInputTx("RmRole", 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 +1067,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 +1087,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, 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 - 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 - } - 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) - } - evv := ev.(types.EventMsgCall) - ret := evv.Return - if err := f(ret); err != nil { - t.Fatal(err) - } +// 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) } -// 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 +// 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) { + 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.SNativeStringToPermFlag(TrimmedString(snativeAddress)) + if err != nil { + t.Fatal(err) + } + doug.Permissions.Base.Set(perm, true) } + 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]) fmt.Println("subscribing to", types.EventStringAccReceive(snativeAddress)) - _, exception := execTxWaitEvent(t, blockCache, tx, types.EventStringAccReceive(snativeAddress)) - if exception == "" { - t.Fatal("Expected exception") + ev, exception := execTxWaitEvent(t, blockCache, tx, types.EventStringAccReceive(snativeAddress)) + if exception == ExceptionTimeOut { + t.Fatal("Timed out waiting for event") + } + 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") + } + } +} + +func testSNativeTxExpectFail(t *testing.T, blockCache *BlockCache, snativeArgs ptypes.SNativeArgs) { + testSNativeTx(t, false, blockCache, 0, snativeArgs) +} + +func testSNativeTxExpectPass(t *testing.T, blockCache *BlockCache, perm ptypes.PermFlag, snativeArgs ptypes.SNativeArgs) { + testSNativeTx(t, true, blockCache, perm, snativeArgs) +} + +func testSNativeTx(t *testing.T, expectPass bool, blockCache *BlockCache, perm ptypes.PermFlag, snativeArgs ptypes.SNativeArgs) { + if expectPass { + acc := blockCache.GetAccount(user[0].Address) + acc.Permissions.Base.Set(perm, true) + blockCache.UpdateAccount(acc) + } + tx, _ := types.NewSNativeTx(blockCache, user[0].PubKey, snativeArgs) + tx.Sign(chainID, user[0]) + 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 +1171,58 @@ 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 "HasBase", "UnsetBase": data = LeftPadBytes(user.Address, 32) data = append(data, Uint64ToWord256(uint64(perm)).Bytes()...) - case "setBasePerm": + case "SetBase": data = LeftPadBytes(user.Address, 32) data = append(data, Uint64ToWord256(uint64(perm)).Bytes()...) data = append(data, boolToWord256(val).Bytes()...) - case "setGlobalPerm": + case "SetGlobal": data = Uint64ToWord256(uint64(perm)).Bytes() data = append(data, boolToWord256(val).Bytes()...) - case "clearBasePerm": + case "ClearBase": } 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.SNativeArgs) { + switch name { + case "HasBase": + snativeArgs = &ptypes.HasBaseArgs{user.Address, perm} + case "UnsetBase": + snativeArgs = &ptypes.UnsetBaseArgs{user.Address, perm} + case "SetBase": + snativeArgs = &ptypes.SetBaseArgs{user.Address, perm, val} + case "SetGlobal": + snativeArgs = &ptypes.SetGlobalArgs{perm, val} + case "ClearBase": + } + 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.SNativeArgs) { + switch name { + case "HasRole": + snativeArgs = &ptypes.HasRoleArgs{user.Address, role} + case "AddRole": + snativeArgs = &ptypes.AddRoleArgs{user.Address, role} + case "RmRole": + 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 diff --git a/types/events.go b/types/events.go index c6d46bcbb..8a78d3606 100644 --- a/types/events.go +++ b/types/events.go @@ -22,6 +22,10 @@ func EventStringLogEvent(addr []byte) string { return fmt.Sprintf("Log/%X", addr) } +func EventStringSNative(name string) string { + return fmt.Sprintf("SNative/%s", name) +} + func EventStringBond() string { return "Bond" } diff --git a/types/tx.go b/types/tx.go index 990b47468..76d80b646 100644 --- a/types/tx.go +++ b/types/tx.go @@ -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: + - SNativeTx (CapTx ?) */ + 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 + TxTypeSNative = 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{&SNativeTx{}, TxTypeSNative}, ) //----------------------------------------------------------------------------- @@ -314,6 +323,27 @@ func (tx *DupeoutTx) String() string { //----------------------------------------------------------------------------- +type SNativeTx struct { + Input *TxInput `json:"input"` + SNative ptypes.SNativeArgs `json:"snative"` +} + +// TODO: check the tx.SNative encoding ... +func (tx *SNativeTx) 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":%v`, TxTypeSNative, tx.SNative)), w, n, err) + binary.WriteTo([]byte(`,"input":`), w, n, err) + tx.Input.WriteSignBytes(w, n, err) + binary.WriteTo([]byte(Fmt(`,"snative":%s`, tx.SNative)), w, n, err) + binary.WriteTo([]byte(`}]}`), w, n, err) +} + +func (tx *SNativeTx) String() string { + return Fmt("SNativeTx{%v -> %v}", tx.Input, tx.SNative) +} + +//----------------------------------------------------------------------------- + // This should match the leaf hashes of Block.Data.Hash()'s SimpleMerkleTree. func TxID(chainID string, tx Tx) []byte { signBytes := acm.SignBytes(chainID, tx) diff --git a/types/tx_utils.go b/types/tx_utils.go index eb7aae126..c90e7624f 100644 --- a/types/tx_utils.go +++ b/types/tx_utils.go @@ -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) } + +//---------------------------------------------------------------------------- +// SNativeTx interface for creating tx + +func NewSNativeTx(st AccountGetter, from acm.PubKey, snativeArgs ptypes.SNativeArgs) (*SNativeTx, 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 NewSNativeTxWithNonce(from, snativeArgs, nonce), nil +} + +func NewSNativeTxWithNonce(from acm.PubKey, snativeArgs ptypes.SNativeArgs, nonce int) *SNativeTx { + addr := from.Address() + input := &TxInput{ + Address: addr, + Amount: 1, // NOTE: amounts can't be 0 ... + Sequence: nonce, + Signature: acm.SignatureEd25519{}, + PubKey: from, + } + + return &SNativeTx{ + Input: input, + SNative: snativeArgs, + } +} + +func (tx *SNativeTx) Sign(chainID string, privAccount *acm.PrivAccount) { + tx.Input.PubKey = privAccount.PubKey + tx.Input.Signature = privAccount.Sign(chainID, tx) +} diff --git a/vm/snative.go b/vm/snative.go index 16f45a6d7..7df081fba 100644 --- a/vm/snative.go +++ b/vm/snative.go @@ -7,17 +7,11 @@ import ( ptypes "github.com/tendermint/tendermint/permission/types" ) -//------------------------------------------------------------------------------------------------ -// 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, +type snativeInfo struct { + PermFlag ptypes.PermFlag + NArgs int + ArgsError error + Executable SNativeContract } // Takes an appState so it can lookup/update accounts, @@ -25,6 +19,37 @@ var RegisteredSNativeContracts = map[Word256]SNativeContract{ // and some input bytes (presumably 32byte words) type SNativeContract func(appState AppState, acc *Account, input []byte) (output []byte, err error) +//------------------------------------------------------------------------------------------------ +// Registered SNative contracts + +// sets the number of arguments, a friendly error message, and the snative function ("executable") +func getSNativeInfo(permFlag ptypes.PermFlag) *snativeInfo { + si := &snativeInfo{PermFlag: permFlag} + var errS string + switch permFlag { + case ptypes.HasBase: + si.NArgs, errS, si.Executable = 2, "hasBasePerm() takes two arguments (address, permission number)", hasBasePerm + case ptypes.SetBase: + si.NArgs, errS, si.Executable = 3, "setBasePerm() takes three arguments (address, permission number, permission value)", setBasePerm + case ptypes.UnsetBase: + si.NArgs, errS, si.Executable = 2, "unsetBasePerm() takes two arguments (address, permission number)", unsetBasePerm + case ptypes.SetGlobal: + si.NArgs, errS, si.Executable = 2, "setGlobalPerm() takes two arguments (permission number, permission value)", setGlobalPerm + case ptypes.ClearBase: + // + case ptypes.HasRole: + si.NArgs, errS, si.Executable = 2, "hasRole() takes two arguments (address, role)", hasRole + case ptypes.AddRole: + si.NArgs, errS, si.Executable = 2, "addRole() takes two arguments (address, role)", addRole + case ptypes.RmRole: + si.NArgs, errS, si.Executable = 2, "rmRole() takes two arguments (address, role)", rmRole + default: + PanicSanity(Fmt("should never happen. PermFlag: %b", permFlag)) + } + si.ArgsError = fmt.Errorf(errS) + return si +} + //----------------------------------------------------------------------------- // snative are native contracts that can access and manipulate the chain state // (in particular the permissions values) @@ -32,12 +57,6 @@ type SNativeContract func(appState AppState, acc *Account, input []byte) (output // 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"} - } - if len(args) != 2*32 { - return nil, fmt.Errorf("hasBasePerm() takes two arguments (address, permission number)") - } addr, permNum := returnTwoArgs(args) vmAcc := appState.GetAccount(addr) if vmAcc == nil { @@ -58,12 +77,6 @@ func hasBasePerm(appState AppState, acc *Account, args []byte) (output []byte, e } func setBasePerm(appState AppState, acc *Account, args []byte) (output []byte, err error) { - if !HasPermission(appState, acc, ptypes.SetBasePerm) { - return nil, ErrInvalidPermission{acc.Address, "SetBasePerm"} - } - if len(args) != 3*32 { - return nil, fmt.Errorf("setBasePerm() takes three arguments (address, permission number, permission value)") - } addr, permNum, perm := returnThreeArgs(args) vmAcc := appState.GetAccount(addr) if vmAcc == nil { @@ -83,12 +96,6 @@ func setBasePerm(appState AppState, acc *Account, args []byte) (output []byte, e } func unsetBasePerm(appState AppState, acc *Account, args []byte) (output []byte, err error) { - if !HasPermission(appState, acc, ptypes.UnsetBasePerm) { - return nil, ErrInvalidPermission{acc.Address, "UnsetBasePerm"} - } - if len(args) != 2*32 { - return nil, fmt.Errorf("unsetBasePerm() takes two arguments (address, permission number)") - } addr, permNum := returnTwoArgs(args) vmAcc := appState.GetAccount(addr) if vmAcc == nil { @@ -107,12 +114,6 @@ func unsetBasePerm(appState AppState, acc *Account, args []byte) (output []byte, } func setGlobalPerm(appState AppState, acc *Account, args []byte) (output []byte, err error) { - if !HasPermission(appState, acc, ptypes.SetGlobalPerm) { - return nil, ErrInvalidPermission{acc.Address, "SetGlobalPerm"} - } - if len(args) != 2*32 { - return nil, fmt.Errorf("setGlobalPerm() takes two arguments (permission number, permission value)") - } permNum, perm := returnTwoArgs(args) vmAcc := appState.GetAccount(ptypes.GlobalPermissionsAddress256) if vmAcc == nil { @@ -133,19 +134,10 @@ func setGlobalPerm(appState AppState, acc *Account, args []byte) (output []byte, // 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"} - } - if len(args) != 2*32 { - return nil, fmt.Errorf("hasRole() takes two arguments (address, role)") - } addr, role := returnTwoArgs(args) vmAcc := appState.GetAccount(addr) if vmAcc == nil { @@ -163,12 +155,6 @@ func hasRole(appState AppState, acc *Account, args []byte) (output []byte, err e } func addRole(appState AppState, acc *Account, args []byte) (output []byte, err error) { - if !HasPermission(appState, acc, ptypes.AddRole) { - return nil, ErrInvalidPermission{acc.Address, "AddRole"} - } - if len(args) != 2*32 { - return nil, fmt.Errorf("addRole() takes two arguments (address, role)") - } addr, role := returnTwoArgs(args) vmAcc := appState.GetAccount(addr) if vmAcc == nil { @@ -187,12 +173,6 @@ func addRole(appState AppState, acc *Account, args []byte) (output []byte, err e } func rmRole(appState AppState, acc *Account, args []byte) (output []byte, err error) { - if !HasPermission(appState, acc, ptypes.RmRole) { - return nil, ErrInvalidPermission{acc.Address, "RmRole"} - } - if len(args) != 2*32 { - return nil, fmt.Errorf("rmRole() takes two arguments (address, role)") - } addr, role := returnTwoArgs(args) vmAcc := appState.GetAccount(addr) if vmAcc == nil { @@ -232,28 +212,17 @@ func ValidPermN(n ptypes.PermFlag) bool { 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, -} diff --git a/vm/vm.go b/vm/vm.go index bf36356f5..d837abbdf 100644 --- a/vm/vm.go +++ b/vm/vm.go @@ -95,6 +95,46 @@ 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, + }) + } +} + +// call an snative contract (includes event processing) +// addr and permFlag refer to the same snative's address and it's permFlag +func (vm *VM) callSNative(addr Word256, permFlag ptypes.PermFlag, caller *Account, input []byte, value int64, gas *int64) (ret []byte, err error) { + exception := new(string) + // fire the post call event (including exception if applicable) + defer vm.fireEvent(exception, &ret, caller, &Account{Address: addr}, input, value, gas) + + if !HasPermission(vm.appState, caller, permFlag) { + err = ErrInvalidPermission{caller.Address, addr.TrimmedString()} + *exception = err.Error() + return + } + snInfo := getSNativeInfo(permFlag) + if len(input) != snInfo.NArgs*32 { + err = snInfo.ArgsError + *exception = err.Error() + return + } + // SNATIVE ACCESS + ret, err = snInfo.Executable(vm.appState, caller, input) + // END SNATIVE ACCESS + if err != nil { + *exception = err.Error() + } + return +} + // 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 +142,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() @@ -779,19 +797,27 @@ 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} + // nil account means either the address is the name of an snative, + // or we're sending funds to a new account + + // trim the 0s off the address and check if its known + trimmedAddr := addr.TrimmedString() + if permFlag, unknownPermErr := ptypes.SNativeStringToPermFlag(trimmedAddr); unknownPermErr == nil { + ret, err = vm.callSNative(addr, permFlag, callee, input, value, gas) } 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} + vm.appState.UpdateAccount(acc) + // send funds to new account + ret, err = vm.Call(callee, acc, acc.Code, args, value, gas) } - vm.appState.UpdateAccount(acc) + } 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) } }