From f75b6aff7475aeb253d40f4abf09c75f2c7ea6cd Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Thu, 21 May 2015 01:13:55 -0400 Subject: [PATCH] CreateAccount permission through SendTx and CALL (to unknown accounts) --- rpc/core/accounts.go | 1 + state/execution.go | 32 ++++++- state/permissions_test.go | 174 +++++++++++++++++++++++++++++++++++++- vm/vm.go | 5 +- 4 files changed, 206 insertions(+), 6 deletions(-) diff --git a/rpc/core/accounts.go b/rpc/core/accounts.go index 5b27e05ef..43a356f34 100644 --- a/rpc/core/accounts.go +++ b/rpc/core/accounts.go @@ -16,6 +16,7 @@ func GetAccount(address []byte) (*acm.Account, error) { cache := mempoolReactor.Mempool.GetCache() account := cache.GetAccount(address) if account == nil { + // XXX: shouldn't we return "account not found"? account = &acm.Account{ Address: address, PubKey: nil, diff --git a/state/execution.go b/state/execution.go index f2a6d2373..924b8cb2c 100644 --- a/state/execution.go +++ b/state/execution.go @@ -165,6 +165,7 @@ func getOrMakeOutputs(state AccountGetter, accounts map[string]*account.Account, accounts = make(map[string]*account.Account) } + var checkedCreatePerms bool for _, out := range outs { // Account shouldn't be duplicated if _, ok := accounts[string(out.Address)]; ok { @@ -173,6 +174,12 @@ func getOrMakeOutputs(state AccountGetter, accounts map[string]*account.Account, acc := state.GetAccount(out.Address) // output account may be nil (new) if acc == nil { + if !checkedCreatePerms { + if !hasCreateAccountPermission(state, accounts) { + return nil, fmt.Errorf("At least one input does not have permission to create accounts") + } + checkedCreatePerms = true + } acc = &account.Account{ Address: out.Address, PubKey: nil, @@ -312,6 +319,7 @@ func ExecTx(blockCache *BlockCache, tx_ types.Tx, runCall bool, evc events.Firea } // add outputs to accounts map + // if any outputs don't exist, all inputs must have CreateAccount perm accounts, err = getOrMakeOutputs(blockCache, accounts, tx.Outputs) if err != nil { return err @@ -364,7 +372,7 @@ func ExecTx(blockCache *BlockCache, tx_ types.Tx, runCall bool, evc events.Firea createAccount := len(tx.Address) == 0 if createAccount { - if !hasCreatePermission(blockCache, inAcc) { + if !hasCreateContractPermission(blockCache, inAcc) { return fmt.Errorf("Account %X does not have Create permission", tx.Input.Address) } } else { @@ -634,6 +642,17 @@ func ExecTx(blockCache *BlockCache, tx_ types.Tx, runCall bool, evc events.Firea return err } + // add outputs to accounts map + // if any outputs don't exist, all inputs must have CreateAccount perm + // though outputs aren't created until unbonding/release time + canCreate := hasCreateAccountPermission(blockCache, accounts) + for _, out := range tx.UnbondTo { + acc := blockCache.GetAccount(out.Address) + if acc == nil && !canCreate { + return fmt.Errorf("At least one input does not have permission to create accounts") + } + } + bondAcc := blockCache.GetAccount(tx.PubKey.Address()) if !hasBondPermission(blockCache, bondAcc) { return fmt.Errorf("The bonder does not have permission to bond") @@ -821,10 +840,19 @@ func hasCallPermission(state AccountGetter, acc *account.Account) bool { return HasPermission(state, acc, ptypes.Call) } -func hasCreatePermission(state AccountGetter, acc *account.Account) bool { +func hasCreateContractPermission(state AccountGetter, acc *account.Account) bool { return HasPermission(state, acc, ptypes.CreateContract) } +func hasCreateAccountPermission(state AccountGetter, accs map[string]*account.Account) bool { + for _, acc := range accs { + if !HasPermission(state, acc, ptypes.CreateAccount) { + return false + } + } + return true +} + func hasBondPermission(state AccountGetter, acc *account.Account) bool { return HasPermission(state, acc, ptypes.Bond) } diff --git a/state/permissions_test.go b/state/permissions_test.go index c5e3e8a5a..74deb2a4f 100644 --- a/state/permissions_test.go +++ b/state/permissions_test.go @@ -16,7 +16,7 @@ import ( ) /* -To Test: +Permission Tests: - SendTx: x - 1 input, no perm, call perm, create perm @@ -46,7 +46,19 @@ x - 1 bonder with perm, input with send x - 1 bonder with perm, input with bond x - 2 inputs, one with perm one without -- SendTx for new account ? CALL for new account? +- SendTx for new account +x - 1 input, 1 unknown ouput, input with send, not create (fail) +x - 1 input, 1 unknown ouput, input with send and create (pass) +x - 2 inputs, 1 unknown ouput, both inputs with send, one with create, one without (fail) +x - 2 inputs, 1 known output, 1 unknown ouput, one input with create, one without (fail) +x - 2 inputs, 1 unknown ouput, both inputs with send, both inputs with create (pass ) +x - 2 inputs, 1 known output, 1 unknown ouput, both inputs with create, (pass) + + +- CALL for new account +x - unknown output, without create (fail) +x - unknown output, with create (pass) + - Gendoug: - base: has,set,unset @@ -55,7 +67,7 @@ x - 2 inputs, one with perm one without */ // keys -var user = makeUsers(5) +var user = makeUsers(10) func makeUsers(n int) []*account.PrivAccount { accounts := []*account.PrivAccount{} @@ -151,6 +163,22 @@ func TestSendFails(t *testing.T) { } else { fmt.Println(err) } + + // simple send tx to unknown account without create_account perm should fail + acc := blockCache.GetAccount(user[3].Address) + acc.Permissions.Base.Set(ptypes.Send, true) + blockCache.UpdateAccount(acc) + tx = types.NewSendTx() + if err := tx.AddInput(blockCache, user[3].PubKey, 5); err != nil { + t.Fatal(err) + } + tx.AddOutput(user[6].Address, 5) + tx.SignInput(0, user[3]) + if err := ExecTx(blockCache, tx, true, nil); err == nil { + t.Fatal("Expected error") + } else { + fmt.Println(err) + } } func TestCallFails(t *testing.T) { @@ -621,6 +649,146 @@ func TestBondPermission(t *testing.T) { } } +func TestCreateAccountPermission(t *testing.T) { + stateDB := dbm.GetDB("state") + genDoc := newBaseGenDoc(PermsAllFalse, PermsAllFalse) + genDoc.Accounts[0].Permissions.Base.Set(ptypes.Send, true) // give the 0 account permission + genDoc.Accounts[1].Permissions.Base.Set(ptypes.Send, true) // give the 0 account permission + genDoc.Accounts[0].Permissions.Base.Set(ptypes.CreateAccount, true) // give the 0 account permission + st := MakeGenesisState(stateDB, &genDoc) + blockCache := NewBlockCache(st) + + //---------------------------------------------------------- + // SendTx to unknown account + + // A single input, having the permission, should succeed + tx := types.NewSendTx() + if err := tx.AddInput(blockCache, user[0].PubKey, 5); err != nil { + t.Fatal(err) + } + tx.AddOutput(user[6].Address, 5) + tx.SignInput(0, user[0]) + if err := ExecTx(blockCache, tx, true, nil); err != nil { + t.Fatal("Transaction failed", err) + } + + // Two inputs, both with send, one with create, one without, should fail + tx = types.NewSendTx() + if err := tx.AddInput(blockCache, user[0].PubKey, 5); err != nil { + t.Fatal(err) + } + if err := tx.AddInput(blockCache, user[1].PubKey, 5); err != nil { + t.Fatal(err) + } + tx.AddOutput(user[7].Address, 10) + tx.SignInput(0, user[0]) + tx.SignInput(1, user[1]) + if err := ExecTx(blockCache, tx, true, nil); err == nil { + t.Fatal("Expected error") + } else { + fmt.Println(err) + } + + // Two inputs, both with send, one with create, one without, two ouputs (one known, one unknown) should fail + tx = types.NewSendTx() + if err := tx.AddInput(blockCache, user[0].PubKey, 5); err != nil { + t.Fatal(err) + } + if err := tx.AddInput(blockCache, user[1].PubKey, 5); err != nil { + t.Fatal(err) + } + tx.AddOutput(user[7].Address, 4) + tx.AddOutput(user[4].Address, 6) + tx.SignInput(0, user[0]) + tx.SignInput(1, user[1]) + if err := ExecTx(blockCache, tx, true, nil); err == nil { + t.Fatal("Expected error") + } else { + fmt.Println(err) + } + + // Two inputs, both with send, both with create, should pass + acc := blockCache.GetAccount(user[1].Address) + acc.Permissions.Base.Set(ptypes.CreateAccount, true) + blockCache.UpdateAccount(acc) + tx = types.NewSendTx() + if err := tx.AddInput(blockCache, user[0].PubKey, 5); err != nil { + t.Fatal(err) + } + if err := tx.AddInput(blockCache, user[1].PubKey, 5); err != nil { + t.Fatal(err) + } + tx.AddOutput(user[7].Address, 10) + tx.SignInput(0, user[0]) + tx.SignInput(1, user[1]) + if err := ExecTx(blockCache, tx, true, nil); err != nil { + t.Fatal("Unexpected error", err) + } + + // Two inputs, both with send, both with create, two outputs (one known, one unknown) should pass + tx = types.NewSendTx() + if err := tx.AddInput(blockCache, user[0].PubKey, 5); err != nil { + t.Fatal(err) + } + if err := tx.AddInput(blockCache, user[1].PubKey, 5); err != nil { + t.Fatal(err) + } + tx.AddOutput(user[7].Address, 7) + tx.AddOutput(user[4].Address, 3) + tx.SignInput(0, user[0]) + tx.SignInput(1, user[1]) + if err := ExecTx(blockCache, tx, true, nil); err != nil { + t.Fatal("Unexpected error", err) + } + + //---------------------------------------------------------- + // CALL to unknown account + + acc = blockCache.GetAccount(user[0].Address) + acc.Permissions.Base.Set(ptypes.Call, true) + blockCache.UpdateAccount(acc) + + // call to contract that calls unknown account - without create_account perm + // create contract that calls the simple contract + contractCode := callContractCode(user[9].Address) + caller1ContractAddr := NewContractAddress(user[4].Address, 101) + caller1Acc := &account.Account{ + Address: caller1ContractAddr, + Balance: 0, + Code: contractCode, + Sequence: 0, + StorageRoot: Zero256.Bytes(), + Permissions: ptypes.NewAccountPermissions(), + } + blockCache.UpdateAccount(caller1Acc) + + // A single input, having the permission, but the contract doesn't have permission + txCall, _ := types.NewCallTx(blockCache, user[0].PubKey, caller1ContractAddr, nil, 100, 10000, 100) + txCall.Sign(user[0]) + + // we need to subscribe to the Receive event to detect the exception + _, exception := execTxWaitEvent(t, blockCache, txCall, types.EventStringAccReceive(caller1ContractAddr)) // + if exception == "" { + t.Fatal("Expected exception") + } + + // NOTE: for a contract to be able to CreateAccount, it must be able to call + // NOTE: for a user to be able to CreateAccount, it must be able to send! + caller1Acc.Permissions.Base.Set(ptypes.CreateAccount, true) + caller1Acc.Permissions.Base.Set(ptypes.Call, true) + blockCache.UpdateAccount(caller1Acc) + // A single input, having the permission, but the contract doesn't have permission + txCall, _ = types.NewCallTx(blockCache, user[0].PubKey, caller1ContractAddr, nil, 100, 10000, 100) + txCall.Sign(user[0]) + + // we need to subscribe to the Receive event to detect the exception + _, exception = execTxWaitEvent(t, blockCache, txCall, types.EventStringAccReceive(caller1ContractAddr)) // + if exception != "" { + t.Fatal("Unexpected exception", exception) + } + +} + //------------------------------------------------------------------------------------- // helpers diff --git a/vm/vm.go b/vm/vm.go index c28760c7b..cac76c360 100644 --- a/vm/vm.go +++ b/vm/vm.go @@ -701,7 +701,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas case CREATE: // 0xF0 if vm.perms && !vm.HasPermission(callee, ptypes.CreateContract) { - return nil, ErrPermission{"create"} + return nil, ErrPermission{"create_contract"} } contractValue := stack.Pop64() offset, size := stack.Pop64(), stack.Pop64() @@ -780,6 +780,9 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas if acc == nil { // if we have not seen the account before, create it // so we can send funds + if vm.perms && !vm.HasPermission(caller, ptypes.CreateAccount) { + return nil, ErrPermission{"create_account"} + } acc = &Account{ Address: addr, }