diff --git a/account/account_test.go b/account/account_test.go index ca3e41fdba161931d24a8610ded01aee33301110..68a5b1eef23b67083ce9295953ca06700763ce50 100644 --- a/account/account_test.go +++ b/account/account_test.go @@ -22,8 +22,7 @@ import ( "fmt" "github.com/hyperledger/burrow/crypto" - "github.com/hyperledger/burrow/permission" - "github.com/hyperledger/burrow/permission/types" + ptypes "github.com/hyperledger/burrow/permission/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/tendermint/go-wire" @@ -79,10 +78,10 @@ func TestAccountSerialise(t *testing.T) { func TestDecodeConcrete(t *testing.T) { concreteAcc := NewConcreteAccountFromSecret("Super Semi Secret") - concreteAcc.Permissions = types.AccountPermissions{ - Base: types.BasePermissions{ - Perms: permission.SetGlobal, - SetBit: permission.SetGlobal, + concreteAcc.Permissions = ptypes.AccountPermissions{ + Base: ptypes.BasePermissions{ + Perms: ptypes.SetGlobal, + SetBit: ptypes.SetGlobal, }, Roles: []string{"bums"}, } diff --git a/account/state/state_cache_test.go b/account/state/state_cache_test.go index 92171155cf68cf2d796a83159c6d1fcb0ee14bb9..6df18eb923680239f1feb1268f39a5d135eac463 100644 --- a/account/state/state_cache_test.go +++ b/account/state/state_cache_test.go @@ -1,15 +1,14 @@ package state import ( - "testing" - "fmt" + "testing" acm "github.com/hyperledger/burrow/account" "github.com/hyperledger/burrow/binary" "github.com/hyperledger/burrow/crypto" "github.com/hyperledger/burrow/execution/evm/asm" - "github.com/hyperledger/burrow/permission" + ptypes "github.com/hyperledger/burrow/permission/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -290,11 +289,11 @@ func TestStateCache_get(t *testing.T) { func testAccounts() *MemoryState { acc1 := acm.NewConcreteAccountFromSecret("acc1") - acc1.Permissions.Base.Perms = permission.AddRole | permission.Send + acc1.Permissions.Base.Perms = ptypes.AddRole | ptypes.Send acc1.Permissions.Base.SetBit = acc1.Permissions.Base.Perms acc2 := acm.NewConcreteAccountFromSecret("acc2") - acc2.Permissions.Base.Perms = permission.AddRole | permission.Send + acc2.Permissions.Base.Perms = ptypes.AddRole | ptypes.Send acc2.Permissions.Base.SetBit = acc1.Permissions.Base.Perms acc2.Code, _ = acm.NewBytecode(asm.PUSH1, 0x20) diff --git a/blockchain/blockchain.go b/blockchain/blockchain.go index ec125ea7b4192f524c2ce39261df74ab65822409..98e48b14a237d2c37a3a2035e42d369521ef237f 100644 --- a/blockchain/blockchain.go +++ b/blockchain/blockchain.go @@ -33,9 +33,7 @@ const DefaultValidatorsWindowSize = 10 var stateKey = []byte("BlockchainState") -type BlockchainInfo interface { - GenesisHash() []byte - GenesisDoc() genesis.GenesisDoc +type TipInfo interface { ChainID() string LastBlockHeight() uint64 LastBlockTime() time.Time @@ -43,6 +41,12 @@ type BlockchainInfo interface { AppHashAfterLastBlock() []byte } +type BlockchainInfo interface { + TipInfo + GenesisHash() []byte + GenesisDoc() genesis.GenesisDoc +} + type Root struct { genesisHash []byte genesisDoc genesis.GenesisDoc diff --git a/client/rpc/client.go b/client/rpc/client.go index e940ee3e15e1a26d442309d04088618ca110c90b..9e5364246cc38ae2975b50f2252391db73ae46be 100644 --- a/client/rpc/client.go +++ b/client/rpc/client.go @@ -19,14 +19,13 @@ import ( "fmt" "strconv" - "github.com/hyperledger/burrow/crypto" - ptypes "github.com/hyperledger/burrow/permission" - "github.com/hyperledger/burrow/txs/payload" - "github.com/hyperledger/burrow/client" + "github.com/hyperledger/burrow/crypto" "github.com/hyperledger/burrow/keys" "github.com/hyperledger/burrow/permission/snatives" + ptypes "github.com/hyperledger/burrow/permission/types" "github.com/hyperledger/burrow/txs" + "github.com/hyperledger/burrow/txs/payload" ) //------------------------------------------------------------------------------------ diff --git a/client/websocket_client.go b/client/websocket_client.go index 81be41982a8cc735f934bcbec2b702dfbbc7a4cb..5d4253441abee05ed3305e8bf81965b62a38fbe2 100644 --- a/client/websocket_client.go +++ b/client/websocket_client.go @@ -22,6 +22,7 @@ import ( "encoding/json" "github.com/hyperledger/burrow/crypto" + "github.com/hyperledger/burrow/execution/errors" exeEvents "github.com/hyperledger/burrow/execution/events" "github.com/hyperledger/burrow/logging" "github.com/hyperledger/burrow/logging/structure" @@ -170,12 +171,12 @@ func (burrowNodeWebsocketClient *burrowNodeWebsocketClient) WaitForConfirmation( continue } - if eventDataTx.Exception != "" { + if eventDataTx.Exception != nil && eventDataTx.Exception.Code != errors.ErrorCodeExecutionReverted { confirmationChannel <- Confirmation{ BlockHash: latestBlockHash, EventDataTx: eventDataTx, - Exception: fmt.Errorf("transaction confirmed but execution gave exception: %v", - eventDataTx.Exception), + Exception: errors.Wrap(eventDataTx.Exception, + "transaction confirmed but execution gave exception: %v"), Error: nil, } return diff --git a/core/kernel.go b/core/kernel.go index eceaec854897465455f7fcc15b709ae21221085d..18f52126913db41dab63b1d77e9511846dbccd39 100644 --- a/core/kernel.go +++ b/core/kernel.go @@ -94,10 +94,10 @@ func NewKernel(ctx context.Context, keyClient keys.KeyClient, privValidator tm_t } tmGenesisDoc := tendermint.DeriveGenesisDoc(genesisDoc) - checker := execution.NewBatchChecker(state, tmGenesisDoc.ChainID, blockchain.Tip, logger) + checker := execution.NewBatchChecker(state, blockchain.Tip, logger) emitter := event.NewEmitter(logger) - committer := execution.NewBatchCommitter(state, tmGenesisDoc.ChainID, blockchain.Tip, emitter, logger, exeOptions...) + committer := execution.NewBatchCommitter(state, blockchain.Tip, emitter, logger, exeOptions...) tmNode, err := tendermint.NewNode(tmConf, privValidator, tmGenesisDoc, blockchain, checker, committer, tmLogger) if err != nil { diff --git a/core/kernel_test.go b/core/kernel_test.go index 68cb5f9dd34264b09d0d65de7c577cf26d55eaa8..d242de8e9946a882c68cd54e894a156168248754 100644 --- a/core/kernel_test.go +++ b/core/kernel_test.go @@ -2,13 +2,11 @@ package core import ( "context" + "fmt" "os" "testing" - "time" - "fmt" - "github.com/hyperledger/burrow/consensus/tendermint" "github.com/hyperledger/burrow/consensus/tendermint/validator" "github.com/hyperledger/burrow/genesis" @@ -16,8 +14,8 @@ import ( "github.com/hyperledger/burrow/logging" "github.com/hyperledger/burrow/rpc" "github.com/stretchr/testify/assert" - tm_config "github.com/tendermint/tendermint/config" - tm_types "github.com/tendermint/tendermint/types" + tmConfig "github.com/tendermint/tendermint/config" + tmTypes "github.com/tendermint/tendermint/types" ) const testDir = "./test_scratch/kernel_test" @@ -26,7 +24,7 @@ func TestBootThenShutdown(t *testing.T) { os.RemoveAll(testDir) os.MkdirAll(testDir, 0777) os.Chdir(testDir) - tmConf := tm_config.DefaultConfig() + tmConf := tmConfig.DefaultConfig() //logger, _, _ := lifecycle.NewStdErrLogger() logger := logging.NewNoopLogger() genesisDoc, _, privateValidators := genesis.NewDeterministicGenesis(123).GenesisDoc(1, true, 1000, 1, true, 1000) @@ -38,7 +36,7 @@ func TestBootShutdownResume(t *testing.T) { os.RemoveAll(testDir) os.MkdirAll(testDir, 0777) os.Chdir(testDir) - tmConf := tm_config.DefaultConfig() + tmConf := tmConfig.DefaultConfig() //logger, _, _ := lifecycle.NewStdErrLogger() logger := logging.NewNoopLogger() genesisDoc, _, privateValidators := genesis.NewDeterministicGenesis(123).GenesisDoc(1, true, 1000, 1, true, 1000) @@ -46,7 +44,7 @@ func TestBootShutdownResume(t *testing.T) { i := int64(1) // asserts we get a consecutive run of blocks - blockChecker := func(block *tm_types.EventDataNewBlock) bool { + blockChecker := func(block *tmTypes.EventDataNewBlock) bool { assert.Equal(t, i, block.Block.Height) i++ // stop every third block @@ -61,9 +59,9 @@ func TestBootShutdownResume(t *testing.T) { assert.Error(t, bootWaitBlocksShutdown(privValidator, genesisDoc, tmConf, logger, blockChecker)) } -func bootWaitBlocksShutdown(privValidator tm_types.PrivValidator, genesisDoc *genesis.GenesisDoc, - tmConf *tm_config.Config, logger *logging.Logger, - blockChecker func(block *tm_types.EventDataNewBlock) (cont bool)) error { +func bootWaitBlocksShutdown(privValidator tmTypes.PrivValidator, genesisDoc *genesis.GenesisDoc, + tmConf *tmConfig.Config, logger *logging.Logger, + blockChecker func(block *tmTypes.EventDataNewBlock) (cont bool)) error { keyStore := keys.NewKeyStore(keys.DefaultKeysDir, false, logger) keyClient := keys.NewLocalKeyClient(keyStore, logging.NewNoopLogger()) @@ -78,7 +76,7 @@ func bootWaitBlocksShutdown(privValidator tm_types.PrivValidator, genesisDoc *ge return err } - ch := make(chan *tm_types.EventDataNewBlock) + ch := make(chan *tmTypes.EventDataNewBlock) tendermint.SubscribeNewBlock(context.Background(), kern.Emitter, "TestBootShutdownResume", ch) cont := true for cont { diff --git a/event/emitter.go b/event/emitter.go index ec9f1e31187c6e436d12ec1396ad57bd55dc3ce1..738104fdea49cdd1b1f15e8709257fa887862e60 100644 --- a/event/emitter.go +++ b/event/emitter.go @@ -42,6 +42,12 @@ type Publisher interface { Publish(ctx context.Context, message interface{}, tags map[string]interface{}) error } +type PublisherFunc func(ctx context.Context, message interface{}, tags map[string]interface{}) error + +func (pf PublisherFunc) Publish(ctx context.Context, message interface{}, tags map[string]interface{}) error { + return pf(ctx, message, tags) +} + type Emitter interface { Subscribable Publisher diff --git a/execution/errors/errors.go b/execution/errors/errors.go new file mode 100644 index 0000000000000000000000000000000000000000..20248d408272f12abf709fcdc61546b68c27dbff --- /dev/null +++ b/execution/errors/errors.go @@ -0,0 +1,142 @@ +package errors + +import ( + "fmt" +) + +type CodedError interface { + error + ErrorCode() ErrorCode +} + +type ErrorCode int8 + +const ( + ErrorCodeGeneric ErrorCode = iota + ErrorCodeUnknownAddress + ErrorCodeInsufficientBalance + ErrorCodeInvalidJumpDest + ErrorCodeInsufficientGas + ErrorCodeMemoryOutOfBounds + ErrorCodeCodeOutOfBounds + ErrorCodeInputOutOfBounds + ErrorCodeReturnDataOutOfBounds + ErrorCodeCallStackOverflow + ErrorCodeCallStackUnderflow + ErrorCodeDataStackOverflow + ErrorCodeDataStackUnderflow + ErrorCodeInvalidContract + ErrorCodeNativeContractCodeCopy + ErrorCodeExecutionAborted + ErrorCodeExecutionReverted + ErrorCodePermissionDenied + ErrorCodeNativeFunction +) + +func (ec ErrorCode) ErrorCode() ErrorCode { + return ec +} + +func (ec ErrorCode) Error() string { + switch ec { + case ErrorCodeUnknownAddress: + return "Unknown address" + case ErrorCodeInsufficientBalance: + return "Insufficient balance" + case ErrorCodeInvalidJumpDest: + return "Invalid jump dest" + case ErrorCodeInsufficientGas: + return "Insufficient gas" + case ErrorCodeMemoryOutOfBounds: + return "Memory out of bounds" + case ErrorCodeCodeOutOfBounds: + return "Code out of bounds" + case ErrorCodeInputOutOfBounds: + return "Input out of bounds" + case ErrorCodeReturnDataOutOfBounds: + return "Return data out of bounds" + case ErrorCodeCallStackOverflow: + return "Call stack overflow" + case ErrorCodeCallStackUnderflow: + return "Call stack underflow" + case ErrorCodeDataStackOverflow: + return "Data stack overflow" + case ErrorCodeDataStackUnderflow: + return "Data stack underflow" + case ErrorCodeInvalidContract: + return "Invalid contract" + case ErrorCodeNativeContractCodeCopy: + return "Tried to copy native contract code" + case ErrorCodeExecutionAborted: + return "Execution aborted" + case ErrorCodeExecutionReverted: + return "Execution reverted" + case ErrorCodeNativeFunction: + return "Native function error" + default: + return "Generic error" + } +} + +// Exception provides a serialisable coded error for the VM +type Exception struct { + Code ErrorCode + Exception string +} + +func NewCodedError(errorCode ErrorCode, exception string) *Exception { + if exception == "" { + return nil + } + return &Exception{ + Code: errorCode, + Exception: exception, + } +} + +// Wraps any error as a Exception +func AsCodedError(err error) *Exception { + if err == nil { + return nil + } + switch e := err.(type) { + case *Exception: + return e + case CodedError: + return NewCodedError(e.ErrorCode(), e.Error()) + default: + return NewCodedError(ErrorCodeGeneric, err.Error()) + } +} + +func Wrap(err CodedError, message string) *Exception { + return NewCodedError(err.ErrorCode(), message+": "+err.Error()) +} + +func Errorf(format string, a ...interface{}) CodedError { + return ErrorCodef(ErrorCodeGeneric, format, a...) +} + +func ErrorCodef(errorCode ErrorCode, format string, a ...interface{}) CodedError { + return NewCodedError(errorCode, fmt.Sprintf(format, a...)) +} + +func (e *Exception) AsError() error { + // thanks go, you dick + if e == nil { + return nil + } + return e +} + +func (e *Exception) ErrorCode() ErrorCode { + return e.Code +} + +func (e *Exception) String() string { + return e.Error() +} + +func (e *Exception) Error() string { + return fmt.Sprintf("VM Error %v: %s", e.Code, e.Exception) +} diff --git a/execution/errors/native.go b/execution/errors/native.go new file mode 100644 index 0000000000000000000000000000000000000000..df35e9a4f1379b37c6368764156e214ed0a70087 --- /dev/null +++ b/execution/errors/native.go @@ -0,0 +1,20 @@ +package errors + +import ( + "fmt" + + "github.com/hyperledger/burrow/crypto" +) + +type LacksSNativePermission struct { + Address crypto.Address + SNative string +} + +func (e LacksSNativePermission) Error() string { + return fmt.Sprintf("account %s does not have SNative function call permission: %s", e.Address, e.SNative) +} + +func (e LacksSNativePermission) ErrorCode() ErrorCode { + return ErrorCodeNativeFunction +} diff --git a/execution/errors/vm.go b/execution/errors/vm.go new file mode 100644 index 0000000000000000000000000000000000000000..d8b164c230fd94e490dd4bd91154fad93a68f9fb --- /dev/null +++ b/execution/errors/vm.go @@ -0,0 +1,60 @@ +package errors + +import ( + "bytes" + "fmt" + + "github.com/hyperledger/burrow/crypto" + "github.com/hyperledger/burrow/permission/types" +) + +type PermissionDenied struct { + Perm types.PermFlag +} + +func (err PermissionDenied) ErrorCode() ErrorCode { + return ErrorCodePermissionDenied +} + +func (err PermissionDenied) Error() string { + return fmt.Sprintf("Contract does not have permission to %v", err.Perm) +} + +type NestedCall struct { + NestedError CodedError + Caller crypto.Address + Callee crypto.Address + StackDepth int +} + +func (err NestedCall) ErrorCode() ErrorCode { + return err.NestedError.ErrorCode() +} + +func (err NestedCall) Error() string { + return fmt.Sprintf("error in nested call at depth %v: %s (callee) -> %s (caller): %v", + err.StackDepth, err.Callee, err.Caller, err.NestedError) +} + +type Call struct { + CallError CodedError + NestedErrors []NestedCall +} + +func (err Call) ErrorCode() ErrorCode { + return err.CallError.ErrorCode() +} + +func (err Call) Error() string { + buf := new(bytes.Buffer) + buf.WriteString("call error: ") + buf.WriteString(err.CallError.Error()) + if len(err.NestedErrors) > 0 { + buf.WriteString(", nested call errors:\n") + for _, nestedErr := range err.NestedErrors { + buf.WriteString(nestedErr.Error()) + buf.WriteByte('\n') + } + } + return buf.String() +} diff --git a/execution/events/events.go b/execution/events/events.go index fd28bfe896d42f7e8910f5cf208028c7c0f658a3..6a7e64faa7e725a2838eb08865c5f2ab266a6440 100644 --- a/execution/events/events.go +++ b/execution/events/events.go @@ -7,6 +7,8 @@ import ( "github.com/hyperledger/burrow/crypto" "github.com/hyperledger/burrow/event" + "github.com/hyperledger/burrow/execution/errors" + ptypes "github.com/hyperledger/burrow/permission/types" "github.com/hyperledger/burrow/txs" "github.com/hyperledger/burrow/txs/payload" "github.com/tmthrgd/go-hex" @@ -15,7 +17,7 @@ import ( func EventStringAccountInput(addr crypto.Address) string { return fmt.Sprintf("Acc/%s/Input", addr) } func EventStringAccountOutput(addr crypto.Address) string { return fmt.Sprintf("Acc/%s/Output", addr) } func EventStringNameReg(name string) string { return fmt.Sprintf("NameReg/%s", name) } -func EventStringPermissions(name string) string { return fmt.Sprintf("Permissions/%s", name) } +func EventStringPermissions(perm ptypes.PermFlag) string { return fmt.Sprintf("Permissions/%v", perm) } func EventStringBond() string { return "Bond" } func EventStringUnbond() string { return "Unbond" } func EventStringRebond() string { return "Rebond" } @@ -24,7 +26,7 @@ func EventStringRebond() string { return "Rebond" } type EventDataTx struct { Tx *txs.Tx Return []byte - Exception string + Exception *errors.Exception } // For re-use @@ -54,7 +56,7 @@ func SubscribeAccountOutputSendTx(ctx context.Context, subscribable event.Subscr } func PublishAccountOutput(publisher event.Publisher, address crypto.Address, tx *txs.Tx, ret []byte, - exception string) error { + exception *errors.Exception) error { return event.PublishWithEventID(publisher, EventStringAccountOutput(address), &EventDataTx{ @@ -70,7 +72,7 @@ func PublishAccountOutput(publisher event.Publisher, address crypto.Address, tx } func PublishAccountInput(publisher event.Publisher, address crypto.Address, tx *txs.Tx, ret []byte, - exception string) error { + exception *errors.Exception) error { return event.PublishWithEventID(publisher, EventStringAccountInput(address), &EventDataTx{ @@ -98,14 +100,14 @@ func PublishNameReg(publisher event.Publisher, tx *txs.Tx) error { }) } -func PublishPermissions(publisher event.Publisher, name string, tx *txs.Tx) error { +func PublishPermissions(publisher event.Publisher, perm ptypes.PermFlag, tx *txs.Tx) error { _, ok := tx.Payload.(*payload.PermissionsTx) if !ok { return fmt.Errorf("Tx payload must be PermissionsTx to PublishPermissions") } - return event.PublishWithEventID(publisher, EventStringPermissions(name), &EventDataTx{Tx: tx}, + return event.PublishWithEventID(publisher, EventStringPermissions(perm), &EventDataTx{Tx: tx}, map[string]interface{}{ - "name": name, + "name": perm.String(), event.TxTypeKey: tx.Type().String(), event.TxHashKey: hex.EncodeUpperToString(tx.Hash()), }) diff --git a/execution/evm/events/events.go b/execution/evm/events/events.go index 9ae628a7a829ac0c858811cd5643910abbffc7da..9095d7687fa84b88a99585bdecf5e7a2d420fa04 100644 --- a/execution/evm/events/events.go +++ b/execution/evm/events/events.go @@ -21,6 +21,7 @@ import ( . "github.com/hyperledger/burrow/binary" "github.com/hyperledger/burrow/crypto" "github.com/hyperledger/burrow/event" + "github.com/hyperledger/burrow/execution/errors" "github.com/tmthrgd/go-hex" ) @@ -38,7 +39,7 @@ type EventDataCall struct { TxHash []byte StackDepth int Return []byte - Exception string + Exception *errors.Exception } type CallData struct { diff --git a/execution/evm/native.go b/execution/evm/native.go index 70f00ed6d2a25c31a42de210f29161f0905cb68b..4a7c7e615141faf3dd92b50fe4ec095d6a8cbdb4 100644 --- a/execution/evm/native.go +++ b/execution/evm/native.go @@ -20,13 +20,15 @@ import ( acm "github.com/hyperledger/burrow/account" "github.com/hyperledger/burrow/account/state" . "github.com/hyperledger/burrow/binary" + "github.com/hyperledger/burrow/crypto" + "github.com/hyperledger/burrow/execution/errors" "github.com/hyperledger/burrow/logging" "golang.org/x/crypto/ripemd160" ) var registeredNativeContracts = make(map[Word256]NativeContract) -func RegisteredNativeContract(address Word256) bool { +func IsRegisteredNativeContract(address Word256) bool { _, ok := registeredNativeContracts[address] return ok } @@ -54,6 +56,21 @@ func registerNativeContracts() { //----------------------------------------------------------------------------- +func ExecuteNativeContract(address Word256, state state.Writer, caller acm.Account, input []byte, gas *uint64, + logger *logging.Logger) ([]byte, errors.CodedError) { + + contract, ok := registeredNativeContracts[address] + if !ok { + return nil, errors.ErrorCodef(errors.ErrorCodeNativeFunction, + "no native contract registered at address: %v", crypto.AddressFromWord256(address)) + } + output, err := contract(state, caller, input, gas, logger) + if err != nil { + return nil, errors.NewCodedError(errors.ErrorCodeNativeFunction, err.Error()) + } + return output, nil +} + type NativeContract func(state state.Writer, caller acm.Account, input []byte, gas *uint64, logger *logging.Logger) (output []byte, err error) @@ -86,7 +103,7 @@ func sha256Func(state state.Writer, caller acm.Account, input []byte, gas *uint6 // Deduct gas gasRequired := uint64((len(input)+31)/32)*GasSha256Word + GasSha256Base if *gas < gasRequired { - return nil, ErrInsufficientGas + return nil, errors.ErrorCodeInsufficientGas } else { *gas -= gasRequired } @@ -102,7 +119,7 @@ func ripemd160Func(state state.Writer, caller acm.Account, input []byte, gas *ui // Deduct gas gasRequired := uint64((len(input)+31)/32)*GasRipemd160Word + GasRipemd160Base if *gas < gasRequired { - return nil, ErrInsufficientGas + return nil, errors.ErrorCodeInsufficientGas } else { *gas -= gasRequired } @@ -118,7 +135,7 @@ func identityFunc(state state.Writer, caller acm.Account, input []byte, gas *uin // Deduct gas gasRequired := uint64((len(input)+31)/32)*GasIdentityWord + GasIdentityBase if *gas < gasRequired { - return nil, ErrInsufficientGas + return nil, errors.ErrorCodeInsufficientGas } else { *gas -= gasRequired } diff --git a/execution/evm/snative.go b/execution/evm/snative.go index 8afbd171c84ed8a05e72c948dcc231958bfd4ee8..7f9c25e57ff60891305b15c58edd02fcb827aed1 100644 --- a/execution/evm/snative.go +++ b/execution/evm/snative.go @@ -23,11 +23,11 @@ import ( "github.com/hyperledger/burrow/account/state" . "github.com/hyperledger/burrow/binary" "github.com/hyperledger/burrow/crypto" + "github.com/hyperledger/burrow/execution/errors" "github.com/hyperledger/burrow/execution/evm/abi" "github.com/hyperledger/burrow/execution/evm/sha3" "github.com/hyperledger/burrow/logging" "github.com/hyperledger/burrow/logging/structure" - "github.com/hyperledger/burrow/permission" ptypes "github.com/hyperledger/burrow/permission/types" ) @@ -98,7 +98,7 @@ func SNativeContracts() map[string]*SNativeContractDescription { abiArg("_role", roleTypeName), }, abiReturn("result", abi.BoolTypeName), - permission.AddRole, + ptypes.AddRole, addRole}, &SNativeFunctionDescription{` @@ -113,7 +113,7 @@ func SNativeContracts() map[string]*SNativeContractDescription { abiArg("_role", roleTypeName), }, abiReturn("result", abi.BoolTypeName), - permission.RemoveRole, + ptypes.RemoveRole, removeRole}, &SNativeFunctionDescription{` @@ -128,7 +128,7 @@ func SNativeContracts() map[string]*SNativeContractDescription { abiArg("_role", roleTypeName), }, abiReturn("result", abi.BoolTypeName), - permission.HasRole, + ptypes.HasRole, hasRole}, &SNativeFunctionDescription{` @@ -145,7 +145,7 @@ func SNativeContracts() map[string]*SNativeContractDescription { abiArg("_set", abi.BoolTypeName), }, abiReturn("result", permFlagTypeName), - permission.SetBase, + ptypes.SetBase, setBase}, &SNativeFunctionDescription{` @@ -159,7 +159,7 @@ func SNativeContracts() map[string]*SNativeContractDescription { abiArg("_account", abi.AddressTypeName), abiArg("_permission", permFlagTypeName)}, abiReturn("result", permFlagTypeName), - permission.UnsetBase, + ptypes.UnsetBase, unsetBase}, &SNativeFunctionDescription{` @@ -173,7 +173,7 @@ func SNativeContracts() map[string]*SNativeContractDescription { abiArg("_account", abi.AddressTypeName), abiArg("_permission", permFlagTypeName)}, abiReturn("result", abi.BoolTypeName), - permission.HasBase, + ptypes.HasBase, hasBase}, &SNativeFunctionDescription{` @@ -187,7 +187,7 @@ func SNativeContracts() map[string]*SNativeContractDescription { abiArg("_permission", permFlagTypeName), abiArg("_set", abi.BoolTypeName)}, abiReturn("result", permFlagTypeName), - permission.SetGlobal, + ptypes.SetGlobal, setGlobal}, ), } @@ -227,15 +227,6 @@ func NewSNativeContract(comment, name string, } } -type ErrLacksSNativePermission struct { - Address crypto.Address - SNative string -} - -func (e ErrLacksSNativePermission) Error() string { - return fmt.Sprintf("account %s does not have SNative function call permission: %s", e.Address, e.SNative) -} - // This function is designed to be called from the EVM once a SNative contract // has been selected. It is also placed in a registry by registerSNativeContracts // So it can be looked up by SNative address @@ -245,8 +236,9 @@ func (contract *SNativeContractDescription) Dispatch(state state.Writer, caller logger = logger.With(structure.ScopeKey, "Dispatch", "contract_name", contract.Name) if len(args) < abi.FunctionSelectorLength { - return nil, fmt.Errorf("SNatives dispatch requires a 4-byte function "+ - "identifier but arguments are only %v bytes long", len(args)) + return nil, errors.ErrorCodef(errors.ErrorCodeNativeFunction, + "SNatives dispatch requires a 4-byte function identifier but arguments are only %v bytes long", + len(args)) } function, err := contract.FunctionByID(abi.FirstFourBytes(args)) @@ -262,12 +254,12 @@ func (contract *SNativeContractDescription) Dispatch(state state.Writer, caller // check if we have permission to call this function if !HasPermission(state, caller, function.PermFlag) { - return nil, ErrLacksSNativePermission{caller.Address(), function.Name} + return nil, errors.LacksSNativePermission{caller.Address(), function.Name} } // ensure there are enough arguments if len(remainingArgs) != function.NArgs()*Word256Length { - return nil, fmt.Errorf("%s() takes %d arguments but got %d (with %d bytes unconsumed - should be 0)", + return nil, errors.ErrorCodef(errors.ErrorCodeNativeFunction, "%s() takes %d arguments but got %d (with %d bytes unconsumed - should be 0)", function.Name, function.NArgs(), len(remainingArgs)/Word256Length, len(remainingArgs)%Word256Length) } @@ -284,11 +276,11 @@ func (contract *SNativeContractDescription) Address() (address crypto.Address) { } // Get function by calling identifier FunctionSelector -func (contract *SNativeContractDescription) FunctionByID(id abi.FunctionSelector) (*SNativeFunctionDescription, error) { +func (contract *SNativeContractDescription) FunctionByID(id abi.FunctionSelector) (*SNativeFunctionDescription, errors.CodedError) { f, ok := contract.functionsByID[id] if !ok { return nil, - fmt.Errorf("unknown SNative function with ID %x", id) + errors.ErrorCodef(errors.ErrorCodeNativeFunction, "unknown SNative function with ID %x", id) } return f, nil } @@ -527,7 +519,7 @@ func removeRole(stateWriter state.Writer, caller acm.Account, args []byte, gas * // Checks if a permission flag is valid (a known base chain or snative permission) func ValidPermN(n ptypes.PermFlag) bool { - return n <= permission.AllPermFlags + return n <= ptypes.AllPermFlags } // Get the global BasePermissions diff --git a/execution/evm/snative_test.go b/execution/evm/snative_test.go index 3f5e5eae9cc0dba28c35f849dd587312b35f65b3..e9b4150db7256f160dede1eaab8e73962fae5cb1 100644 --- a/execution/evm/snative_test.go +++ b/execution/evm/snative_test.go @@ -23,10 +23,11 @@ import ( acm "github.com/hyperledger/burrow/account" . "github.com/hyperledger/burrow/binary" "github.com/hyperledger/burrow/crypto" + "github.com/hyperledger/burrow/execution/errors" "github.com/hyperledger/burrow/execution/evm/abi" "github.com/hyperledger/burrow/execution/evm/asm/bc" "github.com/hyperledger/burrow/execution/evm/sha3" - "github.com/hyperledger/burrow/permission" + permission "github.com/hyperledger/burrow/permission/types" ptypes "github.com/hyperledger/burrow/permission/types" "github.com/stretchr/testify/assert" ) @@ -87,7 +88,7 @@ func TestSNativeContractDescription_Dispatch(t *testing.T) { if !assert.Error(t, err, "Should fail due to lack of permissions") { return } - assert.IsType(t, err, ErrLacksSNativePermission{}) + assert.IsType(t, err, errors.LacksSNativePermission{}) // Grant all permissions and dispatch should success caller.SetPermissions(allAccountPermissions()) diff --git a/execution/evm/stack.go b/execution/evm/stack.go index 3e9dfd5b8987707f68334e6b56f7044bc8fd409c..9a244265c1cf895955982a4f80dc4307a2d88559 100644 --- a/execution/evm/stack.go +++ b/execution/evm/stack.go @@ -20,6 +20,7 @@ import ( "math/big" . "github.com/hyperledger/burrow/binary" + "github.com/hyperledger/burrow/execution/errors" ) // Not goroutine safe @@ -28,10 +29,10 @@ type Stack struct { ptr int gas *uint64 - err *error + err *errors.CodedError } -func NewStack(capacity int, gas *uint64, err *error) *Stack { +func NewStack(capacity int, gas *uint64, err *errors.CodedError) *Stack { return &Stack{ data: make([]Word256, capacity), ptr: 0, @@ -44,11 +45,11 @@ func (st *Stack) useGas(gasToUse uint64) { if *st.gas > gasToUse { *st.gas -= gasToUse } else { - st.setErr(ErrInsufficientGas) + st.setErr(errors.ErrorCodeInsufficientGas) } } -func (st *Stack) setErr(err error) { +func (st *Stack) setErr(err errors.CodedError) { if *st.err == nil { *st.err = err } @@ -57,7 +58,7 @@ func (st *Stack) setErr(err error) { func (st *Stack) Push(d Word256) { st.useGas(GasStackOp) if st.ptr == cap(st.data) { - st.setErr(ErrDataStackOverflow) + st.setErr(errors.ErrorCodeDataStackOverflow) return } st.data[st.ptr] = d @@ -92,7 +93,7 @@ func (st *Stack) PushBigInt(bigInt *big.Int) Word256 { func (st *Stack) Pop() Word256 { st.useGas(GasStackOp) if st.ptr == 0 { - st.setErr(ErrDataStackUnderflow) + st.setErr(errors.ErrorCodeDataStackUnderflow) return Zero256 } st.ptr-- @@ -103,18 +104,18 @@ func (st *Stack) PopBytes() []byte { return st.Pop().Bytes() } -func (st *Stack) Pop64() (int64, error) { +func (st *Stack) Pop64() (int64, errors.CodedError) { d := st.Pop() if Is64BitOverflow(d) { - return 0, fmt.Errorf("int64 overflow from word: %v", d) + return 0, errors.ErrorCodef(errors.ErrorCodeCallStackOverflow, "int64 overflow from word: %v", d) } return Int64FromWord256(d), nil } -func (st *Stack) PopU64() (uint64, error) { +func (st *Stack) PopU64() (uint64, errors.CodedError) { d := st.Pop() if Is64BitOverflow(d) { - return 0, fmt.Errorf("uint64 overflow from word: %v", d) + return 0, errors.ErrorCodef(errors.ErrorCodeCallStackOverflow, "int64 overflow from word: %v", d) } return Uint64FromWord256(d), nil } @@ -135,7 +136,7 @@ func (st *Stack) Len() int { func (st *Stack) Swap(n int) { st.useGas(GasStackOp) if st.ptr < n { - st.setErr(ErrDataStackUnderflow) + st.setErr(errors.ErrorCodeDataStackUnderflow) return } st.data[st.ptr-n], st.data[st.ptr-1] = st.data[st.ptr-1], st.data[st.ptr-n] @@ -144,7 +145,7 @@ func (st *Stack) Swap(n int) { func (st *Stack) Dup(n int) { st.useGas(GasStackOp) if st.ptr < n { - st.setErr(ErrDataStackUnderflow) + st.setErr(errors.ErrorCodeDataStackUnderflow) return } st.Push(st.data[st.ptr-n]) @@ -153,7 +154,7 @@ func (st *Stack) Dup(n int) { // Not an opcode, costs no gas. func (st *Stack) Peek() Word256 { if st.ptr == 0 { - st.setErr(ErrDataStackUnderflow) + st.setErr(errors.ErrorCodeDataStackUnderflow) return Zero256 } return st.data[st.ptr-1] diff --git a/execution/evm/vm.go b/execution/evm/vm.go index 37e9ec0658cc31da92b6979453833772acb7d606..ec4eb07e1e02f96ec6116da7ff978b8474a02efc 100644 --- a/execution/evm/vm.go +++ b/execution/evm/vm.go @@ -16,7 +16,6 @@ package evm import ( "bytes" - "errors" "fmt" "io/ioutil" "math/big" @@ -27,77 +26,19 @@ import ( . "github.com/hyperledger/burrow/binary" "github.com/hyperledger/burrow/crypto" "github.com/hyperledger/burrow/event" + "github.com/hyperledger/burrow/execution/errors" . "github.com/hyperledger/burrow/execution/evm/asm" "github.com/hyperledger/burrow/execution/evm/events" "github.com/hyperledger/burrow/execution/evm/sha3" "github.com/hyperledger/burrow/logging" - "github.com/hyperledger/burrow/permission" ptypes "github.com/hyperledger/burrow/permission/types" ) -var ( - ErrUnknownAddress = errors.New("Unknown address") - ErrInsufficientBalance = errors.New("Insufficient balance") - ErrInvalidJumpDest = errors.New("Invalid jump dest") - ErrInsufficientGas = errors.New("Insufficient gas") - ErrMemoryOutOfBounds = errors.New("Memory out of bounds") - ErrCodeOutOfBounds = errors.New("Code out of bounds") - ErrInputOutOfBounds = errors.New("Input out of bounds") - ErrReturnDataOutOfBounds = errors.New("Return data out of bounds") - ErrCallStackOverflow = errors.New("Call stack overflow") - ErrCallStackUnderflow = errors.New("Call stack underflow") - ErrDataStackOverflow = errors.New("Data stack overflow") - ErrDataStackUnderflow = errors.New("Data stack underflow") - ErrInvalidContract = errors.New("Invalid contract") - ErrNativeContractCodeCopy = errors.New("Tried to copy native contract code") - ErrExecutionAborted = errors.New("Execution aborted") - ErrExecutionReverted = errors.New("Execution reverted") -) - const ( dataStackCapacity = 1024 callStackCapacity = 100 // TODO ensure usage. ) -type ErrPermission struct { - typ string -} - -func (err ErrPermission) Error() string { - return fmt.Sprintf("Contract does not have permission to %s", err.typ) -} - -type ErrNestedCall struct { - NestedError error - Caller crypto.Address - Callee crypto.Address - StackDepth int -} - -func (err ErrNestedCall) Error() string { - return fmt.Sprintf("error in nested call at depth %v: %s (callee) -> %s (caller): %v", - err.StackDepth, err.Callee, err.Caller, err.NestedError) -} - -type ErrCall struct { - CallError error - NestedErrors []ErrNestedCall -} - -func (err ErrCall) Error() string { - buf := new(bytes.Buffer) - buf.WriteString("call error: ") - buf.WriteString(err.CallError.Error()) - if len(err.NestedErrors) > 0 { - buf.WriteString(", nested call errors:\n") - for _, nestedErr := range err.NestedErrors { - buf.WriteString(nestedErr.Error()) - buf.WriteByte('\n') - } - } - return buf.String() -} - type Params struct { BlockHeight uint64 BlockHash Word256 @@ -111,7 +52,7 @@ type VM struct { origin crypto.Address txHash []byte stackDepth int - nestedCallErrors []ErrNestedCall + nestedCallErrors []errors.NestedCall publisher event.Publisher logger *logging.Logger returnData []byte @@ -157,40 +98,41 @@ func HasPermission(stateWriter state.Writer, acc acm.Account, perm ptypes.PermFl return value } -func (vm *VM) fireCallEvent(exception *string, output *[]byte, callerAddress, calleeAddress crypto.Address, input []byte, value uint64, gas *uint64) { +func (vm *VM) fireCallEvent(exception *errors.CodedError, output *[]byte, callerAddress, calleeAddress crypto.Address, input []byte, value uint64, gas *uint64) { // fire the post call event (including exception if applicable) if vm.publisher != nil { - events.PublishAccountCall(vm.publisher, calleeAddress, &events.EventDataCall{ - CallData: &events.CallData{ - Caller: callerAddress, - Callee: calleeAddress, - Data: input, - Value: value, - Gas: *gas, - }, - Origin: vm.origin, - TxHash: vm.txHash, - StackDepth: vm.stackDepth, - Return: *output, - Exception: *exception, - }) + events.PublishAccountCall(vm.publisher, calleeAddress, + &events.EventDataCall{ + CallData: &events.CallData{ + Caller: callerAddress, + Callee: calleeAddress, + Data: input, + Value: value, + Gas: *gas, + }, + Origin: vm.origin, + TxHash: vm.txHash, + StackDepth: vm.stackDepth, + Return: *output, + Exception: errors.AsCodedError(*exception), + }) } } // CONTRACT state is aware of caller and callee, so we can just mutate them. // CONTRACT code and input are not mutated. // CONTRACT returned 'ret' is a new compact slice. -// value: To be transferred from caller to callee. Refunded upon error. +// value: To be transferred from caller to callee. Refunded upon errors.CodedError. // gas: Available gas. No refunds for gas. // code: May be nil, since the CALL opcode may be used to send value from contracts to accounts -func (vm *VM) Call(callState state.Cache, caller, callee acm.MutableAccount, code, input []byte, value uint64, gas *uint64) (output []byte, err error) { +func (vm *VM) Call(callState state.Cache, caller, callee acm.MutableAccount, code, input []byte, value uint64, gas *uint64) (output []byte, err errors.CodedError) { - exception := new(string) + exception := new(errors.CodedError) // fire the post call event (including exception if applicable) defer vm.fireCallEvent(exception, &output, caller.Address(), callee.Address(), input, value, gas) if err = transfer(caller, callee, value); err != nil { - *exception = err.Error() + *exception = err return } //childCallState @@ -201,15 +143,14 @@ func (vm *VM) Call(callState state.Cache, caller, callee acm.MutableAccount, cod output, err = vm.call(childCallState, caller, callee, code, input, value, gas) vm.stackDepth -= 1 if err != nil { - err = ErrCall{ + *exception = errors.Call{ CallError: err, NestedErrors: vm.nestedCallErrors, } - *exception = err.Error() transferErr := transfer(callee, caller, value) if transferErr != nil { - return nil, fmt.Errorf("error transferring value %v %s (callee) -> %s (caller)", - value, callee, caller) + return nil, errors.Wrap(transferErr, + fmt.Sprintf("error transferring value %v %s (callee) -> %s (caller)", value, callee, caller)) } } else { // Copy any state updates from child call frame into current call frame @@ -228,7 +169,7 @@ func (vm *VM) Call(callState state.Cache, caller, callee acm.MutableAccount, cod // The intent of delegate call is to run the code of the callee in the storage context of the caller; // while preserving the original caller to the previous callee. // Different to the normal CALL or CALLCODE, the value does not need to be transferred to the callee. -func (vm *VM) DelegateCall(callState state.Cache, caller acm.Account, callee acm.MutableAccount, code, input []byte, value uint64, gas *uint64) (output []byte, err error) { +func (vm *VM) DelegateCall(callState state.Cache, caller acm.Account, callee acm.MutableAccount, code, input []byte, value uint64, gas *uint64) (output []byte, err errors.CodedError) { exception := new(string) // fire the post call event (including exception if applicable) @@ -257,18 +198,18 @@ func (vm *VM) DelegateCall(callState state.Cache, caller acm.Account, callee acm // Try to deduct gasToUse from gasLeft. If ok return false, otherwise // set err and return true. -func useGasNegative(gasLeft *uint64, gasToUse uint64, err *error) bool { +func useGasNegative(gasLeft *uint64, gasToUse uint64, err *errors.CodedError) bool { if *gasLeft >= gasToUse { *gasLeft -= gasToUse return false } else if *err == nil { - *err = ErrInsufficientGas + *err = errors.ErrorCodeInsufficientGas } return true } // Just like Call() but does not transfer 'value' or modify the callDepth. -func (vm *VM) call(callState state.Cache, caller acm.Account, callee acm.MutableAccount, code, input []byte, value uint64, gas *uint64) (output []byte, err error) { +func (vm *VM) call(callState state.Cache, caller acm.Account, callee acm.MutableAccount, code, input []byte, value uint64, gas *uint64) (output []byte, err errors.CodedError) { vm.Debugf("(%d) (%X) %X (code=%d) gas: %v (d) %X\n", vm.stackDepth, caller.Address().Bytes()[:4], callee.Address(), len(callee.Code()), *gas, input) @@ -555,7 +496,7 @@ func (vm *VM) call(callState state.Cache, caller acm.Account, callee acm.Mutable data, memErr := memory.Read(offset, size) if memErr != nil { vm.Debugf(" => Memory err: %s", memErr) - return nil, firstErr(err, ErrMemoryOutOfBounds) + return nil, firstErr(err, errors.ErrorCodeMemoryOutOfBounds) } data = sha3.Sha3(data) stack.PushBytes(data) @@ -575,7 +516,7 @@ func (vm *VM) call(callState state.Cache, caller acm.Account, callee acm.Mutable return nil, firstErr(err, errAcc) } if acc == nil { - return nil, firstErr(err, ErrUnknownAddress) + return nil, firstErr(err, errors.ErrorCodeUnknownAddress) } balance := acc.Balance() stack.PushU64(balance) @@ -600,7 +541,7 @@ func (vm *VM) call(callState state.Cache, caller acm.Account, callee acm.Mutable } data, ok := subslice(input, offset, 32) if !ok { - return nil, firstErr(err, ErrInputOutOfBounds) + return nil, firstErr(err, errors.ErrorCodeInputOutOfBounds) } res := LeftPadWord256(data) stack.Push(res) @@ -622,12 +563,12 @@ func (vm *VM) call(callState state.Cache, caller acm.Account, callee acm.Mutable } data, ok := subslice(input, inputOff, length) if !ok { - return nil, firstErr(err, ErrInputOutOfBounds) + return nil, firstErr(err, errors.ErrorCodeInputOutOfBounds) } memErr := memory.Write(memOff, data) if memErr != nil { vm.Debugf(" => Memory err: %s", memErr) - return nil, firstErr(err, ErrMemoryOutOfBounds) + return nil, firstErr(err, errors.ErrorCodeMemoryOutOfBounds) } vm.Debugf(" => [%v, %v, %v] %X\n", memOff, inputOff, length, data) @@ -648,12 +589,12 @@ func (vm *VM) call(callState state.Cache, caller acm.Account, callee acm.Mutable } data, ok := subslice(code, codeOff, length) if !ok { - return nil, firstErr(err, ErrCodeOutOfBounds) + return nil, firstErr(err, errors.ErrorCodeCodeOutOfBounds) } memErr := memory.Write(memOff, data) if memErr != nil { vm.Debugf(" => Memory err: %s", memErr) - return nil, firstErr(err, ErrMemoryOutOfBounds) + return nil, firstErr(err, errors.ErrorCodeMemoryOutOfBounds) } vm.Debugf(" => [%v, %v, %v] %X\n", memOff, codeOff, length, data) @@ -672,7 +613,7 @@ func (vm *VM) call(callState state.Cache, caller acm.Account, callee acm.Mutable } if acc == nil { if _, ok := registeredNativeContracts[addr]; !ok { - return nil, firstErr(err, ErrUnknownAddress) + return nil, firstErr(err, errors.ErrorCodeUnknownAddress) } vm.Debugf(" => returning code size of 1 to indicated existence of native contract at %X\n", addr) stack.Push(One256) @@ -694,9 +635,9 @@ func (vm *VM) call(callState state.Cache, caller acm.Account, callee acm.Mutable if acc == nil { if _, ok := registeredNativeContracts[addr]; ok { vm.Debugf(" => attempted to copy native contract at %X but this is not supported\n", addr) - return nil, firstErr(err, ErrNativeContractCodeCopy) + return nil, firstErr(err, errors.ErrorCodeNativeContractCodeCopy) } - return nil, firstErr(err, ErrUnknownAddress) + return nil, firstErr(err, errors.ErrorCodeUnknownAddress) } code := acc.Code() memOff := stack.PopBigInt() @@ -710,12 +651,12 @@ func (vm *VM) call(callState state.Cache, caller acm.Account, callee acm.Mutable } data, ok := subslice(code, codeOff, length) if !ok { - return nil, firstErr(err, ErrCodeOutOfBounds) + return nil, firstErr(err, errors.ErrorCodeCodeOutOfBounds) } memErr := memory.Write(memOff, data) if memErr != nil { vm.Debugf(" => Memory err: %s", memErr) - return nil, firstErr(err, ErrMemoryOutOfBounds) + return nil, firstErr(err, errors.ErrorCodeMemoryOutOfBounds) } vm.Debugf(" => [%v, %v, %v] %X\n", memOff, codeOff, length, data) @@ -729,7 +670,7 @@ func (vm *VM) call(callState state.Cache, caller acm.Account, callee acm.Mutable end := new(big.Int).Add(outputOff, length) if end.BitLen() > 64 || uint64(len(vm.returnData)) < end.Uint64() { - return nil, ErrReturnDataOutOfBounds + return nil, errors.ErrorCodeReturnDataOutOfBounds } data := vm.returnData @@ -737,7 +678,7 @@ func (vm *VM) call(callState state.Cache, caller acm.Account, callee acm.Mutable memErr := memory.Write(memOff, data) if memErr != nil { vm.Debugf(" => Memory err: %s", memErr) - return nil, firstErr(err, ErrMemoryOutOfBounds) + return nil, firstErr(err, errors.ErrorCodeMemoryOutOfBounds) } vm.Debugf(" => [%v, %v, %v] %X\n", memOff, outputOff, length, data) @@ -772,7 +713,7 @@ func (vm *VM) call(callState state.Cache, caller acm.Account, callee acm.Mutable data, memErr := memory.Read(offset, BigWord256Length) if memErr != nil { vm.Debugf(" => Memory err: %s", memErr) - return nil, firstErr(err, ErrMemoryOutOfBounds) + return nil, firstErr(err, errors.ErrorCodeMemoryOutOfBounds) } stack.Push(LeftPadWord256(data)) vm.Debugf(" => 0x%X @ 0x%X\n", data, offset) @@ -782,7 +723,7 @@ func (vm *VM) call(callState state.Cache, caller acm.Account, callee acm.Mutable memErr := memory.Write(offset, data.Bytes()) if memErr != nil { vm.Debugf(" => Memory err: %s", memErr) - return nil, firstErr(err, ErrMemoryOutOfBounds) + return nil, firstErr(err, errors.ErrorCodeMemoryOutOfBounds) } vm.Debugf(" => 0x%X @ 0x%X\n", data, offset) @@ -796,7 +737,7 @@ func (vm *VM) call(callState state.Cache, caller acm.Account, callee acm.Mutable memErr := memory.Write(offset, []byte{val}) if memErr != nil { vm.Debugf(" => Memory err: %s", memErr) - return nil, firstErr(err, ErrMemoryOutOfBounds) + return nil, firstErr(err, errors.ErrorCodeMemoryOutOfBounds) } vm.Debugf(" => [%v] 0x%X\n", offset, val) @@ -867,7 +808,7 @@ func (vm *VM) call(callState state.Cache, caller acm.Account, callee acm.Mutable a := int64(op - PUSH1 + 1) codeSegment, ok := subslice(code, pc+1, a) if !ok { - return nil, firstErr(err, ErrCodeOutOfBounds) + return nil, firstErr(err, errors.ErrorCodeCodeOutOfBounds) } res := LeftPadWord256(codeSegment) stack.Push(res) @@ -896,7 +837,7 @@ func (vm *VM) call(callState state.Cache, caller acm.Account, callee acm.Mutable data, memErr := memory.Read(offset, size) if memErr != nil { vm.Debugf(" => Memory err: %s", memErr) - return nil, firstErr(err, ErrMemoryOutOfBounds) + return nil, firstErr(err, errors.ErrorCodeMemoryOutOfBounds) } if vm.publisher != nil { events.PublishLogEvent(vm.publisher, callee.Address(), &events.EventDataLog{ @@ -911,8 +852,8 @@ func (vm *VM) call(callState state.Cache, caller acm.Account, callee acm.Mutable case CREATE: // 0xF0 vm.returnData = nil - if !HasPermission(callState, callee, permission.CreateContract) { - return nil, ErrPermission{"create_contract"} + if !HasPermission(callState, callee, ptypes.CreateContract) { + return nil, errors.PermissionDenied{Perm: ptypes.CreateContract} } contractValue, popErr := stack.PopU64() if popErr != nil { @@ -922,16 +863,16 @@ func (vm *VM) call(callState state.Cache, caller acm.Account, callee acm.Mutable input, memErr := memory.Read(offset, size) if memErr != nil { vm.Debugf(" => Memory err: %s", memErr) - return nil, firstErr(err, ErrMemoryOutOfBounds) + return nil, firstErr(err, errors.ErrorCodeMemoryOutOfBounds) } // Check balance if callee.Balance() < uint64(contractValue) { - return nil, firstErr(err, ErrInsufficientBalance) + return nil, firstErr(err, errors.ErrorCodeInsufficientBalance) } // TODO charge for gas to create account _ the code length * GasCreateByte - var gasErr error + var gasErr errors.CodedError if useGasNegative(gas, GasCreateAccount, &gasErr) { return nil, firstErr(err, gasErr) } @@ -942,24 +883,24 @@ func (vm *VM) call(callState state.Cache, caller acm.Account, callee acm.Mutable // Run the input to get the contract code. // NOTE: no need to copy 'input' as per Call contract. - ret, err_ := vm.Call(callState, callee, newAccount, input, input, contractValue, gas) - if err_ != nil { + ret, callErr := vm.Call(callState, callee, newAccount, input, input, contractValue, gas) + if callErr != nil { stack.Push(Zero256) + // Note we both set the return buffer and return the result normally vm.returnData = ret + if callErr.ErrorCode() == errors.ErrorCodeExecutionReverted { + return ret, callErr + } } else { newAccount.SetCode(ret) // Set the code (ret need not be copied as per Call contract) stack.Push(newAccount.Address().Word256()) } - if err_ == ErrExecutionReverted { - return nil, ErrExecutionReverted - } - case CALL, CALLCODE, DELEGATECALL: // 0xF1, 0xF2, 0xF4 vm.returnData = nil - if !HasPermission(callState, callee, permission.Call) { - return nil, ErrPermission{"call"} + if !HasPermission(callState, callee, ptypes.Call) { + return nil, errors.PermissionDenied{Perm: ptypes.Call} } gasLimit, popErr := stack.PopU64() if popErr != nil { @@ -991,12 +932,12 @@ func (vm *VM) call(callState state.Cache, caller acm.Account, callee acm.Mutable args, memErr := memory.Read(inOffset, inSize) if memErr != nil { vm.Debugf(" => Memory err: %s", memErr) - return nil, firstErr(err, ErrMemoryOutOfBounds) + return nil, firstErr(err, errors.ErrorCodeMemoryOutOfBounds) } // Ensure that gasLimit is reasonable if *gas < gasLimit { - // EIP150 - the 63/64 rule - rather than error we pass this specified fraction of the total available gas + // EIP150 - the 63/64 rule - rather than errors.CodedError we pass this specified fraction of the total available gas gasLimit = *gas - *gas/64 } // NOTE: we will return any used gas later. @@ -1004,19 +945,14 @@ func (vm *VM) call(callState state.Cache, caller acm.Account, callee acm.Mutable // Begin execution var ret []byte - var callErr error + var callErr errors.CodedError - if nativeContract := registeredNativeContracts[addr]; nativeContract != nil { + if IsRegisteredNativeContract(addr) { // Native contract - ret, callErr = nativeContract(callState, callee, args, &gasLimit, logger) - + ret, callErr = ExecuteNativeContract(addr, callState, callee, args, &gasLimit, logger) // for now we fire the Call event. maybe later we'll fire more particulars - var exception string - if callErr != nil { - exception = callErr.Error() - } // NOTE: these fire call go_events and not particular go_events for eg name reg or permissions - vm.fireCallEvent(&exception, &ret, callee.Address(), crypto.AddressFromWord256(addr), args, value, &gasLimit) + vm.fireCallEvent(&callErr, &ret, callee.Address(), crypto.AddressFromWord256(addr), args, value, &gasLimit) } else { // EVM contract if useGasNegative(gas, GasGetAccount, &callErr) { @@ -1027,24 +963,24 @@ func (vm *VM) call(callState state.Cache, caller acm.Account, callee acm.Mutable return nil, firstErr(callErr, errAcc) } // since CALL is used also for sending funds, - // acc may not exist yet. This is an error for + // acc may not exist yet. This is an errors.CodedError for // CALLCODE, but not for CALL, though I don't think // ethereum actually cares if op == CALLCODE { if acc == nil { - return nil, firstErr(callErr, ErrUnknownAddress) + return nil, firstErr(callErr, errors.ErrorCodeUnknownAddress) } ret, callErr = vm.Call(callState, callee, callee, acc.Code(), args, value, &gasLimit) } else if op == DELEGATECALL { if acc == nil { - return nil, firstErr(callErr, ErrUnknownAddress) + return nil, firstErr(callErr, errors.ErrorCodeUnknownAddress) } ret, callErr = vm.DelegateCall(callState, caller, callee, acc.Code(), args, value, &gasLimit) } else { // nil account means we're sending funds to a new account if acc == nil { - if !HasPermission(callState, caller, permission.CreateAccount) { - return nil, ErrPermission{"create_account"} + if !HasPermission(callState, caller, ptypes.CreateAccount) { + return nil, errors.PermissionDenied{Perm: ptypes.CreateAccount} } acc = acm.ConcreteAccount{Address: crypto.AddressFromWord256(addr)}.MutableAccount() } @@ -1070,8 +1006,8 @@ func (vm *VM) call(callState state.Cache, caller acm.Account, callee acm.Mutable // Push result if callErr != nil { vm.Debugf("error from nested sub-call (depth: %v): %s\n", vm.stackDepth, callErr.Error()) - // So we can return nested error if the top level return is an error - vm.nestedCallErrors = append(vm.nestedCallErrors, ErrNestedCall{ + // So we can return nested errors.CodedError if the top level return is an errors.CodedError + vm.nestedCallErrors = append(vm.nestedCallErrors, errors.NestedCall{ NestedError: callErr, StackDepth: vm.stackDepth, Caller: caller.Address(), @@ -1079,7 +1015,7 @@ func (vm *VM) call(callState state.Cache, caller acm.Account, callee acm.Mutable }) stack.Push(Zero256) - if callErr == ErrExecutionReverted { + if callErr.ErrorCode() == errors.ErrorCodeExecutionReverted { memory.Write(retOffset, RightPadBytes(ret, int(retSize))) } } else { @@ -1091,7 +1027,7 @@ func (vm *VM) call(callState state.Cache, caller acm.Account, callee acm.Mutable memErr := memory.Write(retOffset, RightPadBytes(ret, int(retSize))) if memErr != nil { vm.Debugf(" => Memory err: %s", memErr) - return nil, firstErr(callErr, ErrMemoryOutOfBounds) + return nil, firstErr(callErr, errors.ErrorCodeMemoryOutOfBounds) } } @@ -1105,7 +1041,7 @@ func (vm *VM) call(callState state.Cache, caller acm.Account, callee acm.Mutable output, memErr := memory.Read(offset, size) if memErr != nil { vm.Debugf(" => Memory err: %s", memErr) - return nil, firstErr(err, ErrMemoryOutOfBounds) + return nil, firstErr(err, errors.ErrorCodeMemoryOutOfBounds) } vm.Debugf(" => [%v, %v] (%d) 0x%X\n", offset, size, len(output), output) return output, nil @@ -1115,14 +1051,14 @@ func (vm *VM) call(callState state.Cache, caller acm.Account, callee acm.Mutable output, memErr := memory.Read(offset, size) if memErr != nil { vm.Debugf(" => Memory err: %s", memErr) - return nil, firstErr(err, ErrMemoryOutOfBounds) + return nil, firstErr(err, errors.ErrorCodeMemoryOutOfBounds) } vm.Debugf(" => [%v, %v] (%d) 0x%X\n", offset, size, len(output), output) - return output, ErrExecutionReverted + return output, errors.ErrorCodeExecutionReverted case INVALID: //0xFE - return nil, ErrExecutionAborted + return nil, errors.ErrorCodeExecutionAborted case SELFDESTRUCT: // 0xFF addr := stack.Pop() @@ -1134,14 +1070,14 @@ func (vm *VM) call(callState state.Cache, caller acm.Account, callee acm.Mutable return nil, firstErr(err, errAcc) } if receiver == nil { - var gasErr error + var gasErr errors.CodedError if useGasNegative(gas, GasCreateAccount, &gasErr) { return nil, firstErr(err, gasErr) } - if !HasPermission(callState, callee, permission.CreateContract) { - return nil, firstErr(err, ErrPermission{"create_contract"}) + if !HasPermission(callState, callee, ptypes.CreateContract) { + return nil, firstErr(err, errors.PermissionDenied{Perm: ptypes.CreateContract}) } - var createErr error + var createErr errors.CodedError receiver, createErr = vm.createAccount(callState, callee, logger) if createErr != nil { return nil, firstErr(err, createErr) @@ -1162,25 +1098,25 @@ func (vm *VM) call(callState state.Cache, caller acm.Account, callee acm.Mutable return nil, nil case STATICCALL, CREATE2: - return nil, fmt.Errorf("%s not yet implemented", op.Name()) + return nil, errors.Errorf("%s not yet implemented", op.Name()) default: vm.Debugf("(pc) %-3v Unknown opcode %X\n", pc, op) - return nil, fmt.Errorf("unknown opcode %X", op) + return nil, errors.Errorf("unknown opcode %X", op) } pc++ } } -func (vm *VM) createAccount(callState state.Cache, callee acm.MutableAccount, logger *logging.Logger) (acm.MutableAccount, error) { +func (vm *VM) createAccount(callState state.Cache, callee acm.MutableAccount, logger *logging.Logger) (acm.MutableAccount, errors.CodedError) { newAccount := DeriveNewAccount(callee, state.GlobalAccountPermissions(callState), logger) err := callState.UpdateAccount(newAccount) if err != nil { - return nil, err + return nil, errors.AsCodedError(err) } err = callState.UpdateAccount(callee) if err != nil { - return nil, err + return nil, errors.AsCodedError(err) } return newAccount, nil } @@ -1216,33 +1152,33 @@ func codeGetOp(code []byte, n int64) OpCode { } } -func (vm *VM) jump(code []byte, to int64, pc *int64) (err error) { +func (vm *VM) jump(code []byte, to int64, pc *int64) (err errors.CodedError) { dest := codeGetOp(code, to) if dest != JUMPDEST { vm.Debugf(" ~> %v invalid jump dest %v\n", to, dest) - return ErrInvalidJumpDest + return errors.ErrorCodeInvalidJumpDest } vm.Debugf(" ~> %v\n", to) *pc = to return nil } -func firstErr(errA, errB error) error { +func firstErr(errA, errB error) errors.CodedError { if errA != nil { - return errA + return errors.AsCodedError(errA) } else { - return errB + return errors.AsCodedError(errB) } } -func transfer(from, to acm.MutableAccount, amount uint64) error { +func transfer(from, to acm.MutableAccount, amount uint64) errors.CodedError { if from.Balance() < amount { - return ErrInsufficientBalance + return errors.ErrorCodeInsufficientBalance } else { from.SubtractFromBalance(amount) _, err := to.AddToBalance(amount) if err != nil { - return err + return errors.AsCodedError(err) } } return nil diff --git a/execution/evm/vm_test.go b/execution/evm/vm_test.go index fcd008459d25238492445e8acf065ef7dce91b8a..b7752b66c324ece4cc1d0f97d4f497934084e3cb 100644 --- a/execution/evm/vm_test.go +++ b/execution/evm/vm_test.go @@ -17,7 +17,6 @@ package evm import ( "context" "encoding/hex" - "errors" "fmt" "strconv" "testing" @@ -28,6 +27,7 @@ import ( . "github.com/hyperledger/burrow/binary" "github.com/hyperledger/burrow/crypto" "github.com/hyperledger/burrow/event" + "github.com/hyperledger/burrow/execution/errors" . "github.com/hyperledger/burrow/execution/evm/asm" . "github.com/hyperledger/burrow/execution/evm/asm/bc" evm_events "github.com/hyperledger/burrow/execution/evm/events" @@ -663,8 +663,8 @@ func TestRevert(t *testing.T) { 0x67, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, PUSH1, 0x00, MSTORE, PUSH1, 0x0E, PUSH1, 0x00, REVERT)*/ - output, err := ourVm.Call(cache, account1, account2, bytecode, []byte{}, 0, &gas) - assert.Error(t, err, "Expected execution reverted error") + output, cErr := ourVm.Call(cache, account1, account2, bytecode, []byte{}, 0, &gas) + assert.Error(t, cErr, "Expected execution reverted error") storageVal, err := cache.GetStorage(account1.Address(), LeftPadWord256(key)) assert.Equal(t, LeftPadWord256(value), storageVal) @@ -884,7 +884,7 @@ func TestInvalid(t *testing.T) { 0x00, 0x00, 0x00, PUSH1, 0x00, MSTORE, PUSH1, 0x0E, PUSH1, 0x00, INVALID) output, err := ourVm.Call(cache, account1, account2, bytecode, []byte{}, 0, &gas) - expected := "call error: " + ErrExecutionAborted.Error() + expected := errors.ErrorCodeExecutionAborted.Error() assert.EqualError(t, err, expected) t.Logf("Output: %v Error: %v\n", output, err) @@ -1021,8 +1021,8 @@ func runVMWaitError(vmCache state.Cache, ourVm *VM, caller, callee acm.MutableAc } select { case eventDataCall := <-eventCh: - if eventDataCall.Exception != "" { - return output, errors.New(eventDataCall.Exception) + if eventDataCall.Exception != nil { + return output, eventDataCall.Exception } return output, nil } diff --git a/execution/execution.go b/execution/execution.go index 74eab81fe51fae147219e0fbcd3bc4935a478cc7..74e6ce5fc1a97eb9ae198bbf665dab18d96357ce 100644 --- a/execution/execution.go +++ b/execution/execution.go @@ -25,26 +25,25 @@ import ( bcm "github.com/hyperledger/burrow/blockchain" "github.com/hyperledger/burrow/crypto" "github.com/hyperledger/burrow/event" - "github.com/hyperledger/burrow/execution/events" "github.com/hyperledger/burrow/execution/evm" + "github.com/hyperledger/burrow/execution/executors" "github.com/hyperledger/burrow/execution/names" "github.com/hyperledger/burrow/logging" "github.com/hyperledger/burrow/logging/structure" - "github.com/hyperledger/burrow/permission" - ptypes "github.com/hyperledger/burrow/permission/types" "github.com/hyperledger/burrow/txs" "github.com/hyperledger/burrow/txs/payload" ) -// TODO: make configurable -const GasLimit = uint64(1000000) +type Executor interface { + Execute(txEnv *txs.Envelope) error +} type BatchExecutor interface { // Provides access to write lock for a BatchExecutor so reads can be prevented for the duration of a commit sync.Locker state.Reader // Execute transaction against block cache (i.e. block buffer) - Execute(txEnv *txs.Envelope) error + Executor // Reset executor to underlying State Reset() error } @@ -59,55 +58,105 @@ type BatchCommitter interface { type executor struct { sync.RWMutex - chainID string - tip *bcm.Tip runCall bool state *State stateCache state.Cache - nameRegCache *NameRegCache - publisher event.Publisher + nameRegCache *names.Cache eventCache *event.Cache logger *logging.Logger vmOptions []func(*evm.VM) + txExecutors map[payload.Type]Executor } var _ BatchExecutor = (*executor)(nil) // Wraps a cache of what is variously known as the 'check cache' and 'mempool' -func NewBatchChecker(backend *State, chainID string, tip *bcm.Tip, logger *logging.Logger, +func NewBatchChecker(backend *State, tip *bcm.Tip, logger *logging.Logger, options ...ExecutionOption) BatchExecutor { - return newExecutor("CheckCache", false, backend, chainID, tip, event.NewNoOpPublisher(), + return newExecutor("CheckCache", false, backend, tip, event.NewNoOpPublisher(), logger.WithScope("NewBatchExecutor"), options...) } -func NewBatchCommitter(backend *State, chainID string, tip *bcm.Tip, publisher event.Publisher, logger *logging.Logger, +func NewBatchCommitter(backend *State, tip *bcm.Tip, publisher event.Publisher, logger *logging.Logger, options ...ExecutionOption) BatchCommitter { - return newExecutor("CommitCache", true, backend, chainID, tip, publisher, + return newExecutor("CommitCache", true, backend, tip, publisher, logger.WithScope("NewBatchCommitter"), options...) } -func newExecutor(name string, runCall bool, backend *State, chainID string, tip *bcm.Tip, publisher event.Publisher, +func newExecutor(name string, runCall bool, backend *State, tip *bcm.Tip, publisher event.Publisher, logger *logging.Logger, options ...ExecutionOption) *executor { exe := &executor{ - chainID: chainID, - tip: tip, runCall: runCall, state: backend, stateCache: state.NewCache(backend, state.Name(name)), - nameRegCache: NewNameRegCache(backend), - publisher: publisher, eventCache: event.NewEventCache(publisher), + nameRegCache: names.NewCache(backend), logger: logger.With(structure.ComponentKey, "Executor"), } for _, option := range options { option(exe) } + exe.txExecutors = map[payload.Type]Executor{ + payload.TypeSend: &executors.SendContext{ + StateWriter: exe.stateCache, + EventPublisher: exe.eventCache, + Logger: exe.logger, + }, + payload.TypeCall: &executors.CallContext{ + StateWriter: exe.stateCache, + EventPublisher: exe.eventCache, + Tip: tip, + RunCall: runCall, + VMOptions: exe.vmOptions, + Logger: exe.logger, + }, + payload.TypeName: &executors.NameContext{ + StateWriter: exe.stateCache, + EventPublisher: exe.eventCache, + NameReg: exe.nameRegCache, + Tip: tip, + Logger: exe.logger, + }, + payload.TypePermissions: &executors.PermissionsContext{ + StateWriter: exe.stateCache, + EventPublisher: exe.eventCache, + Logger: exe.logger, + }, + } return exe } +// If the tx is invalid, an error will be returned. +// Unlike ExecBlock(), state will not be altered. +func (exe *executor) Execute(txEnv *txs.Envelope) (err error) { + defer func() { + if r := recover(); r != nil { + err = fmt.Errorf("recovered from panic in executor.Execute(%s): %v\n%s", txEnv.String(), r, + debug.Stack()) + } + }() + + logger := exe.logger.WithScope("executor.Execute(tx txs.Tx)").With( + "run_call", exe.runCall, + "tx_hash", txEnv.Tx.Hash()) + + logger.TraceMsg("Executing transaction", "tx", txEnv.String()) + + // Verify transaction signature against inputs + err = txEnv.Verify(exe.stateCache) + if err != nil { + return err + } + + if txExecutor, ok := exe.txExecutors[txEnv.Tx.Type()]; ok { + return txExecutor.Execute(txEnv) + } + return fmt.Errorf("unknown transaction type: %v", txEnv.Tx.Type()) +} + // executor exposes access to the underlying state cache protected by a RWMutex that prevents access while locked // (during an ABCI commit). while access can occur (and needs to continue for CheckTx/DeliverTx to make progress) // through calls to Execute() external readers will be blocked until the executor is unlocked that allows the Transactor @@ -160,1094 +209,3 @@ func (exe *executor) Reset() error { exe.nameRegCache.Reset(exe.state) return nil } - -// If the tx is invalid, an error will be returned. -// Unlike ExecBlock(), state will not be altered. -func (exe *executor) Execute(txEnv *txs.Envelope) (err error) { - defer func() { - if r := recover(); r != nil { - err = fmt.Errorf("recovered from panic in executor.Execute(%s): %v\n%s", txEnv.String(), r, - debug.Stack()) - } - }() - - logger := exe.logger.WithScope("executor.Execute(tx txs.Tx)").With( - "run_call", exe.runCall, - "tx_hash", txEnv.Tx.Hash()) - logger.TraceMsg("Executing transaction", "tx", txEnv.String()) - // TODO: do something with fees - fees := uint64(0) - - // Verify transaction signature against inputs - err = txEnv.Verify(exe.stateCache) - if err != nil { - return err - } - - // Exec tx - switch tx := txEnv.Tx.Payload.(type) { - case *payload.SendTx: - accounts, err := getInputs(exe.stateCache, tx.Inputs) - if err != nil { - return err - } - - // ensure all inputs have send permissions - if !hasSendPermission(exe.stateCache, accounts, logger) { - return fmt.Errorf("at least one input lacks permission for SendTx") - } - - // add outputs to accounts map - // if any outputs don't exist, all inputs must have CreateAccount perm - accounts, err = getOrMakeOutputs(exe.stateCache, accounts, tx.Outputs, logger) - if err != nil { - return err - } - - inTotal, err := validateInputs(accounts, tx.Inputs) - if err != nil { - return err - } - outTotal, err := validateOutputs(tx.Outputs) - if err != nil { - return err - } - if outTotal > inTotal { - return payload.ErrTxInsufficientFunds - } - fee := inTotal - outTotal - fees += fee - - // Good! Adjust accounts - err = adjustByInputs(accounts, tx.Inputs, logger) - if err != nil { - return err - } - - err = adjustByOutputs(accounts, tx.Outputs) - if err != nil { - return err - } - - for _, acc := range accounts { - exe.stateCache.UpdateAccount(acc) - } - - if exe.eventCache != nil { - for _, i := range tx.Inputs { - events.PublishAccountInput(exe.eventCache, i.Address, txEnv.Tx, nil, "") - } - - for _, o := range tx.Outputs { - events.PublishAccountOutput(exe.eventCache, o.Address, txEnv.Tx, nil, "") - } - } - return nil - - case *payload.CallTx: - var inAcc acm.MutableAccount - var outAcc acm.Account - - // Validate input - inAcc, err := state.GetMutableAccount(exe.stateCache, tx.Input.Address) - if err != nil { - return err - } - if inAcc == nil { - logger.InfoMsg("Cannot find input account", - "tx_input", tx.Input) - return payload.ErrTxInvalidAddress - } - - // Calling a nil destination is defined as requesting contract creation - createContract := tx.Address == nil - if createContract { - if !hasCreateContractPermission(exe.stateCache, inAcc, logger) { - return fmt.Errorf("account %s does not have CreateContract permission", tx.Input.Address) - } - } else { - if !hasCallPermission(exe.stateCache, inAcc, logger) { - return fmt.Errorf("account %s does not have Call permission", tx.Input.Address) - } - } - - err = validateInput(inAcc, tx.Input) - if err != nil { - logger.InfoMsg("validateInput failed", - "tx_input", tx.Input, structure.ErrorKey, err) - return err - } - if tx.Input.Amount < tx.Fee { - logger.InfoMsg("Sender did not send enough to cover the fee", - "tx_input", tx.Input) - return payload.ErrTxInsufficientFunds - } - - if !createContract { - // check if its a native contract - if evm.RegisteredNativeContract(tx.Address.Word256()) { - return fmt.Errorf("attempt to call a native contract at %s, "+ - "but native contracts cannot be called using CallTx. Use a "+ - "contract that calls the native contract or the appropriate tx "+ - "type (eg. PermissionsTx, NameTx)", tx.Address) - } - - // Output account 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 - // Note: tx.Address == nil iff createContract so dereference is okay - outAcc, err = exe.stateCache.GetAccount(*tx.Address) - if err != nil { - return err - } - } - - logger.Trace.Log("output_account", outAcc) - - // Good! - value := tx.Input.Amount - tx.Fee - - logger.TraceMsg("Incrementing sequence number for CallTx", - "tag", "sequence", - "account", inAcc.Address(), - "old_sequence", inAcc.Sequence(), - "new_sequence", inAcc.Sequence()+1) - - inAcc, err = inAcc.IncSequence().SubtractFromBalance(tx.Fee) - if err != nil { - return err - } - - exe.stateCache.UpdateAccount(inAcc) - - // The logic in runCall MUST NOT return. - if exe.runCall { - // VM call variables - var ( - gas uint64 = tx.GasLimit - err error = nil - caller acm.MutableAccount = acm.AsMutableAccount(inAcc) - callee acm.MutableAccount = nil // initialized below - code []byte = nil - ret []byte = nil - txCache = state.NewCache(exe.stateCache, state.Name("TxCache")) - params = evm.Params{ - BlockHeight: exe.tip.LastBlockHeight(), - BlockHash: binary.LeftPadWord256(exe.tip.LastBlockHash()), - BlockTime: exe.tip.LastBlockTime().Unix(), - GasLimit: GasLimit, - } - ) - - if !createContract && (outAcc == nil || len(outAcc.Code()) == 0) { - // 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 (sequence number will prevent re-ordering of those txs) - // but to create with one contract and call with another - // you have to wait a block to avoid a re-ordering attack - // that will take your fees - if outAcc == nil { - logger.InfoMsg("Call to address that does not exist", - "caller_address", inAcc.Address(), - "callee_address", tx.Address) - } else { - logger.InfoMsg("Call to address that holds no code", - "caller_address", inAcc.Address(), - "callee_address", tx.Address) - } - err = payload.ErrTxInvalidAddress - goto CALL_COMPLETE - } - - // get or create callee - if createContract { - // We already checked for permission - callee = evm.DeriveNewAccount(caller, state.GlobalAccountPermissions(exe.state), - logger.With( - "tx", tx.String(), - "tx_hash", txEnv.Tx.Hash(), - "run_call", exe.runCall, - )) - code = tx.Data - logger.TraceMsg("Creating new contract", - "contract_address", callee.Address(), - "init_code", code) - } else { - callee = acm.AsMutableAccount(outAcc) - code = callee.Code() - logger.TraceMsg("Calling existing contract", - "contract_address", callee.Address(), - "input", tx.Data, - "contract_code", code) - } - logger.Trace.Log("callee", callee.Address().String()) - - // Run VM call and sync txCache to exe.blockCache. - { // Capture scope for goto. - // Write caller/callee to txCache. - txCache.UpdateAccount(caller) - txCache.UpdateAccount(callee) - vmach := evm.NewVM(params, caller.Address(), txEnv.Tx.Hash(), logger, exe.vmOptions...) - vmach.SetPublisher(exe.eventCache) - // NOTE: Call() transfers the value from caller to callee iff call succeeds. - ret, err = vmach.Call(txCache, caller, callee, code, tx.Data, value, &gas) - if err != nil { - // Failure. Charge the gas fee. The 'value' was otherwise not transferred. - logger.InfoMsg("Error on execution", - structure.ErrorKey, err) - goto CALL_COMPLETE - } - - logger.TraceMsg("Successful execution") - if createContract { - callee.SetCode(ret) - } - txCache.Sync(exe.stateCache) - } - - CALL_COMPLETE: // err may or may not be nil. - - // Create a receipt from the ret and whether it erred. - logger.TraceMsg("VM call complete", - "caller", caller, - "callee", callee, - "return", ret, - structure.ErrorKey, err) - - // Fire Events for sender and receiver - // a separate event will be fired from vm for each additional call - if exe.eventCache != nil { - exception := "" - if err != nil { - exception = err.Error() - } - events.PublishAccountInput(exe.eventCache, tx.Input.Address, txEnv.Tx, ret, exception) - if tx.Address != nil { - events.PublishAccountOutput(exe.eventCache, *tx.Address, txEnv.Tx, ret, exception) - } - } - } else { - // The mempool does not call txs until - // the proposer determines the order of txs. - // So mempool will skip the actual .Call(), - // and only deduct from the caller's balance. - inAcc, err = inAcc.SubtractFromBalance(value) - if err != nil { - return err - } - if createContract { - // This is done by DeriveNewAccount when runCall == true - logger.TraceMsg("Incrementing sequence number since creates contract", - "tag", "sequence", - "account", inAcc.Address(), - "old_sequence", inAcc.Sequence(), - "new_sequence", inAcc.Sequence()+1) - inAcc.IncSequence() - } - exe.stateCache.UpdateAccount(inAcc) - } - - return nil - - case *payload.NameTx: - // Validate input - inAcc, err := state.GetMutableAccount(exe.stateCache, tx.Input.Address) - if err != nil { - return err - } - if inAcc == nil { - logger.InfoMsg("Cannot find input account", - "tx_input", tx.Input) - return payload.ErrTxInvalidAddress - } - // check permission - if !hasNamePermission(exe.stateCache, inAcc, logger) { - return fmt.Errorf("account %s does not have Name permission", tx.Input.Address) - } - err = validateInput(inAcc, tx.Input) - if err != nil { - logger.InfoMsg("validateInput failed", - "tx_input", tx.Input, structure.ErrorKey, err) - return err - } - if tx.Input.Amount < tx.Fee { - logger.InfoMsg("Sender did not send enough to cover the fee", - "tx_input", tx.Input) - return payload.ErrTxInsufficientFunds - } - - // validate the input strings - if err := tx.ValidateStrings(); err != nil { - return err - } - - value := tx.Input.Amount - tx.Fee - - // let's say cost of a name for one block is len(data) + 32 - costPerBlock := names.NameCostPerBlock(names.NameBaseCost(tx.Name, tx.Data)) - expiresIn := value / uint64(costPerBlock) - lastBlockHeight := exe.tip.LastBlockHeight() - - logger.TraceMsg("New NameTx", - "value", value, - "cost_per_block", costPerBlock, - "expires_in", expiresIn, - "last_block_height", lastBlockHeight) - - // check if the name exists - entry, err := exe.nameRegCache.GetNameRegEntry(tx.Name) - if err != nil { - return err - } - - if entry != nil { - var expired bool - - // if the entry already exists, and hasn't expired, we must be owner - if entry.Expires > lastBlockHeight { - // ensure we are owner - if entry.Owner != tx.Input.Address { - return fmt.Errorf("permission denied: sender %s is trying to update a name (%s) for "+ - "which they are not an owner", tx.Input.Address, tx.Name) - } - } else { - expired = true - } - - // no value and empty data means delete the entry - if value == 0 && len(tx.Data) == 0 { - // maybe we reward you for telling us we can delete this crap - // (owners if not expired, anyone if expired) - logger.TraceMsg("Removing NameReg entry (no value and empty data in tx requests this)", - "name", entry.Name) - err := exe.nameRegCache.RemoveNameRegEntry(entry.Name) - if err != nil { - return err - } - } else { - // update the entry by bumping the expiry - // and changing the data - if expired { - if expiresIn < names.MinNameRegistrationPeriod { - return fmt.Errorf("Names must be registered for at least %d blocks", names.MinNameRegistrationPeriod) - } - entry.Expires = lastBlockHeight + expiresIn - entry.Owner = tx.Input.Address - logger.TraceMsg("An old NameReg entry has expired and been reclaimed", - "name", entry.Name, - "expires_in", expiresIn, - "owner", entry.Owner) - } else { - // since the size of the data may have changed - // we use the total amount of "credit" - oldCredit := (entry.Expires - lastBlockHeight) * names.NameBaseCost(entry.Name, entry.Data) - credit := oldCredit + value - expiresIn = uint64(credit / costPerBlock) - if expiresIn < names.MinNameRegistrationPeriod { - return fmt.Errorf("names must be registered for at least %d blocks", names.MinNameRegistrationPeriod) - } - entry.Expires = lastBlockHeight + expiresIn - logger.TraceMsg("Updated NameReg entry", - "name", entry.Name, - "expires_in", expiresIn, - "old_credit", oldCredit, - "value", value, - "credit", credit) - } - entry.Data = tx.Data - err := exe.nameRegCache.UpdateNameRegEntry(entry) - if err != nil { - return err - } - } - } else { - if expiresIn < names.MinNameRegistrationPeriod { - return fmt.Errorf("Names must be registered for at least %d blocks", names.MinNameRegistrationPeriod) - } - // entry does not exist, so create it - entry = &NameRegEntry{ - Name: tx.Name, - Owner: tx.Input.Address, - Data: tx.Data, - Expires: lastBlockHeight + expiresIn, - } - logger.TraceMsg("Creating NameReg entry", - "name", entry.Name, - "expires_in", expiresIn) - err := exe.nameRegCache.UpdateNameRegEntry(entry) - if err != nil { - return err - } - } - - // TODO: something with the value sent? - - // Good! - logger.TraceMsg("Incrementing sequence number for NameTx", - "tag", "sequence", - "account", inAcc.Address(), - "old_sequence", inAcc.Sequence(), - "new_sequence", inAcc.Sequence()+1) - inAcc.IncSequence() - inAcc, err = inAcc.SubtractFromBalance(value) - if err != nil { - return err - } - exe.stateCache.UpdateAccount(inAcc) - - // TODO: maybe we want to take funds on error and allow txs in that don't do anythingi? - - if exe.eventCache != nil { - events.PublishAccountInput(exe.eventCache, tx.Input.Address, txEnv.Tx, nil, "") - events.PublishNameReg(exe.eventCache, txEnv.Tx) - } - - return nil - - // Consensus related Txs inactivated for now - // TODO! - /* - case *payload.BondTx: - valInfo := exe.blockCache.State().GetValidatorInfo(tx.PublicKey().Address()) - if valInfo != nil { - // TODO: In the future, check that the validator wasn't destroyed, - // add funds, merge UnbondTo outputs, and unbond validator. - return errors.New("Adding coins to existing validators not yet supported") - } - - accounts, err := getInputs(exe.blockCache, tx.Inputs) - if err != nil { - 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(exe.blockCache, accounts) - for _, out := range tx.UnbondTo { - acc := exe.blockCache.GetAccount(out.Address) - if acc == nil && !canCreate { - return fmt.Errorf("At least one input does not have permission to create accounts") - } - } - - bondAcc := exe.blockCache.GetAccount(tx.PublicKey().Address()) - if !hasBondPermission(exe.blockCache, bondAcc) { - return fmt.Errorf("The bonder does not have permission to bond") - } - - if !hasBondOrSendPermission(exe.blockCache, accounts) { - return fmt.Errorf("At least one input lacks permission to bond") - } - - signBytes := acm.SignBytes(exe.chainID, tx) - inTotal, err := validateInputs(accounts, signBytes, tx.Inputs) - if err != nil { - return err - } - if !tx.PublicKey().VerifyBytes(signBytes, tx.Signature) { - return payload.ErrTxInvalidSignature - } - outTotal, err := validateOutputs(tx.UnbondTo) - if err != nil { - return err - } - if outTotal > inTotal { - return payload.ErrTxInsufficientFunds - } - fee := inTotal - outTotal - fees += fee - - // Good! Adjust accounts - adjustByInputs(accounts, tx.Inputs) - for _, acc := range accounts { - exe.blockCache.UpdateAccount(acc) - } - // Add ValidatorInfo - _s.SetValidatorInfo(&txs.ValidatorInfo{ - Address: tx.PublicKey().Address(), - PublicKey: tx.PublicKey(), - UnbondTo: tx.UnbondTo, - FirstBondHeight: _s.lastBlockHeight + 1, - FirstBondAmount: outTotal, - }) - // Add Validator - added := _s.BondedValidators.Add(&txs.Validator{ - Address: tx.PublicKey().Address(), - PublicKey: tx.PublicKey(), - BondHeight: _s.lastBlockHeight + 1, - VotingPower: outTotal, - Accum: 0, - }) - if !added { - PanicCrisis("Failed to add validator") - } - if exe.eventCache != nil { - // TODO: fire for all inputs - exe.eventCache.Fire(txs.EventStringBond(), txs.EventDataTx{tx, nil, ""}) - } - return nil - - case *payload.UnbondTx: - // The validator must be active - _, val := _s.BondedValidators.GetByAddress(tx.Address) - if val == nil { - return payload.ErrTxInvalidAddress - } - - // Verify the signature - signBytes := acm.SignBytes(exe.chainID, tx) - if !val.PublicKey().VerifyBytes(signBytes, tx.Signature) { - return payload.ErrTxInvalidSignature - } - - // tx.Height must be greater than val.LastCommitHeight - if tx.Height <= val.LastCommitHeight { - return errors.New("Invalid unbond height") - } - - // Good! - _s.unbondValidator(val) - if exe.eventCache != nil { - exe.eventCache.Fire(txs.EventStringUnbond(), txs.EventDataTx{tx, nil, ""}) - } - return nil - - case *txs.RebondTx: - // The validator must be inactive - _, val := _s.UnbondingValidators.GetByAddress(tx.Address) - if val == nil { - return payload.ErrTxInvalidAddress - } - - // Verify the signature - signBytes := acm.SignBytes(exe.chainID, tx) - if !val.PublicKey().VerifyBytes(signBytes, tx.Signature) { - return payload.ErrTxInvalidSignature - } - - // tx.Height must be in a suitable range - minRebondHeight := _s.lastBlockHeight - (validatorTimeoutBlocks / 2) - maxRebondHeight := _s.lastBlockHeight + 2 - if !((minRebondHeight <= tx.Height) && (tx.Height <= maxRebondHeight)) { - return errors.New(Fmt("Rebond height not in range. Expected %v <= %v <= %v", - minRebondHeight, tx.Height, maxRebondHeight)) - } - - // Good! - _s.rebondValidator(val) - if exe.eventCache != nil { - exe.eventCache.Fire(txs.EventStringRebond(), txs.EventDataTx{tx, nil, ""}) - } - return nil - - */ - - case *payload.PermissionsTx: - // Validate input - inAcc, err := state.GetMutableAccount(exe.stateCache, tx.Input.Address) - if err != nil { - return err - } - if inAcc == nil { - logger.InfoMsg("Cannot find input account", - "tx_input", tx.Input) - return payload.ErrTxInvalidAddress - } - - err = tx.PermArgs.EnsureValid() - if err != nil { - return fmt.Errorf("PermissionsTx received containing invalid PermArgs: %v", err) - } - - permFlag := tx.PermArgs.PermFlag - // check permission - if !HasPermission(exe.stateCache, inAcc, permFlag, logger) { - return fmt.Errorf("account %s does not have moderator permission %s (%b)", tx.Input.Address, - permission.PermFlagToString(permFlag), permFlag) - } - - err = validateInput(inAcc, tx.Input) - if err != nil { - logger.InfoMsg("validateInput failed", - "tx_input", tx.Input, - structure.ErrorKey, err) - return err - } - - value := tx.Input.Amount - - logger.TraceMsg("New PermissionsTx", - "perm_args", tx.PermArgs.String()) - - var permAcc acm.Account - switch tx.PermArgs.PermFlag { - case permission.HasBase: - // this one doesn't make sense from txs - return fmt.Errorf("HasBase is for contracts, not humans. Just look at the blockchain") - case permission.SetBase: - permAcc, err = mutatePermissions(exe.stateCache, *tx.PermArgs.Address, - func(perms *ptypes.AccountPermissions) error { - return perms.Base.Set(*tx.PermArgs.Permission, *tx.PermArgs.Value) - }) - case permission.UnsetBase: - permAcc, err = mutatePermissions(exe.stateCache, *tx.PermArgs.Address, - func(perms *ptypes.AccountPermissions) error { - return perms.Base.Unset(*tx.PermArgs.Permission) - }) - case permission.SetGlobal: - permAcc, err = mutatePermissions(exe.stateCache, acm.GlobalPermissionsAddress, - func(perms *ptypes.AccountPermissions) error { - return perms.Base.Set(*tx.PermArgs.Permission, *tx.PermArgs.Value) - }) - case permission.HasRole: - return fmt.Errorf("HasRole is for contracts, not humans. Just look at the blockchain") - case permission.AddRole: - permAcc, err = mutatePermissions(exe.stateCache, *tx.PermArgs.Address, - func(perms *ptypes.AccountPermissions) error { - if !perms.AddRole(*tx.PermArgs.Role) { - return fmt.Errorf("role (%s) already exists for account %s", - *tx.PermArgs.Role, *tx.PermArgs.Address) - } - return nil - }) - case permission.RemoveRole: - permAcc, err = mutatePermissions(exe.stateCache, *tx.PermArgs.Address, - func(perms *ptypes.AccountPermissions) error { - if !perms.RmRole(*tx.PermArgs.Role) { - return fmt.Errorf("role (%s) does not exist for account %s", - *tx.PermArgs.Role, *tx.PermArgs.Address) - } - return nil - }) - default: - return fmt.Errorf("invalid permission function: %s", permission.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! - logger.TraceMsg("Incrementing sequence number for PermissionsTx", - "tag", "sequence", - "account", inAcc.Address(), - "old_sequence", inAcc.Sequence(), - "new_sequence", inAcc.Sequence()+1) - inAcc.IncSequence() - inAcc, err = inAcc.SubtractFromBalance(value) - if err != nil { - return err - } - exe.stateCache.UpdateAccount(inAcc) - if permAcc != nil { - exe.stateCache.UpdateAccount(permAcc) - } - - if exe.eventCache != nil { - events.PublishAccountInput(exe.eventCache, tx.Input.Address, txEnv.Tx, nil, "") - events.PublishPermissions(exe.eventCache, permission.PermFlagToString(permFlag), txEnv.Tx) - } - - return nil - - default: - // binary decoding should not let this happen - return fmt.Errorf("unknown Tx type: %#v", tx) - } -} - -func mutatePermissions(stateReader state.Reader, address crypto.Address, - mutator func(*ptypes.AccountPermissions) error) (acm.Account, error) { - - account, err := stateReader.GetAccount(address) - if err != nil { - return nil, err - } - if account == nil { - return nil, fmt.Errorf("could not get account at address %s in order to alter permissions", address) - } - mutableAccount := acm.AsMutableAccount(account) - - return mutableAccount, mutator(mutableAccount.MutablePermissions()) -} - -// ExecBlock stuff is now taken care of by the consensus engine. -// But we leave here for now for reference when we have to do validator updates - -/* - -// NOTE: If an error occurs during block execution, state will be left -// at an invalid state. Copy the state before calling ExecBlock! -func ExecBlock(s *State, block *txs.Block, blockPartsHeader txs.PartSetHeader) error { - err := execBlock(s, block, blockPartsHeader) - if err != nil { - return err - } - // State.Hash should match block.StateHash - stateHash := s.Hash() - if !bytes.Equal(stateHash, block.StateHash) { - return errors.New(Fmt("Invalid state hash. Expected %X, got %X", - stateHash, block.StateHash)) - } - return nil -} - -// executes transactions of a block, does not check block.StateHash -// NOTE: If an error occurs during block execution, state will be left -// at an invalid state. Copy the state before calling execBlock! -func execBlock(s *State, block *txs.Block, blockPartsHeader txs.PartSetHeader) error { - // Basic block validation. - err := block.ValidateBasic(s.chainID, s.lastBlockHeight, s.lastBlockAppHash, s.LastBlockParts, s.lastBlockTime) - if err != nil { - return err - } - - // Validate block LastValidation. - if block.Height == 1 { - if len(block.LastValidation.Precommits) != 0 { - return errors.New("Block at height 1 (first block) should have no LastValidation precommits") - } - } else { - if len(block.LastValidation.Precommits) != s.LastBondedValidators.Size() { - return errors.New(Fmt("Invalid block validation size. Expected %v, got %v", - s.LastBondedValidators.Size(), len(block.LastValidation.Precommits))) - } - err := s.LastBondedValidators.VerifyValidation( - s.chainID, s.lastBlockAppHash, s.LastBlockParts, block.Height-1, block.LastValidation) - if err != nil { - return err - } - } - - // Update Validator.LastCommitHeight as necessary. - for i, precommit := range block.LastValidation.Precommits { - if precommit == nil { - continue - } - _, val := s.LastBondedValidators.GetByIndex(i) - if val == nil { - PanicCrisis(Fmt("Failed to fetch validator at index %v", i)) - } - if _, val_ := s.BondedValidators.GetByAddress(val.Address); val_ != nil { - val_.LastCommitHeight = block.Height - 1 - updated := s.BondedValidators.Update(val_) - if !updated { - PanicCrisis("Failed to update bonded validator LastCommitHeight") - } - } else if _, val_ := s.UnbondingValidators.GetByAddress(val.Address); val_ != nil { - val_.LastCommitHeight = block.Height - 1 - updated := s.UnbondingValidators.Update(val_) - if !updated { - PanicCrisis("Failed to update unbonding validator LastCommitHeight") - } - } else { - PanicCrisis("Could not find validator") - } - } - - // Remember LastBondedValidators - s.LastBondedValidators = s.BondedValidators.Copy() - - // Create BlockCache to cache changes to state. - blockCache := NewBlockCache(s) - - // Execute each tx - for _, tx := range block.Data.Txs { - err := ExecTx(blockCache, tx, true, s.eventCache) - if err != nil { - return InvalidTxError{tx, err} - } - } - - // Now sync the BlockCache to the backend. - blockCache.Sync() - - // If any unbonding periods are over, - // reward account with bonded coins. - toRelease := []*txs.Validator{} - s.UnbondingValidators.Iterate(func(index int, val *txs.Validator) bool { - if val.UnbondHeight+unbondingPeriodBlocks < block.Height { - toRelease = append(toRelease, val) - } - return false - }) - for _, val := range toRelease { - s.releaseValidator(val) - } - - // If any validators haven't signed in a while, - // unbond them, they have timed out. - toTimeout := []*txs.Validator{} - s.BondedValidators.Iterate(func(index int, val *txs.Validator) bool { - lastActivityHeight := MaxInt(val.BondHeight, val.LastCommitHeight) - if lastActivityHeight+validatorTimeoutBlocks < block.Height { - log.Notice("Validator timeout", "validator", val, "height", block.Height) - toTimeout = append(toTimeout, val) - } - return false - }) - for _, val := range toTimeout { - s.unbondValidator(val) - } - - // Increment validator AccumPowers - s.BondedValidators.IncrementAccum(1) - s.lastBlockHeight = block.Height - s.lastBlockAppHash = block.Hash() - s.LastBlockParts = blockPartsHeader - s.lastBlockTime = block.Time - return nil -} -*/ - -// The accounts from the TxInputs must either already have -// acm.PublicKey().(type) != nil, (it must be known), -// or it must be specified in the TxInput. If redeclared, -// the TxInput is modified and input.PublicKey() set to nil. -func getInputs(accountGetter state.AccountGetter, - ins []*payload.TxInput) (map[crypto.Address]acm.MutableAccount, error) { - - accounts := map[crypto.Address]acm.MutableAccount{} - for _, in := range ins { - // Account shouldn't be duplicated - if _, ok := accounts[in.Address]; ok { - return nil, payload.ErrTxDuplicateAddress - } - acc, err := state.GetMutableAccount(accountGetter, in.Address) - if err != nil { - return nil, err - } - if acc == nil { - return nil, payload.ErrTxInvalidAddress - } - accounts[in.Address] = acc - } - return accounts, nil -} - -func getOrMakeOutputs(accountGetter state.AccountGetter, accs map[crypto.Address]acm.MutableAccount, - outs []*payload.TxOutput, logger *logging.Logger) (map[crypto.Address]acm.MutableAccount, error) { - if accs == nil { - accs = make(map[crypto.Address]acm.MutableAccount) - } - - // we should err if an account is being created but the inputs don't have permission - var checkedCreatePerms bool - for _, out := range outs { - // Account shouldn't be duplicated - if _, ok := accs[out.Address]; ok { - return nil, payload.ErrTxDuplicateAddress - } - acc, err := state.GetMutableAccount(accountGetter, out.Address) - if err != nil { - return nil, err - } - // output account may be nil (new) - if acc == nil { - if !checkedCreatePerms { - if !hasCreateAccountPermission(accountGetter, accs, logger) { - return nil, fmt.Errorf("at least one input does not have permission to create accounts") - } - checkedCreatePerms = true - } - acc = acm.ConcreteAccount{ - Address: out.Address, - Sequence: 0, - Balance: 0, - Permissions: permission.ZeroAccountPermissions, - }.MutableAccount() - } - accs[out.Address] = acc - } - return accs, nil -} - -func validateInputs(accs map[crypto.Address]acm.MutableAccount, ins []*payload.TxInput) (uint64, error) { - total := uint64(0) - for _, in := range ins { - acc := accs[in.Address] - if acc == nil { - return 0, fmt.Errorf("validateInputs() expects account in accounts, but account %s not found", in.Address) - } - err := validateInput(acc, in) - if err != nil { - return 0, err - } - // Good. Add amount to total - total += in.Amount - } - return total, nil -} - -func validateInput(acc acm.MutableAccount, in *payload.TxInput) error { - // Check TxInput basic - if err := in.ValidateBasic(); err != nil { - return err - } - // Check sequences - if acc.Sequence()+1 != uint64(in.Sequence) { - return payload.ErrTxInvalidSequence{ - Got: in.Sequence, - Expected: acc.Sequence() + uint64(1), - } - } - // Check amount - if acc.Balance() < uint64(in.Amount) { - return payload.ErrTxInsufficientFunds - } - return nil -} - -func validateOutputs(outs []*payload.TxOutput) (uint64, error) { - total := uint64(0) - for _, out := range outs { - // Check TxOutput basic - if err := out.ValidateBasic(); err != nil { - return 0, err - } - // Good. Add amount to total - total += out.Amount - } - return total, nil -} - -func adjustByInputs(accs map[crypto.Address]acm.MutableAccount, ins []*payload.TxInput, logger *logging.Logger) error { - for _, in := range ins { - acc := accs[in.Address] - if acc == nil { - return fmt.Errorf("adjustByInputs() expects account in accounts, but account %s not found", in.Address) - } - if acc.Balance() < in.Amount { - panic("adjustByInputs() expects sufficient funds") - return fmt.Errorf("adjustByInputs() expects sufficient funds but account %s only has balance %v and "+ - "we are deducting %v", in.Address, acc.Balance(), in.Amount) - } - acc, err := acc.SubtractFromBalance(in.Amount) - if err != nil { - return err - } - logger.TraceMsg("Incrementing sequence number for SendTx (adjustByInputs)", - "tag", "sequence", - "account", acc.Address(), - "old_sequence", acc.Sequence(), - "new_sequence", acc.Sequence()+1) - acc.IncSequence() - } - return nil -} - -func adjustByOutputs(accs map[crypto.Address]acm.MutableAccount, outs []*payload.TxOutput) error { - for _, out := range outs { - acc := accs[out.Address] - if acc == nil { - return fmt.Errorf("adjustByOutputs() expects account in accounts, but account %s not found", - out.Address) - } - _, err := acc.AddToBalance(out.Amount) - if err != nil { - return err - } - } - return nil -} - -//--------------------------------------------------------------- - -// Get permission on an account or fall back to global value -func HasPermission(accountGetter state.AccountGetter, acc acm.Account, perm ptypes.PermFlag, logger *logging.Logger) bool { - if perm > permission.AllPermFlags { - logger.InfoMsg( - fmt.Sprintf("HasPermission called on invalid permission 0b%b (invalid) > 0b%b (maximum) ", - perm, permission.AllPermFlags), - "invalid_permission", perm, - "maximum_permission", permission.AllPermFlags) - return false - } - - permString := permission.String(perm) - - v, err := acc.Permissions().Base.Compose(state.GlobalAccountPermissions(accountGetter).Base).Get(perm) - if err != nil { - logger.TraceMsg("Error obtaining permission value (will default to false/deny)", - "perm_flag", permString, - structure.ErrorKey, err) - } - - if v { - logger.TraceMsg("Account has permission", - "account_address", acc.Address, - "perm_flag", permString) - } else { - logger.TraceMsg("Account does not have permission", - "account_address", acc.Address, - "perm_flag", permString) - } - return v -} - -// TODO: for debug log the failed accounts -func hasSendPermission(accountGetter state.AccountGetter, accs map[crypto.Address]acm.MutableAccount, - logger *logging.Logger) bool { - for _, acc := range accs { - if !HasPermission(accountGetter, acc, permission.Send, logger) { - return false - } - } - return true -} - -func hasNamePermission(accountGetter state.AccountGetter, acc acm.Account, - logger *logging.Logger) bool { - return HasPermission(accountGetter, acc, permission.Name, logger) -} - -func hasCallPermission(accountGetter state.AccountGetter, acc acm.Account, - logger *logging.Logger) bool { - return HasPermission(accountGetter, acc, permission.Call, logger) -} - -func hasCreateContractPermission(accountGetter state.AccountGetter, acc acm.Account, - logger *logging.Logger) bool { - return HasPermission(accountGetter, acc, permission.CreateContract, logger) -} - -func hasCreateAccountPermission(accountGetter state.AccountGetter, accs map[crypto.Address]acm.MutableAccount, - logger *logging.Logger) bool { - for _, acc := range accs { - if !HasPermission(accountGetter, acc, permission.CreateAccount, logger) { - return false - } - } - return true -} - -func hasBondPermission(accountGetter state.AccountGetter, acc acm.Account, - logger *logging.Logger) bool { - return HasPermission(accountGetter, acc, permission.Bond, logger) -} - -func hasBondOrSendPermission(accountGetter state.AccountGetter, accs map[crypto.Address]acm.Account, - logger *logging.Logger) bool { - for _, acc := range accs { - if !HasPermission(accountGetter, acc, permission.Bond, logger) { - if !HasPermission(accountGetter, acc, permission.Send, logger) { - return false - } - } - } - return true -} - -//----------------------------------------------------------------------------- - -type InvalidTxError struct { - Tx txs.Tx - Reason error -} - -func (txErr InvalidTxError) Error() string { - return fmt.Sprintf("Invalid tx: [%v] reason: [%v]", txErr.Tx, txErr.Reason) -} diff --git a/execution/execution_test.go b/execution/execution_test.go index e846840971b8f741ffa2bf54a8f425161728fbd7..93fb03b443a9602ba5118f9312e080ad2f8a5bde 100644 --- a/execution/execution_test.go +++ b/execution/execution_test.go @@ -30,7 +30,7 @@ import ( bcm "github.com/hyperledger/burrow/blockchain" "github.com/hyperledger/burrow/crypto" "github.com/hyperledger/burrow/event" - exe_events "github.com/hyperledger/burrow/execution/events" + "github.com/hyperledger/burrow/execution/errors" "github.com/hyperledger/burrow/execution/evm" . "github.com/hyperledger/burrow/execution/evm/asm" "github.com/hyperledger/burrow/execution/evm/asm/bc" @@ -139,9 +139,10 @@ func newBlockchain(genesisDoc *genesis.GenesisDoc) *bcm.Blockchain { return bc } -func makeExecutor(state *State) *executor { - return newExecutor("makeExecutorCache", true, state, testChainID, - newBlockchain(testGenesisDoc).Tip, event.NewEmitter(logger), logger) +func makeExecutor(state *State) (*executor, event.Emitter) { + emitter := event.NewEmitter(logger) + return newExecutor("makeExecutorCache", true, state, + newBlockchain(testGenesisDoc).Tip, emitter, logger), emitter } func newBaseGenDoc(globalPerm, accountPerm ptypes.AccountPermissions) genesis.GenesisDoc { @@ -187,12 +188,12 @@ func TestSendFails(t *testing.T) { stateDB := dbm.NewDB("state", dbBackend, dbDir) defer stateDB.Close() genDoc := newBaseGenDoc(permission.ZeroAccountPermissions, permission.ZeroAccountPermissions) - genDoc.Accounts[1].Permissions.Base.Set(permission.Send, true) - genDoc.Accounts[2].Permissions.Base.Set(permission.Call, true) - genDoc.Accounts[3].Permissions.Base.Set(permission.CreateContract, true) + genDoc.Accounts[1].Permissions.Base.Set(ptypes.Send, true) + genDoc.Accounts[2].Permissions.Base.Set(ptypes.Call, true) + genDoc.Accounts[3].Permissions.Base.Set(ptypes.CreateContract, true) st, err := MakeGenesisState(stateDB, &genDoc) require.NoError(t, err) - batchCommitter := makeExecutor(st) + batchCommitter, _ := makeExecutor(st) //------------------- // send txs @@ -224,7 +225,7 @@ func TestSendFails(t *testing.T) { // simple send tx to unknown account without create_account perm should fail acc := getAccount(batchCommitter.stateCache, users[3].Address()) - acc.MutablePermissions().Base.Set(permission.Send, true) + acc.MutablePermissions().Base.Set(ptypes.Send, true) batchCommitter.stateCache.UpdateAccount(acc) tx = payload.NewSendTx() if err := tx.AddInput(batchCommitter.stateCache, users[3].PublicKey(), 5); err != nil { @@ -238,11 +239,11 @@ func TestName(t *testing.T) { stateDB := dbm.NewDB("state", dbBackend, dbDir) defer stateDB.Close() genDoc := newBaseGenDoc(permission.ZeroAccountPermissions, permission.ZeroAccountPermissions) - genDoc.Accounts[0].Permissions.Base.Set(permission.Send, true) - genDoc.Accounts[1].Permissions.Base.Set(permission.Name, true) + genDoc.Accounts[0].Permissions.Base.Set(ptypes.Send, true) + genDoc.Accounts[1].Permissions.Base.Set(ptypes.Name, true) st, err := MakeGenesisState(stateDB, &genDoc) require.NoError(t, err) - batchCommitter := makeExecutor(st) + batchCommitter, _ := makeExecutor(st) //------------------- // name txs @@ -266,12 +267,12 @@ func TestCallFails(t *testing.T) { stateDB := dbm.NewDB("state", dbBackend, dbDir) defer stateDB.Close() genDoc := newBaseGenDoc(permission.ZeroAccountPermissions, permission.ZeroAccountPermissions) - genDoc.Accounts[1].Permissions.Base.Set(permission.Send, true) - genDoc.Accounts[2].Permissions.Base.Set(permission.Call, true) - genDoc.Accounts[3].Permissions.Base.Set(permission.CreateContract, true) + genDoc.Accounts[1].Permissions.Base.Set(ptypes.Send, true) + genDoc.Accounts[2].Permissions.Base.Set(ptypes.Call, true) + genDoc.Accounts[3].Permissions.Base.Set(ptypes.CreateContract, true) st, err := MakeGenesisState(stateDB, &genDoc) require.NoError(t, err) - batchCommitter := makeExecutor(st) + batchCommitter, _ := makeExecutor(st) //------------------- // call txs @@ -309,10 +310,10 @@ func TestSendPermission(t *testing.T) { stateDB := dbm.NewDB("state", dbBackend, dbDir) defer stateDB.Close() genDoc := newBaseGenDoc(permission.ZeroAccountPermissions, permission.ZeroAccountPermissions) - genDoc.Accounts[0].Permissions.Base.Set(permission.Send, true) // give the 0 account permission + genDoc.Accounts[0].Permissions.Base.Set(ptypes.Send, true) // give the 0 account permission st, err := MakeGenesisState(stateDB, &genDoc) require.NoError(t, err) - batchCommitter := makeExecutor(st) + batchCommitter, _ := makeExecutor(st) // A single input, having the permission, should succeed tx := payload.NewSendTx() @@ -334,10 +335,10 @@ func TestCallPermission(t *testing.T) { stateDB := dbm.NewDB("state", dbBackend, dbDir) defer stateDB.Close() genDoc := newBaseGenDoc(permission.ZeroAccountPermissions, permission.ZeroAccountPermissions) - genDoc.Accounts[0].Permissions.Base.Set(permission.Call, true) // give the 0 account permission + genDoc.Accounts[0].Permissions.Base.Set(ptypes.Call, true) // give the 0 account permission st, err := MakeGenesisState(stateDB, &genDoc) require.NoError(t, err) - batchCommitter := makeExecutor(st) + batchCommitter, emitter := makeExecutor(st) //------------------------------ // call to simple contract @@ -382,27 +383,23 @@ func TestCallPermission(t *testing.T) { require.NoError(t, txEnv.Sign(users[0])) // we need to subscribe to the Call event to detect the exception - _, exception := execTxWaitEvent(t, batchCommitter, txEnv, evm_events.EventStringAccountCall(caller1ContractAddr)) // - if exception == "" { - t.Fatal("Expected exception") - } + _, err = execTxWaitAccountCall(t, batchCommitter, emitter, txEnv, caller1ContractAddr) // + require.Error(t, err) //---------------------------------------------------------- // call to contract that calls simple contract - with perm fmt.Println("\n##### CALL TO SIMPLE CONTRACT (PASS)") // A single input, having the permission, and the contract has permission - caller1Acc.MutablePermissions().Base.Set(permission.Call, true) + caller1Acc.MutablePermissions().Base.Set(ptypes.Call, true) batchCommitter.stateCache.UpdateAccount(caller1Acc) tx, _ = payload.NewCallTx(batchCommitter.stateCache, users[0].PublicKey(), &caller1ContractAddr, nil, 100, 10000, 100) txEnv = txs.Enclose(testChainID, tx) require.NoError(t, txEnv.Sign(users[0])) // we need to subscribe to the Call event to detect the exception - _, exception = execTxWaitEvent(t, batchCommitter, txEnv, evm_events.EventStringAccountCall(caller1ContractAddr)) // - if exception != "" { - t.Fatal("Unexpected exception:", exception) - } + _, err = execTxWaitAccountCall(t, batchCommitter, emitter, txEnv, caller1ContractAddr) // + require.NoError(t, err) //---------------------------------------------------------- // call to contract that calls contract that calls simple contract - without perm @@ -420,8 +417,8 @@ func TestCallPermission(t *testing.T) { StorageRoot: Zero256.Bytes(), Permissions: permission.ZeroAccountPermissions, }.MutableAccount() - caller1Acc.MutablePermissions().Base.Set(permission.Call, false) - caller2Acc.MutablePermissions().Base.Set(permission.Call, true) + caller1Acc.MutablePermissions().Base.Set(ptypes.Call, false) + caller2Acc.MutablePermissions().Base.Set(ptypes.Call, true) batchCommitter.stateCache.UpdateAccount(caller1Acc) batchCommitter.stateCache.UpdateAccount(caller2Acc) @@ -429,10 +426,8 @@ func TestCallPermission(t *testing.T) { txEnv = txs.Enclose(testChainID, tx) require.NoError(t, txEnv.Sign(users[0])) // we need to subscribe to the Call event to detect the exception - _, exception = execTxWaitEvent(t, batchCommitter, txEnv, evm_events.EventStringAccountCall(caller1ContractAddr)) // - if exception == "" { - t.Fatal("Expected exception") - } + _, err = execTxWaitAccountCall(t, batchCommitter, emitter, txEnv, caller1ContractAddr) // + require.Error(t, err) //---------------------------------------------------------- // call to contract that calls contract that calls simple contract - without perm @@ -440,7 +435,7 @@ func TestCallPermission(t *testing.T) { // both caller1 and caller2 have permission fmt.Println("\n##### CALL TO CONTRACT CALLING SIMPLE CONTRACT (PASS)") - caller1Acc.MutablePermissions().Base.Set(permission.Call, true) + caller1Acc.MutablePermissions().Base.Set(ptypes.Call, true) batchCommitter.stateCache.UpdateAccount(caller1Acc) tx, _ = payload.NewCallTx(batchCommitter.stateCache, users[0].PublicKey(), &caller2ContractAddr, nil, 100, 10000, 100) @@ -448,21 +443,19 @@ func TestCallPermission(t *testing.T) { require.NoError(t, txEnv.Sign(users[0])) // we need to subscribe to the Call event to detect the exception - _, exception = execTxWaitEvent(t, batchCommitter, txEnv, evm_events.EventStringAccountCall(caller1ContractAddr)) // - if exception != "" { - t.Fatal("Unexpected exception", exception) - } + _, err = execTxWaitAccountCall(t, batchCommitter, emitter, txEnv, caller1ContractAddr) // + require.NoError(t, err) } func TestCreatePermission(t *testing.T) { stateDB := dbm.NewDB("state", dbBackend, dbDir) defer stateDB.Close() genDoc := newBaseGenDoc(permission.ZeroAccountPermissions, permission.ZeroAccountPermissions) - genDoc.Accounts[0].Permissions.Base.Set(permission.CreateContract, true) // give the 0 account permission - genDoc.Accounts[0].Permissions.Base.Set(permission.Call, true) // give the 0 account permission + genDoc.Accounts[0].Permissions.Base.Set(ptypes.CreateContract, true) // give the 0 account permission + genDoc.Accounts[0].Permissions.Base.Set(ptypes.Call, true) // give the 0 account permission st, err := MakeGenesisState(stateDB, &genDoc) require.NoError(t, err) - batchCommitter := makeExecutor(st) + batchCommitter, emitter := makeExecutor(st) //------------------------------ // create a simple contract @@ -517,16 +510,14 @@ func TestCreatePermission(t *testing.T) { txEnv := txs.Enclose(testChainID, tx) require.NoError(t, txEnv.Sign(users[0])) // we need to subscribe to the Call event to detect the exception - _, exception := execTxWaitEvent(t, batchCommitter, txEnv, evm_events.EventStringAccountCall(contractAddr)) // - if exception == "" { - t.Fatal("expected exception") - } + _, err = execTxWaitAccountCall(t, batchCommitter, emitter, txEnv, contractAddr) // + require.Error(t, err) //------------------------------ // call the contract (should PASS) fmt.Println("\n###### CALL THE FACTORY (PASS)") - contractAcc.MutablePermissions().Base.Set(permission.CreateContract, true) + contractAcc.MutablePermissions().Base.Set(ptypes.CreateContract, true) batchCommitter.stateCache.UpdateAccount(contractAcc) // A single input, having the permission, should succeed @@ -534,10 +525,8 @@ func TestCreatePermission(t *testing.T) { txEnv = txs.Enclose(testChainID, tx) require.NoError(t, txEnv.Sign(users[0])) // we need to subscribe to the Call event to detect the exception - _, exception = execTxWaitEvent(t, batchCommitter, txEnv, evm_events.EventStringAccountCall(contractAddr)) // - if exception != "" { - t.Fatal("unexpected exception", exception) - } + _, err = execTxWaitAccountCall(t, batchCommitter, emitter, txEnv, contractAddr) // + require.NoError(t, err) //-------------------------------- fmt.Println("\n##### CALL to empty address") @@ -552,8 +541,8 @@ func TestCreatePermission(t *testing.T) { StorageRoot: Zero256.Bytes(), Permissions: permission.ZeroAccountPermissions, }.MutableAccount() - contractAcc.MutablePermissions().Base.Set(permission.Call, true) - contractAcc.MutablePermissions().Base.Set(permission.CreateContract, true) + contractAcc.MutablePermissions().Base.Set(ptypes.Call, true) + contractAcc.MutablePermissions().Base.Set(ptypes.CreateContract, true) batchCommitter.stateCache.UpdateAccount(contractAcc) // this should call the 0 address but not create ... @@ -561,10 +550,8 @@ func TestCreatePermission(t *testing.T) { txEnv = txs.Enclose(testChainID, tx) require.NoError(t, txEnv.Sign(users[0])) // we need to subscribe to the Call event to detect the exception - _, exception = execTxWaitEvent(t, batchCommitter, txEnv, evm_events.EventStringAccountCall(crypto.Address{})) // - if exception != "" { - t.Fatal("unexpected exception", exception) - } + _, err = execTxWaitAccountCall(t, batchCommitter, emitter, txEnv, crypto.Address{}) // + require.NoError(t, err) zeroAcc := getAccount(batchCommitter.stateCache, crypto.Address{}) if len(zeroAcc.Code()) != 0 { t.Fatal("the zero account was given code from a CALL!") @@ -575,12 +562,12 @@ func TestCreateAccountPermission(t *testing.T) { stateDB := dbm.NewDB("state", dbBackend, dbDir) defer stateDB.Close() genDoc := newBaseGenDoc(permission.ZeroAccountPermissions, permission.ZeroAccountPermissions) - genDoc.Accounts[0].Permissions.Base.Set(permission.Send, true) // give the 0 account permission - genDoc.Accounts[1].Permissions.Base.Set(permission.Send, true) // give the 0 account permission - genDoc.Accounts[0].Permissions.Base.Set(permission.CreateAccount, true) // give the 0 account permission + 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, err := MakeGenesisState(stateDB, &genDoc) require.NoError(t, err) - batchCommitter := makeExecutor(st) + batchCommitter, emitter := makeExecutor(st) //---------------------------------------------------------- // SendTx to unknown account @@ -618,7 +605,7 @@ func TestCreateAccountPermission(t *testing.T) { // Two inputs, both with send, both with create, should pass acc := getAccount(batchCommitter.stateCache, users[1].Address()) - acc.MutablePermissions().Base.Set(permission.CreateAccount, true) + acc.MutablePermissions().Base.Set(ptypes.CreateAccount, true) batchCommitter.stateCache.UpdateAccount(acc) tx = payload.NewSendTx() if err := tx.AddInput(batchCommitter.stateCache, users[0].PublicKey(), 5); err != nil { @@ -646,7 +633,7 @@ func TestCreateAccountPermission(t *testing.T) { // CALL to unknown account acc = getAccount(batchCommitter.stateCache, users[0].Address()) - acc.MutablePermissions().Base.Set(permission.Call, true) + acc.MutablePermissions().Base.Set(ptypes.Call, true) batchCommitter.stateCache.UpdateAccount(acc) // call to contract that calls unknown account - without create_account perm @@ -669,15 +656,13 @@ func TestCreateAccountPermission(t *testing.T) { txCallEnv.Sign(users[0]) // we need to subscribe to the Call event to detect the exception - _, exception := execTxWaitEvent(t, batchCommitter, txCallEnv, evm_events.EventStringAccountCall(caller1ContractAddr)) // - if exception == "" { - t.Fatal("Expected exception") - } + _, err = execTxWaitAccountCall(t, batchCommitter, emitter, txCallEnv, caller1ContractAddr) // + require.Error(t, err) // NOTE: for a contract to be able to CreateAccount, it must be able to call // NOTE: for a users to be able to CreateAccount, it must be able to send! - caller1Acc.MutablePermissions().Base.Set(permission.CreateAccount, true) - caller1Acc.MutablePermissions().Base.Set(permission.Call, true) + caller1Acc.MutablePermissions().Base.Set(ptypes.CreateAccount, true) + caller1Acc.MutablePermissions().Base.Set(ptypes.Call, true) batchCommitter.stateCache.UpdateAccount(caller1Acc) // A single input, having the permission, but the contract doesn't have permission txCall, _ = payload.NewCallTx(batchCommitter.stateCache, users[0].PublicKey(), &caller1ContractAddr, nil, 100, 10000, 100) @@ -685,10 +670,8 @@ func TestCreateAccountPermission(t *testing.T) { txCallEnv.Sign(users[0]) // we need to subscribe to the Call event to detect the exception - _, exception = execTxWaitEvent(t, batchCommitter, txCallEnv, evm_events.EventStringAccountCall(caller1ContractAddr)) // - if exception != "" { - t.Fatal("Unexpected exception", exception) - } + _, err = execTxWaitAccountCall(t, batchCommitter, emitter, txCallEnv, caller1ContractAddr) // + require.NoError(t, err) } @@ -703,13 +686,13 @@ func TestSNativeCALL(t *testing.T) { stateDB := dbm.NewDB("state", dbBackend, dbDir) defer stateDB.Close() genDoc := newBaseGenDoc(permission.ZeroAccountPermissions, permission.ZeroAccountPermissions) - genDoc.Accounts[0].Permissions.Base.Set(permission.Call, true) // give the 0 account permission - genDoc.Accounts[3].Permissions.Base.Set(permission.Bond, true) // some arbitrary permission to play with + genDoc.Accounts[0].Permissions.Base.Set(ptypes.Call, true) // give the 0 account permission + genDoc.Accounts[3].Permissions.Base.Set(ptypes.Bond, true) // some arbitrary permission to play with genDoc.Accounts[3].Permissions.AddRole("bumble") genDoc.Accounts[3].Permissions.AddRole("bee") st, err := MakeGenesisState(stateDB, &genDoc) require.NoError(t, err) - batchCommitter := makeExecutor(st) + batchCommitter, emitter := makeExecutor(st) //---------------------------------------------------------- // Test CALL to SNative contracts @@ -724,15 +707,15 @@ func TestSNativeCALL(t *testing.T) { Permissions: permission.ZeroAccountPermissions, }.MutableAccount() - doug.MutablePermissions().Base.Set(permission.Call, true) + doug.MutablePermissions().Base.Set(ptypes.Call, true) //doug.Permissions.Base.Set(permission.HasBase, true) batchCommitter.stateCache.UpdateAccount(doug) fmt.Println("\n#### HasBase") // HasBase - snativeAddress, pF, data := snativePermTestInputCALL("hasBase", users[3], permission.Bond, false) - testSNativeCALLExpectFail(t, batchCommitter, doug, snativeAddress, data) - testSNativeCALLExpectPass(t, batchCommitter, doug, pF, snativeAddress, data, func(ret []byte) error { + snativeAddress, pF, data := snativePermTestInputCALL("hasBase", users[3], ptypes.Bond, false) + testSNativeCALLExpectFail(t, batchCommitter, emitter, doug, snativeAddress, data) + testSNativeCALLExpectPass(t, batchCommitter, emitter, doug, pF, 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) @@ -742,21 +725,21 @@ func TestSNativeCALL(t *testing.T) { fmt.Println("\n#### SetBase") // SetBase - snativeAddress, pF, data = snativePermTestInputCALL("setBase", users[3], permission.Bond, false) - testSNativeCALLExpectFail(t, batchCommitter, doug, snativeAddress, data) - testSNativeCALLExpectPass(t, batchCommitter, doug, pF, snativeAddress, data, func(ret []byte) error { return nil }) - snativeAddress, pF, data = snativePermTestInputCALL("hasBase", users[3], permission.Bond, false) - testSNativeCALLExpectPass(t, batchCommitter, doug, pF, snativeAddress, data, func(ret []byte) error { + snativeAddress, pF, data = snativePermTestInputCALL("setBase", users[3], ptypes.Bond, false) + testSNativeCALLExpectFail(t, batchCommitter, emitter, doug, snativeAddress, data) + testSNativeCALLExpectPass(t, batchCommitter, emitter, doug, pF, snativeAddress, data, func(ret []byte) error { return nil }) + snativeAddress, pF, data = snativePermTestInputCALL("hasBase", users[3], ptypes.Bond, false) + testSNativeCALLExpectPass(t, batchCommitter, emitter, doug, pF, 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, pF, data = snativePermTestInputCALL("setBase", users[3], permission.CreateContract, true) - testSNativeCALLExpectPass(t, batchCommitter, doug, pF, snativeAddress, data, func(ret []byte) error { return nil }) - snativeAddress, pF, data = snativePermTestInputCALL("hasBase", users[3], permission.CreateContract, false) - testSNativeCALLExpectPass(t, batchCommitter, doug, pF, snativeAddress, data, func(ret []byte) error { + snativeAddress, pF, data = snativePermTestInputCALL("setBase", users[3], ptypes.CreateContract, true) + testSNativeCALLExpectPass(t, batchCommitter, emitter, doug, pF, snativeAddress, data, func(ret []byte) error { return nil }) + snativeAddress, pF, data = snativePermTestInputCALL("hasBase", users[3], ptypes.CreateContract, false) + testSNativeCALLExpectPass(t, batchCommitter, emitter, doug, pF, 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) @@ -766,11 +749,11 @@ func TestSNativeCALL(t *testing.T) { fmt.Println("\n#### UnsetBase") // UnsetBase - snativeAddress, pF, data = snativePermTestInputCALL("unsetBase", users[3], permission.CreateContract, false) - testSNativeCALLExpectFail(t, batchCommitter, doug, snativeAddress, data) - testSNativeCALLExpectPass(t, batchCommitter, doug, pF, snativeAddress, data, func(ret []byte) error { return nil }) - snativeAddress, pF, data = snativePermTestInputCALL("hasBase", users[3], permission.CreateContract, false) - testSNativeCALLExpectPass(t, batchCommitter, doug, pF, snativeAddress, data, func(ret []byte) error { + snativeAddress, pF, data = snativePermTestInputCALL("unsetBase", users[3], ptypes.CreateContract, false) + testSNativeCALLExpectFail(t, batchCommitter, emitter, doug, snativeAddress, data) + testSNativeCALLExpectPass(t, batchCommitter, emitter, doug, pF, snativeAddress, data, func(ret []byte) error { return nil }) + snativeAddress, pF, data = snativePermTestInputCALL("hasBase", users[3], ptypes.CreateContract, false) + testSNativeCALLExpectPass(t, batchCommitter, emitter, doug, pF, snativeAddress, data, func(ret []byte) error { if !IsZeros(ret) { return fmt.Errorf("Expected 0. Got %X", ret) } @@ -779,11 +762,11 @@ func TestSNativeCALL(t *testing.T) { fmt.Println("\n#### SetGlobal") // SetGlobalPerm - snativeAddress, pF, data = snativePermTestInputCALL("setGlobal", users[3], permission.CreateContract, true) - testSNativeCALLExpectFail(t, batchCommitter, doug, snativeAddress, data) - testSNativeCALLExpectPass(t, batchCommitter, doug, pF, snativeAddress, data, func(ret []byte) error { return nil }) - snativeAddress, pF, data = snativePermTestInputCALL("hasBase", users[3], permission.CreateContract, false) - testSNativeCALLExpectPass(t, batchCommitter, doug, pF, snativeAddress, data, func(ret []byte) error { + snativeAddress, pF, data = snativePermTestInputCALL("setGlobal", users[3], ptypes.CreateContract, true) + testSNativeCALLExpectFail(t, batchCommitter, emitter, doug, snativeAddress, data) + testSNativeCALLExpectPass(t, batchCommitter, emitter, doug, pF, snativeAddress, data, func(ret []byte) error { return nil }) + snativeAddress, pF, data = snativePermTestInputCALL("hasBase", users[3], ptypes.CreateContract, false) + testSNativeCALLExpectPass(t, batchCommitter, emitter, doug, pF, 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) @@ -794,8 +777,8 @@ func TestSNativeCALL(t *testing.T) { fmt.Println("\n#### HasRole") // HasRole snativeAddress, pF, data = snativeRoleTestInputCALL("hasRole", users[3], "bumble") - testSNativeCALLExpectFail(t, batchCommitter, doug, snativeAddress, data) - testSNativeCALLExpectPass(t, batchCommitter, doug, pF, snativeAddress, data, func(ret []byte) error { + testSNativeCALLExpectFail(t, batchCommitter, emitter, doug, snativeAddress, data) + testSNativeCALLExpectPass(t, batchCommitter, emitter, doug, pF, snativeAddress, data, func(ret []byte) error { if !IsZeros(ret[:31]) || ret[31] != byte(1) { return fmt.Errorf("Expected 1. Got %X", ret) } @@ -805,17 +788,17 @@ func TestSNativeCALL(t *testing.T) { fmt.Println("\n#### AddRole") // AddRole snativeAddress, pF, data = snativeRoleTestInputCALL("hasRole", users[3], "chuck") - testSNativeCALLExpectPass(t, batchCommitter, doug, pF, snativeAddress, data, func(ret []byte) error { + testSNativeCALLExpectPass(t, batchCommitter, emitter, doug, pF, snativeAddress, data, func(ret []byte) error { if !IsZeros(ret) { return fmt.Errorf("Expected 0. Got %X", ret) } return nil }) snativeAddress, pF, data = snativeRoleTestInputCALL("addRole", users[3], "chuck") - testSNativeCALLExpectFail(t, batchCommitter, doug, snativeAddress, data) - testSNativeCALLExpectPass(t, batchCommitter, doug, pF, snativeAddress, data, func(ret []byte) error { return nil }) + testSNativeCALLExpectFail(t, batchCommitter, emitter, doug, snativeAddress, data) + testSNativeCALLExpectPass(t, batchCommitter, emitter, doug, pF, snativeAddress, data, func(ret []byte) error { return nil }) snativeAddress, pF, data = snativeRoleTestInputCALL("hasRole", users[3], "chuck") - testSNativeCALLExpectPass(t, batchCommitter, doug, pF, snativeAddress, data, func(ret []byte) error { + testSNativeCALLExpectPass(t, batchCommitter, emitter, doug, pF, snativeAddress, data, func(ret []byte) error { if !IsZeros(ret[:31]) || ret[31] != byte(1) { return fmt.Errorf("Expected 1. Got %X", ret) } @@ -825,10 +808,10 @@ func TestSNativeCALL(t *testing.T) { fmt.Println("\n#### RemoveRole") // RemoveRole snativeAddress, pF, data = snativeRoleTestInputCALL("removeRole", users[3], "chuck") - testSNativeCALLExpectFail(t, batchCommitter, doug, snativeAddress, data) - testSNativeCALLExpectPass(t, batchCommitter, doug, pF, snativeAddress, data, func(ret []byte) error { return nil }) + testSNativeCALLExpectFail(t, batchCommitter, emitter, doug, snativeAddress, data) + testSNativeCALLExpectPass(t, batchCommitter, emitter, doug, pF, snativeAddress, data, func(ret []byte) error { return nil }) snativeAddress, pF, data = snativeRoleTestInputCALL("hasRole", users[3], "chuck") - testSNativeCALLExpectPass(t, batchCommitter, doug, pF, snativeAddress, data, func(ret []byte) error { + testSNativeCALLExpectPass(t, batchCommitter, emitter, doug, pF, snativeAddress, data, func(ret []byte) error { if !IsZeros(ret) { return fmt.Errorf("Expected 0. Got %X", ret) } @@ -840,50 +823,50 @@ func TestSNativeTx(t *testing.T) { stateDB := dbm.NewDB("state", dbBackend, dbDir) defer stateDB.Close() genDoc := newBaseGenDoc(permission.ZeroAccountPermissions, permission.ZeroAccountPermissions) - genDoc.Accounts[0].Permissions.Base.Set(permission.Call, true) // give the 0 account permission - genDoc.Accounts[3].Permissions.Base.Set(permission.Bond, true) // some arbitrary permission to play with + genDoc.Accounts[0].Permissions.Base.Set(ptypes.Call, true) // give the 0 account permission + genDoc.Accounts[3].Permissions.Base.Set(ptypes.Bond, true) // some arbitrary permission to play with genDoc.Accounts[3].Permissions.AddRole("bumble") genDoc.Accounts[3].Permissions.AddRole("bee") st, err := MakeGenesisState(stateDB, &genDoc) require.NoError(t, err) - batchCommitter := makeExecutor(st) + batchCommitter, _ := makeExecutor(st) //---------------------------------------------------------- // Test SNativeTx fmt.Println("\n#### SetBase") // SetBase - snativeArgs := snativePermTestInputTx("setBase", users[3], permission.Bond, false) + snativeArgs := snativePermTestInputTx("setBase", users[3], ptypes.Bond, false) testSNativeTxExpectFail(t, batchCommitter, snativeArgs) - testSNativeTxExpectPass(t, batchCommitter, permission.SetBase, snativeArgs) + testSNativeTxExpectPass(t, batchCommitter, ptypes.SetBase, snativeArgs) acc := getAccount(batchCommitter.stateCache, users[3].Address()) - if v, _ := acc.MutablePermissions().Base.Get(permission.Bond); v { + if v, _ := acc.MutablePermissions().Base.Get(ptypes.Bond); v { t.Fatal("expected permission to be set false") } - snativeArgs = snativePermTestInputTx("setBase", users[3], permission.CreateContract, true) - testSNativeTxExpectPass(t, batchCommitter, permission.SetBase, snativeArgs) + snativeArgs = snativePermTestInputTx("setBase", users[3], ptypes.CreateContract, true) + testSNativeTxExpectPass(t, batchCommitter, ptypes.SetBase, snativeArgs) acc = getAccount(batchCommitter.stateCache, users[3].Address()) - if v, _ := acc.MutablePermissions().Base.Get(permission.CreateContract); !v { + if v, _ := acc.MutablePermissions().Base.Get(ptypes.CreateContract); !v { t.Fatal("expected permission to be set true") } fmt.Println("\n#### UnsetBase") // UnsetBase - snativeArgs = snativePermTestInputTx("unsetBase", users[3], permission.CreateContract, false) + snativeArgs = snativePermTestInputTx("unsetBase", users[3], ptypes.CreateContract, false) testSNativeTxExpectFail(t, batchCommitter, snativeArgs) - testSNativeTxExpectPass(t, batchCommitter, permission.UnsetBase, snativeArgs) + testSNativeTxExpectPass(t, batchCommitter, ptypes.UnsetBase, snativeArgs) acc = getAccount(batchCommitter.stateCache, users[3].Address()) - if v, _ := acc.MutablePermissions().Base.Get(permission.CreateContract); v { + if v, _ := acc.MutablePermissions().Base.Get(ptypes.CreateContract); v { t.Fatal("expected permission to be set false") } fmt.Println("\n#### SetGlobal") // SetGlobalPerm - snativeArgs = snativePermTestInputTx("setGlobal", users[3], permission.CreateContract, true) + snativeArgs = snativePermTestInputTx("setGlobal", users[3], ptypes.CreateContract, true) testSNativeTxExpectFail(t, batchCommitter, snativeArgs) - testSNativeTxExpectPass(t, batchCommitter, permission.SetGlobal, snativeArgs) + testSNativeTxExpectPass(t, batchCommitter, ptypes.SetGlobal, snativeArgs) acc = getAccount(batchCommitter.stateCache, acm.GlobalPermissionsAddress) - if v, _ := acc.MutablePermissions().Base.Get(permission.CreateContract); !v { + if v, _ := acc.MutablePermissions().Base.Get(ptypes.CreateContract); !v { t.Fatal("expected permission to be set true") } @@ -891,7 +874,7 @@ func TestSNativeTx(t *testing.T) { // AddRole snativeArgs = snativeRoleTestInputTx("addRole", users[3], "chuck") testSNativeTxExpectFail(t, batchCommitter, snativeArgs) - testSNativeTxExpectPass(t, batchCommitter, permission.AddRole, snativeArgs) + testSNativeTxExpectPass(t, batchCommitter, ptypes.AddRole, snativeArgs) acc = getAccount(batchCommitter.stateCache, users[3].Address()) if v := acc.Permissions().HasRole("chuck"); !v { t.Fatal("expected role to be added") @@ -901,7 +884,7 @@ func TestSNativeTx(t *testing.T) { // RemoveRole snativeArgs = snativeRoleTestInputTx("removeRole", users[3], "chuck") testSNativeTxExpectFail(t, batchCommitter, snativeArgs) - testSNativeTxExpectPass(t, batchCommitter, permission.RemoveRole, snativeArgs) + testSNativeTxExpectPass(t, batchCommitter, ptypes.RemoveRole, snativeArgs) acc = getAccount(batchCommitter.stateCache, users[3].Address()) if v := acc.Permissions().HasRole("chuck"); v { t.Fatal("expected role to be removed") @@ -993,7 +976,7 @@ func TestNameTxs(t *testing.T) { } } - validateEntry := func(t *testing.T, entry *NameRegEntry, name, data string, addr crypto.Address, expires uint64) { + validateEntry := func(t *testing.T, entry *names.Entry, name, data string, addr crypto.Address, expires uint64) { if entry == nil { t.Fatalf("Could not find name %s", name) @@ -1022,7 +1005,7 @@ func TestNameTxs(t *testing.T) { if err := execTxWithState(state, txEnv); err != nil { t.Fatal(err) } - entry, err := state.GetNameRegEntry(name) + entry, err := state.GetNameEntry(name) require.NoError(t, err) validateEntry(t, entry, name, data, testPrivAccounts[0].Address(), startingBlock+numDesiredBlocks) @@ -1042,7 +1025,7 @@ func TestNameTxs(t *testing.T) { if err := execTxWithStateNewBlock(state, blockchain, txEnv); err != nil { t.Fatal(err) } - entry, err = state.GetNameRegEntry(name) + entry, err = state.GetNameEntry(name) require.NoError(t, err) validateEntry(t, entry, name, data, testPrivAccounts[0].Address(), startingBlock+numDesiredBlocks*2) @@ -1053,7 +1036,7 @@ func TestNameTxs(t *testing.T) { if err := execTxWithStateNewBlock(state, blockchain, txEnv); err != nil { t.Fatal(err) } - entry, err = state.GetNameRegEntry(name) + entry, err = state.GetNameEntry(name) require.NoError(t, err) validateEntry(t, entry, name, data, testPrivAccounts[0].Address(), startingBlock+numDesiredBlocks*3) @@ -1077,7 +1060,7 @@ func TestNameTxs(t *testing.T) { if err := execTxWithStateAndBlockchain(state, blockchain.Tip, txEnv); err != nil { t.Fatal(err) } - entry, err = state.GetNameRegEntry(name) + entry, err = state.GetNameEntry(name) require.NoError(t, err) validateEntry(t, entry, name, data, testPrivAccounts[1].Address(), blockchain.LastBlockHeight()+numDesiredBlocks) @@ -1092,7 +1075,7 @@ func TestNameTxs(t *testing.T) { if err := execTxWithStateAndBlockchain(state, blockchain.Tip, txEnv); err != nil { t.Fatal(err) } - entry, err = state.GetNameRegEntry(name) + entry, err = state.GetNameEntry(name) require.NoError(t, err) validateEntry(t, entry, name, data, testPrivAccounts[1].Address(), blockchain.LastBlockHeight()+numDesiredBlocks) @@ -1105,7 +1088,7 @@ func TestNameTxs(t *testing.T) { if err := execTxWithStateNewBlock(state, blockchain, txEnv); err != nil { t.Fatal(err) } - entry, err = state.GetNameRegEntry(name) + entry, err = state.GetNameEntry(name) require.NoError(t, err) if entry != nil { t.Fatal("Expected removed entry to be nil") @@ -1122,7 +1105,7 @@ func TestNameTxs(t *testing.T) { if err := execTxWithStateAndBlockchain(state, blockchain.Tip, txEnv); err != nil { t.Fatal(err) } - entry, err = state.GetNameRegEntry(name) + entry, err = state.GetNameEntry(name) require.NoError(t, err) validateEntry(t, entry, name, data, testPrivAccounts[0].Address(), blockchain.LastBlockHeight()+numDesiredBlocks) // Fast forward @@ -1138,7 +1121,7 @@ func TestNameTxs(t *testing.T) { if err := execTxWithStateNewBlock(state, blockchain, txEnv); err != nil { t.Fatal(err) } - entry, err = state.GetNameRegEntry(name) + entry, err = state.GetNameEntry(name) require.NoError(t, err) if entry != nil { t.Fatal("Expected removed entry to be nil") @@ -1482,7 +1465,7 @@ proof-of-work chain as proof of what happened while they were gone ` t.Errorf("Unexpected newAcc0 balance. Expected %v, got %v", acc0.Balance()-entryAmount, newAcc0.Balance()) } - entry, err := stateNameTx.GetNameRegEntry(entryName) + entry, err := stateNameTx.GetNameEntry(entryName) require.NoError(t, err) if entry == nil { t.Errorf("Expected an entry but got nil") @@ -1578,7 +1561,7 @@ func TestSelfDestruct(t *testing.T) { tx := payload.NewCallTxWithSequence(acc0PubKey, addressPtr(acc1), nil, sendingAmount, 1000, 0, acc0.Sequence()+1) // we use cache instead of execTxWithState so we can run the tx twice - exe := NewBatchCommitter(state, testChainID, newBlockchain(testGenesisDoc).Tip, event.NewNoOpPublisher(), logger) + exe := NewBatchCommitter(state, newBlockchain(testGenesisDoc).Tip, event.NewNoOpPublisher(), logger) signAndExecute(t, false, exe, testChainID, tx, privAccounts[0]) // if we do it again, we won't get an error, but the self-destruct @@ -1616,7 +1599,7 @@ func signAndExecute(t *testing.T, shoudlFail bool, exe BatchExecutor, chainID st } func execTxWithStateAndBlockchain(state *State, tip *bcm.Tip, txEnv *txs.Envelope) error { - exe := newExecutor("execTxWithStateAndBlockchainCache", true, state, testChainID, tip, + exe := newExecutor("execTxWithStateAndBlockchainCache", true, state, tip, event.NewNoOpPublisher(), logger) if err := exe.Execute(txEnv); err != nil { return err @@ -1671,36 +1654,31 @@ func addressPtr(account acm.Account) *crypto.Address { //------------------------------------------------------------------------------------- // helpers -var ExceptionTimeOut = "timed out waiting for event" +var ExceptionTimeOut = errors.NewCodedError(errors.ErrorCodeGeneric, "timed out waiting for event") // run ExecTx and wait for the Call event on given addr // returns the msg data and an error/exception -func execTxWaitEvent(t *testing.T, batchCommitter *executor, txEnv *txs.Envelope, eventid string) (interface{}, string) { - emitter := event.NewEmitter(logger) - ch := make(chan interface{}) - emitter.Subscribe(context.Background(), "test", event.QueryForEventID(eventid), ch) - evc := event.NewEventCache(emitter) - batchCommitter.eventCache = evc - go func() { - if err := batchCommitter.Execute(txEnv); err != nil { - ch <- err.Error() - } - evc.Flush() - }() +func execTxWaitAccountCall(t *testing.T, batchCommitter *executor, emitter event.Emitter, txEnv *txs.Envelope, + address crypto.Address) (*evm_events.EventDataCall, error) { + + ch := make(chan *evm_events.EventDataCall) + ctx := context.Background() + const subscriber = "exexTxWaitEvent" + //emitter.Subscribe(ctx, subscriber, event.QueryForEventID(eventid), ch) + evm_events.SubscribeAccountCall(ctx, emitter, subscriber, address, txEnv.Tx.Hash(), -1, ch) + defer emitter.UnsubscribeAll(ctx, subscriber) + err := batchCommitter.Execute(txEnv) + if err != nil { + return nil, err + } + batchCommitter.Commit() ticker := time.NewTicker(5 * time.Second) select { - case msg := <-ch: - switch ev := msg.(type) { - case *exe_events.EventDataTx: - return ev, ev.Exception - case *evm_events.EventDataCall: - return ev, ev.Exception - case string: - return nil, ev - default: - return ev, "" - } + case eventDataCall := <-ch: + fmt.Println("MSG: ", eventDataCall) + return eventDataCall, eventDataCall.Exception.AsError() + case <-ticker.C: return nil, ExceptionTimeOut } @@ -1708,18 +1686,18 @@ func execTxWaitEvent(t *testing.T, batchCommitter *executor, txEnv *txs.Envelope } // give a contract perms for an snative, call it, it calls the snative, but shouldn't have permission -func testSNativeCALLExpectFail(t *testing.T, batchCommitter *executor, doug acm.MutableAccount, +func testSNativeCALLExpectFail(t *testing.T, batchCommitter *executor, emitter event.Emitter, doug acm.MutableAccount, snativeAddress crypto.Address, data []byte) { - testSNativeCALL(t, false, batchCommitter, doug, 0, snativeAddress, data, nil) + testSNativeCALL(t, false, batchCommitter, emitter, doug, 0, snativeAddress, data, nil) } // give a contract perms for an snative, call it, it calls the snative, ensure the check funciton (f) succeeds -func testSNativeCALLExpectPass(t *testing.T, batchCommitter *executor, doug acm.MutableAccount, snativePerm ptypes.PermFlag, +func testSNativeCALLExpectPass(t *testing.T, batchCommitter *executor, emitter event.Emitter, doug acm.MutableAccount, snativePerm ptypes.PermFlag, snativeAddress crypto.Address, data []byte, f func([]byte) error) { - testSNativeCALL(t, true, batchCommitter, doug, snativePerm, snativeAddress, data, f) + testSNativeCALL(t, true, batchCommitter, emitter, doug, snativePerm, snativeAddress, data, f) } -func testSNativeCALL(t *testing.T, expectPass bool, batchCommitter *executor, doug acm.MutableAccount, +func testSNativeCALL(t *testing.T, expectPass bool, batchCommitter *executor, emitter event.Emitter, doug acm.MutableAccount, snativePerm ptypes.PermFlag, snativeAddress crypto.Address, data []byte, f func([]byte) error) { if expectPass { doug.MutablePermissions().Base.Set(snativePerm, true) @@ -1733,23 +1711,18 @@ func testSNativeCALL(t *testing.T, expectPass bool, batchCommitter *executor, do txEnv := txs.Enclose(testChainID, tx) require.NoError(t, txEnv.Sign(users[0])) t.Logf("subscribing to %v", evm_events.EventStringAccountCall(snativeAddress)) - ev, exception := execTxWaitEvent(t, batchCommitter, txEnv, evm_events.EventStringAccountCall(snativeAddress)) - if exception == ExceptionTimeOut { + ev, err := execTxWaitAccountCall(t, batchCommitter, emitter, txEnv, snativeAddress) + if err == ExceptionTimeOut { t.Fatal("Timed out waiting for event") } if expectPass { - if exception != "" { - t.Fatal("Unexpected exception", exception) - } - evv := ev.(*evm_events.EventDataCall) - ret := evv.Return + require.NoError(t, err) + ret := ev.Return if err := f(ret); err != nil { t.Fatal(err) } } else { - if exception == "" { - t.Fatal("Expected exception") - } + require.Error(t, err) } } @@ -1820,7 +1793,7 @@ func snativePermTestInputCALL(name string, user acm.AddressableSigner, perm ptyp } data = append(permNameToFuncID(name), data...) var err error - if pF, err = permission.PermStringToFlag(name); err != nil { + if pF, err = ptypes.PermStringToFlag(name); err != nil { panic(fmt.Sprintf("failed to convert perm string (%s) to flag", name)) } return @@ -1850,7 +1823,7 @@ func snativeRoleTestInputCALL(name string, user acm.AddressableSigner, data = append(permNameToFuncID(name), data...) var err error - if pF, err = permission.PermStringToFlag(name); err != nil { + if pF, err = ptypes.PermStringToFlag(name); err != nil { panic(fmt.Sprintf("failed to convert perm string (%s) to flag", name)) } return diff --git a/execution/executors/call.go b/execution/executors/call.go new file mode 100644 index 0000000000000000000000000000000000000000..f377e9147eb14d022400aa3380b26ced004bc292 --- /dev/null +++ b/execution/executors/call.go @@ -0,0 +1,246 @@ +package executors + +import ( + "fmt" + + acm "github.com/hyperledger/burrow/account" + "github.com/hyperledger/burrow/account/state" + "github.com/hyperledger/burrow/binary" + "github.com/hyperledger/burrow/blockchain" + "github.com/hyperledger/burrow/event" + "github.com/hyperledger/burrow/execution/errors" + "github.com/hyperledger/burrow/execution/events" + "github.com/hyperledger/burrow/execution/evm" + "github.com/hyperledger/burrow/logging" + "github.com/hyperledger/burrow/logging/structure" + "github.com/hyperledger/burrow/txs" + "github.com/hyperledger/burrow/txs/payload" +) + +// TODO: make configurable +const GasLimit = uint64(1000000) + +type CallContext struct { + Tip blockchain.TipInfo + StateWriter state.Writer + EventPublisher event.Publisher + RunCall bool + VMOptions []func(*evm.VM) + Logger *logging.Logger + tx *payload.CallTx + txEnv *txs.Envelope +} + +func (ctx *CallContext) Execute(txEnv *txs.Envelope) error { + var ok bool + ctx.tx, ok = txEnv.Tx.Payload.(*payload.CallTx) + if !ok { + return fmt.Errorf("payload must be CallTx, but is: %v", txEnv.Tx.Payload) + } + ctx.txEnv = txEnv + inAcc, outAcc, err := ctx.Precheck() + if err != nil { + return err + } + // That the fee less than the input amount is checked by Precheck + value := ctx.tx.Input.Amount - ctx.tx.Fee + + if ctx.RunCall { + ctx.Deliver(inAcc, outAcc, value) + } else { + ctx.Check(inAcc, value) + } + + return nil +} + +func (ctx *CallContext) Precheck() (acm.MutableAccount, acm.Account, error) { + var outAcc acm.Account + // Validate input + inAcc, err := state.GetMutableAccount(ctx.StateWriter, ctx.tx.Input.Address) + if err != nil { + return nil, nil, err + } + if inAcc == nil { + ctx.Logger.InfoMsg("Cannot find input account", + "tx_input", ctx.tx.Input) + return nil, nil, payload.ErrTxInvalidAddress + } + + err = validateInput(inAcc, ctx.tx.Input) + if err != nil { + ctx.Logger.InfoMsg("validateInput failed", + "tx_input", ctx.tx.Input, structure.ErrorKey, err) + return nil, nil, err + } + if ctx.tx.Input.Amount < ctx.tx.Fee { + ctx.Logger.InfoMsg("Sender did not send enough to cover the fee", + "tx_input", ctx.tx.Input) + return nil, nil, payload.ErrTxInsufficientFunds + } + + ctx.Logger.TraceMsg("Incrementing sequence number for CallTx", + "tag", "sequence", + "account", inAcc.Address(), + "old_sequence", inAcc.Sequence(), + "new_sequence", inAcc.Sequence()+1) + + inAcc, err = inAcc.IncSequence().SubtractFromBalance(ctx.tx.Fee) + if err != nil { + return nil, nil, err + } + + // Calling a nil destination is defined as requesting contract creation + createContract := ctx.tx.Address == nil + + if createContract { + if !hasCreateContractPermission(ctx.StateWriter, inAcc, ctx.Logger) { + return nil, nil, fmt.Errorf("account %s does not have CreateContract permission", ctx.tx.Input.Address) + } + } else { + if !hasCallPermission(ctx.StateWriter, inAcc, ctx.Logger) { + return nil, nil, fmt.Errorf("account %s does not have Call permission", ctx.tx.Input.Address) + } + // check if its a native contract + if evm.IsRegisteredNativeContract(ctx.tx.Address.Word256()) { + return nil, nil, fmt.Errorf("attempt to call a native contract at %s, "+ + "but native contracts cannot be called using CallTx. Use a "+ + "contract that calls the native contract or the appropriate tx "+ + "type (eg. PermissionsTx, NameTx)", ctx.tx.Address) + } + + // Output account 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 + // Note: ctx.tx.Address == nil iff createContract so dereference is okay + outAcc, err = ctx.StateWriter.GetAccount(*ctx.tx.Address) + if err != nil { + return nil, nil, err + } + } + + err = ctx.StateWriter.UpdateAccount(inAcc) + if err != nil { + return nil, nil, err + } + return inAcc, outAcc, nil +} + +func (ctx *CallContext) Check(inAcc acm.MutableAccount, value uint64) error { + createContract := ctx.tx.Address == nil + // The mempool does not call txs until + // the proposer determines the order of txs. + // So mempool will skip the actual .Call(), + // and only deduct from the caller's balance. + inAcc, err := inAcc.SubtractFromBalance(value) + if err != nil { + return err + } + if createContract { + // This is done by DeriveNewAccount when runCall == true + ctx.Logger.TraceMsg("Incrementing sequence number since creates contract", + "tag", "sequence", + "account", inAcc.Address(), + "old_sequence", inAcc.Sequence(), + "new_sequence", inAcc.Sequence()+1) + inAcc.IncSequence() + } + return ctx.StateWriter.UpdateAccount(inAcc) +} + +func (ctx *CallContext) Deliver(inAcc, outAcc acm.Account, value uint64) error { + createContract := ctx.tx.Address == nil + // VM call variables + var ( + gas uint64 = ctx.tx.GasLimit + caller acm.MutableAccount = acm.AsMutableAccount(inAcc) + callee acm.MutableAccount = nil // initialized below + code []byte = nil + ret []byte = nil + txCache = state.NewCache(ctx.StateWriter, state.Name("TxCache")) + params = evm.Params{ + BlockHeight: ctx.Tip.LastBlockHeight(), + BlockHash: binary.LeftPadWord256(ctx.Tip.LastBlockHash()), + BlockTime: ctx.Tip.LastBlockTime().Unix(), + GasLimit: GasLimit, + } + ) + + // get or create callee + if createContract { + // We already checked for permission + callee = evm.DeriveNewAccount(caller, state.GlobalAccountPermissions(ctx.StateWriter), ctx.Logger) + code = ctx.tx.Data + ctx.Logger.TraceMsg("Creating new contract", + "contract_address", callee.Address(), + "init_code", code) + } else { + if outAcc == nil || len(outAcc.Code()) == 0 { + // 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 (sequence number will prevent re-ordering of those txs) + // but to create with one contract and call with another + // you have to wait a block to avoid a re-ordering attack + // that will take your fees + if outAcc == nil { + ctx.Logger.InfoMsg("Call to address that does not exist", + "caller_address", inAcc.Address(), + "callee_address", ctx.tx.Address) + } else { + ctx.Logger.InfoMsg("Call to address that holds no code", + "caller_address", inAcc.Address(), + "callee_address", ctx.tx.Address) + } + ctx.FireCallEvents(nil, payload.ErrTxInvalidAddress) + return nil + } + callee = acm.AsMutableAccount(outAcc) + code = callee.Code() + ctx.Logger.TraceMsg("Calling existing contract", + "contract_address", callee.Address(), + "input", ctx.tx.Data, + "contract_code", code) + } + ctx.Logger.Trace.Log("callee", callee.Address().String()) + + txCache.UpdateAccount(caller) + txCache.UpdateAccount(callee) + vmach := evm.NewVM(params, caller.Address(), ctx.txEnv.Tx.Hash(), ctx.Logger, ctx.VMOptions...) + vmach.SetPublisher(ctx.EventPublisher) + // NOTE: Call() transfers the value from caller to callee iff call succeeds. + ret, exception := vmach.Call(txCache, caller, callee, code, ctx.tx.Data, value, &gas) + if exception != nil { + // Failure. Charge the gas fee. The 'value' was otherwise not transferred. + ctx.Logger.InfoMsg("Error on execution", + structure.ErrorKey, exception) + } else { + ctx.Logger.TraceMsg("Successful execution") + if createContract { + callee.SetCode(ret) + } + err := txCache.Sync(ctx.StateWriter) + if err != nil { + return err + } + } + // Create a receipt from the ret and whether it erred. + ctx.Logger.TraceMsg("VM call complete", + "caller", caller, + "callee", callee, + "return", ret, + structure.ErrorKey, exception) + ctx.FireCallEvents(ret, exception) + return nil +} + +func (ctx *CallContext) FireCallEvents(ret []byte, err error) { + // Fire Events for sender and receiver + // a separate event will be fired from vm for each additional call + if ctx.EventPublisher != nil { + events.PublishAccountInput(ctx.EventPublisher, ctx.tx.Input.Address, ctx.txEnv.Tx, ret, errors.AsCodedError(err)) + if ctx.tx.Address != nil { + events.PublishAccountOutput(ctx.EventPublisher, *ctx.tx.Address, ctx.txEnv.Tx, ret, errors.AsCodedError(err)) + } + } +} diff --git a/execution/executors/name.go b/execution/executors/name.go new file mode 100644 index 0000000000000000000000000000000000000000..dbd2978a82395e5ca5a19adb6c951405f6c800d5 --- /dev/null +++ b/execution/executors/name.go @@ -0,0 +1,185 @@ +package executors + +import ( + "fmt" + + "github.com/hyperledger/burrow/account/state" + "github.com/hyperledger/burrow/blockchain" + "github.com/hyperledger/burrow/event" + "github.com/hyperledger/burrow/execution/events" + "github.com/hyperledger/burrow/execution/names" + "github.com/hyperledger/burrow/logging" + "github.com/hyperledger/burrow/logging/structure" + "github.com/hyperledger/burrow/txs" + "github.com/hyperledger/burrow/txs/payload" +) + +type NameContext struct { + Tip blockchain.TipInfo + StateWriter state.Writer + EventPublisher event.Publisher + NameReg names.Writer + Logger *logging.Logger + tx *payload.NameTx +} + +func (ctx *NameContext) Execute(txEnv *txs.Envelope) error { + var ok bool + ctx.tx, ok = txEnv.Tx.Payload.(*payload.NameTx) + if !ok { + return fmt.Errorf("payload must be NameTx, but is: %v", txEnv.Tx.Payload) + } + // Validate input + inAcc, err := state.GetMutableAccount(ctx.StateWriter, ctx.tx.Input.Address) + if err != nil { + return err + } + if inAcc == nil { + ctx.Logger.InfoMsg("Cannot find input account", + "tx_input", ctx.tx.Input) + return payload.ErrTxInvalidAddress + } + // check permission + if !hasNamePermission(ctx.StateWriter, inAcc, ctx.Logger) { + return fmt.Errorf("account %s does not have Name permission", ctx.tx.Input.Address) + } + err = validateInput(inAcc, ctx.tx.Input) + if err != nil { + ctx.Logger.InfoMsg("validateInput failed", + "tx_input", ctx.tx.Input, structure.ErrorKey, err) + return err + } + if ctx.tx.Input.Amount < ctx.tx.Fee { + ctx.Logger.InfoMsg("Sender did not send enough to cover the fee", + "tx_input", ctx.tx.Input) + return payload.ErrTxInsufficientFunds + } + + // validate the input strings + if err := ctx.tx.ValidateStrings(); err != nil { + return err + } + + value := ctx.tx.Input.Amount - ctx.tx.Fee + + // let's say cost of a name for one block is len(data) + 32 + costPerBlock := names.NameCostPerBlock(names.NameBaseCost(ctx.tx.Name, ctx.tx.Data)) + expiresIn := value / uint64(costPerBlock) + lastBlockHeight := ctx.Tip.LastBlockHeight() + + ctx.Logger.TraceMsg("New NameTx", + "value", value, + "cost_per_block", costPerBlock, + "expires_in", expiresIn, + "last_block_height", lastBlockHeight) + + // check if the name exists + entry, err := ctx.NameReg.GetNameEntry(ctx.tx.Name) + if err != nil { + return err + } + + if entry != nil { + var expired bool + + // if the entry already exists, and hasn't expired, we must be owner + if entry.Expires > lastBlockHeight { + // ensure we are owner + if entry.Owner != ctx.tx.Input.Address { + return fmt.Errorf("permission denied: sender %s is trying to update a name (%s) for "+ + "which they are not an owner", ctx.tx.Input.Address, ctx.tx.Name) + } + } else { + expired = true + } + + // no value and empty data means delete the entry + if value == 0 && len(ctx.tx.Data) == 0 { + // maybe we reward you for telling us we can delete this crap + // (owners if not expired, anyone if expired) + ctx.Logger.TraceMsg("Removing NameReg entry (no value and empty data in tx requests this)", + "name", entry.Name) + err := ctx.NameReg.RemoveNameEntry(entry.Name) + if err != nil { + return err + } + } else { + // update the entry by bumping the expiry + // and changing the data + if expired { + if expiresIn < names.MinNameRegistrationPeriod { + return fmt.Errorf("Names must be registered for at least %d blocks", names.MinNameRegistrationPeriod) + } + entry.Expires = lastBlockHeight + expiresIn + entry.Owner = ctx.tx.Input.Address + ctx.Logger.TraceMsg("An old NameReg entry has expired and been reclaimed", + "name", entry.Name, + "expires_in", expiresIn, + "owner", entry.Owner) + } else { + // since the size of the data may have changed + // we use the total amount of "credit" + oldCredit := (entry.Expires - lastBlockHeight) * names.NameBaseCost(entry.Name, entry.Data) + credit := oldCredit + value + expiresIn = uint64(credit / costPerBlock) + if expiresIn < names.MinNameRegistrationPeriod { + return fmt.Errorf("names must be registered for at least %d blocks", names.MinNameRegistrationPeriod) + } + entry.Expires = lastBlockHeight + expiresIn + ctx.Logger.TraceMsg("Updated NameReg entry", + "name", entry.Name, + "expires_in", expiresIn, + "old_credit", oldCredit, + "value", value, + "credit", credit) + } + entry.Data = ctx.tx.Data + err := ctx.NameReg.UpdateNameEntry(entry) + if err != nil { + return err + } + } + } else { + if expiresIn < names.MinNameRegistrationPeriod { + return fmt.Errorf("Names must be registered for at least %d blocks", names.MinNameRegistrationPeriod) + } + // entry does not exist, so create it + entry = &names.Entry{ + Name: ctx.tx.Name, + Owner: ctx.tx.Input.Address, + Data: ctx.tx.Data, + Expires: lastBlockHeight + expiresIn, + } + ctx.Logger.TraceMsg("Creating NameReg entry", + "name", entry.Name, + "expires_in", expiresIn) + err := ctx.NameReg.UpdateNameEntry(entry) + if err != nil { + return err + } + } + + // TODO: something with the value sent? + + // Good! + ctx.Logger.TraceMsg("Incrementing sequence number for NameTx", + "tag", "sequence", + "account", inAcc.Address(), + "old_sequence", inAcc.Sequence(), + "new_sequence", inAcc.Sequence()+1) + inAcc.IncSequence() + inAcc, err = inAcc.SubtractFromBalance(value) + if err != nil { + return err + } + ctx.StateWriter.UpdateAccount(inAcc) + + // TODO: maybe we want to take funds on error and allow txs in that don't do anythingi? + + if ctx.EventPublisher != nil { + events.PublishAccountInput(ctx.EventPublisher, ctx.tx.Input.Address, txEnv.Tx, nil, nil) + events.PublishNameReg(ctx.EventPublisher, txEnv.Tx) + } + + return nil +} diff --git a/execution/executors/permissions.go b/execution/executors/permissions.go new file mode 100644 index 0000000000000000000000000000000000000000..06dbd8c0acb3ddc1d96ead562557b7cb58dfb7d0 --- /dev/null +++ b/execution/executors/permissions.go @@ -0,0 +1,153 @@ +package executors + +import ( + "fmt" + + acm "github.com/hyperledger/burrow/account" + "github.com/hyperledger/burrow/account/state" + "github.com/hyperledger/burrow/crypto" + "github.com/hyperledger/burrow/event" + "github.com/hyperledger/burrow/execution/events" + "github.com/hyperledger/burrow/logging" + "github.com/hyperledger/burrow/logging/structure" + ptypes "github.com/hyperledger/burrow/permission/types" + "github.com/hyperledger/burrow/txs" + "github.com/hyperledger/burrow/txs/payload" +) + +type PermissionsContext struct { + StateWriter state.Writer + EventPublisher event.Publisher + Logger *logging.Logger + tx *payload.PermissionsTx +} + +func (ctx *PermissionsContext) Execute(txEnv *txs.Envelope) error { + var ok bool + ctx.tx, ok = txEnv.Tx.Payload.(*payload.PermissionsTx) + if !ok { + return fmt.Errorf("payload must be PermissionsTx, but is: %v", txEnv.Tx.Payload) + } + // Validate input + inAcc, err := state.GetMutableAccount(ctx.StateWriter, ctx.tx.Input.Address) + if err != nil { + return err + } + if inAcc == nil { + ctx.Logger.InfoMsg("Cannot find input account", + "tx_input", ctx.tx.Input) + return payload.ErrTxInvalidAddress + } + + err = ctx.tx.PermArgs.EnsureValid() + if err != nil { + return fmt.Errorf("PermissionsTx received containing invalid PermArgs: %v", err) + } + + permFlag := ctx.tx.PermArgs.PermFlag + // check permission + if !HasPermission(ctx.StateWriter, inAcc, permFlag, ctx.Logger) { + return fmt.Errorf("account %s does not have moderator permission %s (%b)", ctx.tx.Input.Address, + permFlag.String(), permFlag) + } + + err = validateInput(inAcc, ctx.tx.Input) + if err != nil { + ctx.Logger.InfoMsg("validateInput failed", + "tx_input", ctx.tx.Input, + structure.ErrorKey, err) + return err + } + + value := ctx.tx.Input.Amount + + ctx.Logger.TraceMsg("New PermissionsTx", + "perm_args", ctx.tx.PermArgs.String()) + + var permAcc acm.Account + switch ctx.tx.PermArgs.PermFlag { + case ptypes.HasBase: + // this one doesn't make sense from txs + return fmt.Errorf("HasBase is for contracts, not humans. Just look at the blockchain") + case ptypes.SetBase: + permAcc, err = mutatePermissions(ctx.StateWriter, *ctx.tx.PermArgs.Address, + func(perms *ptypes.AccountPermissions) error { + return perms.Base.Set(*ctx.tx.PermArgs.Permission, *ctx.tx.PermArgs.Value) + }) + case ptypes.UnsetBase: + permAcc, err = mutatePermissions(ctx.StateWriter, *ctx.tx.PermArgs.Address, + func(perms *ptypes.AccountPermissions) error { + return perms.Base.Unset(*ctx.tx.PermArgs.Permission) + }) + case ptypes.SetGlobal: + permAcc, err = mutatePermissions(ctx.StateWriter, acm.GlobalPermissionsAddress, + func(perms *ptypes.AccountPermissions) error { + return perms.Base.Set(*ctx.tx.PermArgs.Permission, *ctx.tx.PermArgs.Value) + }) + case ptypes.HasRole: + return fmt.Errorf("HasRole is for contracts, not humans. Just look at the blockchain") + case ptypes.AddRole: + permAcc, err = mutatePermissions(ctx.StateWriter, *ctx.tx.PermArgs.Address, + func(perms *ptypes.AccountPermissions) error { + if !perms.AddRole(*ctx.tx.PermArgs.Role) { + return fmt.Errorf("role (%s) already exists for account %s", + *ctx.tx.PermArgs.Role, *ctx.tx.PermArgs.Address) + } + return nil + }) + case ptypes.RemoveRole: + permAcc, err = mutatePermissions(ctx.StateWriter, *ctx.tx.PermArgs.Address, + func(perms *ptypes.AccountPermissions) error { + if !perms.RmRole(*ctx.tx.PermArgs.Role) { + return fmt.Errorf("role (%s) does not exist for account %s", + *ctx.tx.PermArgs.Role, *ctx.tx.PermArgs.Address) + } + return nil + }) + default: + return fmt.Errorf("invalid permission function: %v", 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! + ctx.Logger.TraceMsg("Incrementing sequence number for PermissionsTx", + "tag", "sequence", + "account", inAcc.Address(), + "old_sequence", inAcc.Sequence(), + "new_sequence", inAcc.Sequence()+1) + inAcc.IncSequence() + inAcc, err = inAcc.SubtractFromBalance(value) + if err != nil { + return err + } + ctx.StateWriter.UpdateAccount(inAcc) + if permAcc != nil { + ctx.StateWriter.UpdateAccount(permAcc) + } + + if ctx.EventPublisher != nil { + events.PublishAccountInput(ctx.EventPublisher, ctx.tx.Input.Address, txEnv.Tx, nil, nil) + events.PublishPermissions(ctx.EventPublisher, permFlag, txEnv.Tx) + } + + return nil +} + +func mutatePermissions(stateReader state.Reader, address crypto.Address, + mutator func(*ptypes.AccountPermissions) error) (acm.Account, error) { + + account, err := stateReader.GetAccount(address) + if err != nil { + return nil, err + } + if account == nil { + return nil, fmt.Errorf("could not get account at address %s in order to alter permissions", address) + } + mutableAccount := acm.AsMutableAccount(account) + + return mutableAccount, mutator(mutableAccount.MutablePermissions()) +} diff --git a/execution/executors/send.go b/execution/executors/send.go new file mode 100644 index 0000000000000000000000000000000000000000..6c79eba84f00fd214cc45e92d405c60e29ecbbc8 --- /dev/null +++ b/execution/executors/send.go @@ -0,0 +1,81 @@ +package executors + +import ( + "fmt" + + "github.com/hyperledger/burrow/account/state" + "github.com/hyperledger/burrow/event" + "github.com/hyperledger/burrow/execution/events" + "github.com/hyperledger/burrow/logging" + "github.com/hyperledger/burrow/txs" + "github.com/hyperledger/burrow/txs/payload" +) + +type SendContext struct { + StateWriter state.Writer + EventPublisher event.Publisher + Logger *logging.Logger + tx *payload.SendTx +} + +func (ctx *SendContext) Execute(txEnv *txs.Envelope) error { + var ok bool + ctx.tx, ok = txEnv.Tx.Payload.(*payload.SendTx) + if !ok { + return fmt.Errorf("payload must be NameTx, but is: %v", txEnv.Tx.Payload) + } + accounts, err := getInputs(ctx.StateWriter, ctx.tx.Inputs) + if err != nil { + return err + } + + // ensure all inputs have send permissions + if !hasSendPermission(ctx.StateWriter, accounts, ctx.Logger) { + return fmt.Errorf("at least one input lacks permission for SendTx") + } + + // add outputs to accounts map + // if any outputs don't exist, all inputs must have CreateAccount perm + accounts, err = getOrMakeOutputs(ctx.StateWriter, accounts, ctx.tx.Outputs, ctx.Logger) + if err != nil { + return err + } + + inTotal, err := validateInputs(accounts, ctx.tx.Inputs) + if err != nil { + return err + } + outTotal, err := validateOutputs(ctx.tx.Outputs) + if err != nil { + return err + } + if outTotal > inTotal { + return payload.ErrTxInsufficientFunds + } + + // Good! Adjust accounts + err = adjustByInputs(accounts, ctx.tx.Inputs, ctx.Logger) + if err != nil { + return err + } + + err = adjustByOutputs(accounts, ctx.tx.Outputs) + if err != nil { + return err + } + + for _, acc := range accounts { + ctx.StateWriter.UpdateAccount(acc) + } + + if ctx.EventPublisher != nil { + for _, i := range ctx.tx.Inputs { + events.PublishAccountInput(ctx.EventPublisher, i.Address, txEnv.Tx, nil, nil) + } + + for _, o := range ctx.tx.Outputs { + events.PublishAccountOutput(ctx.EventPublisher, o.Address, txEnv.Tx, nil, nil) + } + } + return nil +} diff --git a/execution/executors/shared.go b/execution/executors/shared.go new file mode 100644 index 0000000000000000000000000000000000000000..8843249b1eaa488400db617ea18676deed2ea647 --- /dev/null +++ b/execution/executors/shared.go @@ -0,0 +1,250 @@ +package executors + +import ( + "fmt" + + acm "github.com/hyperledger/burrow/account" + "github.com/hyperledger/burrow/account/state" + "github.com/hyperledger/burrow/crypto" + "github.com/hyperledger/burrow/logging" + "github.com/hyperledger/burrow/logging/structure" + "github.com/hyperledger/burrow/permission" + ptypes "github.com/hyperledger/burrow/permission/types" + "github.com/hyperledger/burrow/txs/payload" +) + +// The accounts from the TxInputs must either already have +// acm.PublicKey().(type) != nil, (it must be known), +// or it must be specified in the TxInput. If redeclared, +// the TxInput is modified and input.PublicKey() set to nil. +func getInputs(accountGetter state.AccountGetter, + ins []*payload.TxInput) (map[crypto.Address]acm.MutableAccount, error) { + + accounts := map[crypto.Address]acm.MutableAccount{} + for _, in := range ins { + // Account shouldn't be duplicated + if _, ok := accounts[in.Address]; ok { + return nil, payload.ErrTxDuplicateAddress + } + acc, err := state.GetMutableAccount(accountGetter, in.Address) + if err != nil { + return nil, err + } + if acc == nil { + return nil, payload.ErrTxInvalidAddress + } + accounts[in.Address] = acc + } + return accounts, nil +} + +func getOrMakeOutputs(accountGetter state.AccountGetter, accs map[crypto.Address]acm.MutableAccount, + outs []*payload.TxOutput, logger *logging.Logger) (map[crypto.Address]acm.MutableAccount, error) { + if accs == nil { + accs = make(map[crypto.Address]acm.MutableAccount) + } + + // we should err if an account is being created but the inputs don't have permission + var checkedCreatePerms bool + for _, out := range outs { + // Account shouldn't be duplicated + if _, ok := accs[out.Address]; ok { + return nil, payload.ErrTxDuplicateAddress + } + acc, err := state.GetMutableAccount(accountGetter, out.Address) + if err != nil { + return nil, err + } + // output account may be nil (new) + if acc == nil { + if !checkedCreatePerms { + if !hasCreateAccountPermission(accountGetter, accs, logger) { + return nil, fmt.Errorf("at least one input does not have permission to create accounts") + } + checkedCreatePerms = true + } + acc = acm.ConcreteAccount{ + Address: out.Address, + Sequence: 0, + Balance: 0, + Permissions: permission.ZeroAccountPermissions, + }.MutableAccount() + } + accs[out.Address] = acc + } + return accs, nil +} + +func validateInputs(accs map[crypto.Address]acm.MutableAccount, ins []*payload.TxInput) (uint64, error) { + total := uint64(0) + for _, in := range ins { + acc := accs[in.Address] + if acc == nil { + return 0, fmt.Errorf("validateInputs() expects account in accounts, but account %s not found", in.Address) + } + err := validateInput(acc, in) + if err != nil { + return 0, err + } + // Good. Add amount to total + total += in.Amount + } + return total, nil +} + +func validateInput(acc acm.MutableAccount, in *payload.TxInput) error { + // Check TxInput basic + if err := in.ValidateBasic(); err != nil { + return err + } + // Check sequences + if acc.Sequence()+1 != uint64(in.Sequence) { + return payload.ErrTxInvalidSequence{ + Got: in.Sequence, + Expected: acc.Sequence() + uint64(1), + } + } + // Check amount + if acc.Balance() < uint64(in.Amount) { + return payload.ErrTxInsufficientFunds + } + return nil +} + +func validateOutputs(outs []*payload.TxOutput) (uint64, error) { + total := uint64(0) + for _, out := range outs { + // Check TxOutput basic + if err := out.ValidateBasic(); err != nil { + return 0, err + } + // Good. Add amount to total + total += out.Amount + } + return total, nil +} + +func adjustByInputs(accs map[crypto.Address]acm.MutableAccount, ins []*payload.TxInput, logger *logging.Logger) error { + for _, in := range ins { + acc := accs[in.Address] + if acc == nil { + return fmt.Errorf("adjustByInputs() expects account in accounts, but account %s not found", in.Address) + } + if acc.Balance() < in.Amount { + panic("adjustByInputs() expects sufficient funds") + return fmt.Errorf("adjustByInputs() expects sufficient funds but account %s only has balance %v and "+ + "we are deducting %v", in.Address, acc.Balance(), in.Amount) + } + acc, err := acc.SubtractFromBalance(in.Amount) + if err != nil { + return err + } + logger.TraceMsg("Incrementing sequence number for SendTx (adjustByInputs)", + "tag", "sequence", + "account", acc.Address(), + "old_sequence", acc.Sequence(), + "new_sequence", acc.Sequence()+1) + acc.IncSequence() + } + return nil +} + +func adjustByOutputs(accs map[crypto.Address]acm.MutableAccount, outs []*payload.TxOutput) error { + for _, out := range outs { + acc := accs[out.Address] + if acc == nil { + return fmt.Errorf("adjustByOutputs() expects account in accounts, but account %s not found", + out.Address) + } + _, err := acc.AddToBalance(out.Amount) + if err != nil { + return err + } + } + return nil +} + +//--------------------------------------------------------------- + +// Get permission on an account or fall back to global value +func HasPermission(accountGetter state.AccountGetter, acc acm.Account, perm ptypes.PermFlag, logger *logging.Logger) bool { + if perm > ptypes.AllPermFlags { + logger.InfoMsg( + fmt.Sprintf("HasPermission called on invalid permission 0b%b (invalid) > 0b%b (maximum) ", + perm, ptypes.AllPermFlags), + "invalid_permission", perm, + "maximum_permission", ptypes.AllPermFlags) + return false + } + + v, err := acc.Permissions().Base.Compose(state.GlobalAccountPermissions(accountGetter).Base).Get(perm) + if err != nil { + logger.TraceMsg("Error obtaining permission value (will default to false/deny)", + "perm_flag", perm.String(), + structure.ErrorKey, err) + } + + if v { + logger.TraceMsg("Account has permission", + "account_address", acc.Address, + "perm_flag", perm.String()) + } else { + logger.TraceMsg("Account does not have permission", + "account_address", acc.Address, + "perm_flag", perm.String()) + } + return v +} + +// TODO: for debug log the failed accounts +func hasSendPermission(accountGetter state.AccountGetter, accs map[crypto.Address]acm.MutableAccount, + logger *logging.Logger) bool { + for _, acc := range accs { + if !HasPermission(accountGetter, acc, ptypes.Send, logger) { + return false + } + } + return true +} + +func hasNamePermission(accountGetter state.AccountGetter, acc acm.Account, + logger *logging.Logger) bool { + return HasPermission(accountGetter, acc, ptypes.Name, logger) +} + +func hasCallPermission(accountGetter state.AccountGetter, acc acm.Account, + logger *logging.Logger) bool { + return HasPermission(accountGetter, acc, ptypes.Call, logger) +} + +func hasCreateContractPermission(accountGetter state.AccountGetter, acc acm.Account, + logger *logging.Logger) bool { + return HasPermission(accountGetter, acc, ptypes.CreateContract, logger) +} + +func hasCreateAccountPermission(accountGetter state.AccountGetter, accs map[crypto.Address]acm.MutableAccount, + logger *logging.Logger) bool { + for _, acc := range accs { + if !HasPermission(accountGetter, acc, ptypes.CreateAccount, logger) { + return false + } + } + return true +} + +func hasBondPermission(accountGetter state.AccountGetter, acc acm.Account, + logger *logging.Logger) bool { + return HasPermission(accountGetter, acc, ptypes.Bond, logger) +} + +func hasBondOrSendPermission(accountGetter state.AccountGetter, accs map[crypto.Address]acm.Account, + logger *logging.Logger) bool { + for _, acc := range accs { + if !HasPermission(accountGetter, acc, ptypes.Bond, logger) { + if !HasPermission(accountGetter, acc, ptypes.Send, logger) { + return false + } + } + } + return true +} diff --git a/execution/namereg.go b/execution/namereg.go deleted file mode 100644 index 0c57b351c540e019ae6ab2f68d7e61bb8c5f650c..0000000000000000000000000000000000000000 --- a/execution/namereg.go +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package execution - -import "github.com/hyperledger/burrow/crypto" - -// NameReg provides a global key value store based on Name, Data pairs that are subject to expiry and ownership by an -// account. -type NameRegEntry struct { - // registered name for the entry - Name string - // address that created the entry - Owner crypto.Address - // data to store under this name - Data string - // block at which this entry expires - Expires uint64 -} - -type NameRegGetter interface { - GetNameRegEntry(name string) (*NameRegEntry, error) -} - -type NameRegUpdater interface { - // Updates the name entry creating it if it does not exist - UpdateNameRegEntry(entry *NameRegEntry) error - // Remove the name entry - RemoveNameRegEntry(name string) error -} - -type NameRegWriter interface { - NameRegGetter - NameRegUpdater -} - -type NameRegIterable interface { - NameRegGetter - IterateNameRegEntries(consumer func(*NameRegEntry) (stop bool)) (stopped bool, err error) -} diff --git a/execution/namereg_cache.go b/execution/names/cache.go similarity index 65% rename from execution/namereg_cache.go rename to execution/names/cache.go index 5cb0c63aacd2b8d1851f3901fb93dae044cd7ae2..074ff290eeba3cc401baf3a4d47a51dcd4fa02f0 100644 --- a/execution/namereg_cache.go +++ b/execution/names/cache.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package execution +package names import ( "fmt" @@ -20,33 +20,33 @@ import ( "sync" ) -// The NameRegCache helps prevent unnecessary IAVLTree updates and garbage generation. -type NameRegCache struct { +// The Cache helps prevent unnecessary IAVLTree updates and garbage generation. +type Cache struct { sync.RWMutex - backend NameRegGetter + backend Getter names map[string]*nameInfo } type nameInfo struct { sync.RWMutex - entry *NameRegEntry + entry *Entry removed bool updated bool } -var _ NameRegWriter = &NameRegCache{} +var _ Writer = &Cache{} -// Returns a NameRegCache that wraps an underlying NameRegCacheGetter to use on a cache miss, can write to an -// output NameRegWriter via Sync. +// Returns a Cache that wraps an underlying NameRegCacheGetter to use on a cache miss, can write to an +// output Writer via Sync. // Not goroutine safe, use syncStateCache if you need concurrent access -func NewNameRegCache(backend NameRegGetter) *NameRegCache { - return &NameRegCache{ +func NewCache(backend Getter) *Cache { + return &Cache{ backend: backend, names: make(map[string]*nameInfo), } } -func (cache *NameRegCache) GetNameRegEntry(name string) (*NameRegEntry, error) { +func (cache *Cache) GetNameEntry(name string) (*Entry, error) { nameInfo, err := cache.get(name) if err != nil { return nil, err @@ -59,7 +59,7 @@ func (cache *NameRegCache) GetNameRegEntry(name string) (*NameRegEntry, error) { return nameInfo.entry, nil } -func (cache *NameRegCache) UpdateNameRegEntry(entry *NameRegEntry) error { +func (cache *Cache) UpdateNameEntry(entry *Entry) error { nameInfo, err := cache.get(entry.Name) if err != nil { return err @@ -67,7 +67,7 @@ func (cache *NameRegCache) UpdateNameRegEntry(entry *NameRegEntry) error { nameInfo.Lock() defer nameInfo.Unlock() if nameInfo.removed { - return fmt.Errorf("UpdateNameRegEntry on a removed name: %s", nameInfo.entry.Name) + return fmt.Errorf("UpdateNameEntry on a removed name: %s", nameInfo.entry.Name) } nameInfo.entry = entry @@ -75,7 +75,7 @@ func (cache *NameRegCache) UpdateNameRegEntry(entry *NameRegEntry) error { return nil } -func (cache *NameRegCache) RemoveNameRegEntry(name string) error { +func (cache *Cache) RemoveNameEntry(name string) error { nameInfo, err := cache.get(name) if err != nil { return err @@ -83,15 +83,15 @@ func (cache *NameRegCache) RemoveNameRegEntry(name string) error { nameInfo.Lock() defer nameInfo.Unlock() if nameInfo.removed { - return fmt.Errorf("RemoveNameRegEntry on removed name: %s", name) + return fmt.Errorf("RemoveNameEntry on removed name: %s", name) } nameInfo.removed = true return nil } -// Writes whatever is in the cache to the output NameRegWriter state. Does not flush the cache, to do that call Reset() +// Writes whatever is in the cache to the output Writer state. Does not flush the cache, to do that call Reset() // after Sync or use Flusth if your wish to use the output state as your next backend -func (cache *NameRegCache) Sync(state NameRegWriter) error { +func (cache *Cache) Sync(state Writer) error { cache.Lock() defer cache.Unlock() // Determine order for names @@ -107,13 +107,13 @@ func (cache *NameRegCache) Sync(state NameRegWriter) error { nameInfo := cache.names[name] nameInfo.RLock() if nameInfo.removed { - err := state.RemoveNameRegEntry(name) + err := state.RemoveNameEntry(name) if err != nil { nameInfo.RUnlock() return err } } else if nameInfo.updated { - err := state.UpdateNameRegEntry(nameInfo.entry) + err := state.UpdateNameEntry(nameInfo.entry) if err != nil { nameInfo.RUnlock() return err @@ -125,15 +125,15 @@ func (cache *NameRegCache) Sync(state NameRegWriter) error { } // Resets the cache to empty initialising the backing map to the same size as the previous iteration. -func (cache *NameRegCache) Reset(backend NameRegGetter) { +func (cache *Cache) Reset(backend Getter) { cache.Lock() defer cache.Unlock() cache.backend = backend cache.names = make(map[string]*nameInfo) } -// Syncs the NameRegCache and Resets it to use NameRegWriter as the backend NameRegGetter -func (cache *NameRegCache) Flush(state NameRegWriter) error { +// Syncs the Cache and Resets it to use Writer as the backend Getter +func (cache *Cache) Flush(state Writer) error { err := cache.Sync(state) if err != nil { return err @@ -142,12 +142,12 @@ func (cache *NameRegCache) Flush(state NameRegWriter) error { return nil } -func (cache *NameRegCache) Backend() NameRegGetter { +func (cache *Cache) Backend() Getter { return cache.backend } // Get the cache accountInfo item creating it if necessary -func (cache *NameRegCache) get(name string) (*nameInfo, error) { +func (cache *Cache) get(name string) (*nameInfo, error) { cache.RLock() nmeInfo := cache.names[name] cache.RUnlock() @@ -156,7 +156,7 @@ func (cache *NameRegCache) get(name string) (*nameInfo, error) { defer cache.Unlock() nmeInfo = cache.names[name] if nmeInfo == nil { - entry, err := cache.backend.GetNameRegEntry(name) + entry, err := cache.backend.GetNameEntry(name) if err != nil { return nil, err } diff --git a/execution/namereg_cache_test.go b/execution/names/cache_test.go similarity index 98% rename from execution/namereg_cache_test.go rename to execution/names/cache_test.go index 7671133b8d934c25bb9a8c531faeadd8a84d729a..7b52912c9556a514564a4d588ba94c802f74c9f6 100644 --- a/execution/namereg_cache_test.go +++ b/execution/names/cache_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package execution +package names import ( "testing" diff --git a/execution/names/names.go b/execution/names/names.go index cfa656d696c3f920b8759a71746a17a6b2211ce6..586fb7b3ee77f23131a5aeb165bed566ff033762 100644 --- a/execution/names/names.go +++ b/execution/names/names.go @@ -14,6 +14,8 @@ package names +import "github.com/hyperledger/burrow/crypto" + var ( MinNameRegistrationPeriod uint64 = 5 @@ -29,6 +31,40 @@ var ( MaxDataLength = 1 << 16 ) +// NameReg provides a global key value store based on Name, Data pairs that are subject to expiry and ownership by an +// account. +type Entry struct { + // registered name for the entry + Name string + // address that created the entry + Owner crypto.Address + // data to store under this name + Data string + // block at which this entry expires + Expires uint64 +} + +type Getter interface { + GetNameEntry(name string) (*Entry, error) +} + +type Updater interface { + // Updates the name entry creating it if it does not exist + UpdateNameEntry(entry *Entry) error + // Remove the name entry + RemoveNameEntry(name string) error +} + +type Writer interface { + Getter + Updater +} + +type Iterable interface { + Getter + IterateNameEntries(consumer func(*Entry) (stop bool)) (stopped bool, err error) +} + // base cost is "effective" number of bytes func NameBaseCost(name, data string) uint64 { return uint64(len(data) + 32) diff --git a/execution/state.go b/execution/state.go index 7ccc79196f5d61145ff22a9286e351ec05a9963b..469b10828a267aa2bfd173da7a665c7aff233f55 100644 --- a/execution/state.go +++ b/execution/state.go @@ -28,7 +28,8 @@ import ( "github.com/hyperledger/burrow/execution/names" "github.com/hyperledger/burrow/genesis" "github.com/hyperledger/burrow/logging" - ptypes "github.com/hyperledger/burrow/permission" + "github.com/hyperledger/burrow/permission" + ptypes "github.com/hyperledger/burrow/permission/types" "github.com/tendermint/go-wire" "github.com/tendermint/iavl" dbm "github.com/tendermint/tmlibs/db" @@ -106,7 +107,7 @@ func MakeGenesisState(db dbm.DB, genesisDoc *genesis.GenesisDoc) (*State, error) // global permissions are saved as the 0 address // so they are included in the accounts tree - globalPerms := ptypes.DefaultAccountPermissions + globalPerms := permission.DefaultAccountPermissions globalPerms = genesisDoc.GlobalPermissions // XXX: make sure the set bits are all true // Without it the HasPermission() functions will fail @@ -270,9 +271,9 @@ func (s *State) IterateStorage(address crypto.Address, //------------------------------------- // State.nameReg -var _ NameRegIterable = &State{} +var _ names.Iterable = &State{} -func (s *State) GetNameRegEntry(name string) (*NameRegEntry, error) { +func (s *State) GetNameEntry(name string) (*names.Entry, error) { _, valueBytes := s.tree.Get(prefixedKey(nameRegPrefix, []byte(name))) if valueBytes == nil { return nil, nil @@ -281,13 +282,13 @@ func (s *State) GetNameRegEntry(name string) (*NameRegEntry, error) { return DecodeNameRegEntry(valueBytes), nil } -func (s *State) IterateNameRegEntries(consumer func(*NameRegEntry) (stop bool)) (stopped bool, err error) { +func (s *State) IterateNameEntries(consumer func(*names.Entry) (stop bool)) (stopped bool, err error) { return s.tree.IterateRange(nameRegStart, nameRegEnd, true, func(key []byte, value []byte) (stop bool) { return consumer(DecodeNameRegEntry(value)) }), nil } -func (s *State) UpdateNameRegEntry(entry *NameRegEntry) error { +func (s *State) UpdateNameEntry(entry *names.Entry) error { w := new(bytes.Buffer) var n int var err error @@ -299,7 +300,7 @@ func (s *State) UpdateNameRegEntry(entry *NameRegEntry) error { return nil } -func (s *State) RemoveNameRegEntry(name string) error { +func (s *State) RemoveNameEntry(name string) error { s.tree.Remove(prefixedKey(nameRegPrefix, []byte(name))) return nil } @@ -314,19 +315,19 @@ func (s *State) Copy(db dbm.DB) *State { return state } -func DecodeNameRegEntry(entryBytes []byte) *NameRegEntry { +func DecodeNameRegEntry(entryBytes []byte) *names.Entry { var n int var err error value := NameRegDecode(bytes.NewBuffer(entryBytes), &n, &err) - return value.(*NameRegEntry) + return value.(*names.Entry) } func NameRegEncode(o interface{}, w io.Writer, n *int, err *error) { - wire.WriteBinary(o.(*NameRegEntry), w, n, err) + wire.WriteBinary(o.(*names.Entry), w, n, err) } func NameRegDecode(r io.Reader, n *int, err *error) interface{} { - return wire.ReadBinary(&NameRegEntry{}, r, names.MaxDataLength, n, err) + return wire.ReadBinary(&names.Entry{}, r, names.MaxDataLength, n, err) } func prefixedKey(prefix string, suffices ...[]byte) []byte { diff --git a/execution/state_test.go b/execution/state_test.go index 6ad9c32192ec5efc1cec705138a5ef83f2cb400d..e72076b1359f6d07294c123a2a36b6637fd32278 100644 --- a/execution/state_test.go +++ b/execution/state_test.go @@ -18,7 +18,7 @@ import ( "testing" acm "github.com/hyperledger/burrow/account" - "github.com/hyperledger/burrow/permission" + permission "github.com/hyperledger/burrow/permission/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/tendermint/tmlibs/db" diff --git a/execution/transactor.go b/execution/transactor.go index bc0c9b9c69bc05bef889d5066da099d8002128f7..042eb11b20f89b2f34595e867a5b316d346e69d0 100644 --- a/execution/transactor.go +++ b/execution/transactor.go @@ -29,9 +29,11 @@ import ( "github.com/hyperledger/burrow/consensus/tendermint/codes" "github.com/hyperledger/burrow/crypto" "github.com/hyperledger/burrow/event" + "github.com/hyperledger/burrow/execution/errors" exe_events "github.com/hyperledger/burrow/execution/events" "github.com/hyperledger/burrow/execution/evm" evm_events "github.com/hyperledger/burrow/execution/evm/events" + "github.com/hyperledger/burrow/execution/executors" "github.com/hyperledger/burrow/logging" "github.com/hyperledger/burrow/logging/structure" "github.com/hyperledger/burrow/txs" @@ -71,7 +73,7 @@ func NewTransactor(tip *blockchain.Tip, eventEmitter event.Emitter, func (trans *Transactor) Call(reader state.Reader, fromAddress, toAddress crypto.Address, data []byte) (call *Call, err error) { - if evm.RegisteredNativeContract(toAddress.Word256()) { + if evm.IsRegisteredNativeContract(toAddress.Word256()) { return nil, fmt.Errorf("attempt to call native contract at address "+ "%X, but native contracts can not be called directly. Use a deployed "+ "contract that calls the native function instead", toAddress) @@ -235,8 +237,8 @@ func (trans *Transactor) TransactAndHold(sequentialSigningAccount *SequentialSig case <-timer.C: return nil, fmt.Errorf("transaction timed out TxHash: %X", expectedReceipt.TxHash) case eventDataCall := <-ch: - if eventDataCall.Exception != "" { - return nil, fmt.Errorf("error when transacting: " + eventDataCall.Exception) + if eventDataCall.Exception != nil && eventDataCall.Exception.Code != errors.ErrorCodeExecutionReverted { + return nil, fmt.Errorf("error when transacting: %v", eventDataCall.Exception) } else { return eventDataCall, nil } @@ -392,6 +394,6 @@ func vmParams(tip *blockchain.Tip) evm.Params { BlockHeight: tip.LastBlockHeight(), BlockHash: binary.LeftPadWord256(tip.LastBlockHash()), BlockTime: tip.LastBlockTime().Unix(), - GasLimit: GasLimit, + GasLimit: executors.GasLimit, } } diff --git a/genesis/spec/genesis_spec_test.go b/genesis/spec/genesis_spec_test.go index ca91146fb71be6356ffd55abd31cec4590fc0b0b..6ce214dc434fda5299990847385feb9cb110a513 100644 --- a/genesis/spec/genesis_spec_test.go +++ b/genesis/spec/genesis_spec_test.go @@ -5,7 +5,7 @@ import ( "github.com/hyperledger/burrow/crypto" "github.com/hyperledger/burrow/keys/mock" - "github.com/hyperledger/burrow/permission" + permission "github.com/hyperledger/burrow/permission/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/genesis/spec/presets.go b/genesis/spec/presets.go index 312ec7a79a78888f687210a56e218c32ad16a4e4..9454520c9f284666349d8b2334c59879e1c169d0 100644 --- a/genesis/spec/presets.go +++ b/genesis/spec/presets.go @@ -3,7 +3,7 @@ package spec import ( "sort" - "github.com/hyperledger/burrow/permission" + permission "github.com/hyperledger/burrow/permission/types" ) // Files here can be used as starting points for building various 'chain types' but are otherwise diff --git a/genesis/spec/presets_test.go b/genesis/spec/presets_test.go index 8e578640e8790aab8923006e7ec8d864a3cec5b5..dde6f296c7103fa5ca33dcaf0416aa797c440265 100644 --- a/genesis/spec/presets_test.go +++ b/genesis/spec/presets_test.go @@ -4,7 +4,7 @@ import ( "testing" "github.com/hyperledger/burrow/keys/mock" - "github.com/hyperledger/burrow/permission" + permission "github.com/hyperledger/burrow/permission/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/permission/permissions.go b/permission/permissions.go index 5bfcf61c9205c1a6ae222e8110298759c8cd8a9a..48b5bd9dd3bd2e11353993c8ce96964017a8a75a 100644 --- a/permission/permissions.go +++ b/permission/permissions.go @@ -15,82 +15,9 @@ package permission import ( - "fmt" - "strings" - "github.com/hyperledger/burrow/permission/types" ) -//------------------------------------------------------------------------------------------------ - -// Base permission references are like unix (the index is already bit shifted) -const ( - // Chain permissions. - // These permissions grant the ability for accounts to perform certain transition within the execution package - // Root is a reserved permission currently unused that may be used in the future to grant super-user privileges - // for instance to a governance contract - Root types.PermFlag = 1 << iota // 1 - // Send permits an account to issue a SendTx to transfer value from one account to another. Note that value can - // still be transferred with a CallTx by specifying an Amount in the InputTx. Funding an account is the basic - // prerequisite for an account to act in the system so is often used as a surrogate for 'account creation' when - // sending to a unknown account - in order for this to be permitted the input account needs the CreateAccount - // permission in addition. - Send // 2 - // Call permits and account to issue a CallTx, which can be used to call (run) the code of an existing - // account/contract (these are synonymous in Burrow/EVM). A CallTx can be used to create an account if it points to - // a nil address - in order for an account to be permitted to do this the input (calling) account needs the - // CreateContract permission in addition. - Call // 4 - // CreateContract permits the input account of a CallTx to create a new contract/account when CallTx.Address is nil - // and permits an executing contract in the EVM to create a new contract programmatically. - CreateContract // 8 - // CreateAccount permits an input account of a SendTx to add value to non-existing (unfunded) accounts - CreateAccount // 16 - // Bond is a reserved permission for making changes to the validator set - currently unused - Bond // 32 - // Name permits manipulation of the name registry by allowing an account to issue a NameTx - Name // 64 - - // Moderator permissions. - // These permissions concern the alteration of the chain permissions listed above. Each permission relates to a - // particular canonical permission mutation or query function. When an account is granted a moderation permission - // it is permitted to call that function. See snative.go for a marked-up description of what each function does. - HasBase - SetBase - UnsetBase - SetGlobal - HasRole - AddRole - RemoveRole - - NumPermissions uint = 14 // NOTE Adjust this too. We can support upto 64 - - TopPermFlag types.PermFlag = 1 << (NumPermissions - 1) - AllPermFlags types.PermFlag = TopPermFlag | (TopPermFlag - 1) - DefaultPermFlags types.PermFlag = Send | Call | CreateContract | CreateAccount | Bond | Name | HasBase | HasRole - - // Chain permissions strings - RootString string = "root" - SendString = "send" - CallString = "call" - CreateContractString = "createContract" - CreateAccountString = "createAccount" - BondString = "bond" - NameString = "name" - - // Moderator permissions strings - HasBaseString = "hasBase" - SetBaseString = "setBase" - UnsetBaseString = "unsetBase" - SetGlobalString = "setGlobal" - HasRoleString = "hasRole" - AddRoleString = "addRole" - RemoveRoleString = "removeRole" - UnknownString = "#-UNKNOWN-#" - - AllString = "all" -) - var ( ZeroBasePermissions = types.BasePermissions{0, 0} ZeroAccountPermissions = types.AccountPermissions{ @@ -98,99 +25,16 @@ var ( } DefaultAccountPermissions = types.AccountPermissions{ Base: types.BasePermissions{ - Perms: DefaultPermFlags, - SetBit: AllPermFlags, + Perms: types.DefaultPermFlags, + SetBit: types.AllPermFlags, }, Roles: []string{}, } AllAccountPermissions = types.AccountPermissions{ Base: types.BasePermissions{ - Perms: AllPermFlags, - SetBit: AllPermFlags, + Perms: types.AllPermFlags, + SetBit: types.AllPermFlags, }, Roles: []string{}, } ) - -//--------------------------------------------------------------------------------------------- - -//-------------------------------------------------------------------------------- -// string utilities - -// Returns the string name of a single bit non-composite PermFlag, or otherwise UnknownString -// See BasePermissionsToStringList to generate a string representation of a composite PermFlag -func PermFlagToString(pf types.PermFlag) string { - switch pf { - case AllPermFlags: - return AllString - case Root: - return RootString - case Send: - return SendString - case Call: - return CallString - case CreateContract: - return CreateContractString - case CreateAccount: - return CreateAccountString - case Bond: - return BondString - case Name: - return NameString - case HasBase: - return HasBaseString - case SetBase: - return SetBaseString - case UnsetBase: - return UnsetBaseString - case SetGlobal: - return SetGlobalString - case HasRole: - return HasRoleString - case AddRole: - return AddRoleString - case RemoveRole: - return RemoveRoleString - default: - return UnknownString - } -} - -// PermStringToFlag maps camel- and snake case strings to the -// the corresponding permission flag. -func PermStringToFlag(perm string) (types.PermFlag, error) { - switch strings.ToLower(perm) { - case AllString: - return AllPermFlags, nil - case RootString: - return Root, nil - case SendString: - return Send, nil - case CallString: - return Call, nil - case CreateContractString, "createcontract", "create_contract": - return CreateContract, nil - case CreateAccountString, "createaccount", "create_account": - return CreateAccount, nil - case BondString: - return Bond, nil - case NameString: - return Name, nil - case HasBaseString, "hasbase", "has_base": - return HasBase, nil - case SetBaseString, "setbase", "set_base": - return SetBase, nil - case UnsetBaseString, "unsetbase", "unset_base": - return UnsetBase, nil - case SetGlobalString, "setglobal", "set_global": - return SetGlobal, nil - case HasRoleString, "hasrole", "has_role": - return HasRole, nil - case AddRoleString, "addrole", "add_role": - return AddRole, nil - case RemoveRoleString, "removerole", "rmrole", "rm_role": - return RemoveRole, nil - default: - return 0, fmt.Errorf("unknown permission %s", perm) - } -} diff --git a/permission/snatives/snatives.go b/permission/snatives/snatives.go index 40e560ea9087903b220e5eaec49a82a3721ce352..5b47b8af5e894c0b9f5cdf6e26947db2ad398c35 100644 --- a/permission/snatives/snatives.go +++ b/permission/snatives/snatives.go @@ -21,6 +21,7 @@ import ( "github.com/hyperledger/burrow/crypto" "github.com/hyperledger/burrow/permission" "github.com/hyperledger/burrow/permission/types" + ptypes "github.com/hyperledger/burrow/permission/types" ) //--------------------------------------------------------------------------------------------------- @@ -36,12 +37,12 @@ type PermArgs struct { func (pa PermArgs) String() string { body := make([]string, 0, 5) - body = append(body, fmt.Sprintf("PermFlag: %s", permission.String(pa.PermFlag))) + body = append(body, fmt.Sprintf("PermFlag: %v", permission.String(pa.PermFlag))) if pa.Address != nil { body = append(body, fmt.Sprintf("Address: %s", *pa.Address)) } if pa.Permission != nil { - body = append(body, fmt.Sprintf("Permission: %s", permission.String(*pa.Permission))) + body = append(body, fmt.Sprintf("Permission: %v", permission.String(*pa.Permission))) } if pa.Role != nil { body = append(body, fmt.Sprintf("Role: %s", *pa.Role)) @@ -55,10 +56,10 @@ func (pa PermArgs) String() string { func (pa PermArgs) EnsureValid() error { pf := pa.PermFlag // Address - if pa.Address == nil && pf != permission.SetGlobal { + if pa.Address == nil && pf != ptypes.SetGlobal { return fmt.Errorf("PermArgs for PermFlag %v requires Address to be provided but was nil", pf) } - if pf == permission.HasRole || pf == permission.AddRole || pf == permission.RemoveRole { + if pf == ptypes.HasRole || pf == ptypes.AddRole || pf == ptypes.RemoveRole { // Role if pa.Role == nil { return fmt.Errorf("PermArgs for PermFlag %v requires Role to be provided but was nil", pf) @@ -67,7 +68,7 @@ func (pa PermArgs) EnsureValid() error { } else if pa.Permission == nil { return fmt.Errorf("PermArgs for PermFlag %v requires Permission to be provided but was nil", pf) // Value - } else if (pf == permission.SetBase || pf == permission.SetGlobal) && pa.Value == nil { + } else if (pf == ptypes.SetBase || pf == ptypes.SetGlobal) && pa.Value == nil { return fmt.Errorf("PermArgs for PermFlag %v requires Value to be provided but was nil", pf) } return nil @@ -75,7 +76,7 @@ func (pa PermArgs) EnsureValid() error { func HasBaseArgs(address crypto.Address, permFlag types.PermFlag) PermArgs { return PermArgs{ - PermFlag: permission.HasBase, + PermFlag: ptypes.HasBase, Address: &address, Permission: &permFlag, } @@ -83,7 +84,7 @@ func HasBaseArgs(address crypto.Address, permFlag types.PermFlag) PermArgs { func SetBaseArgs(address crypto.Address, permFlag types.PermFlag, value bool) PermArgs { return PermArgs{ - PermFlag: permission.SetBase, + PermFlag: ptypes.SetBase, Address: &address, Permission: &permFlag, Value: &value, @@ -92,7 +93,7 @@ func SetBaseArgs(address crypto.Address, permFlag types.PermFlag, value bool) Pe func UnsetBaseArgs(address crypto.Address, permFlag types.PermFlag) PermArgs { return PermArgs{ - PermFlag: permission.UnsetBase, + PermFlag: ptypes.UnsetBase, Address: &address, Permission: &permFlag, } @@ -100,7 +101,7 @@ func UnsetBaseArgs(address crypto.Address, permFlag types.PermFlag) PermArgs { func SetGlobalArgs(permFlag types.PermFlag, value bool) PermArgs { return PermArgs{ - PermFlag: permission.SetGlobal, + PermFlag: ptypes.SetGlobal, Permission: &permFlag, Value: &value, } @@ -108,7 +109,7 @@ func SetGlobalArgs(permFlag types.PermFlag, value bool) PermArgs { func HasRoleArgs(address crypto.Address, role string) PermArgs { return PermArgs{ - PermFlag: permission.HasRole, + PermFlag: ptypes.HasRole, Address: &address, Role: &role, } @@ -116,7 +117,7 @@ func HasRoleArgs(address crypto.Address, role string) PermArgs { func AddRoleArgs(address crypto.Address, role string) PermArgs { return PermArgs{ - PermFlag: permission.AddRole, + PermFlag: ptypes.AddRole, Address: &address, Role: &role, } @@ -124,7 +125,7 @@ func AddRoleArgs(address crypto.Address, role string) PermArgs { func RemoveRoleArgs(address crypto.Address, role string) PermArgs { return PermArgs{ - PermFlag: permission.RemoveRole, + PermFlag: ptypes.RemoveRole, Address: &address, Role: &role, } diff --git a/permission/snatives/snatives_test.go b/permission/snatives/snatives_test.go index 76545151df366826e1f24deb5a6f100af8e48b2d..9441ed15eaccc20c7391e3ce4452ec01933ecb4a 100644 --- a/permission/snatives/snatives_test.go +++ b/permission/snatives/snatives_test.go @@ -3,7 +3,7 @@ package snatives import ( "testing" - "github.com/hyperledger/burrow/permission" + permission "github.com/hyperledger/burrow/permission/types" "github.com/stretchr/testify/assert" ) diff --git a/permission/types/base_permissions.go b/permission/types/base_permissions.go index fe0dd5e32abe7f1db5dd33cb1906a784e3f8129a..401f46d1225a4589311cfa8472ce52310bbd576c 100644 --- a/permission/types/base_permissions.go +++ b/permission/types/base_permissions.go @@ -2,24 +2,6 @@ package types import "fmt" -// A particular permission -type PermFlag uint64 - -// permission number out of bounds -type ErrInvalidPermission PermFlag - -func (e ErrInvalidPermission) Error() string { - return fmt.Sprintf("invalid permission %d", e) -} - -// set=false. This error should be caught and the global -// value fetched for the permission by the caller -type ErrValueNotSet PermFlag - -func (e ErrValueNotSet) Error() string { - return fmt.Sprintf("the value for permission %d is not set", e) -} - // Base chain permissions struct type BasePermissions struct { // bit array with "has"/"doesn't have" for each permission diff --git a/permission/errors.go b/permission/types/errors.go similarity index 59% rename from permission/errors.go rename to permission/types/errors.go index 795b621cac31d5a52159c6a16bafa24ce4106e68..0b022277e9352561b2fe83a655f750c6a146d21f 100644 --- a/permission/errors.go +++ b/permission/types/errors.go @@ -12,7 +12,22 @@ // See the License for the specific language governing permissions and // limitations under the License. -package permission +package types -//------------------------------------------------------------------------------------------------ -// Some errors +import ( + "fmt" +) + +type ErrInvalidPermission PermFlag + +func (e ErrInvalidPermission) Error() string { + return fmt.Sprintf("invalid permission %d", e) +} + +// set=false. This error should be caught and the global +// value fetched for the permission by the caller +type ErrValueNotSet PermFlag + +func (e ErrValueNotSet) Error() string { + return fmt.Sprintf("the value for permission %d is not set", e) +} diff --git a/permission/types/perm_flag.go b/permission/types/perm_flag.go new file mode 100644 index 0000000000000000000000000000000000000000..c749aa308b6f3e1b292b0e30cefc4a4030958b24 --- /dev/null +++ b/permission/types/perm_flag.go @@ -0,0 +1,155 @@ +package types + +import ( + "fmt" + "strings" +) + +// Base permission references are like unix (the index is already bit shifted) +const ( + // Chain permissions. + // These permissions grant the ability for accounts to perform certain transition within the execution package + // Root is a reserved permission currently unused that may be used in the future to grant super-user privileges + // for instance to a governance contract + Root PermFlag = 1 << iota // 1 + // Send permits an account to issue a SendTx to transfer value from one account to another. Note that value can + // still be transferred with a CallTx by specifying an Amount in the InputTx. Funding an account is the basic + // prerequisite for an account to act in the system so is often used as a surrogate for 'account creation' when + // sending to a unknown account - in order for this to be permitted the input account needs the CreateAccount + // permission in addition. + Send // 2 + // Call permits and account to issue a CallTx, which can be used to call (run) the code of an existing + // account/contract (these are synonymous in Burrow/EVM). A CallTx can be used to create an account if it points to + // a nil address - in order for an account to be permitted to do this the input (calling) account needs the + // CreateContract permission in addition. + Call // 4 + // CreateContract permits the input account of a CallTx to create a new contract/account when CallTx.Address is nil + // and permits an executing contract in the EVM to create a new contract programmatically. + CreateContract // 8 + // CreateAccount permits an input account of a SendTx to add value to non-existing (unfunded) accounts + CreateAccount // 16 + // Bond is a reserved permission for making changes to the validator set - currently unused + Bond // 32 + // Name permits manipulation of the name registry by allowing an account to issue a NameTx + Name // 64 + + // Moderator permissions. + // These permissions concern the alteration of the chain permissions listed above. Each permission relates to a + // particular canonical permission mutation or query function. When an account is granted a moderation permission + // it is permitted to call that function. See snative.go for a marked-up description of what each function does. + HasBase + SetBase + UnsetBase + SetGlobal + HasRole + AddRole + RemoveRole + + 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 + + // Chain permissions strings + RootString string = "root" + SendString = "send" + CallString = "call" + CreateContractString = "createContract" + CreateAccountString = "createAccount" + BondString = "bond" + NameString = "name" + + // Moderator permissions strings + HasBaseString = "hasBase" + SetBaseString = "setBase" + UnsetBaseString = "unsetBase" + SetGlobalString = "setGlobal" + HasRoleString = "hasRole" + AddRoleString = "addRole" + RemoveRoleString = "removeRole" + UnknownString = "#-UNKNOWN-#" + + AllString = "all" +) + +// A particular permission +type PermFlag uint64 + +// Returns the string name of a single bit non-composite PermFlag, or otherwise UnknownString +// See BasePermissionsToStringList to generate a string representation of a composite PermFlag +func (pf PermFlag) String() string { + switch pf { + case AllPermFlags: + return AllString + case Root: + return RootString + case Send: + return SendString + case Call: + return CallString + case CreateContract: + return CreateContractString + case CreateAccount: + return CreateAccountString + case Bond: + return BondString + case Name: + return NameString + case HasBase: + return HasBaseString + case SetBase: + return SetBaseString + case UnsetBase: + return UnsetBaseString + case SetGlobal: + return SetGlobalString + case HasRole: + return HasRoleString + case AddRole: + return AddRoleString + case RemoveRole: + return RemoveRoleString + default: + return UnknownString + } +} + +// PermStringToFlag maps camel- and snake case strings to the +// the corresponding permission flag. +func PermStringToFlag(perm string) (PermFlag, error) { + switch strings.ToLower(perm) { + case AllString: + return AllPermFlags, nil + case RootString: + return Root, nil + case SendString: + return Send, nil + case CallString: + return Call, nil + case CreateContractString, "createcontract", "create_contract": + return CreateContract, nil + case CreateAccountString, "createaccount", "create_account": + return CreateAccount, nil + case BondString: + return Bond, nil + case NameString: + return Name, nil + case HasBaseString, "hasbase", "has_base": + return HasBase, nil + case SetBaseString, "setbase", "set_base": + return SetBase, nil + case UnsetBaseString, "unsetbase", "unset_base": + return UnsetBase, nil + case SetGlobalString, "setglobal", "set_global": + return SetGlobal, nil + case HasRoleString, "hasrole", "has_role": + return HasRole, nil + case AddRoleString, "addrole", "add_role": + return AddRole, nil + case RemoveRoleString, "removerole", "rmrole", "rm_role": + return RemoveRole, nil + default: + return 0, fmt.Errorf("unknown permission %s", perm) + } +} diff --git a/permission/permissions_test.go b/permission/types/perm_flag_test.go similarity index 91% rename from permission/permissions_test.go rename to permission/types/perm_flag_test.go index 5d930a7c703809a04adbf4cfe2512249b1490481..2ec7a6edb257f51ff897cc7d29151df051706e3a 100644 --- a/permission/permissions_test.go +++ b/permission/types/perm_flag_test.go @@ -1,4 +1,4 @@ -package permission +package types import ( "testing" diff --git a/permission/util.go b/permission/util.go index 656761cd83030127313ede68c528ffe8973b00cd..7470b94d90b27ee48b04cf40b67b215c8370f4c2 100644 --- a/permission/util.go +++ b/permission/util.go @@ -45,7 +45,7 @@ func convertPermissionsMapStringIntToBasePermissions(permissions map[string]bool basePermissions := ZeroBasePermissions for permissionName, value := range permissions { - permissionsFlag, err := PermStringToFlag(permissionName) + permissionsFlag, err := types.PermStringToFlag(permissionName) if err != nil { return basePermissions, err } @@ -74,7 +74,7 @@ func BasePermissionsFromStringList(permissions []string) (types.BasePermissions, func PermFlagFromStringList(permissions []string) (types.PermFlag, error) { var permFlag types.PermFlag for _, perm := range permissions { - flag, err := PermStringToFlag(perm) + flag, err := types.PermStringToFlag(perm) if err != nil { return permFlag, err } @@ -92,15 +92,15 @@ func BasePermissionsToStringList(basePermissions types.BasePermissions) ([]strin // Creates a list of individual permission flag strings from a possibly composite PermFlag // by projecting out each bit and adding its permission string if it is set func PermFlagToStringList(permFlag types.PermFlag) ([]string, error) { - permStrings := make([]string, 0, NumPermissions) - if permFlag > AllPermFlags { + permStrings := make([]string, 0, types.NumPermissions) + if permFlag > types.AllPermFlags { return nil, fmt.Errorf("resultant permission 0b%b is invalid: has permission flag set above top flag 0b%b", - permFlag, TopPermFlag) + permFlag, types.TopPermFlag) } - for i := uint(0); i < NumPermissions; i++ { + for i := uint(0); i < types.NumPermissions; i++ { permFlag := permFlag & (1 << i) if permFlag > 0 { - permStrings = append(permStrings, PermFlagToString(permFlag)) + permStrings = append(permStrings, permFlag.String()) } } return permStrings, nil @@ -110,7 +110,7 @@ func PermFlagToStringList(permFlag types.PermFlag) ([]string, error) { func BasePermissionsString(basePermissions types.BasePermissions) string { permStrings, err := BasePermissionsToStringList(basePermissions) if err != nil { - return UnknownString + return types.UnknownString } return strings.Join(permStrings, " | ") } @@ -118,7 +118,7 @@ func BasePermissionsString(basePermissions types.BasePermissions) string { func String(permFlag types.PermFlag) string { permStrings, err := PermFlagToStringList(permFlag) if err != nil { - return UnknownString + return types.UnknownString } return strings.Join(permStrings, " | ") } diff --git a/permission/util_test.go b/permission/util_test.go index 4d1c806b1073c39c83bc7a6d1a18f85a1117dfb6..780c1651cb91900798536133288d0a8cdfeed134 100644 --- a/permission/util_test.go +++ b/permission/util_test.go @@ -9,15 +9,15 @@ import ( ) func TestBasePermissionsFromStringList(t *testing.T) { - basePerms, err := BasePermissionsFromStringList([]string{HasRoleString, CreateContractString, SendString}) + basePerms, err := BasePermissionsFromStringList([]string{types.HasRoleString, types.CreateContractString, types.SendString}) require.NoError(t, err) - permFlag := HasRole | CreateContract | Send + permFlag := types.HasRole | types.CreateContract | types.Send assert.Equal(t, permFlag, basePerms.Perms) assert.Equal(t, permFlag, basePerms.SetBit) - basePerms, err = BasePermissionsFromStringList([]string{AllString}) + basePerms, err = BasePermissionsFromStringList([]string{types.AllString}) require.NoError(t, err) - permFlag = AllPermFlags + permFlag = types.AllPermFlags assert.Equal(t, permFlag, basePerms.Perms) assert.Equal(t, permFlag, basePerms.SetBit) @@ -26,21 +26,21 @@ func TestBasePermissionsFromStringList(t *testing.T) { } func TestBasePermissionsToStringList(t *testing.T) { - permStrings, err := BasePermissionsToStringList(allSetBasePermission(Root | HasRole | SetBase | Call)) + permStrings, err := BasePermissionsToStringList(allSetBasePermission(types.Root | types.HasRole | types.SetBase | types.Call)) require.NoError(t, err) assert.Equal(t, []string{"root", "call", "setBase", "hasRole"}, permStrings) - permStrings, err = BasePermissionsToStringList(allSetBasePermission(AllPermFlags)) + permStrings, err = BasePermissionsToStringList(allSetBasePermission(types.AllPermFlags)) require.NoError(t, err) assert.Equal(t, []string{"root", "send", "call", "createContract", "createAccount", "bond", "name", "hasBase", "setBase", "unsetBase", "setGlobal", "hasRole", "addRole", "removeRole"}, permStrings) - permStrings, err = BasePermissionsToStringList(allSetBasePermission(AllPermFlags + 1)) + permStrings, err = BasePermissionsToStringList(allSetBasePermission(types.AllPermFlags + 1)) assert.Error(t, err) } func TestBasePermissionsString(t *testing.T) { - permissionString := BasePermissionsString(allSetBasePermission(AllPermFlags &^ Root)) + permissionString := BasePermissionsString(allSetBasePermission(types.AllPermFlags &^ types.Root)) assert.Equal(t, "send | call | createContract | createAccount | bond | name | hasBase | "+ "setBase | unsetBase | setGlobal | hasRole | addRole | removeRole", permissionString) } diff --git a/rpc/filters/namereg.go b/rpc/filters/namereg.go index 10634e7104a931abf563cd096bc25d2056f3e0a1..6d822fb177c21221a627d8cb4f8c90211de744a9 100644 --- a/rpc/filters/namereg.go +++ b/rpc/filters/namereg.go @@ -20,7 +20,7 @@ import ( "fmt" "sync" - "github.com/hyperledger/burrow/execution" + "github.com/hyperledger/burrow/execution/names" ) func NewNameRegFilterFactory() *FilterFactory { @@ -82,7 +82,7 @@ func (nrnf *NameRegNameFilter) Configure(fd *FilterData) error { } func (nrnf *NameRegNameFilter) Match(v interface{}) bool { - nre, ok := v.(*execution.NameRegEntry) + nre, ok := v.(*names.Entry) if !ok { return false } @@ -121,7 +121,7 @@ func (nrof *NameRegOwnerFilter) Configure(fd *FilterData) error { } func (nrof *NameRegOwnerFilter) Match(v interface{}) bool { - nre, ok := v.(*execution.NameRegEntry) + nre, ok := v.(*names.Entry) if !ok { return false } @@ -157,7 +157,7 @@ func (nrdf *NameRegDataFilter) Configure(fd *FilterData) error { } func (nrdf *NameRegDataFilter) Match(v interface{}) bool { - nre, ok := v.(*execution.NameRegEntry) + nre, ok := v.(*names.Entry) if !ok { return false } @@ -188,7 +188,7 @@ func (nref *NameRegExpiresFilter) Configure(fd *FilterData) error { } func (nref *NameRegExpiresFilter) Match(v interface{}) bool { - nre, ok := v.(*execution.NameRegEntry) + nre, ok := v.(*names.Entry) if !ok { return false } diff --git a/rpc/result.go b/rpc/result.go index 8fd7a96fa883cb46028d34236b2519b7ecacd3d7..4dc921fc20bbf8a63c6d7970e743599285cc912f 100644 --- a/rpc/result.go +++ b/rpc/result.go @@ -23,6 +23,7 @@ import ( "github.com/hyperledger/burrow/execution" exeEvents "github.com/hyperledger/burrow/execution/events" evmEvents "github.com/hyperledger/burrow/execution/evm/events" + "github.com/hyperledger/burrow/execution/names" "github.com/hyperledger/burrow/genesis" "github.com/hyperledger/burrow/txs" "github.com/tendermint/go-amino" @@ -161,7 +162,7 @@ type ResultPeers struct { type ResultListNames struct { BlockHeight uint64 - Names []*execution.NameRegEntry + Names []*names.Entry } type ResultGeneratePrivateAccount struct { @@ -205,7 +206,7 @@ type ResultListUnconfirmedTxs struct { } type ResultGetName struct { - Entry *execution.NameRegEntry + Entry *names.Entry } type ResultGenesis struct { diff --git a/rpc/service.go b/rpc/service.go index 7348118ecb244fd990b0653fc01644945f4b1653..dab10524d4d88570b4e7eaa365f5bea2232b4a1f 100644 --- a/rpc/service.go +++ b/rpc/service.go @@ -26,6 +26,7 @@ import ( "github.com/hyperledger/burrow/crypto" "github.com/hyperledger/burrow/event" "github.com/hyperledger/burrow/execution" + "github.com/hyperledger/burrow/execution/names" "github.com/hyperledger/burrow/keys" "github.com/hyperledger/burrow/logging" "github.com/hyperledger/burrow/logging/structure" @@ -45,7 +46,7 @@ const AccountsRingMutexCount = 100 type Service struct { ctx context.Context state state.Iterable - nameReg execution.NameRegIterable + nameReg names.Iterable mempoolAccounts *execution.Accounts subscribable event.Subscribable blockchain *bcm.Blockchain @@ -54,7 +55,7 @@ type Service struct { logger *logging.Logger } -func NewService(ctx context.Context, state state.Iterable, nameReg execution.NameRegIterable, +func NewService(ctx context.Context, state state.Iterable, nameReg names.Iterable, checker state.Reader, subscribable event.Subscribable, blockchain *bcm.Blockchain, keyClient keys.KeyClient, transactor *execution.Transactor, nodeView *query.NodeView, logger *logging.Logger) *Service { @@ -320,7 +321,7 @@ func (s *Service) GetAccountHumanReadable(address crypto.Address) (*ResultGetAcc // Name registry func (s *Service) GetName(name string) (*ResultGetName, error) { - entry, err := s.nameReg.GetNameRegEntry(name) + entry, err := s.nameReg.GetNameEntry(name) if err != nil { return nil, err } @@ -330,17 +331,17 @@ func (s *Service) GetName(name string) (*ResultGetName, error) { return &ResultGetName{Entry: entry}, nil } -func (s *Service) ListNames(predicate func(*execution.NameRegEntry) bool) (*ResultListNames, error) { - var names []*execution.NameRegEntry - s.nameReg.IterateNameRegEntries(func(entry *execution.NameRegEntry) (stop bool) { +func (s *Service) ListNames(predicate func(*names.Entry) bool) (*ResultListNames, error) { + var nms []*names.Entry + s.nameReg.IterateNameEntries(func(entry *names.Entry) (stop bool) { if predicate(entry) { - names = append(names, entry) + nms = append(nms, entry) } return }) return &ResultListNames{ BlockHeight: s.blockchain.Tip.LastBlockHeight(), - Names: names, + Names: nms, }, nil } diff --git a/rpc/tm/client/client.go b/rpc/tm/client/client.go index 8dbdc716799e55e8ffc35dcf537f86e91abc257c..1b03a76e348cd6ae9042376aab316b7dfc41d488 100644 --- a/rpc/tm/client/client.go +++ b/rpc/tm/client/client.go @@ -20,7 +20,7 @@ import ( acm "github.com/hyperledger/burrow/account" "github.com/hyperledger/burrow/crypto" - "github.com/hyperledger/burrow/execution" + "github.com/hyperledger/burrow/execution/names" "github.com/hyperledger/burrow/rpc" "github.com/hyperledger/burrow/rpc/tm" "github.com/hyperledger/burrow/txs" @@ -125,7 +125,7 @@ func Call(client RPCClient, fromAddress, toAddress crypto.Address, data []byte) return res, nil } -func GetName(client RPCClient, name string) (*execution.NameRegEntry, error) { +func GetName(client RPCClient, name string) (*names.Entry, error) { res := new(rpc.ResultGetName) _, err := client.Call(tm.GetName, pmap("name", name), res) if err != nil { diff --git a/rpc/tm/integration/shared.go b/rpc/tm/integration/shared.go index 9939c1e378ae8b652a46b92c0c352904c79c454f..d01322d005a5fbcf400ae64cc7326b997eb98d62 100644 --- a/rpc/tm/integration/shared.go +++ b/rpc/tm/integration/shared.go @@ -26,7 +26,7 @@ import ( "github.com/hyperledger/burrow/binary" "github.com/hyperledger/burrow/core/integration" "github.com/hyperledger/burrow/crypto" - "github.com/hyperledger/burrow/execution" + "github.com/hyperledger/burrow/execution/names" "github.com/hyperledger/burrow/rpc" tmClient "github.com/hyperledger/burrow/rpc/tm/client" rpcClient "github.com/hyperledger/burrow/rpc/tm/lib/client" @@ -173,7 +173,7 @@ func callContract(t *testing.T, client tmClient.RPCClient, fromAddress, toAddres } // get the namereg entry -func getNameRegEntry(t *testing.T, client tmClient.RPCClient, name string) *execution.NameRegEntry { +func getNameRegEntry(t *testing.T, client tmClient.RPCClient, name string) *names.Entry { entry, err := tmClient.GetName(client, name) if err != nil { t.Fatal(err) diff --git a/rpc/tm/integration/websocket_helpers.go b/rpc/tm/integration/websocket_helpers.go index 0844f0e78211e96674917c12eefda230f98cf165..9355829e4f2be8bf816040aaec8126d7cc7b7782 100644 --- a/rpc/tm/integration/websocket_helpers.go +++ b/rpc/tm/integration/websocket_helpers.go @@ -254,8 +254,8 @@ func unmarshalValidateSend(amt uint64, toAddr crypto.Address, resultEvent *rpc.R if data == nil { return fmt.Errorf("event data %v is not EventDataTx", resultEvent) } - if data.Exception != "" { - return fmt.Errorf(data.Exception) + if data.Exception == nil { + return data.Exception.AsError() } tx := data.Tx.Payload.(*payload.SendTx) if tx.Inputs[0].Address != privateAccounts[0].Address() { @@ -278,8 +278,8 @@ func unmarshalValidateTx(amt uint64, returnCode []byte) resultEventChecker { if data == nil { return true, fmt.Errorf("event data %v is not EventDataTx", *resultEvent) } - if data.Exception != "" { - return true, fmt.Errorf(data.Exception) + if data.Exception != nil { + return true, data.Exception.AsError() } tx := data.Tx.Payload.(*payload.CallTx) if tx.Input.Address != privateAccounts[0].Address() { @@ -304,8 +304,8 @@ func unmarshalValidateCall(origin crypto.Address, returnCode []byte, txid *[]byt if data == nil { return true, fmt.Errorf("event data %v is not EventDataTx", *resultEvent) } - if data.Exception != "" { - return true, fmt.Errorf(data.Exception) + if data.Exception != nil { + return true, data.Exception.AsError() } if data.Origin != origin { return true, fmt.Errorf("origin does not match up! Got %s, expected %s", data.Origin, origin) diff --git a/rpc/tm/methods.go b/rpc/tm/methods.go index 12807a640cde692828be707ce505fad760e14c18..47883f6d70e7e595e9cbf385598daead4de0a417 100644 --- a/rpc/tm/methods.go +++ b/rpc/tm/methods.go @@ -8,7 +8,7 @@ import ( acm "github.com/hyperledger/burrow/account" "github.com/hyperledger/burrow/crypto" "github.com/hyperledger/burrow/event" - "github.com/hyperledger/burrow/execution" + "github.com/hyperledger/burrow/execution/names" "github.com/hyperledger/burrow/logging" "github.com/hyperledger/burrow/rpc" "github.com/hyperledger/burrow/rpc/tm/lib/server" @@ -168,7 +168,7 @@ func GetRoutes(service *rpc.Service, logger *logging.Logger) map[string]*server. // Names GetName: server.NewRPCFunc(service.GetName, "name"), ListNames: server.NewRPCFunc(func() (*rpc.ResultListNames, error) { - return service.ListNames(func(*execution.NameRegEntry) bool { + return service.ListNames(func(*names.Entry) bool { return true }) }, ""), diff --git a/rpc/v0/methods.go b/rpc/v0/methods.go index 26e788dd51ca51fdb117c015d19db8ace1d77f26..e391f5333aa608097d5eda79d9ab8fe6355b2532 100644 --- a/rpc/v0/methods.go +++ b/rpc/v0/methods.go @@ -20,6 +20,7 @@ import ( acm "github.com/hyperledger/burrow/account" "github.com/hyperledger/burrow/crypto" "github.com/hyperledger/burrow/execution" + "github.com/hyperledger/burrow/execution/names" "github.com/hyperledger/burrow/logging" "github.com/hyperledger/burrow/rpc" "github.com/hyperledger/burrow/rpc/filters" @@ -257,11 +258,11 @@ func GetMethods(codec rpc.Codec, service *rpc.Service, logger *logging.Logger) m if err != nil { return nil, rpc.INVALID_PARAMS, err } - ce, err := service.Transactor().TransactAndHold(inputAccount, address, param.Data, param.GasLimit, param.Fee) + eventDataCall, err := service.Transactor().TransactAndHold(inputAccount, address, param.Data, param.GasLimit, param.Fee) if err != nil { return nil, rpc.INTERNAL_ERROR, err } - return ce, 0, nil + return eventDataCall, 0, nil }, SEND: func(request *rpc.RPCRequest, requester interface{}) (interface{}, int, error) { param := &SendParam{} @@ -347,7 +348,7 @@ func GetMethods(codec rpc.Codec, service *rpc.Service, logger *logging.Logger) m if err != nil { return nil, rpc.INVALID_PARAMS, err } - list, err := service.ListNames(func(entry *execution.NameRegEntry) bool { + list, err := service.ListNames(func(entry *names.Entry) bool { return filter.Match(entry) }) if err != nil { diff --git a/txs/tx_test.go b/txs/tx_test.go index ada6bcd607b1964a943ae5fccb90f306afe69644..bb916e86ab3a144dfaf9d170d1a195a8b7ac01ed 100644 --- a/txs/tx_test.go +++ b/txs/tx_test.go @@ -21,8 +21,8 @@ import ( acm "github.com/hyperledger/burrow/account" "github.com/hyperledger/burrow/crypto" - ptypes "github.com/hyperledger/burrow/permission" "github.com/hyperledger/burrow/permission/snatives" + ptypes "github.com/hyperledger/burrow/permission/types" "github.com/hyperledger/burrow/txs/payload" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require"