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..74242ae48 100644 --- a/permission/types/permissions.go +++ b/permission/types/permissions.go @@ -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. + // chain permissions + Root PermFlag = 1 << iota // 1 + Send // 2 + Call // 4 + CreateContract // 8 + CreateAccount // 16 + Bond // 32 + Name // 64 - TopBasePermFlag PermFlag = 1 << (NumBasePermissions - 1) - AllBasePermFlags PermFlag = TopBasePermFlag | (TopBasePermFlag - 1) - AllPermFlags PermFlag = AllBasePermFlags | AllSNativePermFlags - DefaultBasePermFlags PermFlag = Send | Call | CreateContract | CreateAccount | Bond | Name + // 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) } diff --git a/permission/types/snatives.go b/permission/types/snatives.go index be1d0b249..5057bd42d 100644 --- a/permission/types/snatives.go +++ b/permission/types/snatives.go @@ -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 -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 +//--------------------------------------------------------------------------------------------------- +// PermissionsTx.PermArgs interface and argument encoding - TopSNativePermFlag PermFlag = FirstSNativePermFlag << (NumSNativePermissions - 1) - AllSNativePermFlags PermFlag = (TopSNativePermFlag | (TopSNativePermFlag - 1)) &^ (FirstSNativePermFlag - 1) +// 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 ( + 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 +} diff --git a/state/execution.go b/state/execution.go index e04f92293..7b43c1202 100644 --- a/state/execution.go +++ b/state/execution.go @@ -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} - } 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 + // 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 { + 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 } diff --git a/state/permissions_test.go b/state/permissions_test.go index ad3199a9c..162fb154b 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) @@ -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 + // 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("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("#### 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("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("#### 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("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("#### 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 - }) + 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") + } - // 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 - }) - - 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("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, 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.PermStringToFlag(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.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.NewPermissionsTx(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 +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 diff --git a/types/events.go b/types/events.go index c6d46bcbb..a9a3ff489 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 EventStringPermissions(name string) string { + return fmt.Sprintf("Permissions/%s", name) +} + func EventStringBond() string { return "Bond" } diff --git a/types/tx.go b/types/tx.go index 990b47468..3ebb143e2 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: + - 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) diff --git a/types/tx_test.go b/types/tx_test.go index 8966b2efd..82ba6c757 100644 --- a/types/tx_test.go +++ b/types/tx_test.go @@ -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) + } +} diff --git a/types/tx_utils.go b/types/tx_utils.go index eb7aae126..3750483da 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) } + +//---------------------------------------------------------------------------- +// 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) +} diff --git a/vm/native.go b/vm/native.go index 27f2e8f4b..004c10e9c 100644 --- a/vm/native.go +++ b/vm/native.go @@ -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 { diff --git a/vm/snative.go b/vm/snative.go index 16f45a6d7..2fd3b0f94 100644 --- a/vm/snative.go +++ b/vm/snative.go @@ -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, -} diff --git a/vm/vm.go b/vm/vm.go index bf36356f5..64420699d 100644 --- a/vm/vm.go +++ b/vm/vm.go @@ -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) } }